透过一道CTF题目学到的小知识点
字数 1163 2025-08-15 21:31:52
PHP浮点数精度问题与CTF题目解析
1. 题目概述
这道CTF题目展示了一个利用PHP浮点数精度问题绕过安全检测的案例。题目核心是一个PHP类trick,通过精心构造的浮点数可以绕过md5比较检查,从而获取flag。
2. 题目源码分析
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']);
3. 解题关键点
3.1 题目要求
trick1和trick2长度不超过5trick1 !== $trick2(类型和值不完全相同)md5($trick1) === md5($trick2)(MD5值相同)$trick1 != $trick2(值不相等)
3.2 浮点数精度问题
PHP(以及大多数编程语言)使用IEEE 754双精度格式表示浮点数,这会导致:
- 某些十进制小数无法精确表示为二进制浮点数(如0.1、0.7等)
- 浮点数运算会产生微小误差
- 序列化/反序列化过程中可能保留这些误差
3.3 利用方法
构造两个数值上"相等"但表示不同的浮点数:
$trick1 = 0.01;
$trick2 = 0.1 * 0.1; // 实际结果为0.010000000000000002
序列化后的结果:
O:5:"trick":2:{s:6:"trick1";d:0.01;s:6:"trick2";d:0.010000000000000002;}
3.4 为什么能绕过检测
- 字符串转换:
$this->trick1 = (string)$this->trick1将0.01转为"0.01" - 长度检查:两者字符串表示长度都不超过5
- MD5比较:PHP的MD5计算对浮点数会先转为字符串,而"0.01"和"0.010000000000000002"的MD5相同
- 值比较:原始值0.01和0.010000000000000002不严格相等
4. PHP浮点数精度详解
4.1 精度问题表现
- 十进制转二进制不精确(如0.1 → 0.1000000000000000055511151231257827021181583404541015625)
- 运算误差累积(如0.1+0.7 ≠ 0.8)
- 不同PHP版本序列化表现不同:
- PHP 5-7.0:序列化为0.100000000000001等形式
- PHP 7.1+:序列化为0.1等简洁形式
4.2 官方文档说明
PHP官方文档指出:
- 浮点数精度有限,最大相对误差约1.11e-16
- 复合运算会传递和放大误差
- 永远不要直接比较两个浮点数是否相等
5. 其他解法
5.1 使用INF(无穷大)
O:5:"trick":2:{s:6:"trick1";d:INF;s:6:"trick2";d:INF;}
5.2 使用NAN(非数字)
O:5:"trick":2:{s:6:"trick1";d:NAN;s:6:"trick2";d:NAN;}
这两种特殊浮点数值的MD5计算结果相同但值不严格相等。
6. 防御措施
- 避免直接比较浮点数,应使用误差范围比较
- 对关键比较使用
abs($a-$b) < $epsilon形式 - 考虑使用BC Math或GMP扩展进行高精度计算
- 对用户输入的浮点数进行规范化处理
7. 总结
这道CTF题目展示了:
- PHP浮点数精度问题的实际表现
- 序列化/反序列化过程中的精度保持
- 字符串转换对浮点数的影响
- 如何利用语言特性绕过安全检测
理解这些底层细节对于安全研究和漏洞挖掘至关重要。