审计时发现这篇课堂纪要被我漏掉了——欣课上讲的”有限字符 RCE”专题,是整期课里最 CTF 的一讲。补记如下。
0x01 题目模型:16 个字符怎么执行命令
一类经典 CTF 题:代码只执行 eval($_GET[1]) 之类,但你的命令总长度被限制在 16 字符以内(或者干脆禁用字母数字下划线)——system('ls') 都塞不下,怎么办。课堂给了三条路,按直接程度排序:
① 反引号执行:最直接。反引号在 shell 语义里就是命令替换,16 字符够写短命令:
`ls`;`nl *`; # 配合通配符缩短
② file_put_contents + 伪协议包含:第一条塞不下完整攻击时,用有限的字符写一个新文件,再用包含类漏洞执行它——把”一次短命令”拆成”写入 + 触发”两步,绕开长度限制:
file_put_contents('x.php','<?php eval($_GET[0]);'); # 拆成多次 GET 分批写
③ 临时文件 + 问号通配符:最优雅的一路,见 0x02。
0x02 通配符匹配临时文件:让 PHP 替你落地 webshell
核心认知:PHP 处理文件上传时,无论业务代码写什么,文件都会先落到临时目录(/tmp/phpXXXXXX,6 位随机字符),请求结束后才删除。
在”禁用字母数字下划线美元符”的极端限制下,用 ?(匹配任意单字符)构造 pattern 让 shell 帮我们找到并执行它:
. /???/??????????> # /tmp/phpXXXXXX = 8 个问号匹配 php+6位随机
两个技巧点(笔记原话):利用时间排序(ls -t 思路,最新的临时文件排最前)和通配符精准匹配(? 数量必须卡准 php + 6 位长度的总位数,用 [@-[] 一类字符区间排除干扰文件)。请求发出后立刻用第二个连接执行,抢在 PHP 清理临时文件之前——这就是经典的”条件竞速”。
0x03 preg_replace /e:\1 分组即代码执行
一个高危正则参数的历史漏洞:PHP 7.3 之前,preg_replace 的 /e 修饰符会把替换串当 PHP 代码执行,而替换串里的 \1 引用第一个捕获分组——如果分组内容是用户可控的,\1 被展开后就在执行用户的代码:
preg_replace('/(.*)/e', '\\1', $_GET['x']); // x=<?php system('whoami')?> → RCE
payload 里特意构造 \1 就是为了吃掉分组内容。Windows/Linux 环境在这个洞上没有本质区别,本地测一次即可。7.3+ 该修饰符已移除,但老系统(又是老系统)仍会遇到。
0x04 冷知识:GET 参数里的点号会变成下划线
PHP 处理 GET 请求时,参数名里的 . 和空格会被自动转成 _——?a.b=1 到后端是 $_GET['a_b']。写扫描器/利用脚本时如果参数名带点,按原名传永远收不到,这是查半天”参数没生效”的经典原因。
0x05 环境杂记
课堂顺带的两条运维经验:Ubuntu 长时间运行后 Docker/SSH 偶发失联,重启解决(运行时长导致的系统怪病,别死磕配置);preg_replace 这类历史特性测试时用 php -S 0.0.0.0:7788 起本地服务最快,注意目录下没有 index 文件会 404。
相关旧文
- 命令执行与代码执行
- 文件包含的高阶玩法(临时文件包含的完整版)
- SQL 注入绕过手册(正则回溯是同一族”打崩正则引擎”思路)