目录
一、研究代码
1.1 总结:
二、第二个问题
2.1解答
三、第三个问题
3.1解答
一、研究代码
<?php
$mysqli = new mysqli("localhost", "root", "abc123", "cat");/* check connection */
if ($mysqli->connect_errno) {printf("Connect failed: %s\n", $mysqli->connect_error);exit();
}$mysqli->query("set names utf8");$username = addslashes($_GET['username']);if ($username === 'admin') {die('Permission denied!');
}/* Select queries return a resultset */
$sql = "SELECT * FROM `table1` WHERE username='{$username}'";if ($result = $mysqli->query( $sql )) {printf("Select returned %d rows.\n", $result->num_rows);while ($row = $result->fetch_array(MYSQLI_ASSOC)){var_dump($row);}/* free result set */$result->close();
} else {var_dump($mysqli->error);
}$mysqli->close();
创建数据库cat,创建表如下
CREATE TABLE `table1` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,`username` varchar(255) COLLATE latin1_general_ci NOT NULL,`password` varchar(255) COLLATE latin1_general_ci NOT NULL,PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
首先看看代码,当username=admin的时候,会打印Permission denied!,那我们首先第一个目的就是不让程序认为username=admin,那就是添加字符试试%c2
很明显打印出来了那原因是什么呢
造成这个问题的根本原因是,Mysql字段的字符集和php mysqli客户端设置的字符集不相同。
set names utf8
的意思是将客户端的字符集设置为utf8。我们打开mysql控制台,依次执行SHOW VARIABLES LIKE 'character_set_%';
、set names utf8;
、SHOW VARIABLES LIKE 'character_set_%';
,即可得到如下结果:
如上图,在默认情况下,mysql字符集为latin1,而执行了set names utf8;
以后,character_set_client
、character_set_connection
、character_set_results
等与客户端相关的配置字符集都变成了utf8,但character_set_database
、character_set_server
等服务端相关的字符集还是latin1。
这就是该Trick的核心,因为这一条语句,导致客户端、服务端的字符集出现了差别。既然有差别,Mysql在执行查询的时候,就涉及到字符集的转换。
重点:
-
MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
-
进行内部操作前将请求数据从character_set_connection转换为内部操作字符集
执行顺序:
utf8 --> utf8 --> latin1
1.1 总结:
不完整的汉字导致转utf-8的时候被抛弃了
二、第二个问题
为什么%c1又不可以
2.1解答
这就涉及到Mysql编码相关的知识了,先看看维基百科吧。
UTF-8编码是变长编码,可能有1~4个字节表示:
-
一字节时范围是[00-7F]
-
两字节时范围是C0-DF
-
三字节时范围是E0-EF[80-BF]
-
四字节时范围是F0-F780-BF
然后根据RFC 3629规范,又有一些字节值是不允许出现在UTF-8编码中的:
所以最终,UTF-8第一字节的取值范围是:00-7F、C2-F4,这也是我在admin后面加上80-C1、F5-FF等字符时会抛出错误的原因。
关于所有的UTF-8字符,在这个表中一一看到: http://utf8-chartable.de/unicode-utf8-table.pl
三、第三个问题
为什么%F0也不行,这个明明不在上面的编码规范中
3.1解答
F0-F4是四字节才有的,所以我传入username=admin%F0
也将抛出错误。
如果你需要Mysql支持四字节的utf-8,可以使用utf8mb4
编码。我将原始代码中的set names改成set names utf8mb4
,再看看效果: