- 前置知识
- 1、PHP 松散比较(`==` vs `===`)
- 2、 `0e` 绕过?
- 正文
- 一、代码审计
- md5函数知识点讲解
- 二、构造请求参数
- 三、PHP 松散比较(`==`)
- 四、构造数组实现MD5 绕过
- 五、PHP 严格比较(`===`)
- 1、为什么 `0e` 绕过在这里失效?
- 2、为什么数组绕过仍然有效?
- 六、构造数组实现MD5 绕过
前置知识
1、PHP 松散比较(==vs===)
PHP 有两种比较运算符:
==(松散比较):只比较值,会进行类型转换===(严格比较):同时比较值和类型,不会进行类型转换
2、0e绕过?
当一个字符串的 MD5 哈希值形如0eXXXXXXXXX(即0e开头,后面全是数字),PHP 的==会把它当作科学计数法处理:
0e123在科学计数法中等于0 × 10¹²³=0- 因此两个不同的
0e开头的 MD5 值用==比较,结果都为true(因为0 == 0)
经典0e碰撞对:
| 字符串 | MD5 值 |
|---|---|
QNKCDZO | 0e830400451993494058024219903391 |
s878926199a | 0e545993274517709034328855841020 |
s155964671a | 0e342768416822451524974117254469 |
s214587387a | 0e848240448830537924465865611904 |
更简单的解法:数组绕过:
除了0e绕过,还有更通用的数组绕过方法:
在 PHP 中:
md5(数组)→ 返回NULL(因为md5()期望字符串参数,传入数组会报错但返回NULL)NULL == NULL→true(松散比较)- 不同数组
$a != $b→true
所以只需要传a[]=1&b[]=2即可绕过。
正文
一、代码审计
访问目标首页http://ceb7428be7b64f06d52ca0af.http-ctf2.dasctf.com:80/,页面自动跳转到./leveldo4.php。
访问./leveldo4.php,响应头中包含一个提示:
Hint: select * from 'admin' where password=md5($pass,true)页面是一个密码输入框,提交的密码会经过md5($pass, true)处理后拼接到 SQL 查询中。
md5函数知识点讲解
md5($string, $raw_output)函数的第二个参数$raw_output:
- 默认
false:返回32 字符的十六进制字符串(如"d41d8cd98f00b204e9800998ecf8427e") - 设为
true:返回16 字节的原始二进制数据(可能包含不可见字符)
当md5($pass, true)的输出直接拼接到 SQL 查询时,如果原始二进制数据中包含单引号'等 SQL 特殊字符,就可能造成 SQL 注入。
因此,我们需要找一个字符串,它的 MD5 原始二进制值恰好构成 SQL 注入语句。经过前人验证,字符串ffifdyop的 MD5 原始二进制值为:
' or '6É]é®ãR^ìÿ在 SQL 中解析后等效于:
select*from'admin'wherepassword=''or'6É...'由于'6É...'不为假,'' or '6É...'结果为真,从而绕过密码验证。
其他可用字符串:
129581926211651571912466741651878684928→ MD5 raw:06da5430449f8f6f23dfc1276f722738- 但
ffifdyop是最经典、最简洁的 payload。
二、构造请求参数
向./leveldo4.php发送 GET 请求,参数为password=ffifdyop:
GET /leveldo4.php?password=ffifdyop HTTP/1.1 Host: ceb7428be7b64f06d52ca0af.http-ctf2.dasctf.com
响应成功,页面底部出现 JavaScript 重定向:
<script>window.location.replace('./levels91.php')</script>三、PHP 松散比较(==)
访问./levels91.php,页面 HTML 注释中泄露了 PHP 源码:
$a=$_GET['a'];$b=$_GET['b'];if($a!=$b&&md5($a)==md5($b)){// wow, glzjin wants a girl friend.条件要求:
$a != $b:两个值不相等(松散比较)md5($a) == md5($b):两个值的 MD5 哈希松散相等(==)
四、构造数组实现MD5 绕过
向./levels91.php发送 GET 请求,使用数组绕过:
GET /levels91.php?a[]=1&b[]=2 HTTP/1.1 Host: ceb7428be7b64f06d52ca0af.http-ctf2.dasctf.com响应中出现 JavaScript 重定向:
<script>window.location.replace('./levell14.php')</script>五、PHP 严格比较(===)
访问./levell14.php,页面直接显示了 PHP 源码:
<?phperror_reporting(0);include"flag.php";highlight_file(__FILE__);if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){echo$flag;}条件要求:
$_POST['param1'] !== $_POST['param2']:严格不相等(类型和值都不同)md5($_POST['param1']) === md5($_POST['param2']):MD5 哈希严格相等(===)
1、为什么0e绕过在这里失效?
===严格比较要求类型和值都相等:
"0e830400451993494058024219903391" === "0e545993274517709034328855841020"→false- 即使它们数值上都是 0,但字符串内容不同,严格比较会直接返回
false
2、为什么数组绕过仍然有效?
当 PHP 的md5()函数接收到数组参数时:
- PHP 会抛出警告(Warning),但仍然返回
NULL md5($_POST['param1'])→NULLmd5($_POST['param2'])→NULLNULL === NULL→true(类型相同NULL,值相同NULL)
同时:
$_POST['param1'] !== $_POST['param2']→true(两个不同的数组)- 因为
$a = array(0 => 1)和$b = array(0 => 2)是两个不同的数组对象
因此数组绕过对===同样有效!
六、构造数组实现MD5 绕过
向./levell14.php发送 POST 请求,使用数组绕过:
POST /levell14.php HTTP/1.1 Host: ceb7428be7b64f06d52ca0af.http-ctf2.dasctf.com Content-Type: application/x-www-form-urlencoded Content-Length: 21 param1[]=1¶m2[]=2响应体中成功输出 Flag。