ruoyi 解题步骤
ruoyiapi 遭到劫持,在一定条件下会触发跳转到外部链接。
附件:
反编译后在这个文件: com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.class
这个拦截器里有两个核心方法:preHandle() 和 preRedirect()。
跳转外链的逻辑就藏在这里。
Flag: flag{preRedirect}
某化工厂监控网络出现异常波动
你拿到了一份汇总的抓包文件。
工厂应急 Q1
问题: 谁把泵关了?
提交格式: flag{0xtransaction_id_0xfunction_code_0xcoil_address}
解题脚本:
# Extract Modbus/TCP Write Single Coil packet and get MBAP Transaction ID, Function Code, Coil Address
import struct, socket
path = "/mnt/data/challenge.pcap"
data = open(path, "rb").read()
def parse_pcap(data):
magic = struct.unpack("<I", data[:4])[0]
if magic == 0xa1b2c3d4:
endian = "<"; nano = False
elif magic == 0xd4c3b2a1:
endian = ">"; nano = False
elif magic == 0xa1b23c4d:
endian = "<"; nano = True
elif magic == 0x4d3cb2a1:
endian = ">"; nano = True
else:
raise SystemExit("Unknown pcap format")
offset = 24
packets = []
while offset + 16 <= len(data):
ts_sec, ts_usec, incl_len, orig_len = struct.unpack(endian + "IIII", data[offset:offset + 16])
offset += 16
pkt = data[offset:offset + incl_len]
offset += incl_len
if nano:
ts_usec = ts_usec / 1000.0
packets.append((ts_sec, ts_usec, pkt))
return packets
pkts = parse_pcap(data)
def parse_ipv4_tcp(pkt):
if len(pkt) < 54: return None
eth_type = struct.unpack("!H", pkt[12:14])[0]
if eth_type != 0x0800: return None
ver_ihl = pkt[14]
ihl = (ver_ihl & 0x0f) * 4
if (ver_ihl >> 4) != 4: return None
total_len = struct.unpack("!H", pkt[16:18])[0]
proto = pkt[23]
src = socket.inet_ntoa(pkt[26:30])
dst = socket.inet_ntoa(pkt[30:34])
if proto != 6: return None
off_ip = 14 + ihl
if len(pkt) < off_ip + 20: return None
tcph = pkt[off_ip:off_ip + 20]
srcp, dstp, seq, ack, off_flags, win, csum, urgp = struct.unpack("!HHIIHHHH", tcph)
doff = ((off_flags >> 12) & 0xF) * 4
off_tcp = off_ip + doff
payload = pkt[off_tcp:14 + total_len]
return {"src": src, "dst": dst, "sport": srcp, "dport": dstp, "payload": payload, "tcph": tcph}
records = []
for ts_sec, ts_usec, pkt in pkts:
info = parse_ipv4_tcp(pkt)
if not info: continue
pay = info["payload"]
if not pay or len(pay) < 12: continue
# MBAP header: 7 bytes, then PDU
# func code at byte 7 (0-based within payload)
func = pay[7]
if func == 5: # Write Single Coil
# coil address and value
addr = (pay[8] << 8) | pay[9]
val = (pay[10] << 8) | pay[11]
if val == 0 and addr in (13, 0x000d):
# parse transaction id (first 2 bytes big-endian)
trans_id = (pay[0] << 8) | pay[1]
protocol_id = (pay[2] << 8) | pay[3]
length = (pay[4] << 8) | pay[5]
unit_id = pay[6]
records.append({
"ts": ts_sec + ts_usec / 1e6,
"src": info["src"], "dst": info["dst"],
"trans_id": trans_id, "protocol_id": protocol_id, "length": length, "unit_id": unit_id,
"func": func, "addr": addr, "val": val
})
records结果:
[{'ts': 1741789589.614693,
'src': '192.168.10.20',
'dst': '192.168.10.30',
'trans_id': 6699,
'protocol_id': 0,
'length': 6,
'unit_id': 1,
'func': 5,
'addr': 13,
'val': 0}]Flag: flag{0x1a2b_0x05_0x000d}
工厂应急 Q2
问题: 被写入的 NodeId
在数据中出现: WriteRequest:NodeId=ns=2;s=Pump/SpeedSetpoint;Value=1200;
说明被写入的节点是: ns=2;s=Pump/SpeedSetpoint
Flag: flag{ns=2;s=Pump/SpeedSetpoint}
工厂应急 Q3
问题: 工程站域名解析结果
找出工程站域名 engws.plant.local 的 A 记录解析结果(目标 IP)。
Flag: flag{198.51.100.5}
工厂应急 Q4
问题: 确定 HMI(源:10.0.0.x)到工程站(目的:10.0.0.x)上首个成功发起的时间点。
提交格式: flag{YYYY-MM-DDTHH:MM:SSZ}(UTC,精确到秒)
之前拿到的是首个从 HMI (10.0.0.5) → 工程站 (10.0.0.8) 的 TCP 连接建立相关数据。
Flag: flag{2025-03-12T14:22:09Z}
工厂应急 Q5
问题: 在横向后不久,HMI 对工程站发起了 HTTP 请求。提交请求的 Host 与 URI,用下划线连接。
Flag: flag{engws.plant.local_/rpc}
京公网安备11010802044340号