在mysql中有几种常见编码:
mysql> show variables like 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
mysql> set names utf8;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
在默认情况下, mysql字符集为latin1, 而执行了set names utf8;
以后, character_set_client
, character_set_connection
, character_set_results
等与客户端相关的配置字符集都变成utf8, 但character_set_database
, character_set_server
等服务端相关的字符集还是latin1, 因为这一条语句, 导致客户端和服务端的字符集出现了差别, 既然有差别, mysql在执行查询的时候, 就设计到字符集的转换。
我们用php连接mysql服务器的时候设置的编码set names gbk
, 就等同于:
set character_set_client='gbk' //客户端编码
set charseter_set_connection='gbk' //连接器编码
set charsetter_set_results='gbk'//返回值编码
连接顺序为: 客户端 <= 连接器 <= 服务端
, 这中间如果有哪一个编码不一致就很容易导致乱码的问题,
2008年鸟哥曾在博客中讲解了Mysql字符集:(http://www.laruence.com/2008/01/05/12.html)
MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection; 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集
在使用php连接mysql服务器的时候, 当设置 "set character_set_client=gbk"
(设置其他两个不会触发sql注入) 这时就会导致一个编码转换的注入问题, 也就是我们熟悉的宽字节注入
我们来分析下, 当我们提交id=1%df’
的时候,假设后端用了addslashes函数处理sql语句变成了select * from articles where id=1%df\'
如果php 连接mysql的时候设置了client客户端的字符集为gbk, 那么mysql服务器对查询语句就会进行GBK转码, mysql服务器数据库一般都是latin1编码,而gbk是两个字符表示一个汉字,因此,`%df 和转义字符%5c就会组成成一个%df%5c的汉字, 吃掉了一个转义符,导致单引号逃逸
解决方法:
- 一般官方推荐把charackter_set_client 设置为binary 或utf-8
- 使用mysql_real_escape_string()函数, 该函数会自动识别编码问题
另外在高版本的mysql中好像修复了该问题
范围: 据gbk编码,第一个字节ascii码大于128, 即0x81~0xff
<?php
if ($username === 'admin') {
if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1') {
die('Permission denied!');
}
}
$result = $mysqli->query("SELECT * FROM z_users where username = '{$username}' and password = '{$password}'");
核心代码如上
那么,为什么执行SELECT * FROM user WHERE username='admin\xC2' and password='admin'
却可以查出用户名是admin的记录?
当设置客户端的字符集位utf8的时候,这时mysql的编码转换为:
utf8-utf8-latin1
最后执行将数据库里的admin
和传入的admin\xC2
进行比较的时候, admin
是一个latin1字符串
大佬猜测原因是Mysql在转换字符集的时候, 将不完整的字符给忽略了
参考: https://www.leavesongs.com/PENETRATION/mysql-charset-trick.html