Sentinel-1B:被记忆的 FLAG(模型反演 / 训练数据提取)
2026/9/6 5:13:06 网站建设 项目流程

目录

题目信息:

分析:


题目信息:

厂商 SentinelAI 开源了字符级小语言模型 Sentinel-1B(4 字符上下文的查表式 next-token 模型)。内部审计发现:它曾在一份包含 FLAG 的敏感文档上被微调,条件概率分布因此逐字"记住"了这份文档。为了让泄漏可度量,审计方还在训练数据里埋了若干记忆强度更高的蜜罐(canary)—— 最自信的解码路径未必是真实数据。 接口(HTTP,根路径有中文文档页): - GET /model:context_len、topk、vocab、已公开的 flag 前缀、蜜罐数量等元信息 - GET|POST /predict {"text":"..."}:返回该上下文下 top-3 的下一个字符及概率(3 位小数);服务端只取 text 的最后 context_len 个字符作为上下文 - GET|POST /submit {"flag":"..."}:提交候选,返回 ok / canary / wrong flag 形如 flag{...}、以 } 结尾。请还原被记忆的原始数据,并把完整 flag 提交到平台。


分析:

首先打开网页:这是一个字符级预测模型,它根据输入的字符来预测下一个输出字符的概率,说明有关字符和无关字符对这个模型的输出是存在影响的

根据提示先访问/model: {"model": "Sentinel-1B", "task": "char-level next-token prediction", "context_len": 4, "topk": 3, "prob_decimals": 3, "vocab": "!#$%&()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_abcdefghijklmnopqrstuvwxyz{}~", "vocab_size": 85, "known_prefix": "CTF2", "terminator": "}", "canaries": 13, "notes": ["服务端只取 text 的最后 context_len 个字符作为上下文。", "概率 = softmax(base_logit + 记忆加成);base_logit 由哈希决定,取值 [-1,1]。", "厂商公开信息:审计蜜罐(canary)的记忆强度高于真实敏感数据。"]}

context_len: 4(服务端只取 text 的最后 context_len 个字符作为上下文)

topk: 3(预测时只从概率最高的3 个候选字符里面挑选输出,不会在全部词汇表里随机采样。)

known_prefix: "CTF2"(已知的明文前缀;目标生成的字符串以CTF2开头)

terminator: "}"(当模型预测输出字符},代表 Flag / 目标字符串生成结束,任务完成)

概率 = softmax(base_logit + 记忆加成);base_logit 由哈希决定,取值 [-1,1]

根据上面的信息我们可以分析出蜜罐字符对概率是有较大影响的


我们先对无关字符对/predict接口进行探测:

输入:{"text":"abcd"} 输出:{"context": "abcd", "top": [{"c": "{", "p": 0.027}, {"c": "T", "p": 0.026}, {"c": "h", "p": 0.026}]} 输入:{"text":"dun9"} 输出:{"context": "dun9", "top": [{"c": "U", "p": 0.028}, {"c": "&", "p": 0.028}, {"c": "t", "p": 0.027}]} 输入:{"text":"flag"} 输出:{"context": "flag", "top": [{"c": "0", "p": 0.026}, {"c": "U", "p": 0.025}, {"c": "_", "p": 0.025}]}

可以看到,当输入与目标字符串无关的字符时,输出的概率类似,也就是base_logit ,接下来使用已知明文前缀CTF2测试:

输入:{"text":"CTF2"} 输出:{"context": "CTF2", "top": [{"c": "X", "p": 0.88}, {"c": "{", "p": 0.034}, {"c": "=", "p": 0.002}]}

