第二届“湾区杯”网络安全大赛初赛writeup

第二届“湾区杯”网络安全大赛初赛writeup
Aurorp1g这里整理了一下第二届”湾区杯”网络安全大赛初赛的部分writeup。
Crypto
Cold Forge
FROST 门限签名的 nonce 高位泄漏攻击:从格攻击到解封发布包
结果:flag{bd9b026e-b58e-416c-bf8f-d815573c35a6}
1. 题目概述
题目给出两个文件:
| 文件 | 内容 |
|---|---|
frost_telemetry.json | 一台门限签名设备的公开 FROST transcript 导出,外加采集探针对每次签名会话有效 nonce 的量化观测值(高位泄漏) |
sealed_release.json | 用 ChaCha20-Poly1305 加密的发布内容 |
目标:恢复正确的阈值私钥,重建出确定性的发布签名,从而解封发布包。
攻击链全貌:
1 | 40 个 FROST transcript(每位签名者 20 个) |
2. 数据格式解析
2.1 frost_telemetry.json 关键字段
1 | { |
message_hex 解码为明文:Cold Forge release manifest v1: approve threshold recovery
2.2 单条 transcript 结构
1 | { |
关键观测:nonce_msb 是 38 个十六进制字符 = 152 bit。曲线阶是 256 位,丢弃低 104 位后恰好剩 256 − 104 = 152 位,与 discarded_low_bits: 104 完全吻合。也就是说,每个会话的有效 nonce 的高 152 位已知,只差低 104 位未知(外加 ±1 个桶的抖动)。
所有 40 条 transcript 经校验:
- 所有
D、E均在 secp256k1 曲线上; rho、challenge、z均在[0, n)内。
注意:每条 transcript 是独立会话(D、E 各不相同),且 signer1 与 signer2 的 challenge 无一相同,说明它们不构成同一份 FROST 聚合签名,而是被采集设备分别记录的不同会话——用途正是泄露各自私钥分片。
2.3 sealed_release.json
1 | { |
ciphertext 58 字节 = 42 字节密文 + 16 字节 Poly1305 MAC tag。AAD 字符串与遥测中 release_kdf 的域前缀 "cold-forge/release/v1" 一致,说明 KDF 与 AEAD 共用同一域分隔符。
3. FROST 门限签名背景
FROST(Flexible Round-Optimized Schnorr Threshold Signatures)是 Schnorr 签名的 (t, n) 门限变体。本题 t = n = 2。
对每个参与签名者 i:
- Round 1:随机生成两个 nonce
(d_i, e_i),广播承诺1
D_i = d_i·G , E_i = e_i·G
- Round 2:计算 binding factor(对全部承诺与消息的哈希)每个签名者的有效 nonce(即其贡献的”签名 nonce”)为
1
rho_i = H(commitments, ...)
聚合承诺为1
k_i = d_i + e_i·rho_i (mod n)
R = Σ_i (D_i + rho_i·E_i)。 - 计算全局 challenge
1
c = H(R || PK || m)
- 每个签名者用自己的私钥分片 s_i 和拉格朗日系数 λ_i 计算响应
1
2z_i = d_i + e_i·rho_i + λ_i·c·s_i
= k_i + λ_i·c·s_i (mod n) ← 核心方程 - 聚合得到最终签名
(R, z = Σ z_i)。
对本攻击最重要的单点事实:把 z_i = k_i + λ_i·c·s_i (mod n) 看成关于分片 s_i 的线性方程。每个会话泄漏了 k_i 的高位——这就把恢复分片问题变成了一个经典的可格攻击问题。
本题里每条 transcript 已直接给出 rho、challenge、z,且拉格朗日系数 λ 已知:
1 | λ1 = 2, λ2 = -1 (mod n) |
于是两个参与者用拉格朗日插值可合成群私钥:
1 | sk = λ1·s1 + λ2·s2 = 2·s1 − s2 (mod n) |
4. 泄漏建模:从观测到 Hidden Number Problem
4.1 单条方程欠定
对 signer i 的第 j 条 transcript:
1 | z_j = k_j + λ_i·c_j·s_i (mod n) |
已知:z_j、c_j、λ_i,以及 k_j 的高 152 位(记为 t_j = nonce_msb_j × 2^104)。
未知:s_i(256 位)、k_j 的低 104 位 u_j。
1 | k_j = t_j·2^104 + u_j , |u_j| ≤ 2^105 (含 ±1 桶抖动,见下) |
一条方程有两个未知量(s_i 和 u_j),欠定——单条 transcript 无法解出分片。
4.2 桶抖动的含义
泄漏模型 msb_quantisation_with_bucket_jitter 表示:探针把 nonce 按 2^104 为桶量化上报,且可能发生 ±1 个桶的偏差。因此真实 u_j = k_j − t_j·2^104 的范围不是 [0, 2^104) 而是 [−2^104, 2^105)。取安全上界 |u_j| < 2^105(实现中放宽到 2^106 以保证容错)。
4.3 化成 HNP
把核心方程改写成关于分片 s_i 的线性同余:
1 | z_j = k_j + λ_i·c_j·s_i (mod n) |
记
1 | a_j = λ_i·c_j (mod n) |
则
1 | a_j·s_i − b_j ≡ −u_j (mod n) , |u_j| < 2^105 |
这正是经典的 Hidden Number Problem(HNP,隐数问题):给定若干条 a_j·x ≡ b_j + e_j (mod n),其中误差 e_j 很小,求未知大数 x。
每条 transcript 提供 152 − 104 = 48 位”净信息”?更准确地说:高位已知部分(152 位)越多,越容易恢复 256 位私钥。20 条 transcript 提供的约束远超恢复一个 256 位数的需求(经验规则:总已知位数 20 × 152 ≈ 3040 ≫ 256),格攻击必然可行。
5. 格攻击求解 HNP(CVP-embedding + LLL)
5.1 CVP 视角
把误差项挪到等式右边:
1 | a_j·s_i − k'_j·n = b_j + u_j (k'_j 为商) |
写成向量形式(记 u = (u_1,...,u_n),b = (b_1,...,b_n)):
1 | s_i·(a_1,...,a_n) − Σ_j k'_j·(n·e_j) = b + u |
左边属于由向量 {n·e_j} 与 a = (a_1,...,a_n) 张成的格 L'。因此:
b距离某个格点p ∈ L'很近,且p − b = u是一个小向量。
这是 Closest Vector Problem(CVP,最近向量问题)。求出 p 后,任意一分量 p_1 = a_1·s_i − k'_1·n,故
1 | s_i = p_1 · a_1^{-1} (mod n) |
5.2 用 Embedding 把 CVP 转成 SVP
直接解 CVP 比较麻烦,标准做法是 embedding(嵌入法):把目标向量 b 连同一个大权重塞进格的一维,把 CVP 变成高一维的 SVP。
构造格 L'' ⊂ Z^{n+1},行向量为:
1 | g_j = (0, ..., n, ..., 0, 0) // 第 j 个坐标 = n,共 n 个 |
考察整数组合 s_i·g_a − g_b − Σ_j m_j·g_j,其中 m_j 选成把前 n 个坐标”归约到中心余数”:
1 | = (u_1, ..., u_n, −C) |
这个向量属于格,且其模长:
1 | ||v|| ≈ sqrt( n·(2^105)² + C² ) |
取 C = 2^104,则 ||v|| ≈ 2^106。
为什么一定能被 LLL 找到? 看格的协体积(covolume):
- 前 n 维子格
{x·a + n·Z^n}的协体积 ≈n^(n−1) = 2^(256·(n−1)); - 最后一维是
C·Z,贡献因子C。
所以 det(L'') ≈ C·n^(n−1) = 2^104·2^(256·19)(n=20 时)。高斯启发式最短向量长度:
1 | λ ≈ sqrt((n+1)/2πe) · det(L'')^(1/(n+1)) ≈ 2^238 |
而我们的目标向量模长只有 ~2^106,比高斯界短约 2^130 倍——它是格内压倒性的最短向量,LLL 约简后必然出现在基中。
5.3 从短向量恢复分片(符号坑)
LLL 输出基后,筛选满足下面条件的行:
1 | |last| == C 且 前 n 个坐标绝对值 < 2^106 |
此时前 n 个坐标就是 ±u。由于 LLL 可能找到 (u, −C) 也可能找到 (−u, +C)(即对应 s_i 或 −s_i 的组合),必须同时尝试两种符号:
1 | p_0 = (b_0 ± u_0) (mod n) |
最后用全部 20 条方程验证候选 x:
1 | 对每个 j: centered(a_j·x − b_j) < 2^106 |
只有真正满足全部方程(且能对上群公钥)的才是正确分片。这一步验证同时天然排除了 LLL 可能给出的”伪短向量”。
5.4 实现要点(fpylll)
1 | from fpylll import IntegerMatrix, LLL |
6. 分片恢复结果与三重验证
对 signer 1 和 signer 2 分别跑上面的 HNP 求解,各用 20 条 transcript,得到:
1 | s1 = 4acb7a4deb36fc64ad41e8a3e373b682f035e18b29aea6336aed739be5a6eeb6 |
合成群私钥:
1 | sk = 2·s1 − s2 (mod n) |
三重验证全部通过:
- 群公钥匹配:
sk·G == group_public✓ —— 这是最关键的外部校验,证明恢复的私钥就是签发群公钥背后的阈值密钥; - 插值一致性:
λ1·s1 + λ2·s2 ≡ sk (mod n)✓; - 逐 transcript 自洽:对每个 transcript 用分片反推有效 nonce其高 152 位与
1
k_j = z_j − λ_i·c_j·s_i (mod n)
nonce_msb的偏差(桶数)40/40 全部 ≤ 1,正好落在泄漏模型声称的抖动范围内 ✓。
7. 重建确定性 BIP340 签名(含关键坑)
7.1 BIP340 签名流程
恢复出 sk 后,需要重建签名。遥测声明:
1 | "release_kdf": "SHA256('cold-forge/release/v1' || bip340_signature)" |
即解封密钥由与封存时完全一致的签名派生。签名必须是确定性的(README 中”确定性的恢复签名”),因此采用 BIP340 的标准确定形式:aux_rand = 0x00×32(32 个零字节)。
BIP340 签名(sk 私钥、m 消息、a 辅助随机数):
1 | d' = sk |
7.2 关键坑:nonce preimage 用的是公钥不是私钥
很多自行实现 BIP340 的人会把 nonce 哈希写错。规范原文(BIP-0340 Default Signing):
rand = hash_BIP0340/nonce(t || bytes(P) || m)
nonce preimage 是 t || bytes(P) || m,其中 bytes(P) 是公钥的 x 坐标(32 字节),不是私钥 bytes(d)!
设计动机:把公钥纳入 nonce 哈希,可防止公钥计算被篡改/调用方传错时泄漏私钥。
排错经验:初版实现误用了 bytes(d),导致签名与官方测试向量全部不符;用 BIP340 官方 test-vectors.csv(bip-0340/test-vectors.csv)逐条对拍后定位到该差异,改为 bytes(P) 后 4 条向量的 match=True。这道题如果不修正此处,永远解不开密文。
7.3 验证签名
用 BIP340 验证算法校验(lift R、计算 e、检查 s·G == R + e·P),签名通过:
1 | sig = d3d4ced5a8b7b334f7a1baaaf71fe68a9b2bb02f218aa39cfa0d978fd5fc108b |
8. KDF 派生与 ChaCha20-Poly1305 解封
8.1 派生密钥
1 | key = SHA256( "cold-forge/release/v1" || sig ) |
域前缀与 AAD 完全一致(都是 "cold-forge/release/v1"),作为域分隔符防止跨用途密钥复用。
1 | key = bd8667881a9b9903b8008f693ac80ef43a8bce95c042ad22fbe1ad24e7e641e3 |
8.2 AEAD 解密
sealed_release.json 给出:
nonce= 12 字节(base64 解码UdfpASn/7RhHRhS4)aad="cold-forge/release/v1"ciphertext= 58 字节 = 42 字节密文 + 16 字节 Poly1305 tag
1 | from Crypto.Cipher import ChaCha20_Poly1305 |
MAC 校验通过,明文为:
1 | flag{bd9b026e-b58e-416c-bf8f-d815573c35a6} |
9. 完整可复现代码
单脚本、自包含(依赖 fpylll、pycryptodome),从两个 JSON 直接输出 flag:
1 | pip install fpylll cysignals pycryptodome |
1 | #!/usr/bin/env python3 |
运行输出:
1 | s1 = 4acb7a4deb36fc64ad41e8a3e373b682f035e18b29aea6336aed739be5a6eeb6 |
10. 漏洞本质与防护建议
10.1 漏洞本质
本挑战是基于 nonce 偏置的格攻击的完整实战。攻击成立依赖两个条件叠加:
- 签名者的有效 nonce 被侧信道量化泄漏:每个会话只泄漏 152/256 位,看似”很多位还安全”,实则 20 个会话累积的线性约束足以在格上恢复 256 位私钥分片;
- FROST 的分片可被线性方程外推:
z = k + λ·c·s一旦 nonce 部分可估,私钥分片就暴露;两个分片再经公开的拉格朗日系数直接合成群私钥。
这再次印证密码学中的铁律:nonce 必须是均匀随机且不可预测的,哪怕只泄漏最高位也足以致命。经典参考攻击包括对 Schnorr/ECDSA 的 HNP 攻击(Howgrave-Graham & Smart;Boneh & Venkatesan 的 HNP 框架;以及针对区块链签名 nonce 偏置的多次实际窃币事件)。
10.2 防护建议
- nonce 生成必须恒定时间、随机、且绝不泄漏任何位;使用 RFC 6979 确定性 nonce 时也要保证实现正确且无侧信道;
- 门限签名设备加装防侧信道硬件(如恒定时间运算、掩码),并定期做泄漏评估;
- 不要让采集/遥测数据包含 nonce 的量化观测——本题的”遥测探针”本身就是安全隐患;
- 密钥轮换与检测:若怀疑存在 nonce 泄漏,应立即轮换分片并全网升级;
- 验证 BIP340 等实现时务必用官方测试向量对拍(本次正是靠 test-vectors.csv 定位到 nonce preimage 用公钥而非私钥的实现错误)。
题解完毕。最终 flag:flag{bd9b026e-b58e-416c-bf8f-d815573c35a6}
TinyNTRU
1. 题目概述
题目提供了一个基于 NTRU 的简化加密方案,命名为 TinyNTRU。我们获得了以下文件:
challenge.py:生成挑战的脚本,读取 flag、生成公私钥、加密 flag,并输出public_key.txt和output.txt。ntru.py:核心 NTRU 加密/解密实现。public_key.txt:包含公钥参数h(多项式)以及系统参数N, p, q等。output.txt:包含加密后的密文列表ciphertexts。
目标:利用已知的公钥和密文,恢复私钥,解密密文得到 flag。
2. 参数分析
从 public_key.txt 中提取关键参数:
N = 127p = 3q = 12289- 公钥多项式
h(127 个系数)
在 ntru.py 中,私钥生成方式如下:
1 | F = sample_sparse_ternary(N, df, rng, avoid_zero=True) # df=7, 系数 ±1,非零 |
因此:
f的常数项为 1,另外 7 个位置是±3,总共有 8 个非零系数。g有 7 个非零系数,均为±3。
私钥向量 (f, g) 的欧几里得范数:
对于 NTRU 格,其维度为 2N = 254,高斯启发式估计的最短向量长度约为:
私钥向量的长度远小于高斯期望,因此它构成了格中的极端短向量,使用 LLL 格基约简即可轻松恢复。
3. NTRU 加密与解密简述
- 加密:随机选择稀疏多项式
r(系数 ±1,dr=6个非零),计算密文e = r * h + m (mod q),其中m是消息多项式(系数在 {0,1,2},即三进制表示)。 - 解密:计算
a = f * e (mod q),然后将系数中心化到(-q/2, q/2],再对p=3取模得到m。
正确性依赖于 f * h ≡ g (mod q),从而 f*e ≡ r*g + f*m (mod q),由于 r 和 g 很小,乘积的系数远小于 q,所以取模后中心化可消除噪声。
4. 攻击原理:NTRU 格与最短向量问题
定义循环卷积多项式环。公钥 h 满足:
等价于存在整数多项式 k 使得:
因此向量 (f, g) 属于格:
该格的一个基可以构造为:
其中 H 是 h 的循环矩阵(H[i][j] = h[(j-i) mod N])。格中的任意向量 (a, b) 满足 a = q u + b h(模意义),且 b 的系数可任意。对于短向量 (g, -f),我们有:
故 (g, -f) 属于该格。另一种常用格基为:
其短向量为 (f, g)。
由于 (g, -f) 的长度极小,LLL 约简可以快速找到它。
5. 恢复私钥的步骤
5.1 构造循环矩阵 H
给定公钥系数 h[0..N-1],构造 N×N 矩阵 H,使得对于任意向量 v,H * v 等于循环卷积 h * v(系数按指数模 N 相加)。具体:
1 | H[i][j] = h[(j - i) mod N] |
这样 H * v 的第 i 个分量为,正是卷积。
5.2 构造格基
使用第一种形式:
这是一个 2N × 2N 的整数矩阵。
5.3 LLL 约简
在 SageMath 中,直接调用 B.LLL(),得到约简基。遍历所有行,检查第二部分(后 N 个系数)是否构成候选 f,即满足:
f[0] = ±1(常数项为 1,取负号也可以,因为-f也满足条件)- 除
f[0]外,其他系数均为0或±3 - 非零系数总数正好为 8(即
1 + df) - 计算
g = f * h mod q,将系数中心化到[-q/2, q/2],检查其是否恰有 7 个非零系数且均为±3
只有同时满足这两点的候选才是真正的私钥。
5.4 解密
有了 f,对每个密文块 c 进行解密:
1 | t = f * c mod q |
然后将三进制系数块转换为字节,最后拼接得到 flag。
6. 实战脚本
使用 SageMath 实现上述过程。脚本 solve.sage 如下(关键部分已加注释):
1 | #!/usr/bin/env sage |
7. 运行结果
执行脚本后输出:
1 | [+] Loaded 2 ciphertext blocks |
Misc
SilentConfig
1. 题目背景
题目描述
安全团队在巡检中发现数据库出现异常查询行为,但线上日志只保留了部分片段。攻击者可能通过 Web 接口读取了某个敏感业务配置。请关联分析附件中的日志,恢复攻击者最终获得的配置值。
Flag 格式:flag{uuid}
附件内容:一份 MySQL 查询日志(片段),记录了攻击者利用 SQL 盲注逐字符窃取 system_setting 表中 sync_token 配置值的过程。
2. 日志格式分析与盲注原理
日志中每条记录形如:
1 | 2026-07-18T10:43:01.060+08:00 ... rows_sent=1 query="...ORD(MID((SELECT value FROM system_setting WHERE name='sync_token'),{位置},{长度}))>{阈值}..." |
关键字段:
rows_sent=1:表示查询返回了 1 行,即WHERE条件为 真 → 当前字符的 ASCII 码 大于 阈值。rows_sent=0:表示查询返回 0 行,即条件为 假 → 当前字符的 ASCII 码 小于或等于 阈值。
攻击者通过不断调整阈值,利用二分查找法确定每个字符的精确 ASCII 值,从而还原整个 sync_token 字符串。
攻击目标:sync_token 是一个标准 UUID(v4)格式,共 36 个字符,其中第 9、14、19、24 位是连字符 -。有效字符集为 0-9(ASCII 48-57)和 a-f(ASCII 97-102),以及连字符(ASCII 45)。
3. 数据提取与分组
从日志中提取所有包含 sync_token 的查询,并按位置(1~36)分组。每个位置通常有多次测试,对应不同的阈值。例如,位置 1 出现如下记录:
1 | ... >53 rows_sent=1 |
这些记录共同决定了该字符的最终 ASCII 值。
4. 位置解析方法
以 位置 1 为例:
>53→ 真,即 ASCII > 53>51→ 真,即 ASCII > 51>55→ 假,即 ASCII ≤ 55>54→ 真,即 ASCII > 54
结合分析:ASCII 必须 > 54 且 ≤ 55,因此唯一可能为 55,对应字符 '7'。
当测试序列不完整时,可能需要利用上下界推断。例如,位置 2 仅出现 >101 为真,而 UUID 字符的最大 ASCII 为 102(f),因此可推断该字符为 102(f)。
5. 所有位置的解析结果
经过对日志中每条记录的二分推导,得到下表(? 表示无法唯一确定):
| 位置 | 日志推断逻辑 | 确定 ASCII | 字符 |
|---|---|---|---|
| 1 | >55假(≤55), >54真(≥55) → 55 | 55 | 7 |
| 2 | >101真,最大102 → 102 | 102 | f |
| 3 | >57真(≥58), >97假(≤97), 且≥58 ≤97 且非数字 → 97 | 97 | a |
| 4 | >53假(≤53), >52假(≤52), >51真(≥52) → 52 | 52 | 4 |
| 5 | >98真(≥99), >99假(≤99) → 99 | 99 | c |
| 6 | >97真(≥98), >98假(≤98) → 98 | 98 | b |
| 7 | >49真(≥50), >50假(≤50) → 50 | 50 | 2 |
| 8 | >99真(≥100), >100假(≤100) → 100 | 100 | d |
| 9 | UUID 固定连字符 | 45 | - |
| 10 | >52真(≥53), >53假(≤53) → 53 | 53 | 5 |
| 11 | >100真(≥101), >101假(≤101) → 101 | 101 | e |
| 12 | >56真(≥57), >57假(≤57) → 57 | 57 | 9 |
| 13 | >57真(≥58), >97假(≤97) → 97 | 97 | a |
| 14 | UUID 固定连字符 | 45 | - |
| 15 | 日志完全缺失该位置的测试 | 缺失 | ? |
| 16 | >99真(≥100), >100假(≤100) → 100 | 100 | d |
| 17 | >53真(≥54), >54假(≤54) → 54 | 54 | 6 |
| 18 | >53真(≥54), >54假(≤54) → 54 | 54 | 6 |
| 19 | UUID 固定连字符 | 45 | - |
| 20 | >57真(≥58), >97真(≥98), 缺少更高阈值测试 | 98~102 | b/c/d/e/f? |
| 21 | >55真(≥56), >56假(≤56) → 56 | 56 | 8 |
| 22 | >101真,最大102 → 102 | 102 | f |
| 23 | >48真(≥49), >49假(≤49) → 49 | 49 | 1 |
| 24 | UUID 固定连字符 | 45 | - |
| 25 | >50真(≥51), >51假(≤51) → 51 | 51 | 3 |
| 26 | >98真(≥99), >99假(≤99) → 99 | 99 | c |
| 27 | >56真(≥57), >57假(≤57) → 57 | 57 | 9 |
| 28 | >49真(≥50), >50假(≤50) → 50 | 50 | 2 |
| 29 | >54真(≥55), >55假(≤55) → 55 | 55 | 7 |
| 30 | >48假(≤48), 最小 ASCII 为 48 → 48 | 48 | 0 |
| 31 | >57真(≥58), >97假(≤97) → 97 | 97 | a |
| 32 | >99真(≥100), >100假(≤100) → 100 | 100 | d |
| 33 | >52真(≥53), >53假(≤53) → 53 | 53 | 5 |
| 34 | >48真(≥49), >49假(≤49) → 49 | 49 | 1 |
| 35 | >100真(≥101), >101假(≤101) → 101 | 101 | e |
| 36 | >51真(≥52), >52假(≤52) → 52 | 52 | 4 |
由此得到不完整的 UUID 模板:
1 | 7fa4cb2d-5e9a-?d66-?8f1-3c9270ad51e4 |
其中:
- 第 15 位:
?—— 日志完全缺失该位置的盲注请求,无法直接获得。 - 第 20 位:
?—— 仅知道 ASCII ≥ 98,但缺少>98、>99、>100、>101、>102的测试,故候选字符为b(98)、c(99)、d(100)、e(101)、f(102)。
6. 利用 UUID v4 规范补全缺失位
目标 sync_token 是标准 UUID,遵循 RFC 4122 规范。对于 UUID 版本 4(随机生成)而言:
- 第 15 位(即
time_hi_and_version字段的高半字节)固定为4,表示版本号。 - 第 20 位(
clock_seq_hi_and_reserved字段的高两位)称为变体,必须为8、9、a或b,但最常见的变体是8、9、a、b中的前三位被保留,实际随机生成时,该位固定为8、9、a或b之一。然而我们已知该字符 ASCII ≥ 98,而8(56)、9(57)、a(97) 均不满足 ≥98,因此唯一剩余候选是b(98)。
因此,缺失的两处可以唯一确定为:
- 位置 15 →
4 - 位置 20 →
b
完整 UUID 为:
1 | 7fa4cb2d-5e9a-4d66-b8f1-3c9270ad51e4 |
7. 最终 Flag
根据题目要求的格式,提交:
1 | flag{7fa4cb2d-5e9a-4d66-b8f1-3c9270ad51e4} |
8. 总结与反思
- 本题考察了对 SQL 盲注日志的分析能力,特别是理解
rows_sent的真假含义,并逆向推导出字符的 ASCII 值。 - 日志不完整是常见情况,需要结合已知的格式规范(如 UUID 标准)补全缺失信息。
- 如果忽略 UUID 的固定位,将无法得到唯一的答案,这也突显了在安全分析中利用领域知识的重要性。
- 攻击者利用二分查找法,只需约 log₂(95) ≈ 7 次请求即可确定一个字符,整个窃取过程在数分钟内完成,提醒我们在真实环境中应对此类盲注漏洞采取严格的输入过滤和异常监控措施。
解题工具:可编写简单的脚本解析日志,自动分组并推导 ASCII,但本题手动分析亦可,关键在于细致与逻辑严密。










