网页出现乱码的原因:
客户端与数据库的数据传输处编码、数据库存储处编码,两者编码不同,就会出现乱码。还有一种情况,PHP没有向浏览器发送header头设置,告诉浏览器用用什么编码来展现,导致乱码。
通常,数据库在创建数据表的时候,就已经设置好了数据库存储端的字符编码。可以看到name字段设置的是gbk,而我们注入的时候,脑子中构造的语句都是select xxx from xxx where name="{$user_name}"这样类似的语句。(下面的gbk支持所有常用中文的简体和繁体,utf-8包含所有的字符,至于为何要这么设置,我个人猜测可能是为了保证数据的唯一性吧。有懂数据库的大佬可以在下方留言,因为感觉这方面转换编码挺复杂的)
正常情况下,整个数据的走向大概如下:
请求过程:用户提交数据--->经过中间件--->到达web应用--->web应用再作为客户端,向数据库服务器发送请求--->数据库服务器接受请求,执行web应用发送的sql语句
web应用作为客户端,向数据库服务器发送请求的时候,会以自身php文件默认的编码,来对信息进行编码,接着,再向数据库发送请求,那么这肯定有一个客户端和服务器建立连接的过程,这个建立连接的过程也是统一编码的过程,依靠的mysql的两个内部变量character_set_client和character_set_connection,只有将character_set_client和character_set_connection的变量值(也就是编码方式)都设置成相同的值,那么才不会出现乱码(是在数据库端进行设置)
之后,服务器将处理结果,以客户端能理解的编码方式,再传给客户端(这里的客户端我理解是web应用),按照上面的来看,客户端能理解的就是gbk了,所以,character_set_results就设置成gbk,这就是服务器返回给客户端的按照gbk编码的结果
而通常程序员在php里写的set names utf8也就是上面这三条语句同时执行的效果。同时,我最上面提到,网页的编码也可能造成乱码,那是因为浏览器端的编码和PHP返回结果(或者说是打印结果)的编码不一致导致的。解决方法就是php用header()来告诉浏览器端用什么编码来解码。这样,也就同时解决了浏览器端可能出现的乱码问题。
宽字节注入的问题就是出在这里:
若数据库端设置了character_set_client为gbk,character_set_connection为gbk,那么php代码接受到的$bar为 %df%27 的时候
addslashes()将用户提交上来的url编码的 %df%27 进行解读,发现%27是单引号的意思,所以就加上了转义字符\进行转义,于是就变成了%df%5c%27,这里的%5c就是\的url编码后的形式。问题就在这里形成了,PHP代码中有set names GBK,那就代表character_set_client,character_set_connection,character_set_results的值都是gbk,而php代码执行mysql($sql)的时候,会进行编码转换!
重点:宽字节注入发生的位置就是PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码。在这次编码中,%df%5c被合并成了一个新的字节,而%27则被当做单引号,这样就实现了闭合!
通过查询gdk编码表,可以看到%df%5c就是那个字符(我不认识,好像是运气的运?)这样的话,由于这里的编码变成了新的字符,那么到数据库端执行的语句就成了select xxx from xxx where name='运' and 1=1-- 实现了闭合,也实现了绕过代码层的过滤。
再总结一下:
当一个Mysql连接请求从客户端传来的时候,服务器认为它的编码是character_set_client,然后会根据character_set_connection把请求进行转码,从character_set_client转成character_set_connection,然后更新到数据库的时候,再转化成字段所对应的编码如果使用了set names指令,那么可以修改character_set_connection的值,也同时会修改character_set_client和character_set_results的值,当从数据库查询数据返回结果的时候,将字段从默认的编码转成character_set_results
另外,据我查阅相关资料,得知网站的正常开发都是:
将一些用户提交的GBK字符使用iconv函数(或者mb_convert_encoding)先转为UTF-8,然后再拼接入SQL语句
像这种情况,我搜集资料的时候,看到有人博客上是这么写的
实际上,我想说,不加那个\不是也是同样的效果吗?还是说GBK转成URF8加个\是为了凑字符?不管了,这里就先记着吧。如果我的想法实施的时候错误了,那就再加个\去尝试。
最后,网站如何才能防止这种宽字符注入攻击呢?
(1)使用mysql_set_charset(GBK)指定字符集
(2)使用mysql_real_escape_string进行转义
原理是,mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面e5和5c拼接为一个宽字节的问题,但是这个“当前字符集”如何确定呢?
就是使用mysql_set_charset进行指定。