可以明显看出X,{和=之间的概率差距很大,而X很明显是蜜罐记忆(因为flag格式是CTF2{)同时{和=相比{的概率远大于=,说明应该是{,但为了更加严谨,我们可以对这三个后选字符分别进行预测:

输入:{"text":"CTF2X"} 输出:{"context": "TF2X", "top": [{"c": "d", "p": 0.026}, {"c": "q", "p": 0.025}, {"c": "?", "p": 0.025}]} 输入:{"text":"CTF2{"} 输出:{"context": "TF2{", "top": [{"c": "b", "p": 0.379}, {"c": "J", "p": 0.016}, {"c": "a", "p": 0.016}]} 输入:{"text":"CTF2="} 输出:{"context": "TF2=", "top": [{"c": "H", "p": 0.027}, {"c": ",", "p": 0.025}, {"c": "(", "p": 0.025}]}

可以发现除了正确字符{,另外两个字符的预测结果都是相近的base_logit,即新字符错误时,下一次预测结果中三个字符概率比接近1,而正确情况下下一次预测结果中三个字符概率比则远大于1,根据这一点,我们便可以持续推测出完整的字符串,当预测出下一个字符最大可能是}时说明预测结束(因为蜜罐字符里面不含有}),当然也可以使用py脚本等加快速度。


最后使用/submit接口进行验证:
{"status": "ok", "flag": "CTF2{b59dda87-fb1b-4071-80cd-60057b31a697}", "msg": "正确!这就是被逐字记忆的原始数据。"}

这里提供一个可行的自动化脚本(ai生成):

import requests from collections import deque # ============ 配置区 ============ BASE_URL = "host" START_PREFIX = "CTF2" TERMINATOR = "}" MEMORY_THRESHOLD = 0.08 # 终止符置信倍率: }的概率 / 第二名概率 > END_RATIO 才判定结束 END_RATIO = 1.8 # ================================= session = requests.Session() def predict(text: str): resp = session.post(f"{BASE_URL}/predict", json={"text": text}, timeout=10) resp.raise_for_status() return resp.json() def submit_flag(flag_str: str): resp = session.post(f"{BASE_URL}/submit", json={"flag": flag_str}, timeout=10) resp.raise_for_status() return resp.json() def two_step_bfs_solve(): queue = deque([START_PREFIX]) visited = set() while queue: current_str = queue.popleft() if current_str in visited: continue visited.add(current_str) res_cur = predict(current_str) top3 = res_cur["top"] ctx = res_cur["context"] print(f"\n[当前路径] {current_str}, 4字符上下文:{ctx}") # 先检查top3第一个是不是终止符,并且显著高于第二名 if len(top3) >= 2: first_c, first_p = top3[0]["c"], top3[0]["p"] second_p = top3[1]["p"] if first_c == TERMINATOR: ratio = first_p / second_p print(f" ⚠️ 检测到首位候选为终止符 }} , p1={first_p:.3f}, p2={second_p:.3f},比值={ratio:.2f}") if ratio > END_RATIO: full_flag = current_str + TERMINATOR print(f"\n🎉 置信达标,完整flag: {full_flag}") submit_result = submit_flag(full_flag) print("提交返回结果:", submit_result) return full_flag else: print(" → }概率优势不足,视作普通噪声字符,继续搜索") # 遍历全部3个候选字符 for cand in top3: c = cand["c"] p_current = cand["p"] new_seq = current_str + c print(f" 试探候选字符 '{c}' 当前步概率={p_current:.3f}") # 终止符不再直接返回,跳过,交给上面的置信度判断 if c == TERMINATOR: print(f" → 跳过,终止符不加入队列") continue # 两步校验核心:向后试探一步 res_next = predict(new_seq) next_max_p = res_next["top"][0]["p"] print(f" → 试探后下一步最高概率 = {next_max_p:.3f}") if next_max_p < MEMORY_THRESHOLD: print(f" [剪枝] 下一步概率落入基线噪声,判定错误/蜜罐分支,放弃") continue print(f" [保留] 检测到记忆信号,加入搜索队列") queue.append(new_seq) print("❌ 队列耗尽,未找到flag,请微调MEMORY_THRESHOLD或END_RATIO参数") return None if __name__ == "__main__": two_step_bfs_solve()

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询