【技术分享】关于PHP内部编码与mysql字符差异问题的研究

   2016-11-22 0
核心提示:作者: bendawangs 预估稿费:400RMB(不服你也来投稿啊!) 投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿 0x01 引入最近稍稍研究了下关于PHP的内部编码的问题,以及mysql的字符差异的问题,分享下心得,如果有误请大家及时指正。至于为什么要介

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

作者: bendawangs

预估稿费:400RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆 网页版 在线投稿

0x01 引入

最近稍稍研究了下关于PHP的内部编码的问题,以及mysql的字符差异的问题,分享下心得,如果有误请大家及时指正。

至于为什么要介绍mysql字符差异问题,是因为普遍将其原因归纳于PHP编码与mysql的UTF-8编码不统一,但实际上这个只是mysql单方面的原因而与PHP的编码方式无关。

另外本文不涉及编码的具体方式。

先引入如下代码:

<?php
$m="可";
echo strlen($m);  
?>

问这里输入几?2还是3呢?

不多废话直接开始测试下就知道了,截图如下:

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

PS:enca是linux下一款用于探测和修改文件编码方式的软件

可以看到,在文件的编码方式为 UTF-8 的时候,代码输出为3,而文件编码为 GB2312 的时候,代码输入为2。

PS:平时linux和windows下文本文件默认都是以UTF-8的形式保存   

也就是说,这里这个该文件的编码类型决定了文件中那个 "可" 字的长度。那么原因是什么呢?

0x02 关于PHP的编码问题

2.1 PHP内部编码

这里需要说明一下,首先PHP内部的字符串只是一个字节序列,并不会保留任何的编码信息,所以可以说说PHP是不关心编码的,即PHP里一个字符就是一字节。

所以你的字符串啊,各种来源的输入啊之类的PHP都是可以识别的,至于能不能显示,能不能处理又是另一回事了。因为无论你在文本编辑器中保存了什么、或是从数据库中得到它,它已经被编码了,就是说已经被决定了占用了几个字节了,在传递给PHP的时候就已经是以字节的形式传递过去了。

所以说对于PHP来说,一个字符及对应一个字节,换言之,我们可以通过控制数据指针访问到字符串的每一个字节。

而PHP对编码的唯一要求就是它要能够保存为ascii的形式,因为它需要从中获取指令。但是这一点我们并不需要担心,因为大部分编码方式都向下兼容ascii。但是也有例外,比如UTF-16就不兼容ascii,所以我们不能用UTF-16来保存PHP源代。

PS:UTF即 Unicode Translation Format ,即把Unicode转作某种格式的意思。标准的UNICODE编码又称UTF-16。所以UTF-16也就可以理解为通常意义上说的UNicode编码。非要说二者区别的话,也就是UTF-16的本质算是一种存储方式吧。

如下图做个测试,我们以UNICODE保存PHP源码,然后访问如下图: 

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

发现PHP都没有被解析直接返回了源码。即PHP源文件已经无法被正常解析了。也就是说我们可以将PHP源代码保存为任何ASCII兼容的编码,因为如果编码的前128个代码点与ASCII相同,那么就意味着PHP可以解析它。或是说PHP支持该编码方式。

PS:一个语言支持某个编码方式是什么意思?例如,Javascript支持Unicode,事实上,Javascript中的任何字符串都是Unicode编码的,即不能在Javascript中有一个不是Unicode编码的字符串。其他语言只是编码感知。在内部,它们以特定的编码,通常是Unicode存储字符串。反过来,他们需要被告知或尝试检测与文本有关的一切的编码。他们需要知道保存源代码的编码,他们应该读取的文件的编码,要输出文本的编码,并且它们根据需要转换编码,其中一些表现形式是作为中间人的Unicode,很显然的代表就是python。

那么到这儿我们就可以理解引入部分那个例子了。 

