CTF实战:从零爆破Web25靶场种子与Token构造全解析
在CTF竞赛中,PHP伪随机数漏洞一直是Web安全赛道的经典考点。本文将带您深入ctf.show的Web25靶场,通过实战演示如何利用php_mt_seed工具逆向破解mt_srand种子值,并最终构造出正确的Token获取Flag。不同于简单的工具使用教程,我们将从攻击者视角完整还原"信息收集→随机数捕获→种子爆破→Token计算→Payload构造"的完整攻击链。
1. 靶场环境侦察与漏洞分析
打开Web25靶场首先看到的是一段PHP代码审计界面。通过system('cat /proc/version')可以获取服务器基础信息,但更关键的是以下代码段:
error_reporting(0); include("flag.php"); if(isset($_GET['r'])){ $r = $_GET['r']; mt_srand(hexdec(substr(md5($flag), 0,8))); $rand = intval($r)-intval(mt_rand()); if((!$rand)){ if($_COOKIE['token']==(mt_rand()+mt_rand())){ echo $flag; } }else{ echo $rand; } }这段代码揭示了三个关键点:
- 种子生成逻辑:
mt_srand(hexdec(substr(md5($flag), 0,8)))表明种子来源于Flag的MD5前8位十六进制转换 - 验证机制:需要满足两个条件:
- GET参数
r的值减去第一个mt_rand()结果等于0 - COOKIE中
token的值等于第二、第三个mt_rand()之和
- GET参数
- 信息泄露点:当
$rand不为0时,会直接输出差值,这将成为我们的突破口
使用Wappalyzer插件可以确认目标服务器运行的是PHP 7.4.x版本——这个信息对后续种子爆破至关重要,因为不同PHP版本的MT算法实现存在差异。
2. 获取初始随机数与种子爆破
根据代码逻辑,我们首先需要获取第一个mt_rand()的值。通过发送?r=0的GET请求:
GET /?r=0 HTTP/1.1 Host: web25.ctf.show服务器返回一个负数值,例如-123456789。这个值实际上是0 - mt_rand()的结果,因此第一个随机数就是123456789。
现在我们需要使用php_mt_seed工具爆破出生成这个随机数的种子。在Kali Linux中操作步骤如下:
# 编译安装php_mt_seed git clone https://github.com/openwall/php_mt_seed.git cd php_mt_seed make # 开始爆破随机数123456789 ./php_mt_seed 123456789爆破过程可能需要几分钟到几小时不等,取决于硬件性能。最终我们会得到几个可能的种子值,例如:
Found 0, trying 0x90000000 - 0xa0000000, speed 106225 seeds per second Found 0, trying 0x8c000000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cf00000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cf80000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfc0000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfe0000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cff0000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cff8000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cffc000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cffe000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfff000 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfff800 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfffc00 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfffe00 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cffff00 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cffff80 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cffffc0 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cffffe0 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfffff0 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfffff8 - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfffffc - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cfffffe - 0x8d000000, speed 106240 seeds per second Found 1, trying 0x8cffffff - 0x8d000000, speed 106240 seeds per second Seed = 0x8cffffff = 2363123205 (PHP 7.1.0+)注意:PHP版本不同会导致相同的种子产生不同的随机数序列。这就是为什么之前确认PHP版本如此重要。
3. Token计算与Payload构造
获得种子值2363123205后,我们可以预测后续的随机数序列。编写以下PHP脚本计算所需的Token值:
<?php mt_srand(2363123205); // 设置已知种子 $first_rand = mt_rand(); // 消耗第一个随机数(已通过r=0获取) $token = mt_rand() + mt_rand(); // 计算第二、第三个随机数之和 echo "Token: ".$token; ?>执行后得到Token值,例如3546321857。现在我们需要构造完整的HTTP请求:
首先发送GET请求获取正确的
r值(即第一个随机数):GET /?r=123456789 HTTP/1.1 Host: web25.ctf.show然后带上计算出的Token发送最终请求:
GET /?r=123456789 HTTP/1.1 Host: web25.ctf.show Cookie: token=3546321857
使用HackBar或Burp Suite重放这个请求,服务器将返回Flag内容。
4. 实战中的变体与防御措施
在实际CTF比赛中,这类题目可能会出现多种变体:
种子隐藏方式不同:
- 使用时间戳作为种子
- 使用固定字符串的哈希值
- 使用多个随机数的组合
验证逻辑变化:
- 要求多个连续随机数的特定组合
- 使用随机数作为加密密钥
- 随机数参与更复杂的运算
防御此类攻击的最佳实践包括:
- 使用
random_int()替代mt_rand() - 避免暴露随机数生成序列
- 对关键操作使用多重验证机制
- 定期更新随机数种子
5. 工具链扩展与自动化思路
对于需要批量处理的情况,可以编写自动化脚本整合整个流程:
import requests import subprocess # 第一步:获取初始随机数 response = requests.get("http://web25.ctf.show/?r=0") first_rand = abs(int(response.text)) # 第二步:爆破种子 process = subprocess.run(["./php_mt_seed", str(first_rand)], capture_output=True) seed = int(process.stdout.split()[-2], 16) # 提取种子值 # 第三步:计算Token php_code = f"""<?php mt_srand({seed}); mt_rand(); echo mt_rand() + mt_rand(); ?>""" token = subprocess.run(["php", "-r", php_code], capture_output=True).stdout # 第四步:获取Flag cookies = {"token": token.decode().strip()} response = requests.get(f"http://web25.ctf.show/?r={first_rand}", cookies=cookies) print(response.text)这个脚本完整实现了从信息收集到最终获取Flag的全自动化过程,在实战中可以显著提高效率。