
先分析一下代码:
php
highlight_file(__FILE__); // 把本文件源码高亮显示在页面上(所以能看到源码)
$res = ["hacker" => $_GET['cmd']]; // 取URL参数 ?cmd=xxx,放进数组,键固定为 hacker
$code = '<?php return ['; // 要写入的文件内容,开头固定
foreach ($res as $key => $value) {
$code .= "'".$key."'=>"."'".$value."',"; // 关键:把 value(=我们的cmd) 直接塞进单引号里,无任何转义
}
$code .= ']; '; // 文件内容结尾固定
$shell_name = md5(uniqid()).".php"; // 用微秒时间戳生成一个随机文件名
file_put_contents($shell_name, $code); // 把拼好的 $code 写成这个 .php 文件
var_dump($shell_name); // 把刚生成的随机文件名打印到页面
把 foreach 展开后,最终写入文件的内容是一个固定模板 ,VALUE 就是我们的 cmd:
php
<?php return ['hacker'=>'VALUE',];
漏洞就在这一行拼接 :程序以为 VALUE 只是个普通字符串值,于是用一对单引号 '...' 把它包起来;但它没有对 VALUE 里可能出现的单引号做转义 。所以只要我们的 cmd 里带 ',我们就能从 "字符串的值" 跳到 "PHP 代码层"。
把固定部分切成两半看:
- 前半段
P = <?php return ['hacker'=>' - 后半段
S = ',]; - 最终文件 = P + 我们的 cmd + S
情况 A:老老实实传 cmd=hello
php
<?php return ['hacker'=>'hello',];
就是一个返回数组的正常文件,访问它没有任何输出(return 的值不会打印)。
情况 B:传入 cmd= ' . F() . ' (F 代表任意函数)
php
<?php return ['hacker'=>'' . F() . '',];
PHP 读到的结构被改写成了:
| 片段 | PHP 的理解 |
|---|---|
['hacker'=> |
开始定义数组元素,键是 hacker |
'' |
一个空字符串 (我们用第一个 ' 关掉了原值的引号,又立刻开了个空串) |
. F() . |
用连接符把函数 F () 的返回值 接进来 → F 此刻被执行 |
'' |
再补一个空字符串(我们用最后那个 ' 重新对上了程序结尾的引号) |
,]; |
数组正常收尾,语法合法 |
于是整个文件语法完全正确,而 F () 在 "PHP 计算这个数组" 的过程中被强制执行------ 逃逸成功。
接下来我们可以用两个函数来执行。
通过scandir('/')取出根目录,得到一个数组,通过print_r()把数组展开显示到屏幕上。
所以传:
bash
?cmd='.print_r(scandir('/')).'

直接访问随机出来的文件名。

然后直接查看flag文件。