因为对于PHP来说,每一个字符就是一个字节,所以strlen和内部编码无关,因此它将计算字节数,而不是字符数。所以该文件的编码类型决定了文件中那个 “可” 字所占用的字节数,从而决定了strlen计算的数目。 

所以这些“可读的字符”的东西是我们人自己的事情,但是PHP语言其实并不在乎。

2.2 关于PHP的乱码问题

有了上面的知识,那么我们可以发现其实PHP内部的编码方式是什么并不是问题关键,问题的关键在于内外内外编码方式的差异。因为PHP不试图解释,转换,编码或以其他方式干扰获取到的字节序列。 该文件甚至可以包含二进制数据或是PHP内部编码并不支持的UNICODE编码的文件,PHP不在乎这些。 

但内部和外部编码必须匹配,譬如我们的汉字要想输入在html页面上,需要设置meta或是用header,将输出的编码方式设置位支持汉字的编码方式。 

所以乱码问题总结就是一个,内部编码与外部编码差异。所以最简单的解决方案是啥?如果是正常情况下使用时:

将PHP的内部编码设置为UTF-8。

将所有源文件保存为UTF-8。

使用UTF-8作为输出编码(不要忘记发送合适的内容类型头)。

将数据库连接设置为使用UTF-8(在MySQL中为SET NAMES UTF8)。

如果可能的话配置一切编码为UTF-8。

PS:这里为啥是UTF-8而不是别的?因为就我查阅的资料来看,它可以表示所有Unicode字符,因此可以取代所有现有的7位和8位编码,并且因为它与ASCII是二进制兼容的,即每个有效的ASCII字符串也是一个有效的UTF-8字符串。

这里再提及一下关于PHP中设置编码的函数,对单个字符串编码的函数就不赘述了,这里只说常用的配置整体环境编码的iconv_set_encoding,过去常用这个来配置默认编码方式:

bool iconv_set_encoding ( string $type , string $charset )
type 的值可以是以下其中任意一个:
    input_encoding
    output_encoding
    internal_encoding

对应与PHP.ini中的 iconv.input_encoding 、iconv.output_encoding、iconv.internal_encoding 。 

但是由于上述三个在PHP5.6之后就已经废弃,三者被统一被default_charset代替,所以没必要再介绍了,而且现在大部分PHP环境的都是5.6以上了。 

所以就这里说说default_charset,即默认编码方式,简单来说就是在  Content-type:xxxx  中输出的默认的字符编码,设置了这个,Content-type 就会是设置的值。默认情况下是ISO8859-1,通常叫做Latin-1。但通常我们都会修改为UTF-8。

0x03 MYSQL的UTF-8编码与字符差异

3.1 MYSQL的UTF-8编码

为什么这里要单挑出UTF-8来讲,因为在MYSQL中除了UTF-8编码,其他编码都和普通一样没有赘述的必要。 

MySQL的UTF-8实际上是完整的UTF-8字符集的大部分实现,而非完整实现。具体来说,MySQL的UTF-8数据编码最多使用3个字节,而编码完整的UTF-8字符集需要4个字节。所以如果需要支持例如星形符号等需要四字节编码的字符,MySQL的UTF-8就无力了。但是从MySQL 5.5.3起,增加了对utf8mb4字符集的支持,每个字符使用最多4个字节,从而支持完整的UTF-8字符集。因此,如果使用MySQL 5.5.3或更高版本,一般设置编码为utf8mb4而不是UTF-8。 

以上便是关于MYSQL的UTF-8编码的简介。

3.2 MYSQL的字符差异问题

实际上这个在HITCON 2016的babytrick题目最后用到了这个,但是可能相对来说更关注__wakeup失效的漏洞了,关于最后的绕过的解释很多WP都把原因归为这里PHP不是UTF-8,而对应的MYSQL执行了mysql_query("SET names utf8")操作,所以产生的MYSQL字符差异。 

