[BJDCTF2020]Easy MD5-学习笔记

📅 2026/8/2 4:51:06 👁️ 阅读次数 📝 编程学习
[BJDCTF2020]Easy MD5-学习笔记
  • 前置知识
    • 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 值
QNKCDZO0e830400451993494058024219903391
s878926199a0e545993274517709034328855841020
s155964671a0e342768416822451524974117254469
s214587387a0e848240448830537924465865611904

更简单的解法:数组绕过:

除了0e绕过,还有更通用的数组绕过方法:

在 PHP 中:

  • md5(数组)→ 返回NULL(因为md5()期望字符串参数,传入数组会报错但返回NULL
  • NULL == NULLtrue(松散比较)
  • 不同数组$a != $btrue

所以只需要传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'])NULL
  • md5($_POST['param2'])NULL
  • NULL === NULLtrue(类型相同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&param2[]=2

响应体中成功输出 Flag。