GitHub:0x4D31/airt:AIRT——一门免费开源的 AI 红队课程,包含 8 个模块和动手操作的 Docker 实验室
📄 本笔记整理自课程官方文档 Module 6:Model Extraction and Inference Attacks
原文:Module 6: Model Extraction & Inference Attacks — AIRT Academy
一句话概括:你不一定要攻破模型,只要能问它足够多次——它的行为、它背过的训练数据、它"认不认识"某个人,全都会顺着返回值漏出来;而 HTTPS 加密挡不住的,是包的长度和时间。 前五个模块我们一直在问"能不能让模型说错话";这一模块换了个问题:能不能把模型本身、以及它吃进去的东西,原样搬走?
这一模块按"从模型到数据,再到信道"排开: 窃取基础(1)→ 查询式克隆(2)→ 训练数据提取(3)→ 成员推断(4)→ 属性推断(5)→ 侧信道三连(6/7/8)→ 硬件电磁(9)→ 模型逆向(10)→ 五道防线(11–15)。 前五章是"从输出里读信息",六到九章是"从信道里读信息",后六章是"怎么挡"。
🗺️ 〇、一页速览
| # | 章节 | 回答的问题 |
|---|---|---|
| 1 | 模型窃取基础 | 模型为什么是资产,成本差多少,"精确复制"和"功能等价"差在哪 |
| 2 | 基于查询的模型窃取 | 三阶段流程、主动学习怎么把查询量压一个数量级 |
| 3 | 训练数据提取 | LLM 为什么会背课文,怎么把课文抠出来,抠出来多少 |
| 4 | 成员推断 | "这条数据在不在训练集里"——影子模型怎么做,合规上意味着什么 |
| 5 | 属性推断 | 不给敏感字段,模型还是把敏感字段告诉你 |
| 6 | 侧信道一:Whisper Leak | HTTPS 加密下,只靠包大小就能判断你在聊什么话题 |
| 7 | 侧信道二:Token 长度 | 不只判断话题,还能逐词还原回复内容 |
| 8 | 侧信道三:时序 | 推测解码与 KV 缓存这两个"性能优化"各自泄露了什么 |
| 9 | TPUXtract | 不用碰 API,量芯片的电磁辐射就能读出架构 |
| 10 | 模型逆向 | 从输出反推输入,人脸识别场景下的"凭名字画脸" |
| 11 | 防御一:限流 | 最便宜的一层,以及它为什么防不住耐心的人 |
| 12 | 防御二:输出扰动 | 加噪、取整、top-k,代价与三个必须注意的坑 |
| 13 | 防御三:水印 | 拦不住就留证据,两种水印与它们的软肋 |
| 14 | 防御四:API 监控 | 五种行为签名 + 异常检测,以及误报代价 |
| 15 | 防御五:差分隐私 | 唯一有形式化保证的一层,以及它不防什么 |
| 16 | 总结与延伸 | 十二条要点 + 动手练习 + 参考文献 |
📌 阅读建议:第 3 章(训练数据提取)和第 6–8 章(侧信道)是本模块两个重心——前者最有法律后果,后者最反直觉("加密了怎么还能被听见")。赶时间优先看这两块 + 第 15 章。
📌 本笔记在第 3、4、6、8 章按原始论文校对了课程口径,其中三处课程表述偏乐观或偏强,已用 🔴 标注并给出论文的原始数字。
📌 与前面模块的关系:Module 5 末尾把"模型窃取"归为供应链攻击的一环,本章是它的完整展开;M5 关心"模型从哪来",M6 关心"模型里的东西怎么出去"——正好是供应链的两端。
💰 一、模型窃取基础:IP、成本与两种目标
1.1 先算一笔账
一个生产级模型里压着四样东西:领域专家经验、标注数据、算力投入、架构选择。课程给出的量级对比:
| 项 | 数量级 |
|---|---|
| 训练一个商用大模型(算力) | 10M – 100M |
| 通过 API 提取克隆核心能力 | 几千美元查询费 |
| 不对称程度 | 六个数量级 |
1.2 四种动机(会叠加)
| 动机 | 说明 |
|---|---|
| IP 盗窃 | 竞争对手直接把克隆体当自家产品卖,绕开授权费、ToS 和先发优势 |
| 跳板 | 有白盒梯度才好造对抗样本——先克隆,就把黑盒问题转成白盒问题,下游攻击有效性提升一个量级 |
| 绕过计费与限流 | 有了本地副本,推理不再按次付费,也不再被使用监控看见 |
| 隐私推断 | 拿到一个持久的本地副本,可以离线反复试探,而 API 侧的审计日志看不到这一切 |
💡 第四条最容易被低估。对着线上 API 做成员推断,每一次试探都在对方的日志里;对着本地克隆体做,没人看得见——这是"先克隆、再挖掘"的战术价值。
1.3 两种目标:精确提取 vs 功能等价
| 精确提取(Exact) | 功能等价(Functional Equivalence) | |
|---|---|---|
| 目标 | 还原精确权重与架构,每个输入(含边界情况)行为一致 | 只在"攻击者关心的输入分布"上行为一致 |
| 可行性 | 小 ReLU 网络上查询量随规模多项式增长,理论上可行;十亿参数级 LLM 计算上不可行 | 商用 NLP 模型几十万次 API 调用即可达成 |
| 现实威胁 | 低 | 高(这才是要防的) |
💡 为什么"功能等价"就够了:商业价值在行为不在权重。用户不会因为权重不同就少付钱;攻击者也不关心你是 Transformer 还是别的,只要答得一样。
1.4 速查卡
攻击目标 克隆行为,不碰权重与架构 攻击面 任何返回 label / probability 的公开预测 API 攻击者成本 API 查询费 + 替代模型训练算力(约为原训练的 1/100 – 1/10000) 受害方损失 营收、竞争优势 + 训练数据主体的隐私风险
🎣 二、基于查询的模型窃取
2.1 三阶段与"信息量"这件事
查询 → 累积 (input, response) → 训练替代模型。简单,但查询什么决定成败:
-
均匀随机采样:笨办法。
-
主动学习:决策边界附近的点携带的信息远多于类别内部的点。用不确定性采样(entropy / margin)、query-by-committee、core-set 选批,可达比均匀采样少一个数量级的查询量。(哈哈哈 分布在哪里都重要)
-
闭环流程:种子集 → 查询 → 选最不确定的一批 → 重训替代模型 → 重复。
💡 为什么连只返回硬标签的 API 也守不住:每一次"属于/不属于某类"的二进制回答都在给决策边界加一条约束,约束够了边界就定下来了——只是收敛更慢,不是不可能。
2.2 软标签 ≫ 硬标签
| API 返回 | 攻击者拿到的信息 | 用法 |
|---|---|---|
概率分布 [cat .72, dog .25, fox .03] | 目标模型在该点附近的置信度几何形状 | 直接当训练目标 → 知识蒸馏 |
仅硬标签 cat | 只有排序,相对分数全丢 | 替代模型侧用 temperature scaling + label smoothing 部分补偿 |
2.3 更省的样本生成:JBDA 等
-
JBDA(Jacobian-based Dataset Augmentation,Papernot 等):对已有标注样本沿雅可比方向迈小步,合成出贴着决策边界的新样本,不花额外 API 调用。
-
无模型方法:用生成模型从零合成多样输入。
-
NLP 专用:prompt chaining——每次只变一个语言维度,用结构化查询集覆盖响应面。
2.4 精简实现(主动学习版)
import numpy as np, requests
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler
from scipy.stats import entropy
class ModelExtractor:
def __init__(self, url, n_classes=2, budget=5000):
self.url, self.n_classes, self.budget = url, n_classes, budget
self.queries, self.responses = [], []
self.scaler = StandardScaler()
self.surrogate = MLPClassifier(hidden_layer_sizes=(100, 50), max_iter=500)
self._fitted = False
def query_target(self, inputs):
for x in inputs:
d = requests.post(self.url, json={"input": x.tolist()}, timeout=10).json()
# 优先吃软标签:有 probabilities 就别用 prediction
lab = d.get("probabilities", None) or d["prediction"]
self.queries.append(x); self.responses.append(lab)
def train_surrogate(self):
X = self.scaler.fit_transform(np.array(self.queries))
self.surrogate.fit(X, np.array(self.responses)); self._fitted = True
def select_uncertain_batch(self, pool, k=100):
if not self._fitted: # 冷启动:随机
return pool[np.random.choice(len(pool), k, replace=False)]
proba = self.surrogate.predict_proba(self.scaler.transform(pool))
unc = np.array([entropy(p) for p in proba])
return pool[np.argsort(unc)[-k:]]
def run(self, sampler, batch=100):
for _ in range(self.budget // batch):
b = self.select_uncertain_batch(sampler(), batch)
self.query_target(b)
if len(self.queries) >= 200:
self.train_surrogate()
return self.surrogate
🔴 笔记补充(三处)
① 示例用
MLPClassifier+ 硬标签交叉熵。如果 API 返回软标签,正确做法是 KL 散度蒸馏(用 logits/概率做目标),直接fit概率向量会浪费掉最值钱的那部分信息。② 不确定性采样依赖替代模型的概率是校准过的;现代神经网络普遍过自信,熵的绝对值不可靠,实践上更稳的是 margin(top1-top2) 或 QBC 投票分歧度。
③ 每轮
train_surrogate都fit_transform全量数据且从零训练,规模一大就跑不动;真实实现应partial_fit+ 固定 scaler。
2.5 速查卡
查询策略 主动学习 > 均匀采样(可省约 10× 查询量) 标签质量 软标签 >> 硬标签(蒸馏 vs 分类) 省样本技巧 JBDA(零额外调用)/ 生成模型 / prompt chaining 硬标签补偿 temperature scaling + label smoothing
🧠 三、从 LLM 里"抠"出训练数据
3.1 为什么模型会背课文
LLM 在技术意义上是训练语料的有损压缩包。记忆化来自三个因素:
| 因素 | 方向 |
|---|---|
| 在语料中重复次数 | 越多越易被记(GPT-2 背下整段 MIT License,因为它在几十万个 GitHub 仓库里逐字出现) |
| 模型容量 | 越大记得越多——参数多,才有地方存冷门样本 |
| 训练轮数 | 反直觉:训得越久记得越牢,同一个样本见多了就拟合得更死 |
k-eidetic memorization(Carlini 等的定义):字符串 s 被模型 k-eidetically 记住 ⟺ 给一个长度 k 的前缀模型就能复现 s,且 s 在训练数据中只出现过一次。它区别于"事实知识"(后者由多个互相印证的样本学来)。
💡 这个定义专治一种抬杠:"模型知道巴黎是法国首都,也算泄露训练数据吗?"——不算,那是知识;只见过一次还能逐字背出来,才是失控的记忆。(哈哈哈 怎么定义什么叫模型的记忆)
3.2 三步流水线
① 生成 ── 大量采样(Carlini 等生成了 600,000 条) ↓ ② 排序 ── 用成员推断指标做筛子:大模型给高似然、参考小模型不给的,最可疑 ↓ ③ 验证 ── 拿原始训练语料比对(当年是与 GPT-2 作者协作 grep 确认的)
两个排序指标:
| 指标 | 公式 | 直觉 |
|---|---|---|
| ratio | log(ppl_small) / log(ppl_large) | 大模型觉得"不意外"、小模型觉得"意外" → 记过 |
| zlib | log(ppl_large) / zlib压缩熵 | 用压缩器当"意外度"基线,比再训一个小模型便宜 |
3.3 结果(Carlini et al., USENIX Security 2021,针对 GPT-2)
| 结果 | 数字 |
|---|---|
| 采样总量 | 600,000 条 |
| 人工核查的候选 | 1,800 条 |
| 确认为逐字记忆 | 604 条(整体真阳性率 33.5%) |
| 最佳配置真阳性率 | 67% |
| 含真实人名 | 46 条 |
| 含联系方式(地址/邮箱/电话等) | 32 条 |
🔴 笔记更正:课程写"最佳配置下 67% 的候选被确认为逐字训练样本",这只是最优那一种配置;论文给出的总体真阳性率是 33.5%(604/1800)。引用时带上后半句才不会被理解成"随便抽样三分之二都是训练数据"。
3.4 对齐之后还抠得出来吗:divergence attack(2023)
GPT-3.5/ChatGPT 经过 RLHF 与指令微调,正常用几乎不吐训练数据。Carlini、Nasr 等 2023 年的工作(arXiv 2311.17035)找到了绕法:
-
手法极简——让模型无限重复一个词("poem poem poem…"),模型最终偏离对齐行为,退化回"互联网文本补全"模式。
-
效果:发散后的吐数据速率是正常状态的 150×。
-
战果:仅花 $200 的 API 查询预算,提取出 10,000+ 条逐字记忆样本(作者明确说这是下界,预算加大还能更多)。
-
结论:对齐不消除记忆化,只是把它藏起来了。
3.5 前缀攻击
训练时见过的任何 prompt 都是其后文的检索键。给 GPT-2 一句 "My address is 1 Main Street",它能接出真实人物的联系信息。
3.6 检测自己模型有没有"背课文"(精简版)
import zlib, numpy as np, torch
def ppl(model, tok, text, device="cpu"):
ids = tok(text, return_tensors="pt").to(device)
with torch.no_grad():
return torch.exp(model(**ids, labels=ids["input_ids"]).loss).item()
def score(big, small, tok, text):
p_big, p_small = ppl(big, tok, text), ppl(small, tok, text)
z = len(zlib.compress(text.encode())) / len(text)
return {"ratio": np.log(p_small) / np.log(p_big), # 越高越可疑
"zlib": np.log(p_big) / z} # 见下方说明
# 用空前缀批量生成 → 按 ratio 降序排 → 人工/语料比对 Top-N
🔴 笔记补充:
zlib这一项的方向最容易搞反。它的分母是压缩熵:文本越"天然"(高熵、难压缩),分母越大、分数越低;而记忆化文本通常是高熵串(UUID、base64、代码片段)。所以实战筛选应按论文做法联合使用——取 ratio 高、且文本本身高熵者——而不是单独看某一个分数排序。示例代码把两个分数并列返回却没给联合阈值,直接用会筛出一堆"通顺的普通文本"。
3.7 速查卡
记忆化成因 重复次数 ↑ / 容量 ↑ / 轮数 ↑ 定义 k-eidetic:k 前缀可复现 且 语料中仅出现一次 流程 生成(60万) → ratio/zlib 排序 → 语料验证 GPT-2 战果 604 条逐字记忆;46 含人名;32 含联系方式 对齐后 重复词发散攻击,速率 150×;$200 换 10,000+ 条
🪪 四、成员推断:这条数据在不在训练集里
4.1 场景与根因
医院训练再入院预测模型,用公开 API 给保险公司查询。攻击者手上有某个人的病历,想知道:这个人是否在训练集里?
-
根因是过拟合:训练过的样本通常 loss 更低、置信度更高、内部表示不同。
-
即使正则化良好的模型,这个差距也可测量;罕见样本、重复样本差距更大。
-
这与第三章是同一个 phenomenon 的两面:能记住 ⇒ 能判别是否见过。
4.2 影子模型(Shokri et al., IEEE S&P 2017)
攻击者看不到目标模型的训练集,那就模拟一个:
① 用"与目标训练集同分布"的数据训练若干影子模型 ② 对每个影子模型:哪些点进了训练(member=1)、哪些没进(member=0)——攻击者全知道 ③ 记录模型对这些点的输出向量,拼成 (输出, 是否成员) 的有标注数据集 ④ 训练一个攻击分类器,拿去套目标模型的输出
| 结果(10,000 条零售记录数据集) | 数字 |
|---|---|
| Google ML 服务(默认配置) | 精度中位数 94% |
Amazon ML 服务(默认 (10, 1e-6)) | 74% |
Amazon(另一配置 (100, 1e-4)) | 84%(50 分位 0.84 / 90 分位 1.00) |
| 召回率 | 接近 1 |
🔴 笔记更正(两处):① 论文给的是精度(precision)的中位数,且召回率≈1,课程简写成"accuracy"会让读者误以为是整体准确率;② 课程只引了 Amazon 的 74%,那是它的默认配置,论文同时给出另一配置下达 84%——平台默认参数直接决定暴露程度,这才是可操作的结论。
4.3 更省事的两种做法
| 方法 | 思路 | 代价 |
|---|---|---|
| 损失阈值 | loss 低于阈值 → 判为成员 | 不用训练影子模型,但要定阈值 |
| 似然比(reference model) | 目标模型 vs 一个在互斥数据上训的参考模型,比值高 → 成员 | 需要额外数据训参考模型 |
4.4 精简实现
import numpy as np from sklearn.neural_network import MLPClassifier from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score class MIA: def __init__(self, n_shadow=4): self.n_shadow, self.atk = n_shadow, LogisticRegression(max_iter=500) self.shadows, self._trained = [], False def _feats(self, model, X): p = model.predict_proba(X) # 整条概率向量(保留校准信号) conf = p.max(axis=1, keepdims=True) ent = -np.sum(p * np.log(p + 1e-9), axis=1, keepdims=True) s = np.sort(p, axis=1)[:, ::-1] gap = (s[:, 0] - s[:, 1]).reshape(-1, 1) return np.hstack([p, conf, ent, gap]) # 6+3 维特征 def train_attack(self, X, y): F, L = [], [] for _ in range(self.n_shadow): idx = np.random.permutation(len(X)); half = len(X) // 2 tr, te = idx[:half], idx[half:] m = MLPClassifier(hidden_layer_sizes=(64, 32), max_iter=300).fit(X[tr], y[tr]) self.shadows.append(m) F.append(np.vstack([self._feats(m, X[tr]), self._feats(m, X[te])])) L += [1] * len(tr) + [0] * len(te) self.atk.fit(np.vstack(F), np.array(L)); self._trained = True def auc(self, target, member_X, nonmember_X): s = np.concatenate([self.atk.predict_proba(self._feats(target, member_X))[:, 1], self.atk.predict_proba(self._feats(target, nonmember_X))[:, 1]]) y = np.concatenate([np.ones(len(member_X)), np.zeros(len(nonmember_X))]) return roc_auc_score(y, s)
🔴 笔记补充(三处)
①
_feats依赖predict_proba——目标 API 只返回硬标签时整套特征退化,只剩 1 个维度,攻击力骤降。这也是"只返回标签"这条缓解措施确实有效的少数场景之一。② 示例里影子模型是
MLPClassifier,目标若是别的架构,攻击分类器的迁移性会下降;论文的做法是尽量让影子模型与目标同架构、同训练流程。③ 示例按 50/50 构造 member/non-member。现实里成员占比往往极低,用 accuracy 评估会骗人,统一看 AUC 或 balanced accuracy。
4.5 为什么合规部门该关心
| 法规 | 冲突点 |
|---|---|
| GDPR 第 17 条 (被遗忘权) | 个人可要求删除数据;但已部署的模型若能被推断出成员关系,删除就不成立——除非重训或做机器遗忘(machine unlearning) |
| HIPAA | 未去标识化的健康信息用于训练,若成员关系可被推断,机构即暴露于责任 |
| 监管趋势 | 越来越把"可被证明的成员推断漏洞"直接认定为合规失败,而不是纸面风险 |
4.6 速查卡
根因 过拟合(成员 loss↓ / 置信度↑);罕见或重复样本差距更大 主力方法 影子模型(Shokri 2017)+ 攻击分类器 轻量方法 损失阈值 / 参考模型似然比 关键数字 Google 94% · Amazon 74%(均为默认配置下的精度中位数) 评估指标 一律用 AUC,别用 accuracy 合规 GDPR Art.17 / HIPAA → 需 machine unlearning 或重训
🏷️ 五、属性推断:模型顺手泄露的额外标签
5.1 与成员推断的区别
| 成员推断 | 属性推断 | |
|---|---|---|
| 问题 | 这个人在不在训练集里? | 这个人有什么没告诉模型的敏感属性? |
| 粒度 | 二值 | 多值、细粒度 |
| 发生时机 | 训练时 | 查询时也能发生——任何预测 API 都可能泄露查询对象的属性 |
5.2 机制:相关性被"吸收"了
信贷模型训练时同时用了"许可特征"(收入、信用历史、额度)和"受保护属性"(种族、性别、邮编作为代理变量)。即使推理时把受保护属性从特征集里删掉,模型也已经在训练中吸收了相关性。攻击者知道目标的部分特征 → 查询 → 从预测值反推受保护属性。
-
形式化(Yeom et al., 2018):只要模型学会了利用"敏感属性 ↔ 标签"的相关性,攻击就成立。
-
手法:训练一个重建模型,用 (已知特征 + 模型输出) → 敏感属性的分类器。
-
研究发现:即使带显式公平性约束训练的模型,仍会通过输出分布泄露受保护属性。
5.3 LLM 上更肥的攻击面
-
LLM 输出开放、细致,可从写作风格预测年龄、性别、政治倾向、国籍。
-
用用户交互日志微调过的模型,会对微调集里的用户产生系统性不同的响应——哪怕当前查询者不是本人。
5.4 交叉比对(杀伤力最大的变体)
攻击者不只用模型:模型输出 + 公开数据库 / 社交档案 / 选民登记 / 房产记录。
例:医疗模型对
(age=54, zip=90210, diagnosis=T2D)给出 73% 再入院风险。单独看什么都不说明;与公开记录交叉后,足以唯一锁定一个自然人并推断其完整病史。
5.5 缓解与合规
| 层 | 措施 | 效果 |
|---|---|---|
| 训练时 | 公平性对抗训练(惩罚可从中间表示推断受保护属性的判别器) | 有效但会降性能 |
| 部署时 | 只返回硬标签,不返置信度 | 减少信息量,不消除风险 |
| 系统层 | 联邦学习 + 差分隐私 | 限制个体信息被编码进权重 |
-
合规:若泄露的受保护特征被用于就业、信贷、保险等重大决策,可能构成违法歧视(EU AI Act 禁止性 AI 实践;CCPA 对敏感特征推断的保护)。→ 预测类 API 上线前应做属性推断审计。
🔴 笔记补充:课程建议"只返回硬标签",这对被动观察型攻击者有效,但挡不住能主动查询的攻击者——他可以对某一维度做二分搜索逼近决策边界,把连续信息重新"问"出来。真正兜底的是查询预算 + 输出稳定性(对同一输入的响应不随微小扰动剧烈变化),而不是输出格式。
5.6 速查卡
问题类型 细粒度(敏感属性值),发生在查询时 机制 训练吸收了"受保护属性 ↔ 标签"的相关性 手法 重建模型 + 公开数据交叉比对 LLM 加成 写作风格推断人口统计;微调日志泄露用户特征 缓解 公平对抗训练 > 硬标签 > (兜底)查询预算 合规 EU AI Act / CCPA → 上线前做属性推断审计
📡 六、侧信道一:Whisper Leak(加密流量也能听出话题)
6.1 前提是错的:TLS 保护内容,不保护形状
TLS 流密码(AES-GCM / ChaCha20)保持长度:
size(密文) = size(明文) + 常数
LLM 流式输出 = 一个 token 一个包:
token "the" (3 字符) → 3 字节包
token "antidisestablishmentarianism" (28 字符) → 28 字节包
⇒ 包大小序列 == token 长度序列
⇒ 不同话题的 token 长度分布系统性不同(量子物理用长冷僻词、烹饪用短常用词、法律夹拉丁短语)
⇒ 训练一个分类器,只看包大小就能判话题。全程不解密一个字节。
攻击者模型:被动网络观察者(ISP、被控路由器、恶意 Wi-Fi 热点)。不需要主动干扰。
6.2 实验与结果(arXiv 2511.03675, 2025)
| 项 | 数字 |
|---|---|
| 测试模型 | 28 个商用 LLM(OpenAI、Anthropic、Google、Microsoft、xAI、Mistral、DeepSeek、Meta、Amazon 等) |
| 每模型流量 | 最多 21,716 条查询(100 条目标话题变体 + 11,716 条 Quora 噪声) |
| 分类器 | LightGBM / BiLSTM+attention / DistilBERT(包大小分 50 桶离散化) |
| 效果 | 多数模型 AUPRC > 98%,全模型平均 96.8% |
| 极端不平衡下 | 噪声:目标 = 10,000:1 时,17/28 个模型达到 100% 精度 @ 5–20% 召回 |
| 最难打 | Google Gemini(81–84%)与 Amazon Nova(71–77%) |
🔴 笔记更正(重要):课程写"以超过 98% 的准确率判断话题"。论文的口径是 AUPRC(精确率-召回率曲线下面积),不是 accuracy。在 10,000:1 这种极端不平衡下,AUPRC 98% 对应的召回只有 5–20%——意思是"命中率不高,但命中时几乎不会错",而不是"98% 的对话都被判对话题"。这个区别直接决定风险判断:它更适合"大海捞针式盯梢",不是"全量窃听"。
6.3 精简实现(抓包 → 特征 → 分类)
import subprocess, numpy as np
from collections import namedtuple
import joblib
Trace = namedtuple("Trace", ["sizes", "timings"])
def capture(host, dur=30, iface="eth0"):
"""仅可在你被授权监控的网络上运行。"""
cmd = ["tcpdump", "-i", iface, "-nn", "-q", "-tt",
f"host {host} and port 443", "-c", "2000"]
sizes, ts, prev = [], [], None
for line in subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode().splitlines():
p = line.split()
try:
t = float(p[0]); sizes.append(int([x for x in p if x.isdigit()][-1]))
if prev is not None: ts.append(t - prev)
prev = t
except (ValueError, IndexError):
continue
return Trace(sizes, ts)
def featurize(tr, max_len=512):
s = np.array(tr.sizes[:max_len], dtype=np.float32)
t = np.array(tr.timings[:max_len], dtype=np.float32)
return np.concatenate([np.pad(s, (0, max_len - len(s))),
np.pad(t, (0, max_len - len(t)))]).reshape(1, -1)
# clf = joblib.load("whisperleak_lgbm.joblib") → clf.predict_proba(featurize(tr))
6.4 缓解:三种都只是"降低"
论文评估了三种缓解,结论是每一种都降低攻击效果,但没有一种提供完整保护:
| 缓解 | 做法 | 评价 |
|---|---|---|
| 随机填充 | 给每个流式 token 追加随机长度串 | Cloudflare 在首次披露后已部署;残留的时间信息仍有可分类信号 |
| Token 批处理 | 攒若干 token 再发一个包 | 论文中效果最好的一类,但牺牲首字延迟 |
| 包注入 | 插入干扰包打乱节奏 | 降低但不彻底 |
彻底方案是恒定形状流量(所有响应补齐到固定大小、token 批量发送),这会与"低延迟流式体验"正面冲突——这正是该漏洞至今没有行业级根治的原因。
🔴 笔记更正:课程说"唯一完全有效的防御是恒定形状流量"。论文行文中三种缓解被明确描述为"each reduces attack effectiveness, none provides complete protection",而恒定形状是彻底方案而非"唯一有效的"——对多数厂商而言,随机填充 + 批处理的组合是现实可得的收益,别因为做不到恒定形状就干脆不做。
6.5 速查卡
根因 TLS 保长度 + 逐 token 流式 + 话题影响 token 长度分布 攻击者 被动观察者(ISP / 路由器 / 热点),零解密 规模 28 模型 · 21,716 查询/模型 · 三种分类器 效果 AUPRC>98%(非 accuracy!);10,000:1 噪声下 17/28 达 100% 精度@5–20% 召回 缓解 padding + batching + injection(均为降低,非根治);根治=恒定形状流量
📏 七、侧信道二:Token 长度(逐词还原回复内容)
7.1 目标升级:从"什么话题"到"具体哪个词"
Whisper Leak 判话题;Weiss 等(2024,Microsoft Security Blog 2025) 更进一步——还原逐词内容。
观察:包大小序列 = 每个 token 的字节长度序列
[4, 5, 1, 3, 6, 2, 7, ...]
反推:用另一个 LLM 做"受约束生成"——
生成一句通顺的话,且它的分词结果长度序列恰好等于上面那个序列
关键:tokenizer 词表固定且公开(如 OpenAI 的 tiktoken)
⇒ 这是一个被强约束的生成问题,比无约束生成容易得多
结果:约 27% 的 token 被精确重建。
7.2 为什么结构上难修
不是 TLS 的 bug,是流式 API 设计的固有属性。同时满足下面三条即中招:
| 条件 | 说明 |
|---|---|
| 固定 tokenizer | token 长度可从公开词表推出 |
| 逐 token 流式 | 一包一 token |
| 保长流密码 | 密文长度 = 明文长度 |
彻底缓解只有两条:(a) 给所有 token 加确定性 padding 使长度一致,(b) 多 token 打包再加密——都增加延迟、削弱流式体验。Cloudflare 在 CDN 层做了 per-token 随机填充,后续工作仍测出了残余泄露。
7.3 敏感部署的处置建议
医疗、法律、金融、政务场景使用流式 API,等于把回复内容泄露给用户与服务商之间任何有网络可见性的人:企业代理、VPN 供应商、ISP、乃至国家级监测设施。
| 场景 | 建议 |
|---|---|
| 高敏感 | 直接关闭流式(完整响应一次性返回) |
| 极高敏感 | 本地/内网部署推理,不暴露外部网络路径 |
| 必须流式 | token 批处理 + 随机填充 + 接受残余风险 |
🔴 笔记补充:"27% 精确 token 重建"常被媒体读成"能还原 27% 的对话内容"。两者差别很大——重建出来的是满足长度约束的"最合理句子",不是原文。真正的风险不是"读懂整段话",而是足以判定回复中是否出现了敏感医学诊断、法律意见或特定人名/术语。另外该攻击依赖已知 tokenizer,自研分词器的私有部署不在这个结论范围内。(哈哈哈 还是猜)
7.4 速查卡
泄露 包大小 → token 字节长度 → 受约束生成还原词句 结果 ~27% token 级精确重建(≠ 27% 内容被读懂) 成立条件 固定 tokenizer + 逐 token 流式 + 保长流密码 缓解 token 批处理 / 全 token 等长 padding(均牺牲延迟) 高敏感 关流式,或本地部署
⏱️ 八、侧信道三:时序——推测解码与 KV 缓存
性能优化也在泄露信息。两类:
8.1 推测解码侧信道(Wei et al., arXiv 2411.01076)
小 draft 模型先猜若干 token,大模型并行验证:猜对了就一次接受多个(该轮包更大),猜错了退回单步自回归(包更小)。accept/reject 的轨迹依赖输入内容。
| 方案 | 查询指纹识别准确率 |
|---|---|
| REST | ~100% |
| BiLD | 95% |
| LADE | 92% |
| EAGLE(远程 vLLM) | 77.6% |
-
只需要 30 个训练样本就能建指纹;没有真实 prompt 时,用公开数据也能训出有效指纹。
-
更狠的一条:恶意用户可反过来探 RAG 的检索数据存储——构造输入去试探哪些 token 被成功猜中,以 >25 tokens/秒的速率泄出 datastore 内容。对检索语料含机密文档的系统是重击。
8.2 对照:Carlini & Nasr(2024)
同是研究推理优化的时序泄露,但用包间到达时间而非包大小。结果是:在同样的 vLLM 设置上只有 14.4%(对比 Wei 等的 77.6%)。
💡 这个对比是本模块最有工程价值的一条结论:包大小是干净信号,包时间是噪声信号。做侧信道防护时优先消除长度差异;做检测时反之——长度突变比时间抖动更可信。
8.3 InputSnatch:KV 缓存 / 前缀缓存时序(arXiv 2411.18191)
vLLM、TensorRT-LLM 都有前缀缓存:两个请求共享前缀就复用 KV cache,于是命中比未命中明显更快(TTFT 更低)。攻击者构造候选前缀,测 TTFT 判断命中,逐段加长,还原别人的 prompt——HTTPS 也挡不住。
[攻击者] ── 构造候选前缀 "What are the symptoms of ..." ──→ [LLM API]
│ 测 TTFT
┌───────────────────────────────────┘
TTFT < 阈值 ? ── 是 ─→ 缓存命中:该前缀与受害者查询重合
│ 否 ─→ 缓存未命中:换下一个候选
↓(ML 引导的输入构造器迭代加长前缀)
由命中片段拼接出受害者的完整查询
| 结果 | 数字 |
|---|---|
| 判定缓存命中前缀长度 | 87.13% 准确率 |
| 医疗问答(静态 prompt)提取疾病名 | 62% |
| 医疗问答提取精确症状描述 | 13.5% |
| 法律咨询 RAG(语义缓存)语义级提取 | 43% – 100% |
| 完整输入精确提取 / 语义级 | 12.5% / 79.5% |
🔴 笔记补充:课程只引了"62% 疾病名 / 13.5% 症状"。论文摘要同时给出完整输入精确提取只有 12.5%、语义级 79.5%。别把 62% 读成"整条 prompt 有六成被完整还原"——精确还原率很低,但语义级还原(意思猜对)相当高,而后者对隐私来说往往已经够用。
8.4 速查卡
推测解码 accept/reject 轨迹 → 包大小 → 查询指纹(REST ~100% / EAGLE 77.6%)
反向利用:>25 tok/s 泄出 RAG datastore
包间时间 Carlini&Nasr 仅 14.4% → 时间信号比大小信号噪声大得多
KV 缓存 前缀命中 → TTFT 更低 → 逐段还原他人 prompt
医疗疾病名 62% / 症状 13.5% / 法律语义级 43–100%
缓解 禁用跨用户共享 prefix cache · 语义缓存按租户隔离 · 恒定延迟返回
🔌 九、TPUXtract:从芯片电磁辐射里读架构
9.1 思路:逐层匹配,而不是整体匹配
神经网络推理在 TPU/GPU/NPU 上会产生与运算相关的电磁辐射。不同层类型(卷积 / 全连接 / 注意力)、不同尺寸、不同连接方式,功耗与 EM 特征都不同。
关键在于数据是逐层顺序流过网络的:
采集整段推理的 EM 时域信号 ↓ 按时间窗切片 ↓ 每个时间窗 vs 预特征化的"层类型指纹库"逐个比对 ↓ 逐层还原 搜索复杂度:整体匹配(指数级) → 逐层匹配(每层独立小搜索)
结果:提取神经网络超参数的准确率 99.91%(北卡州立大学团队,针对 Google TPU)。
9.2 能拿到什么、拿不到什么
| 能提取 | 提取不到 |
|---|---|
| 层数(深度) | 权重(实际训练出来的数值参数) |
| 层类型(全连接/卷积/注意力/归一化) | |
| 每层维度(神经元数 / 通道数) | |
| 连接方式(skip connection、注意力头数) |
💡 "知道架构 = 拿到大楼蓝图,但不知道里面摆的家具。" 然而蓝图价值极大:API 提取的搜索空间被大幅收窄(克隆更高效)、暴露架构创新、为定向对抗攻击提供路线图。对 Transformer 类 LLM,完整提取还需额外步骤(把某些层权重置零以隔离其他层),论文论证了可行性。
9.3 威胁模型与防护
-
假设被挑战:云厂商一直认为多租户隔离足以防止物理访问,共同托管数据中心里从相邻硬件测量可能就够了。任何在"非端到端物理可控"的硬件上跑 AI 推理的组织,都面临架构保密风险。
-
防护(均来自密码硬件安全领域):硬件级 EM 屏蔽、噪声注入电路、功耗掩蔽。
🔴 笔记补充:这一章最容易被误读成"云上租户能隔空测出别人模型"。论文的威胁模型是近场/物理接触测量(边缘 TPU 与实验台设置),不是远程。把它与 Whisper Leak 区分开:Whisper Leak 是任何人都能做的远程被动攻击,TPUXtract 是需要物理条件的攻击——风险排序时不应给后者同等权重。
9.4 速查卡
原理 EM 辐射 ∝ 层配置;数据逐层流动 ⇒ 可按时间窗逐层匹配 结果 99.91% 超参数提取;只出架构,不出权重 价值 收窄 API 克隆搜索空间 / 暴露架构创新 / 定向对抗路线图 前提 物理接近(近场或相邻机架),非远程 防护 EM 屏蔽 · 噪声注入电路 · 功耗掩蔽
🎭 十、模型逆向:从输出反推输入
10.1 问题反过来问
正常:这个输入,模型预测什么? 逆向:什么样的输入,会让模型给出这个预测?
人脸识别场景最刺眼:给模型 + 一个名字 + 置信度返回值 → 还原出可辨认的人脸图像,而攻击者从未见过此人的真实照片。
10.2 Fredrikson et al.(CCS 2015)
-
机制:从随机噪声出发,在输入空间梯度上升,最大化目标类别的置信度,同时加 L2 正则防止退化解。
-
之所以可行:模型参数里已经编码了足够多的面部特征信息才能做出高置信判断,梯度可以把它解码回图像空间。
-
效率:在决策树场景(生活方式问卷)重建最快 1.4 秒即可完成。
10.3 精简实现(白盒 + 黑盒 NES)
import torch, torch.nn as nn, torch.optim as optim, numpy as np
def white_box_inversion(model, target_class, shape, iters=2000, lr=0.01, reg=1e-3):
model.eval()
x = torch.rand(shape, requires_grad=True)
opt = optim.Adam([x], lr=lr); crit = nn.CrossEntropyLoss()
t = torch.tensor([target_class])
for _ in range(iters):
opt.zero_grad()
xc = torch.clamp(x, 0., 1.)
logits = model(xc)
loss = crit(logits, t) + reg * torch.norm(xc, p=2) # 正则项防退化解
loss.backward(); opt.step()
return x.detach().clamp(0, 1)
def black_box_inversion(query_fn, target_class, shape, iters=5000, pop=50, sigma=0.1, lr=0.01):
"""NES:用随机方向的得分差估计梯度(无需访问模型内部)。"""
x = np.random.uniform(0, 1, shape).astype(np.float32)
for _ in range(iters):
n = np.random.randn(pop, *shape).astype(np.float32)
r = np.array([query_fn(np.clip(x + sigma * ni, 0, 1))[target_class] for ni in n])
g = (r[:, None] * n.reshape(pop, -1)).mean(axis=0).reshape(shape) / sigma
x = np.clip(x + lr * g, 0, 1)
return x
10.4 现代版本:生成模型当先验
用 GAN / 扩散模型把搜索约束在同域数据的潜空间里,重建图既逼真又语义有效。已有工作在生产级人脸识别模型上做到 64×64 分辨率、人类评估者可辨认的重建。
🔴 笔记补充(两处)
① 黑盒 NES 版本每步要 pop 次查询(示例里 50),5000 步 = 25 万次调用。它之所以在实验里可行、在真实 API 上难,完全是因为没有查询预算——这再次说明限流是性价比最高的一层防御。
② 别把模型逆向当成"还原训练集里的真实照片"。梯度优化收敛到的是该类别的原型(prototype),它可能像某个人,但通常是"模型对这个类的概括"。真正的隐私危害在于属性泄露(能看出性别、种族、大致年龄、是否戴眼镜),而不是"拿到证件照"。(哈哈哈 神奇吧)
10.5 速查卡
定义 反转推理方向:什么输入对应这个预测 经典 Fredrikson CCS 2015:名字 + 置信度 → 可辨认人脸 机制 输入空间梯度上升 + L2 正则;决策树场景最快 1.4s 黑盒 NES 估梯度,代价是海量查询(每步 pop 次) 现代 GAN / 扩散做先验 → 64×64 可辨认 防护 返回硬标签 + 查询预算(后者才是兜底)
🚦 十一、防御一:限流与自适应节流
11.1 为什么它排第一
提取需要几千到几十万次调用。限流直接抬高攻击的时间成本与金钱成本,且是唯一"立刻能上线"的一层。
11.2 静态限流的两个短板
| 短板 | 说明 |
|---|---|
| 误伤 | 重度合法用户被卡死 |
| 易绕 | 攻击者把查询分散到多账号 / 多 IP / 多地域 |
11.3 自适应:看"行为形状"而不是只看次数
提取查询往往过于规律(等间隔、按结构化方式覆盖输入空间);合法查询则不规则且语义连贯。
import time, collections, numpy as np
from dataclasses import dataclass, field
@dataclass
class Rec:
times: collections.deque = field(default_factory=lambda: collections.deque(maxlen=1000))
count: int = 0
flags: int = 0
throttled_until: float = 0.0
class AdaptiveLimiter:
def __init__(self, per_min=60, daily=5000, cv_thresh=0.15, mult=4.0):
self.per_min, self.daily = per_min, daily
self.cv_thresh, self.mult = cv_thresh, mult
self.users = {}
def _too_regular(self, r):
"""间隔的变异系数 CV 过低 = 太规律 = 像脚本。"""
ts = sorted(list(r.times)[-50:])
if len(ts) < 20: return False
iv = np.diff(ts)
return iv.mean() > 0 and (iv.std() / iv.mean()) < self.cv_thresh
def check(self, uid):
r = self.users.setdefault(uid, Rec()); now = time.time()
if now < r.throttled_until: return {"allowed": False, "reason": "throttled"}
if sum(1 for t in r.times if t > now - 60) >= self.per_min:
return {"allowed": False, "reason": "rate_limit"}
if r.count >= self.daily: return {"allowed": False, "reason": "daily_budget"}
if self._too_regular(r):
r.flags += 1
sec = min(60 * self.mult ** r.flags, 86400) # 渐进节流
r.throttled_until = now + sec
return {"allowed": False, "reason": "extraction_pattern", "sec": sec}
r.times.append(now); r.count += 1
return {"allowed": True}
🔴 笔记补充(三处)
① CV < 0.15 这种"太规律"判据,加个随机抖动就绕过去了;它只能筛最低级的脚本。真正难伪造的是语义连贯性(见第 14 章)。
② 限流的维度必须是账号 / API Key,不是 IP——NAT、办公出口、云主机都会让 IP 维度失真(既误伤又无效)。
③
flags只增不减、mult ** flags指数增长,会导致一次误判永久封禁。生产实现必须给 flags 加时间衰减和冷却恢复。
11.4 速查卡
作用 抬高提取的时间/金钱成本,唯一可立即上线的一层 静态短板 误伤重度用户 · 多账号多 IP 可绕 自适应 规律性检测(CV)+ 每用户预算 + 渐进节流 维度 按账号/Key,不按 IP 必配 flags 衰减机制,避免一次误判永久封禁
🌫️ 十二、防御二:输出扰动
12.1 思路
替代模型的保真度取决于标签质量。把标签弄脏:加校准噪声、取整、只给 top-k。艺术在于够脏到拖慢提取,又够干净到合法用户能用。
12.2 三种手段
| 手段 | 做法 | 说明 |
|---|---|---|
| Laplace 机制 | 加尺度为 Δf/ε 的拉普拉斯噪声 | 概率向量的 L1 敏感度 = 2 → 噪声尺度 2/ε;保证 (ε,0)-DP |
| Gaussian 机制 | 加 σ ∝ L2 敏感度的高斯噪声 | 对应 (ε,δ)-DP,δ 通常取 1e-5 |
| 取整 / top-k | 概率保留 2 位小数;只返前 k 类 | 非概率型,简单但有实效;保序(多数合法用户只需要排序) |
import numpy as np
class Perturb:
def __init__(self, mode="laplace", eps=1.0, dp=2, top_k=3):
self.mode, self.eps, self.dp, self.top_k = mode, eps, dp, top_k
def __call__(self, p):
p = np.asarray(p, dtype=np.float64)
if self.mode == "laplace": q = p + np.random.laplace(0, 2.0 / self.eps, p.shape)
elif self.mode == "gaussian":
s = np.sqrt(2) * np.sqrt(2 * np.log(1.25 / 1e-5)) / self.eps
q = p + np.random.normal(0, s, p.shape)
elif self.mode == "rounding": q = np.round(p, self.dp)
else: q = self._topk(p)
q = np.clip(q, 0, 1)
return q / q.sum() if q.sum() > 0 else np.ones_like(q) / len(q)
def _topk(self, p):
r = np.zeros_like(p); idx = np.argsort(p)[-self.top_k:]
r[idx] = p[idx]; return r
12.3 权衡
课程给出的实用区间:ε = 1.0 – 5.0,足以让替代模型的训练信号退化 15–30%,同时合法用户的预测精度仍在可接受范围。(哈哈哈 人生何处不权衡)
🔴 笔记补充(三条,其中第一条最关键)
① 独立噪声可以被"重复查询求平均"抵消。攻击者对同一个输入查 N 次取均值,噪声方差降到 1/N。所以输出扰动必须绑定"每用户查询预算"才成立——单独部署等于没部署。这也是为什么第 11 章不是可选项。
② 代码里
clip → 归一化是后处理。确定性后处理在形式上不破坏 DP 保证(后处理不变性),但要注意它改变了输出的分布形态,论证敏感度时应以加噪后、归一化前的那一步为准。③ 取整到 2 位小数仍保留 1e-2 量级的信息,单靠它挡不住蒸馏(攻击者大不了多查几次凑统计量)。有效的是 top-k + 取整 + 查询预算三者叠加,指望单一手段都会失望。
12.4 速查卡
手段 Laplace/Gaussian 加噪(有 DP 保证)· 取整 · top-k 参数 ε=1–5 实用区间,替代模型信号退化 15–30% ⚠ 关键 独立噪声可被重复查询平均掉 → 必须配查询预算 推荐组合 top-k + 取整 + 每用户预算
🖋️ 十三、防御三:模型水印
13.1 思路转变
限流和扰动是"别让它偷";水印是"假设它偷了,我能证明"。把可验证签名埋进模型行为,让它跟着蒸馏一起被复制。
13.2 两种水印
| 后门式(行为)水印 | 参数空间水印(Uchida et al., 2017) | |
|---|---|---|
| 埋在哪 | 一组秘密触发集(特殊纹理 → 指定"水印类",超高置信) | 用正则项把 bit string 嵌进某层权重分布 |
| 验证方式 | 用触发集查询可疑模型,看是否复现水印行为 | 计算权重向量与密钥矩阵的内积 |
| 优势 | 黑盒可验;蒸馏会一并复制 | 对微调、剪枝更稳健 |
| 短板 | 触发器被发现即成后门 | 需白盒访问才能验(只有对方公开了模型才成立) |
13.3 三个绕不过去的局限
-
可被去除:知道有水印的对手可以微调、剪枝、或再蒸馏到一个全新模型。
-
自身是攻击面:后门式水印与后门攻击同构——触发模式一旦泄露,等于给模型留了一个已知后门。
-
只做事后归因:不阻止提取造成本身的损失。
替代路线:radioactive data——在训练数据里加不可感知扰动,让任何在其上训练的模型都带标记;即使对手是从偷来的数据从零训练(而不是从 API 蒸馏),也能归因。
🔴 笔记补充:课程说水印"为 IP 诉讼提供强有力的法律证据"。技术上看要更保守:水印的价值主要是法律与威慑,不是防护。而且部署后门式水印意味着新增了一组必须按最高密级保管的触发集——如果触发集的保管强度低于模型权重,你就把一个"取证工具"变成了"备用后门"。上线前应先问:谁持有触发集?泄露后的处置流程是什么?(哈哈哈 难绷)
13.4 速查卡
定位 事后归因,不是事前防护 后门式 秘密触发集,黑盒可验,蒸馏可传递;触发器泄露=已知后门 参数式 嵌权重分布,抗微调/剪枝,但需白盒验证 去除手段 再蒸馏 / 微调 / 剪枝 数据层 radioactive data:对手从零重训也能归因
📊 十四、防御四:API 行为监控
14.1 五种提取行为签名
| 签名 | 表现 |
|---|---|
| 查询分布异常 | 均匀或按信息论准则覆盖输入空间,分布不像自然使用 |
| 查询速度异常 | 单账号或关联账号以接近上限的速率调用 |
| 语义连贯性低 | NLP 场景出现半随机文本、边界输入、语法异常构造 |
| 无反馈模式 | 合法用户会追着错误/低置信结果追问;机器人不会 |
| 跨账号协同 | 多个账号查询模式相似,或分片覆盖输入空间的不同区域 |
14.2 精简实现(六维特征 + Isolation Forest)
import numpy as np, collections, math
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
class ExtractorDetector:
"""特征:0 每分钟查询数 / 1 间隔 CV / 2 输入长度熵 / 3 置信度均值 / 4 置信度 CV / 5 唯一输入占比"""
def __init__(self, window=100, contamination=0.05):
self.window, self.hist = window, collections.defaultdict(list)
self.det = IsolationForest(contamination=contamination, random_state=42)
self.sc, self._fit = StandardScaler(), False
def record(self, uid, ts, length, conf, h):
self.hist[uid].append({"ts": ts, "len": length, "conf": conf, "h": h})
def _feat(self, uid):
h = self.hist[uid][-self.window:]
if len(h) < 2: return np.zeros(6)
ts = np.array([x["ts"] for x in h]); ln = np.array([x["len"] for x in h])
cf = np.array([x["conf"] for x in h])
span = ts.max() - ts.min(); qpm = len(h) / span * 60 if span > 0 else 0
iv = np.diff(np.sort(ts)); cv = iv.std() / iv.mean() if iv.mean() > 0 else 0
c = collections.Counter(ln); tot = sum(c.values())
ent = -sum((v / tot) * math.log2(v / tot + 1e-9) for v in c.values())
uniq = len({x["h"] for x in h}) / len(h)
return np.array([qpm, cv, ent, cf.mean(), cf.std() / max(cf.mean(), 1e-9), uniq])
def fit(self, uids):
X = np.array([self._feat(u) for u in uids if len(self.hist[u]) >= 10])
self.det.fit(self.sc.fit_transform(X)); self._fit = True
def score(self, uid): # 分数越低越可疑
return self.det.decision_function(self.sc.transform(self._feat(uid).reshape(1, -1)))[0]
14.3 误报是真实成本
自动封禁会打中重度用户、正在联调的开发者、研究者。论文的工程建议:
检测触发 → 先加强监控与节流 → 再考虑封禁 → 高价值账号必须人工复核
🔴 笔记补充(两处)
①
contamination=0.05是硬先验(假设 5% 用户是恶意的),它会强行把 5% 的用户标成异常。生产上应先离线标定,或改成分位数阈值 + 人工校准。② 特征 5「唯一输入占比高」对批量推理型合法业务天然接近 1(例如每天跑一遍全量客户打分),会被稳定误判。应先用业务画像把这类账号加入白名单基线,再谈异常检测。
14.4 速查卡
签名 分布异常 · 速度异常 · 语义不连贯 · 无反馈 · 跨账号协同 特征 6 维(速率 / 规律性 / 长度熵 / 置信度均值与波动 / 唯一率) 模型 Isolation Forest(无监督,无需攻击样本) 红线 先监控与节流,再封禁;高价值账号人工复核 调参 contamination 需离线标定;批量推理业务先入白名单
🔐 十五、防御五:差分隐私(唯一有形式化保证的一层)
15.1 与前四层的本质区别
前四层都在推理时做事;DP 改的是训练过程本身,限制任何单条训练样本对最终模型的影响。
形式定义:随机机制 M 满足 (ε,δ)-DP,若对任意相邻数据集 D、D′(相差恰好一条记录)与任意输出集合 S:
Pr[M(D) ∈ S] ≤ e^ε · Pr[M(D′) ∈ S] + δ
-
ε 越小越隐私;δ=0 为纯 DP(绝对保证),δ>0(近似 DP,通常 δ < 1/n)换来显著更好的效用。
-
隐私预算 ε 是累计的——每一次查询、访问、训练步都在花它。
15.2 DP-SGD(Abadi et al., 2016)
标准 SGD 改两处:
| 改动 | 作用 |
|---|---|
| 逐样本梯度裁剪 | 不按 batch 平均,而是逐条算梯度并裁剪到 L2 范数 C → 单条样本对更新的影响有界 |
| 加高斯噪声 | 在裁剪后的梯度和上加 σ ∝ C 的噪声,σ 按目标 (ε,δ) 标定 |
代价是精度下降。ε<1 强隐私但明显掉点;ε=1–10 中等隐私,分类基准上通常掉 2–5%。
15.3 用 Opacus(精简版)
import torch.nn as nn
from opacus import PrivacyEngine
from opacus.utils.batch_memory_manager import BatchMemoryManager
def train_dp(model, loader, opt, epochs, eps=1.0, delta=1e-5, max_grad_norm=1.0, device="cpu"):
pe = PrivacyEngine()
model, opt, loader = pe.make_private_with_epsilon(
module=model, optimizer=opt, data_loader=loader, epochs=epochs,
target_epsilon=eps, target_delta=delta, max_grad_norm=max_grad_norm)
crit = nn.CrossEntropyLoss()
for ep in range(1, epochs + 1):
model.train()
with BatchMemoryManager(data_loader=loader, max_physical_batch_size=64,
optimizer=opt) as safe:
for x, y in safe:
opt.zero_grad(); loss = crit(model(x.to(device)), y.to(device))
loss.backward(); opt.step()
print(f"epoch {ep} | ε spent = {pe.get_epsilon(delta):.2f}")
return pe.get_epsilon(delta)
ε 档位速查(分类任务经验值) ε ≈ 0.1 强隐私,精度损失 ~10–20%;成员推断退化到接近随机猜 ε ≈ 1.0 强隐私,损失 ~3–8%;医疗/金融数据集的实用区间 ε ≈ 10 中等隐私,损失 ~1–3%;降低但**不消除**成员推断风险 ε > 100 弱隐私,接近原始效用;对执着的攻击者几乎没有保护
15.4 部署现实
| 项 | 现实 |
|---|---|
| 开销 | 逐样本梯度 → 内存 2–3×、时间 1.5–2×(Opacus) |
| 层兼容 | BatchNorm 与 DP-SGD 不兼容(跨样本混合信息),须换 GroupNorm / LayerNorm |
| 大模型 | DP 微调比 DP 预训练现实——步数少,隐私预算花得更有效 |
🔴 笔记补充(三条,第一条最重要)
① DP 防的是隐私推断(成员 / 属性 / 训练数据提取),它不防模型窃取。功能等价克隆照样做得成——攻击者要的是行为,DP 恰恰是保证"行为不依赖任何单条数据",两件事互不覆盖。本模块最容易犯的错就是把 DP 当成模型窃取的答案。
② ε 是累计预算,不是一次性的。多次训练、多次发布、多次评估都在花它;上线前要用 accountant 明确"这个模型还剩多少预算",否则报告里的 ε=1 没有意义。
③ ε > 100 基本等于没保护。审计时看到"我们用了 DP"要立刻追问 ε 和 δ 的具体值——只有给出数字才是可审计的回答(这也正是 DP 相对启发式防御的最大优势)。
15.5 速查卡
定位 训练时防御,唯一有形式化定量保证的一层 机制 DP-SGD = 逐样本裁剪(L2≤C) + 高斯噪声 参数 ε<1 强/掉点大;ε=1–10 实用;ε>100 几乎无效 工程 内存 2–3× · 时间 1.5–2× · BatchNorm→GroupNorm/LayerNorm LLM 优先 DP 微调,而非 DP 预训练 ⚠ 不防 模型窃取(功能等价克隆)
🧾 十六、总结与延伸
16.1 十二条要点
-
模型窃取是经济不对称的:几百万到上亿美元训出来的能力,几千美元查询费就能克隆出功能等价体。
-
偷模型往往只是第一步——真正的目的是把黑盒变白盒(造对抗样本)、绕开计费,或拿一份可离线挖掘的副本做隐私推断。
-
训练数据提取不是理论:GPT-2 上确认 604 条逐字记忆(46 条含真名、32 条含联系方式);对齐后的 ChatGPT 用重复词发散攻击仍能吐出 10,000+ 条。
-
对齐不消除记忆化,只是藏起来——这是"安全训练"边界的一个明确注脚。
-
成员推断把模型输出变成隐私探测器:默认配置下 Google 94% / Amazon 74%(精度中位数),并直接撞上 GDPR 第 17 条与 HIPAA。
-
属性推断在查询时发生,光靠"推理时不给受保护字段"防不住——相关性已被吸收进权重。
-
HTTPS 不保护形状:Whisper Leak 在 28 个商用 LLM 上用包大小达到 AUPRC>98%;但要记住它是 AUPRC 不是准确率,召回只有 5–20%。
-
Token 长度侧信道能逐词还原回复(~27% token 级精确),根因是流式 API 设计,修起来必然牺牲延迟。
-
性能优化也是侧信道:推测解码的 accept/reject 轨迹(最高 ~100% 指纹)、KV 缓存命中的 TTFT 差(可还原他人 prompt)。
-
硬件层也不安全:TPUXtract 从电磁辐射里读出架构(99.91%),但拿不到权重,且需要物理条件。
-
五道防线各有覆盖盲区:限流防耐心/分布式攻击者弱;扰动会被重复查询平均掉;水印只做事后归因且自带后门风险;监控有误报成本;DP 是唯一有保证的,但不防窃取。
-
只有分层才成立:预算 + 扰动 + 水印 + 监控 + DP 叠加,让攻击者"过了限流还拿到脏数据,克隆出来还带着水印"。
16.2 五道防线对照表
| 防线 | 挡住什么 | 挡不住什么 | 主要代价 |
|---|---|---|---|
| 限流 | 大规模快速提取 | 分布式慢速攻击 | 误伤重度用户 |
| 输出扰动 | 高保真蒸馏 | 重复查询求平均 | 合法用户精度 |
| 水印 | 事后归因(威慑) | 提取本身 | 触发集成为新攻击面 |
| API 监控 | 有行为特征的攻击 | 伪装良好的慢速攻击 | 误报与人工复核 |
| 差分隐私 | 成员/属性/训练数据推断 | 模型窃取 | 精度 + 2–3× 算力 |
16.3 动手练习
| 练习 | 目的 | 提示 |
|---|---|---|
| 跑一遍主动学习提取 | 体会"信息量"差异 | 对比随机采样与熵采样,同样查询预算下看替代模型精度差多少 |
| 对同一输入查 20 次取均值 | 亲眼看噪声被平均掉 | 直接验证第 12 章第 ① 条补充 |
| 用 ratio 指标给自家模型做记忆化体检 | 找"背课文" | 只取 Top-20 人工看,成本很低 |
| 用 tcpdump 抓一次自己的流式对话 | 理解 Whisper Leak | 只看包大小序列,不看内容,试着猜话题 |
| 给模型加 top-k + 取整 | 量化扰动代价 | 同时测合法精度与替代模型保真度 |
| 用 Opacus 跑一个 ε=1 的小模型 | 建立 DP 手感 | 对比 ε=0.1 / 1 / 10 的精度与训练耗时 |
| 画一张"你的模型对外暴露了什么"清单 | 资产梳理 | 列清:返回字段、是否流式、限流维度、缓存是否跨用户共享 |
16.4 参考文献
A. 直接引用的一手文献
| 编号 | 文献 | 对应章节 |
|---|---|---|
| arXiv 1602.02697 | Practical Black-Box Attacks against Machine Learning(Papernot, McDaniel, Goodfellow, Jha)——替代模型训练与 JBDA 的出处 | 2 |
| USENIX Security 2021 / arXiv 2012.07805 | Extracting Training Data from Large Language Models(Carlini 等)——604 条逐字记忆、k-eidetic 定义、ratio/zlib 指标 | 3 |
| arXiv 2311.17035 | Scalable Extraction of Training Data from (Production) Language Models(Nasr, Carlini 等)——发散攻击 150×、$200 换 10,000+ 条 | 3 |
| arXiv 1610.05820 | Membership Inference Attacks Against Machine Learning Models(Shokri 等)——影子模型方法、94%/74% | 4 |
| CCS 2015 | Model Inversion Attacks that Exploit Confidence Information and Basic Countermeasures(Fredrikson, Jha, Ristenpart) | 10 |
| arXiv 2511.03675 | Whisper Leak: A Side-Channel Attack on Large Language Models(Microsoft)——28 模型、21,716 查询、三种缓解评估 | 6 |
| arXiv 2411.01076 | When Speculation Spills Secrets: Side Channels via Speculative Decoding in LLMs(Wei 等)——REST ~100%、>25 tok/s 数据泄露 | 8 |
| arXiv 2411.18191 | InputSnatch: Stealing Input in LLM Services via Timing Side-Channel Attacks(Zheng 等)——62%/13.5%/43–100% | 8 |
| arXiv 1607.00133 | Deep Learning with Differential Privacy(Abadi 等)——DP-SGD 与 moments accountant | 15 |
| arXiv 2110.06500 | Differentially Private Fine-tuning of Language Models(Yu 等)——LLM 场景的隐私-效用权衡 | 15 |
| arXiv 2411.05051 | Intellectual Property Protection for Deep Learning Model and Dataset Intelligence(综述)——水印与 IP 保护全景 | 13 |
B. 其他引用资源
| 资源 | 说明 |
|---|---|
| AIRT 课程 Module 6 原文 | 0x4d31.github.io/airt/module6.html |
| AIRT 课程仓库 | github.com/0x4D31/airt |
| Microsoft Security Blog(2025) | Weiss 等 token 长度侧通道的官方披露 |
| Cloudflare Blog(2024) | Mitigating a token-length side-channel attack in our AI products —— per-token 随机填充的工程实践 |
| Keysight Security Blog / Dark Reading(2024–2025) | TPUXtract 的披露与硬件侧信道背景 |
| Opacus 文档与教程(Meta AI) | DP-SGD 工程实现参考 |
16.5 局限性说明
-
Whisper Leak 的 AUPRC 不等于准确率(第 6 章):在 10,000:1 的不平衡设定下对应的是"高精确、低召回",不应外推为"多数对话可被识别";
-
"27% token 重建"是 token 级指标(第 7 章),且依赖已知 tokenizer,不代表可还原完整语义内容;
-
InputSnatch 的 62% 是"疾病名"的成功率(第 8 章),完整输入的精确提取率仅 12.5%;语义级(79.5%)才是真实威胁量级;
-
TPUXtract 需要物理接近(第 9 章),与 Whisper Leak 这类远程被动攻击不属于同一风险等级,且提取不到权重;
-
模型逆向重建的是类别原型而非训练集原图(第 10 章),其危害主要是属性泄露;
-
笔记中对课程口径的多处更正(第 3.3、4.2、6.2、6.4、8.3 节)与对示例代码的补充(第 2.4、4.4、11.3、12.3、14.3、15.4 节)属笔者的补充判断,与课程原文主张无关;
-
文中所有攻击代码均为教学演示,省略了生产所需的错误处理、密钥管理与审计;抓包类代码仅可在你被授权监控的网络上运行。
📄 原文:AI Red Team Academy · Module 6:Model Extraction and Inference Attacks, 本文为个人学习笔记,技术内容仅用于授权范围内的安全研究与防御建设。 请勿在未取得书面授权的系统上使用文中任何技术。 课程内容许可以官方仓库为准(CC BY-SA 4.0 / 代码 MIT)。
本文系统梳理AI红队课程AIRT Module 6核心内容,聚焦模型窃取(功能等价克隆)、训练数据提取、成员/属性推断及三类侧信道攻击(流量形状、Token长度、时序),涵盖Whisper Leak、InputSnatch、TPUXtract等前沿攻击技术;同时详解五层防御体系:自适应限流、输出扰动、模型水印、API行为监控与差分隐私,强调分层防护必要性及各技术的形式化边界与工程局限。

593

被折叠的 条评论
为什么被折叠?