但是就通过上述关于PHP内部编码的分析,我们可以知道,其实这里和PHP内部编码没有关系的,而且题目并没有明确的地方有说明PHP的编码方式。但是问题关键还是在这个问题上,无论PHP是什么编码,根据之前的分析,都不会影响到MYSQL。这样光说也说不清出,直接看下面的例子:

<?php
$con = mysql_connect('localhost','root','');
mysql_query("set names utf8");
mysql_select_db("ctf");
if(stripos($_GET['name'],'bendawang')!==false){
    $name = 'GET OUT!';
}
else{
    $name=$_GET['name'];
}
$sql = "select * from admin where username='$name'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
    echo '<h1>Success!</h1>';
    print_r(mysql_fetch_array($result));
}
else{
    echo "GET OUT!";
}
?>

这是admin的表结构 :

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

我们通过某种方式提前知道了admin表里面有一条记录的username字段值为Bendawang,但是我们要想办法绕过stripos的检测。 

先试一试如下: 

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

果然不行,那么我们试试这个。

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

发现成功绕过并且得到结果了。 

那么这是什么原因呢?真的是因为PHP和MYSQL的编码方式的差异吗? 

此时,我们可以查看下此时执行的mysql日志 

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

能够看出实际上PHP还是把 À( utf-8 bytes为 %C3%80 )传过去了,所以说实际上这里和PHP的编码并没有关系,那么也就是说这个就只是单纯的mysql的内部的问题。
我们再换一种验证方式。 现在,我们清空admin表再往表里插上这样两条数据如下:

insert into admin values(unhex('62656E646177616E67'),'1'),(unhex('62656E64C38077616E67'),'2');

其中

unhex('62656E646177616E67')=bendawang
unhex('62656E64C38077616E67')=bendÀwang

插入完成后如下图: 

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

现在我么来执行这么几条指令来观察下: 

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

不知道大家看出蹊跷来没有,在查询的时候mysql默认 bendawang 和 bendÀwang 是等效的,但是如果真的相比较却又是不同的。 

就好比PHP的弱类型比较一样,也可以理解为MYSQL一种牺牲安全性的人性化设计,考虑到不同国家的编码方式不一样。 

就好比你在浏览器里面输入  http://www。baidu。com  仍然可以访问到 http://www.baidu.com 一样。

所以这里只是mysql单方面的问题。

然后我总结了一下可以这样利用的MYSQL字符。如下(重复就没有整理了):

À a
Ç c
È e
Ì i
Ñ n
Ò o
? s
Ù u
ý y
? z

3.3 补充

最后还要说明下我们在刚开始插入数据的时候执行的语句是

insert into admin values(unhex('62656E646177616E67'),'1'),(unhex('62656E64C38077616E67'),'2');

但为什么不执行下面这个语句呢?

insert into admin values('bendawang','1'),('bendÀwang','2')

因为我的演示环境的windows下的mysql的shell环境,对于不识别的字符统统换成问号 ?(0x3F),也就是说我们所输入的 À 就不再是 %C3%80 了。如下: 

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

但是如果换成linux下的mysql的shell环境就可以用第二种插入方式,因为linux下mysql的shell默认是utf-8的编码,如下:

【技术分享】关于PHP内部编码与mysql字符差异问题的研究

这也是编码方式的问题。

0x04 参考资料

http://kunststube.net/encoding/ 

http://www.i18nqa.com/debug/utf8-debug.html 

http://stackoverflow.com/questions/7861358/strange-characters-in-database-text-%C3%83-%C3%83-%C2%A2-%C3%A2-%E2%82%AC 

https://vigilance.fr/vulnerability/MySQL-SQL-injection-via-multi-byte-characters-5885

【技术分享】关于PHP内部编码与mysql字符差异问题的研究 【技术分享】关于PHP内部编码与mysql字符差异问题的研究

本文由 安全客 原创发布,如需转载请注明来源及本文地址。

