• SQL注入之宽字节注入


    什么是宽字节

    如果一个字符的大小是一个字节的,称为窄字节;如果一个字符的大小是两个字节的,则称为宽字节。

    GB2321、GBK、GB18030、BIG5、Shift_JIS等这些编码都是常说的宽字节,也就是只有两个字节。
    英文默认占一个字节,中午占两个字节。

    什么是宽字节注入?

    原理:宽字节注入发送的位置就是PHP发送请求到MYSQL时字符集使用charater_set_client设置一次编码。在使用PHP连接Mysql的时候,当设置"character_set_client=gbk"时会导致一个编码转换的问题,也就是我们熟悉的宽字节注入。

    宽字节注入是利用mysql的一个特性,mysql在使用GBK编码(GBK就是常说的宽字节之一,实际上只有两个字节)的时候,会认为两个字符是一个汉字(前一个ASCII码要大于128,才到汉字的范围)

    GBK首字节对应0x81-0xFE,尾字节对应0x40-0xFE(除0x7F),例如%df和%5C会结合;GB2312是被GBK兼容的,它的高位范围是0xA1-0xF7,低位范围是0xA1-0xFE(0x5C不在该范围内),因此不能使用编码吃掉%5c

    常见转义函数与配置:
    addslashesmysql_real_escape_stringmysql_escape_string
    php.ini中magic_quote_gpc的配置

    宽字节注入的条件

    1. 数据库为GBK编码
    2. 使用了转义函数,将、POGETST、cookie传递的参数进行过滤,将单引号、双引号、null等敏感字符用转义符\进行转义

    宽字节注入方式

    root %df' or 1=1 #
    
    • 1

    原理:在GBK编码中,反斜杠的编码是%5c,在输入%df后,使得添加反斜杠后形成%df%5c,而%df%5c是繁体字"連",单引号成功逃逸,爆出mysql数据库的错误。

    案例分析

    0x01mysql中的宽字节注入

    我们先搭建一个实验环境。

    源码很简单(注意先关闭自己php环境的magic_quotes_gpc):

    <?php
    //连接数据库部分,注意使用了gbk编码,把数据库信息填写进去
    $conn = mysql_connect('localhost', 'root', 'toor!@#$') or die('bad!');
    mysql_query("SET NAMES 'gbk'");
    mysql_select_db('test', $conn) OR emMsg("连接数据库失败,未找到您填写的数据库");
    //执行sql语句
    $id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
    $sql = "SELECT * FROM news WHERE tid='{$id}'";
    $result = mysql_query($sql, $conn) or die(mysql_error()); //sql出错会报错,方便观察
    ?>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="gbk" />
    <title>新闻</title>
    </head>
    <body>
    <?php
    $row = mysql_fetch_array($result, MYSQL_ASSOC);
    echo "

    {$row['title']}

    {$row['content']}

    \n"; mysql_free_result($result); ?> </body> </html>

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    SQL语句是SELECT * FROM news WHERE tid='{$id}',就是根据文章的id把文章从news表中取出来。

    在这个sql语句前面,我们使用了一个addslashes函数,将$id的值转义。这是通常cms中对sql注入进行的操作,只要我们的输入参数在单引号中,就逃逸不出单引号的限制,无法注入,如下图:
    在这里插入图片描述
    那么怎么逃过addslashes的限制?众所周知addslashes函数产生的效果就是,让'变成\',让引号变得不再是“单引号”,只是一撇而已。一般绕过方式就是,想办法处理\'前面的\

    1.想办法给\前面再加一个\(或单数个即可),变成\\',这样\被转义了,'逃出了限制
    2.想办法把\弄没有。

    我们这里的宽字节注入是利用mysql的一个特性,mysql在使用GBK编码的时候,会认为两个字符是一个汉字(前一个ascii码要大于128,才到汉字的范围)。如果我们输入%df'看会怎样:
    在这里插入图片描述
    我们可以看到,已经报错了。我们看到报错,说明sql语句出错,看到出错说明可以注入了。

    为什么从刚才到现在,只是在'也就是%27前面加了一个%df就报错了?而且从图中可以看到,报错的原因就是多了一个单引号,而单引号前面的反斜杠不见了。

    这就是mysql的特性,因为gbk是多字节编码,他认为两个字节代表一个汉字,所以%df和后面的\也就是%5c变成了一个汉字“運”,而'逃逸了出来。

    因为两个字节代表一个汉字,所以我们可以试试%df%df%27:
    在这里插入图片描述
    不报错了。因为%df%df是一个汉字,%5c%27不是汉字,仍然是\'

    那么mysql怎么判断一个字符是不是汉字,根据gbk编码,第一个字节ascii码大于128,基本上就可以了。比如我们不用%df,用%a1也可以:
    在这里插入图片描述
    %a1%5c他可能不是汉字,但一定会被mysql认为是一个宽字符,就能够让后面的%27逃逸了出来。

    于是我可以构造一个exp出来,查询管理员账号密码:

    -1%aa%27union%20select%201,2,concat(name,0x23,pass)%20from%20admin%23
    
    • 1

    在这里插入图片描述

    0×02 GB2312与GBK的不同

    gb2312和gbk应该都是宽字节家族的一员。但为什么gb2312不能注入
    我们来做个小实验。把内容管理系统中set names修改成gb2312:
    在这里插入图片描述

    结果就是不能注入了:
    在这里插入图片描述

    有些同学不信的话,也可以把数据库编码也改成gb2312,也是不成功的。

    为什么,这归结于gb2312编码的取值范围。它的高位范围是0xA1~0xF7,低位范围是0xA1~0xFE,而\是0x5c,是不在低位范围中的。所以,0x5c根本不是gb2312中的编码,所以自然也是不会被吃掉的。

    所以,把这个思路扩展到世界上所有多字节编码,我们可以这样认为:只要低位的范围中含有0x5c的编码,就可以进行宽字符注入。

    0×03 mysql_real_escape_string解决问题?

    部分cms对宽字节注入有所了解,于是寻求解决方案。在php文档中,大家会发现一个函数,mysql_real_escape_string,文档里说了,考虑到连接的当前字符集。

    009.jpg

    于是,有的cms就把addslashes替换成mysql_real_escape_string,来抵御宽字符注入。我们继续做试验,内容管理系统v1.2:,就用mysql_real_escape_string来过滤输入:

    在这里插入图片描述

    我们来试试能不能注入:

    在这里插入图片描述

    一样没压力注入。为什么,明明我用了mysql_real_escape_string,但却仍然不能抵御宽字符注入。

    原因就是,你没有指定php连接mysql的字符集。我们需要在执行sql语句之前调用一下mysql_set_charset函数,设置当前连接的字符集为gbk。

    在这里插入图片描述

    就可以避免这个问题了:

    在这里插入图片描述

    0×04 宽字符注入的修复

    在上面我们说到了一种修复方法,就是先调用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
    
    • 1

    这几个变量是什么意思?

    当我们的mysql接受到客户端的数据后,会认为他的编码是character_set_client,然后会将之将换成character_set_connection的编码,然后进入具体表和字段后,再转换成字段对应的编码。

    然后,当查询结果产生后,会从表和字段的编码,转换成character_set_results编码,返回给客户端。

    所以,我们将character_set_client设置成binary,就不存在宽字节或多字节的问题了,所有数据以二进制的形式传递,就能有效避免宽字符注入。

    比如,我们的内容管理系统v2.0版本更新如下:

    在这里插入图片描述

    已经不能够注入了:

    在这里插入图片描述

    总结

    总结一下全文中提到的由字符编码引发的安全问题及其解决方案:

    1. gbk编码造成的宽字符注入问题,解决方法是设置character_set_client=binary。
    2. 矫正人们对于mysql_real_escape_string的误解,单独调用set names gbk和mysql_real_escape_string是无法避免宽字符注入问题的。还得调用mysql_set_charset来设置一下字符集。
      参考文章: 字符编码及SQL注入
  • 相关阅读:
    首批智能重卡交付助力双11快运!自动驾驶玩家交出商业化新答卷
    灿芯股份将上会:计划募资6亿元,董事长、总经理均为外籍
    DJYOS 定时器组件硬件接口说明
    PAT 1011 World Cup Betting
    Vue3+Ts各种错误整理
    力扣题目训练(18)
    星辰天合受邀参加红帽2023中国区合作伙伴大会
    捷报频传!苏州箱讯荣获2023年江苏省物流产业服务贡献奖
    Scala数据结构
    Ant vue中表单验证(动态校验、部分校验)
  • 原文地址:https://blog.csdn.net/m0_46467017/article/details/126247133