0x00 正文
先放出这道题的源码
<?php class trick{ public $trick1; public $trick2; public function __destruct(){ $this->trick1 = (string)$this->trick1; if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){ die("你太长了"); } if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){ echo file_get_contents("/flag"); } } }
highlight_file(__FILE__);
unserialize(_GET[trick]);
审计源码可以看到定义了一个trick类,trick1和trick2两个变量,当程序结束,__destruct()方法被调用
trick1被强转为字符串,接下来判断两个变量的长度,当两个变量的长度不大于5时进入下面的逻辑
接着往下,当trick1和trick2不相等并且md5值相等时才能输出flag
本质上是一道简单的题,但是关键点就在于如何绕过,让strick1被强转成字符串后的md5值还和strick2相等
那么重点来了
我们构造序列化的时候需要传的值为
trick1=0.01
trick2=0.1*0.1
为什么要传这两个值呢,别急,我们一点一点往下看
先看我构造序列化的代码
<?php class trick{ public $trick1; public $trick2; public function __destruct(){ $this->trick1 = (string)$this->trick1; if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){ die("你太长了"); } if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){ echo file_get_contents("/flag"); } } } $chr1sto = new trick(); $chr1sto->trick1=0.01; $chr1sto->trick2=0.1*0.1; $s = serialize($chr1sto); print($s);
按照我们之前分析的逻辑,当我们赋值了这两个变量,程序结束,调用析构方法,先将trick1强转为字符串
然后判断两个变量的长度是否大于5
先看一下我们序列化出的结果
O:5:”trick”:2:{s:6:”trick1″;d:0.01;s:6:”trick2″;d:0.010000000000000002;}
有意思的事情出现了
我们赋值的trick2=0.1*0.1反序列化后的结果为0.010000000000000002
按照正常的理解不应该是0.01吗
那为什么会这样呢
这其实是一个浮点数精度的问题
官方文档里是这么说的:
浮点数的精度
浮点数的精度有限。尽管取决于系统,PHP 通常使用 IEEE 754 双精度格式,则由于取整而导致的最大相对误差为 1.11e-16。非基本数学运算可能会给出更大误差,并且要考虑到进行复合运算时的误差传递。
此外,以十进制能够精确表示的有理数如 0.1 或 0.7,无论有多少尾数都不能被内部所使用的二进制精确表示,因此不能在不丢失一点点精度的情况下转换为二进制的格式。这就会造成混乱的结果:例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999999991118…。
所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。如果确实需要更高的精度,应该使用任意精度数学函数或者 gmp 函数。
通俗来说,像0.1,0.7,0.58之类的浮点数十进制值转换为二进制是一个无限长的值,也就是说没办法用二进制精准表示,这个是所有语言的通病,因为计算机底层就是这么规定的,所以会出现精度丢失的问题
以下情况会出现精度丢失的问题
- 十进制数的二进制表示形式可能不精确。
- 使用的数字之间类型不匹配(例如,混合使用浮点型和双精度型)
同样的,这样的浮点数在参与基本运算的时候也会出现精度丢失的情况
经过多次尝试后我发现
显示输出时候会显示为
这是因为php最大精度是14位
如果超出14位会衰减
看下面的例子
别看php现在把0.100000000000001当成了0.1
实际上数还是那个数
还要注意四舍五入的情况
理解了这些之后让我们回到题目
传入这两个值之后会进行strlen()和md5()的判断
这两个函数也受精度缺陷的影响,所以可以绕过这两个函数
这道题就这么出来了
另外,经过我多次尝试
在php5到php7.0的php版本中
如0.1,0.7这样的浮点数单独或经过运算,序列化后打印出的字符串是0.100000000000001,0.69999999999999996这样精度丢失的样子
特殊的是在php5.2.17中打印出来的序列化后的字符串有了更多的位数
而在php7.1中就会序列化为0.1和0.7
这到底是为什么我也没搞明白,有知道的师傅教教我Orz
0x01
文章的最后再加两个这道题的另外两种解法
第一种
两个变量的值都为INF
payload:
O:5:”trick”:2:{s:6:”trick1″;d:INF;s:6:”trick2″;d:INF;}
第二种
两个变量的值都为NAN
payload:
O:5:”trick”:2:{s:6:”trick1″;d:NAN;s:6:”trick2″;d:NAN;}
来源:freebuf.com 2020-08-27 16:45:17 by: Chr1sto
请登录后发表评论
注册