本文地址:http://bobao.360.cn/learning/detail/3209.html

 
标签: PHP MySQL
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • php-fpm进程管理的三种模式 phpfpm子进程
    php-fpm进程管理的三种模式 phpfpm子进程
    php-fpm解读-进程管理的三种模式—程序媛大丽标明转载以示尊重 感谢原作者的分享。php-fpm进程管理一共有三种模式:ondemand、static、dynamic,我们可以在同一个fpm的master配置三种模式,看下图1。php-fpm的工作模式和nginx类似,都是一个master,多个worke
    03-08
  • nginx和php-fpm 是使用 tcp socket 还是 unix s
    tcp socket允许通过网络进程之间的通信,也可以通过loopback进行本地进程之间通信。unix socket允许在本地运行的进程之间进行通信。分析从上面的图片可以看,unix socket减少了不必要的tcp开销,而tcp需要经过loopback,还要申请临时端口和tcp相关资源。但是
    03-08
  • [PHP8] 我参加了PHP8工程师认证初学者考试beta考试
    [PHP8] 我参加了PHP8工程师认证初学者考试beta
    前几天,2022/08/05,PHP工程师认证机构PHP8 技术员认证初级考试宣布实施考试将于 2023 年春季开始。和 beta 测试完成于 2022/09/11所以我收到了。一般社团法人BOSS-CON JAPAN(代表理事:Tadashi Yoshimasa,地点:东京都世田谷区,以下简称“BOSS-CON JAPAN
    03-08
  • 将 PHP Insights 放入旧版 PJ 不是很好吗?谈论
    将 PHP Insights 放入旧版 PJ 不是很好吗?谈论
    介绍在最近的PHP系统开发中,感觉故事在理所当然包含静态分析工具的前提下进行。我的周围现有代码很脏,我很久以前安装了工具,但几乎没有检查已经观察到许多这样的案例。 (这是小说。而不是像 0 或 100 这样不允许单行错误的静态分析,一点一点,逐渐我想介
    03-08
  • PHP基于elasticsearch全文搜索引擎的开发 php使
    1.概述:全文搜索属于最常见的需求,开源的 Elasticsearch (以下简称 Elastic)是目前全文搜索引擎的首选。Elastic 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的接口。Elastic 是 Lucene 的封装,提供了 REST API 的操作接
    02-09
  • php视图操作
    一、视图的基本介绍         视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。        使用视图需要MySQL5及以后的版本支持。        下面是视图的一些常见应用:        重用SQL语句;        简化复杂的S
    02-09
  • php中图像处理的常用函数 php图形图像处理技术
    php中图像处理的常用函数 php图形图像处理技术
    1.imagecreate()函数imagecreate()函数是基于一个调色板的画布。?php $im = imagecreate(200,80);                //创建一个宽200,高80的画布。$white = imagecolorallocate($im,225,35,180);     //设置画布的背景颜色imagegif($im);
    02-09
  • PHP安全之webshell和后门检测
    PHP安全之webshell和后门检测
    基于PHP的应用面临着各种各样的攻击:XSS:对PHP的Web应用而言,跨站脚本是一个易受攻击的点。攻击者可以利用它盗取用户信息。你可以配置Apache,或是写更安全的PHP代码(验证所有用户输入)来防范XSS攻击SQL注入:这是PHP应用中,数据库层的易受攻击点。防范
    02-09
  • php使用时间戳保存时间的意义 PHP获取时间戳
    时间戳记录的是格林尼治时间,使用date格式化的时候会根据你程序设置的不同时区显示不同的时间。如果使用具体时间,则还需要进行多一步转换。
    02-09
  • PHP 获取提交表单数据方法
    PHP $_GET 和 $_POST变量是用来获取表单中的信息的,比如用户输入的信息。PHP表单操作在我们处理HTML表单和PHP表单时,我们要记住的重要一点是:HTML页面中的任何一个表单元素都可以自动的用于PHP脚本:表单举例: htmlbodyform action="welcome.php" method
    02-09
点击排行