Info

这些天在利用sqli-labs来研究sql注入,发现了很多有趣的东西,今天就在这里记录一下宽字节注入以及相关知识点.

宽字节注入

人类有多种语言,然而计算机的语言只有0和1,所以编码就此诞生.宽字节注入的发生背景就是GBK编码.

我们的场景很简单:

mysql_query("SET NAMES 'gbk'");
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$sql = "SELECT * FROM news WHERE tid='{$id}'";

服务端设置连接字符集,将通过addslashes等过滤函数将输入提交到数据库,然而问题在于,过滤函数并不是按照连接字符集来过滤的,所以就导致了以下的现象:

当用户提交%df'时,'因为在addslashes的黑名单内,所以会被过滤为%df\',转成ascii码后变成%df%5c%27. 当数据提交到数据库后,由于设置了GBK为连接字符集,当两个字符中前一个字符的ascii码值大于128时,该编码会把这两个字符视作一个汉字,所以%df%5c被当做一个汉字,从而使'逃逸.

修复

上面提到了,问题的关键在于过滤函数并没有考虑当前连接的字符集.所以要使用mysql_real_escape_string(),该函数会考虑当前连接的字符集.

但是该函数是从mysql->charset这个对象属性中获取字符集,我们上面的mysql_query("SET NAMES 'gbk'");并不会达到这种效果,所以要将其改为mysql_set_charset('gbk'),该函数除了SET NAME 'gbk'(下面会提到)之外,还会将mysql->charset设置为GBK,从而供mysql_real_escape_string()使用.

MySQL字符集转换过程

既然说到字符集,就有必要了解一下其运作过程:

  • MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection
  • 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:

    • 使用每个数据字段的CHARACTER SET设定值
    • 若上述值不存在,则使用对应数据表的DEFAULT CHARACTERSET设定值(MySQL扩展,非SQL标准)
    • 若上述值不存在,则使用对应数据库的DEFAULT CHARACTERSET设定值
    • 若上述值不存在,则使用character_set_server设定值
  • 将操作结果从内部操作字符集转换为character_set_results

我们所执行的SET NAMES的功能如下:

SET NAMES sets the three session system variables character_set_client, character_set_connection, and character_set_results to the given character set. 

几点需要注意的问题

  • URL encode的过程就是把部分url做为字符,按照某种编码方式(如:utf-8,gbk等)编码成二进制的字节码,然后每个字节用一个包含3个字符的字符串 “%xy” 表示,其中xy为该字节的两位十六进制表示形式
  • GET和POST的URL编码方式
  • POST的格式为application/x-www-form-urlencoded,在HTML5中不编码的字符有字母,数字和*-_.
  • chrome中GET只会对空格和单引号进行编码