0x0 背景
- 当某字符的大小为一个字节时,称其字符为窄字节.
- 当某字符的大小为两个字节时,称其字符为宽字节.
- 所有英文默认占一个字节,汉字占两个字节
- 常见的宽字节编码:GB2312,GBK,GB18030,BIG5,Shift_JIS等等
0x1 宽字节注入原理
程序员为了防止sql注入,对用户输入中的单引号(’)进行处理,在单引号前加上斜杠(\)进行转义,这样被处理后的sql语句中,单引号不再具有‘作用’,仅仅是‘内容’而已,换句话说,这个单引号无法发挥和前后单引号闭合的作用,仅仅成为‘内容‘
【再举个例子,要找某位名字里带单引号的用户,搜索的时候,就要让单引号成为内容去搜索,而不能起到其他作用】
而安全测试人员要绕过这个转义处理,使单引号发挥作用,有两个思路:
- 让斜杠(\)失去作用
- 让斜杠(\)消失
第一个思路就是借鉴程序员的防范思路,对斜杠(\)转义,使其失去转义单引号的作用,成为‘内容’
第二个思路就是宽字节注入
当使用宽字节编码,如:GBK时,两个连在一起的字符会被认为是汉字,我们可以在单引号前加一个字符,使其和斜杠(\)组合被认为成汉字,从未达到让斜杠消失的目的,进而使单引号发挥作用
注意:前一个字符的Ascii要大于128,两个字符才能组合成汉字
0x2 注入方法
0x21 黑盒
可以看到,在发现单引号被转义后,当我们加了%df后,sql语句报错,说明单引号发挥了作用,斜杠与%df组合
0x22白盒
1.gbk编码
2.单引号被替换成\’
3.php内置函数addslashes进行转义(lesson33)
4.mysql_real_escape_string
这个mysql内置的函数可以把sql语句中字符串的特殊字符进行转义,同时考虑连接的字符集,可以用来解决宽字节注入,但某些场景仍不行,因为程序没有指定php连接mysql的字符集。
如果要使用这个函数防御,要在执行sql语句之前调用一下mysql_set_charset函数,设置当前连接的字符集为gbk,再调用mysql_real_escape_string来过滤用户输入
0x23 sqlmap
需要注意的是,对于宽字节注入场景,直接
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-32/?id=1"
是找不到注入的
必须得
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-32/?id=1%df%27"
还可以加些参数:
--threads 10 //如果你玩过 msfconsole的话会对这个很熟悉 sqlmap线程最高设置为10
--level 3 //sqlmap默认测试所有的GET和POST参数,当--level的值大于等于2的时候也会测试HTTP Cookie头的值,当大于等于3的时候也会测试User-Agent和HTTP Referer头的值。最高可到5
--risk 3 // 执行测试的风险(0-3,默认为1)risk越高,越慢但是越安全
----search //后面跟参数 -D -T -C 搜索列(S),表(S)和或数据库名称(S) 如果你脑子够聪明,应该知道库列表名中可能会有ctf,flag等字样,结果有时候题目就是这么耿直对吧?
//我最后还是没成功用sqlmap注入
0x3防御
借鉴内容:https://www.leavesongs.com/PENETRATION/mutibyte-sql-inject.html
在0x22.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,就不存在宽字节或多字节的问题了,所有数据以二进制的形式传递,就能有效避免宽字符注入。
比如,我们的phithon内容管理系统v2.0版本更新如下:
已经不能够注入了:
在我审计过的代码中,大部分cms是以这样的方式来避免宽字符注入的。这个方法可以说是有效的,但如果开发者画蛇添足地增加一些东西,会让之前的努力前功尽弃。
0x4总结
在逐渐国际化的今天,推行utf-8编码是大趋势。如果就安全性来说的话,我也觉得使用utf-8编码能够避免很多多字节造成的问题。
不光是gbk,我只是习惯性地把gbk作为一个典型的例子在文中与大家说明。世界上的多字节编码有很多,特别是韩国、日本及一些非英语国家的cms,都可能存在由字符编码造成的安全问题,大家应该有扩展性的思维。
总结一下全文中提到的由字符编码引发的安全问题及其解决方案:
- gbk编码造成的宽字符注入问题,解决方法是设置character_set_client=binary。
- 矫正人们对于mysql_real_escape_string的误解,单独调用set names gbk和mysql_real_escape_string是无法避免宽字符注入问题的。还得调用mysql_set_charset来设置一下字符集。
- 谨慎使用iconv来转换字符串编码,很容易出现问题。只要我们把前端html/js/css所有编码设置成gbk,mysql/php编码设置成gbk,就不会出现乱码问题。不用画蛇添足地去调用iconv转换编码,造成不必要的麻烦。
参考文档:https://www.leavesongs.com/PENETRATION/mutibyte-sql-inject.html