透过一道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 题目要求

  1. trick1trick2长度不超过5
  2. trick1 !== $trick2(类型和值不完全相同)
  3. md5($trick1) === md5($trick2)(MD5值相同)
  4. $trick1 != $trick2(值不相等)

3.2 浮点数精度问题

PHP(以及大多数编程语言)使用IEEE 754双精度格式表示浮点数,这会导致:

  1. 某些十进制小数无法精确表示为二进制浮点数(如0.1、0.7等)
  2. 浮点数运算会产生微小误差
  3. 序列化/反序列化过程中可能保留这些误差

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 为什么能绕过检测

  1. 字符串转换$this->trick1 = (string)$this->trick1将0.01转为"0.01"
  2. 长度检查:两者字符串表示长度都不超过5
  3. MD5比较:PHP的MD5计算对浮点数会先转为字符串,而"0.01"和"0.010000000000000002"的MD5相同
  4. 值比较:原始值0.01和0.010000000000000002不严格相等

4. PHP浮点数精度详解

4.1 精度问题表现

  1. 十进制转二进制不精确(如0.1 → 0.1000000000000000055511151231257827021181583404541015625)
  2. 运算误差累积(如0.1+0.7 ≠ 0.8)
  3. 不同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. 防御措施

  1. 避免直接比较浮点数,应使用误差范围比较
  2. 对关键比较使用abs($a-$b) < $epsilon形式
  3. 考虑使用BC Math或GMP扩展进行高精度计算
  4. 对用户输入的浮点数进行规范化处理

7. 总结

这道CTF题目展示了:

  1. PHP浮点数精度问题的实际表现
  2. 序列化/反序列化过程中的精度保持
  3. 字符串转换对浮点数的影响
  4. 如何利用语言特性绕过安全检测

理解这些底层细节对于安全研究和漏洞挖掘至关重要。

PHP浮点数精度问题与CTF题目解析 1. 题目概述 这道CTF题目展示了一个利用PHP浮点数精度问题绕过安全检测的案例。题目核心是一个PHP类 trick ,通过精心构造的浮点数可以绕过md5比较检查,从而获取flag。 2. 题目源码分析 3. 解题关键点 3.1 题目要求 trick1 和 trick2 长度不超过5 trick1 !== $trick2 (类型和值不完全相同) md5($trick1) === md5($trick2) (MD5值相同) $trick1 != $trick2 (值不相等) 3.2 浮点数精度问题 PHP(以及大多数编程语言)使用IEEE 754双精度格式表示浮点数,这会导致: 某些十进制小数无法精确表示为二进制浮点数(如0.1、0.7等) 浮点数运算会产生微小误差 序列化/反序列化过程中可能保留这些误差 3.3 利用方法 构造两个数值上"相等"但表示不同的浮点数: 序列化后的结果: 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(无穷大) 5.2 使用NAN(非数字) 这两种特殊浮点数值的MD5计算结果相同但值不严格相等。 6. 防御措施 避免直接比较浮点数,应使用误差范围比较 对关键比较使用 abs($a-$b) < $epsilon 形式 考虑使用BC Math或GMP扩展进行高精度计算 对用户输入的浮点数进行规范化处理 7. 总结 这道CTF题目展示了: PHP浮点数精度问题的实际表现 序列化/反序列化过程中的精度保持 字符串转换对浮点数的影响 如何利用语言特性绕过安全检测 理解这些底层细节对于安全研究和漏洞挖掘至关重要。