之前写过一篇宽字符注入的(https://liotree.github.io/2019/05/06/%E5%AE%BD%E5%AD%97%E7%AC%A6%E6%B3%A8%E5%85%A5/)
不过有点太简略了,新写一篇详细的

1.mysql的编码

先来了解一些mysql的知识

mysql 有以下几个字符集的系统变量:

  • character_set_server 内部操作字符集
  • character_set_client 客户端使用的字符集
  • character_set_connection 连接层字符集
  • character_set_results:查询结果字符集
  • character_set_database:当前选中数据库的默认字符集
  • character_set_system:系统元数据(字段名等)字符集

当php操作mysql时,mysql会认为php传过来的数据是character_set_client编码的,接着将数据转为character_set_connection的形式,最后再转为内部操作字符集

内部操作字符集按如下方式确定(引用自https://blog.csdn.net/zy691357966/article/details/79802587):

使用每个数据字段的CHARACTER SET设定值;
若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(MySQL扩展,非SQL标准);
若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
若上述值不存在,则使用character_set_server设定值。

最后将操作结果从内部操作字符集转换为chatacter_set_results

2.宽字符注入原理

都是gbk编码的情况

代码示例:

1
2
3
4
$id = addslashes($_GET['id']);
mysql_query("set names gbk");
$sql = "select * from users where id="+$id;
$result = mysql_query($sql);

当使用了

1
mysql_query("set names gbk");

时,character_set_client,character_set_connection和character_set_result都会被设置成gbk
因此就可以利用gbk编码去吃掉,详情可以参见我之前的文章
https://liotree.github.io/2019/05/06/宽字符注入/

使用了转换编码格式的函数

php中转换编码格式的函数有iconv和mb_convert_encoding

a.从gbk转为utf-8

示例代码

1
2
3
4
mysql_query("set names UTF-8") ;  
$username =iconv("GBK","UTF-8", addslashes($_GET['username'])) ;
$sql = "select * from users where username = ".$username;
$result = mysql_query($sql) ;

加入我们传入一个%e5%5c%27,按照gbk编码也就是

錦’

addslashes会给/(%5c)和’(%27)前面加上/,因此变为%e5%5c%5c%5c%27
%e5%5c%5c%5c%27 转换为utf-8后变为%e9%8c%a6%5c%5c%27
按照utf8即为

錦\\‘

也就是\被转义了,逃逸出了’

b.从utf-8转为gbk

示例代码

1
2
3
4
mysql_query("set names UTF-8") ;  
$username =iconv("UTF-8","gbk", addslashes($_GET['username'])) ;
$sql = "select * from users where username = ".$username;
$result = mysql_query($sql) ;

给username参数传入一个%e9%8c%a6%27,也就是utf-8编码的

錦’

addslashes会将其变成%e9%8c%a6%5c%27,转为gbk编码则变成了
%e5%5c%5c%27,也就是gbk编码的

錦\\‘

这时\被转义了,’逃逸了出来

3.防御宽字符注入

示例代码:

1
2
3
4
5
$username = $_GET['username'];
mysql_set_charset(GBK);
$sql=sprintf("SELECT * FROM users WHERE username='%s'",mysql_real_escape_string($username);
mysql_query($query);
$result = mysql_query($sql);

使用mysql_set_charset(GBK)代替mysql_query(“set names gbk”);
mysql_set_charset除了会set name之外,还会将mysql->charset设置为指定的编码(详细参见http://www.laruence.com/2010/04/12/1396.html)

mysql_real_escape_string和addslashes以及mysql_escape_string的区别就是mysql_real_escape会按照mysql->charset指定的字符集来看待传入的字符串

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$db = mysql_connect('localhost', 'root' ,'root');
mysql_select_db("test");

$a = "\x91\x5c\x27";//"慭'"的gbk编码

var_dump(addslashes($a));
var_dump(mysql_real_escape_string($a, $db));

mysql_query("set names gbk");
var_dump(mysql_real_escape_string($a, $db));

mysql_set_charset("gbk");
var_dump(mysql_real_escape_string($a, $db));
?>

输出

string(5) “慭\\‘“ string(5) “慭\\‘“ string(5) “慭\\‘“ string(4) “慭\‘“

当然,更好的防御方法是使用mysqli或者pdo的参数化查询