新兴攻击面与专项安全 — Web3·数据合规·实战·总汇

📚 本册属于《13-新兴攻击面与专项安全-实战专题》共 7 册中的 第 6 册 本册内容:第十一~十四章:Web3、数据合规、综合实战、全书总汇(共 3,709 行)

全 7 册导航:

分册 内容 规模
00-开篇与名词速查 第 0 册 名词速查 A~F(读任何一章前先扫一眼) 263 行

| 01-应用层安全(AI·API) | 第 1 册 第一、二章:AI/LLM 应用安全、API 安全 | 10,138 行 | | 02-接口与客户端(GraphQL·移动·IoT) | 第 2 册 第三五章:GraphQL/gRPC、移动端、小程序、实时通信与 IoT | 21,168 行 | | 03-主机与内网渗透 | 第 3 册 第六、七章:主机与操作系统安全、内网横向移动与域渗透 | 21,495 行 | | 04-数字取证与供应链 | 第 4 册 第八、九章:数字取证与应急响应 DFIR、软件供应链安全 | 17,172 行 | | 05-云原生与Serverless | 第 5 册 第十章:新兴云原生与 Serverless 安全 | 12,063 行 | | 06-Web3·数据合规·实战·总汇 | 第 6 册 第十一十四章:Web3、数据合规、综合实战、全书总汇 | 3,709 行 ← 本册 |

术语看不懂?回 00-开篇与名词速查 或 00-名词速查手册 Ctrl+F。


第十一章:Web3 与智能合约安全(代码即法律)

本章定位:前面十章,攻击对象是“运行在别人服务器上的程序”——Web、API、主机、云、K8s。 这一章换一个完全不同的世界:代码跑在区块链上,而且直接管钱。 智能合约一旦部署就几乎无法修改,一个漏洞可能就是几千万美元的直接损失。 这不是“网络安全”的附属品,而是一门独立的攻防手艺——合约审计。

为什么普通工程师也要懂一点:

  1. 哪怕你不写合约,面试里“重入攻击”“整数溢出”已经是高频题
  2. Web3 的攻击思路(经济攻击、前置交易)能反过来启发你对传统系统的思考
  3. “代码即法律”的世界里,安全设计必须前置到写代码那一刻,这个理念对任何系统都成立

本章范围:只讲技术安全(合约漏洞、钱包密钥、预言机、MEV 等), 不涉及发币、炒币、投资建议等金融行为。安全审计是一份正经的技术工作。

11.1 Web3 基础与攻击面全景

11.1.1 名词先行课:把 Web3 讲成大白话

很多人学不会 Web3 安全,是因为上来就被“区块链、去中心化、共识机制、预言机”这些词砸晕。 这一节先把最核心的几个名词,用“订酒店/存钱/转账”这类生活场景讲透。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词一:区块链(Blockchain)

  一句话定义:一本【所有人共享、谁也不能偷偷改】的公共账本。

  生活类比:想象一个村,村里不设银行,全村人共同记一本账。
     每记满一页(一个"区块"),就全村长签字确认,然后用胶水粘在上一页后面("链")。
     谁想改第 3 页,就得把第 3 页之后所有页都撕掉重写,
     而且全村人手里的副本都得同时改——几乎不可能,所以账本"不可篡改"。

  三个关键性质:
    · 不可篡改:已上链的数据,改的成本极高(要控制全网算力/节点)
    · 公开透明:所有人都能查每笔交易(所以"匿名"其实只是"假名")
    · 去中心化:不依赖某一个服务器/机构,靠共识机制维护

  致命缺点:慢、贵(要付手续费)、存储极小、写错了也退不回来。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词二:智能合约(Smart Contract)

  一句话定义:一段【部署到区块链上、自动执行、无法修改】的代码。

  生活类比:自动售货机。
     你把钱投进去(发起交易),它自动吐饮料(执行逻辑),
     中间不需要售货员(不需要信任任何中介)。
     但售货机一旦造出来摆街上,你就没法改了——里面有 bug 也只能让它继续卖。

  为什么叫"合约":因为代码里写的就是"规则",比如"存 100 能取 110",
     谁来执行都一样,谁也无法赖账——所以叫"代码即法律(Code is Law)"。

  ★ 安全的关键:因为【无法修改 + 直接管钱】,所以 bug 的代价被无限放大。
     传统程序有 bug 可以热修复、回滚;合约有 bug,只能眼睁睁看钱被转走。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词三:Gas(手续费)

  一句话定义:执行合约每一步操作,都要付的"燃料费"。

  生活类比:出租车计费。
     上车(发起交易)先付起步价,每跑一公里(每执行一步计算/存储)再跳一次表。
     "油"就是 Gas,Gas 单价(gas price)随网络拥堵浮动。

  安全相关性:★ 很多 DoS 攻击就是"让你的操作耗尽 Gas"(gas 不足导致交易回滚)。
     一个会无限循环或无限增长的数组,就是一颗"Gas 炸弹"。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词四:钱包 / 私钥 / 助记词

  一句话定义:钱包 = 私钥的容器;私钥 = 你资产的唯一"密码";助记词 = 私钥的人类可读形式。

  生活类比:私钥就是你家保险柜的【唯一一把钥匙】。
     谁拿到钥匙,谁就能打开柜子拿走所有东西——不需要身份证、不需要密码、不需要你同意。
     助记词(12/24 个英文单词)是把这把"钥匙"翻译成你能抄下来的形式。

  ★ 安全铁律:私钥/助记词一旦泄露 = 资产永久丢失,无法找回。
     "not your keys, not your coins"(钥匙不在你手里,币就不是你的)。
     这也是钓鱼、助记词诈骗、假钱包 App 泛滥的根本原因。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词五:DeFi / 预言机 / 闪电贷(先混个脸熟,后面展开)

  DeFi(去中心化金融):把"银行/交易所"搬到区块链上的应用(借贷、交易、质押)。
    生活类比:没有柜台的"自助银行",规则全靠智能合约执行。

  预言机(Oracle):帮链上合约"读外部世界数据"的桥梁(如 ETH 价格、天气)。
    生活类比:链上是个封闭的房间,看不到外面。预言机就是往房间里"递纸条"的人——
      纸条上写"现在 ETH 是 3000 刀"。★ 递纸条的人如果被收买/被操纵,整个合约就瞎了。

  闪电贷(Flash Loan):在同一笔交易里"借一大笔钱 → 用完 → 立刻还"的零抵押贷款。
    生活类比:银行允许你在同一秒钟内"先借 1 亿、花完、马上还回",只要这 1 秒内能还上就行。
     ★ 攻击者用它"凭空"获得巨量资金,去操纵市场/预言机,这正是闪电贷攻击的可怕之处。

11.1.2 为什么智能合约安全这么“特殊”

传统程序 vs 智能合约,四个根本区别,决定了攻防思路完全不同:

维度 传统程序 智能合约
发布后能否改 能热修、能回滚 ★ 基本不能(除非预埋升级逻辑,而升级逻辑本身又是攻击面)
出 bug 的后果 数据损坏、被黑、可恢复 ★ 直接丢钱,且不可逆,链上转账无法撤销
谁在跑 你自己的服务器,你控制 全网节点都跑,谁都能调用你合约的公开函数
可见性 代码可闭源 ★ 代码(字节码)公开,攻击者可以先读你的代码再打你
调用者 有身份认证、有边界 任何人(任何地址)都能调,没有“登录”概念

结论一句话:合约是你把“带 bug 的钱包”公开挂在街上,还贴了张“里面有钱”的告示。

11.1.3 Web3 攻击面全景图(六层)

把 Web3 安全拆成六层,从上到下,攻击面越来越“深”:

┌─────────────────────────────────────────────────────────────────┐
│ 第 1 层  用户层:钱包、助记词、钓鱼网站、假钱包 App、签名诈骗      │  ← 11.9
├─────────────────────────────────────────────────────────────────┤
│ 第 2 层  前端 dApp 层:网站被篡改、XSS、接口劫持、假合约地址       │  ← 复用第 3 章
├─────────────────────────────────────────────────────────────────┤
│ 第 3 层  智能合约层:重入、溢出、逻辑漏洞、访问控制缺失            │  ← 11.2~11.8 ★ 本章重点
├─────────────────────────────────────────────────────────────────┤
│ 第 4 层  协议/经济层:预言机操纵、闪电贷、MEV、套利攻击            │  ← 11.5~11.7
├─────────────────────────────────────────────────────────────────┤
│ 第 5 层  基础设施层:节点 RPC、桥(跨链桥)、预言机网络            │  ← 简述
├─────────────────────────────────────────────────────────────────┤
│ 第 6 层  生态层:项目方跑路(rug pull)、治理攻击、私钥托管风险    │  ← 11.8/11.9
└─────────────────────────────────────────────────────────────────┘

本章把火力集中在第 3、4 层——这是“智能合约安全审计”的本职工作, 也是面试里真正考你的地方。第 1、6 层只讲最常见的几种(钱包、助记词、Approve)。

11.1.4 智能合约的攻击思路,和传统 Web 有什么不同

传统 Web 攻击者想的是:“我怎么绕过你的校验,做你没授权我做的事。”

智能合约攻击者想的是:“你的代码在什么状态下会做出错误的经济决策?我能不能构造一笔交易, 把你没想到的调用顺序、你没想到的价格、你没想到的调用者塞进去?”

三个核心差异(记住这三个,后面所有漏洞都是它们的展开):

  1. 可组合性(Composability):合约能互相调用,A 调 B,B 又回调 A。 攻击者可以“插入”一个恶意合约进来,在你执行到一半的时候打断你、劫持控制流 → 这就是重入攻击的土壤。

  2. 经济激励:合约的“正确性”不是“逻辑不报错”,而是“经济上不亏”。 攻击者会精算:我投入多少 Gas、能套走多少币。只要利润 > 成本,就有人干。

  3. 交易原子性 + 前置可见:一笔交易要么全部成功要么全部回滚; 而且所有待执行交易都在公开的 mempool 里排队,别人能先看到你的交易、再抢在你前面下手 → MEV/前置交易。

11.1.5 一个“最小可用”的漏洞示例:会漏钱的银行

在看正式漏洞之前,先看一个 20 行的“银行合约”,它几乎集齐了新手会犯的所有错。 后面每一节都会回到这个例子做对照。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ★ 反面教材:一个漏洞百出的"存款银行",用来做错误示范
contract BadBank {
    // 记录每个地址存了多少钱
    mapping(address => uint256) public balances;

    // 存钱:调用者往合约里打钱,记一笔账
    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    // 取钱:把余额转回给调用者
    function withdraw() public {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "no balance");

        // ★ 漏洞 1:先转钱,后扣账 —— 经典的"先斩后奏",为重入攻击埋雷
        (bool ok, ) = msg.sender.call{value: amount}("");
        require(ok, "transfer failed");

        // ★ 漏洞 2:扣账写在了转账之后,如果上面的 call 重入进来,这里还没执行
        balances[msg.sender] = 0;
    }

    // 查询合约里一共有多少钱
    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

这个 withdraw 的问题就是下一节重入攻击的教科书案例: 它在“扣账之前”就先把钱转出去了,而且用的是会触发对方回调的 call。 攻击者只要写一个“收到钱就立刻再调用一次 withdraw”的合约,就能把银行里的钱反复套出来。

★ 别急着看答案,先记住这个例子的两个关键点:

  1. msg.sender.call{value: ...}("") 会把控制权交还给对方(对方能执行任意代码)
  2. 状态更新(扣账)发生在外部调用之后,中间留出了一段“危险窗口”

11.2 智能合约经典漏洞总览

11.2.1 十大漏洞类别速查表

先把“智能合约会出哪些事”画一张全景表。面试时你至少要能把这十类名字和一句话危害说全。

# 漏洞类别 一句话定义 一句话危害 详参
1 重入攻击 Reentrancy 转账时对方回调你的合约,趁你还没扣账反复取钱 一秒钟把合约余额掏空 11.3
2 整数溢出/下溢 Overflow 数字加爆了绕回 0,或减成负数变成天文数字 凭空多出巨额代币 11.4
3 未检查返回值 Unchecked return 调用失败了你却不知道,继续往下执行 逻辑悄悄失效 11.2
4 未检查调用 Unchecked call call 返回值没判断,转账失败当成功 资金假象 11.2
5 访问控制缺失 Missing ACL 关键函数没加“只有管理员能调” 谁都能抽走钱 11.2
6 tx.origin 鉴权 用 tx.origin 判断“是不是本人”,可被中间合约绕过 授权被盗用 11.2
7 预言机/价格操纵 Oracle manip. 合约用“能被操纵的池子价格”做决策 低价买高价卖套利 11.5
8 闪电贷攻击 Flash loan 借巨款瞬间操纵市场,一锤子套利 一次交易卷走千万 11.6
9 前置交易/MEV 别人看到你的交易,抢在你前面下单 你被“三明治”夹 11.7
10 代理合约陷阱 Proxy pitfall 可升级合约的存储冲突/函数遮蔽 升级时把数据覆盖 11.2

11.2.2 三个“防不胜防”的基础错误(虽小但致命)

在深入重入攻击之前,先把三个“看起来低级、但真实项目里反复出现”的小错讲清楚。 它们属于“代码审计时第一眼就要扫”的东西。

错误一:未检查返回值(Unchecked Return Value)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ★ 反面教材:transferFrom 可能失败(比如对方没授权),但这里没判断
contract Unchecked {
    IERC20 public token;

    function badTransfer(address to, uint256 amount) public {
        // ★ 漏洞:transferFrom 返回 bool,这里直接忽略。
        // 如果转账失败(余额不足/没授权),下面的记账还是会执行,
        // 结果就是"账记了,钱没到"。
        token.transferFrom(msg.sender, to, amount);
        recordBookkeeping(to, amount); // 假装钱已经到了
    }

    function goodTransfer(address to, uint256 amount) public {
        // 正确写法:检查返回值,失败就回滚整个交易
        require(token.transferFrom(msg.sender, to, amount), "transfer failed");
        recordBookkeeping(to, amount);
    }

    function recordBookkeeping(address, uint256) internal pure {}
}

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

白话:你叫快递员送东西,他没送到也不告诉你,你以为送到了。 传统 Web 里“调用失败”通常抛异常;合约里很多标准接口(尤其 ERC20)用返回值表示成败, 你不检查,它照样往下走。

错误二:未检查 call 返回值(Unchecked Call)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract UncheckedCall {
    function badWithdraw(address payable to, uint256 amount) public {
        // ★ 漏洞:call 是"低级转账",失败不会自动回滚,只返回 false。
        // 这里不检查,转账失败了也不知道。
        to.call{value: amount}("");
    }

    function goodWithdraw(address payable to, uint256 amount) public {
        // 正确写法:检查返回值
        (bool ok, ) = to.call{value: amount}("");
        require(ok, "withdraw failed");
    }
}

★ 面试加分点:transfer / send 会自动回滚但固定消耗 2300 gas(容易被拒绝), call 不限制 gas 但必须手动检查返回值。现代推荐用 call + 手动检查。 这三点区别(transfer/send/call)是高频考点。

错误三:用 tx.origin 做鉴权(Authorization via tx.origin)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Phishable {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    // ★ 漏洞:用 tx.origin 判断"是不是 owner 在操作"
    function badOnlyOwner() public {
        require(tx.origin == owner, "not owner");
        // ... 关键操作
    }

    function goodOnlyOwner() public {
        // 正确写法:用 msg.sender
        require(msg.sender == owner, "not owner");
        // ... 关键操作
    }
}

// 攻击者合约:诱导 owner 调用它,从而"借" owner 的身份去调 badOnlyOwner
contract Attacker {
    Phishable target;

    constructor(Phishable _target) {
        target = _target;
    }

    // ★ 攻击:owner 如果调了这个函数,那么在这一层:
    //   tx.origin = owner(最初发起交易的人)
    //   msg.sender = Attacker 合约(当前这一层的直接调用者)
    // 于是 target.badOnlyOwner() 里的 tx.origin 仍然等于 owner,鉴权被绕过
    function attack() public {
        target.badOnlyOwner();
    }
}

白话:tx.origin 是“最初按下按钮的人”,msg.sender 是“当前跟你说话的人”。 你用“最初按下按钮的人”做鉴权,攻击者只要骗 owner 点一下它的合约, 就能让 owner 的“最初身份”去执行危险操作。 ★ 铁律:鉴权只用 msg.sender,永远不要用 tx.origin(除非你明确知道自己在做什么)。

11.2.3 访问控制缺失(Missing Access Control)

这是真实项目里最致命、最普遍的一类——重要函数忘了加权限限制,或者权限加错了。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

contract Vault is Ownable {
    // ★ 漏洞:这个"提取全部资金"的函数没加 onlyOwner,
    // 任何地址都能调用它把合约里的钱全转走
    function badDrainAll() public {
        payable(msg.sender).transfer(address(this).balance);
    }

    // 正确写法:加 onlyOwner 修饰符(继承自 OpenZeppelin 的 Ownable)
    function goodDrainAll() public onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }
}

白话:金库的大门忘装锁了。合约里的函数默认谁都能调(这是和传统 Web 最大的区别—— 传统 API 你得先“登录”,合约函数默认“对全网开放”)。 ★ 审计第一动作:把每个 function 过一遍,问一句“这个函数是任何人都能调吗?该不该限权限?”

★ 常见踩坑:Ownable 的 owner 默认是部署者;如果部署脚本忘了 transferOwnership, 那么 owner 就是一个“部署用的临时地址”,这个地址的私钥一旦丢失,合约就“没人能管理”了。

11.2.4 代理合约陷阱(Proxy Pitfall,可升级合约的坑)

“可升级合约”(Proxy)解决了“合约不能改”的问题,但它自己引入了一堆新坑。 这里只讲两个最经典的,面试能说出来就很加分。

陷阱一:存储冲突(Storage Collision)

白话:代理合约里,逻辑合约的变量是按"位置"(slot 0、slot 1、slot 2...)存的,
而不是按"名字"存的。升级后,如果新逻辑合约在 slot 0 放了一个不同类型的变量,
就会把旧数据"张冠李戴",导致数据错乱甚至被覆盖。

★ 规则:升级时,变量的【声明顺序】绝对不能变,只能在【末尾追加】新变量。

陷阱二:函数选择器遮蔽(Function Clashing)

白话:Solidity 调用函数其实靠的是"函数签名算出来的 4 字节选择器"(如 transfer 是 0xa9059cbb)。
如果代理合约里也有一个同名同参函数,代理自己的函数会"遮蔽"逻辑合约的函数,
造成调用了错误的代码。

★ 规则:代理合约自己尽量不放任何业务函数,只放"转发 + 管理"逻辑。

★ 加分点:可升级合约的推荐实现是 EIP-1967(把实现地址存在一个“固定 slot”里)+ 透明代理(Transparent Proxy), 而不是简单的 delegatecall 转发。

11.3 重入攻击(Reentrancy)——合约安全第一课

11.3.1 什么是重入,用大白话讲

一句话定义:你(合约)在转账的时候,把钱先递了出去、但账还没改,
对方趁这个空档,又回头喊了你一次"再取一次",你就又把钱递了一遍……循环往复,直到掏空。

生活类比:银行柜台的"先给钱、后记账"。
  你去取 100 块,柜员先把 100 块递给你(控制权交给你了),
  你接到钱的那一瞬,发现他账本上还没划掉你的余额,于是立刻说"再取 100",
  柜员又递给你 100……直到柜台里的钱全被你拿走。
  ★ 柜员错在哪?他应该【先扣账、再给钱】,就不存在这个空档。

为什么合约里会天然出现这个空档?

因为 Solidity 里给合约转账用的 call,会把执行权交还给收款方(触发它的 receive/fallback 函数)。 如果收款方是一个攻击者写的合约,它就能在这个“交还控制权”的瞬间,再调用你的取款函数一次。 而你的合约这时还没来得及把余额扣掉,于是它又能取一次。

11.3.2 经典重入攻击完整演示

先看一个“标准受害者”合约,再看攻击合约怎么打它:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ★ 受害者:有漏洞的"银行"
contract VulnerableBank {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() public {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "nothing to withdraw");

        // ★ 致命顺序:先转账(触发对方回调),后扣账
        (bool ok, ) = msg.sender.call{value: amount}("");
        require(ok, "withdraw failed");

        // 扣账写在这里 —— 但攻击者已经在上面的 call 里重入并取走了更多钱
        balances[msg.sender] = 0;
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

攻击者合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./VulnerableBank.sol";

// ★ 攻击者:一个"收到钱就再取一次"的合约
contract ReentrancyAttacker {
    VulnerableBank public bank;
    uint256 public attackCount;

    constructor(address _bank) {
        bank = VulnerableBank(_bank);
    }

    // 第一步:先存一点钱,让自己有余额可提
    function attack() external payable {
        require(msg.value >= 1 ether, "need some deposit");
        bank.deposit{value: 1 ether}();   // 存 1 ether
        bank.withdraw();                  // 开始第一次取款
    }

    // ★ 关键:当银行调用 call 给我们转账时,会自动触发这个 receive 函数
    receive() external payable {
        // 只要银行里还有钱,就再取一次
        if (address(bank).balance > 0 && attackCount < 10) {
            attackCount++;
            bank.withdraw();   // ★ 重入!此时银行的 balances[我们] 还没被扣成 0
        }
    }

    // 取走战利品
    function collect() external {
        payable(msg.sender).transfer(address(this).balance);
    }
}

攻击时序图(用文字画出来):

攻击者 attack():
  1. 存 1 ether 到银行 → balances[攻击者] = 1 ether
  2. 调用 withdraw()
       ├─ amount = 1 ether(读余额)
       ├─ call 给攻击者转账 1 ether  ──→ 触发攻击者的 receive()
       │                                    ├─ 看到银行里还有钱 → 再调 withdraw()
       │                                    │      ├─ amount = 1 ether(★ 余额还没扣!还是 1)
       │                                    │      ├─ call 再转 1 ether ──→ 又触发 receive()
       │                                    │      │      └─ ... 循环,直到把银行掏空
       │                                    │      └─ 扣账 balances[攻击者] = 0(第 N 次才执行)
       │                                    └─ 返回
       └─ 扣账 balances[攻击者] = 0(第一次才执行,太晚了)

关键理解:每一层 withdraw 在“读余额”时,看到的都还是旧值(因为上一层的“扣账”还没执行)。 于是同一个 1 ether 被反复取走。根因:状态更新(扣账)晚于外部调用(转账)。

11.3.3 重入的四种变体

面试官常问“重入攻击只有这一种吗”,你要能说出下面四种:

变体一:单函数重入(最常见) 就是上面这种——withdraw 自己反复调用自己。防御:CEI。

变体二:跨函数重入(Cross-function) A 函数转账(触发回调),攻击者在回调里调用另一个有同样缺陷的函数 B。 即使 A 函数自己做了 CEI,B 函数没做,照样被掏。

// 跨函数重入示意:withdraw 做了 CEI,但 transferTo 没做
contract CrossFunction {
    mapping(address => uint256) public balances;
    mapping(address => uint256) public pending;

    function withdraw() public {
        uint256 amount = balances[msg.sender];
        balances[msg.sender] = 0;          // 先扣账(CEI 做到了)
        (bool ok, ) = msg.sender.call{value: amount}("");
        require(ok, "fail");
    }

    // ★ 这个函数里又有一处"先转账后记账",攻击者可在 withdraw 的回调里转进来打这个
    function transferTo(address to) public {
        uint256 amount = pending[msg.sender];
        pending[msg.sender] = 0;
        (bool ok, ) = payable(to).call{value: amount}("");
        require(ok, "fail");
        // ... 某些状态下这里仍有危险窗口
    }
}

变体三:跨合约重入(Cross-contract) 攻击者调合约 A 的取款,在回调里调另一个合约 B 的函数,B 又调回 A。跨越多个合约的重入链。 防御本质一样:每个合约都要自己做好 CEI。

变体四:只读重入(Read-only Reentrancy) ★ 较新的手法:即使你用了 ReentrancyGuard 防住了“转账重入”, 攻击者仍可能利用“你的合约在一个价格/余额已更新但尚未最终结算的中间状态”, 从**只读函数(view)**读到不一致的数据,拿去误导另一个依赖你合约的协议。 防御:让外部依赖读到的状态始终保持一致,或使用“先更新状态再暴露”的最终性设计。

面试话术:“重入不只有一种,本质都是’外部调用时状态不一致’。常见四种:单函数、跨函数、跨合约、只读重入。 前三种靠 CEI + ReentrancyGuard 防,第四种要额外注意状态一致性和 finality。”

11.3.4 三种防御手段(都要会)

防御一:CEI(Checks-Effects-Interactions,检查-生效-交互)

口诀:先检查(Checks)→ 再改状态(Effects)→ 最后才碰外部(Interactions)。

把上面的 VulnerableBank 改成:
  function withdraw() public {
      uint256 amount = balances[msg.sender];   // Checks:读余额
      require(amount > 0, "nothing");           // Checks:校验
      balances[msg.sender] = 0;                 // Effects:★ 先扣账!
      (bool ok, ) = msg.sender.call{value: amount}("");  // Interactions:最后才转账
      require(ok, "fail");
  }
  这样即使对方回调重入,第二次进来时 balances 已经是 0,取不到了。

防御二:ReentrancyGuard(互斥锁)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ★ 用一个"锁"标记,禁止函数重入
contract GuardedBank {
    mapping(address => uint256) public balances;
    bool private locked;   // ★ 锁

    modifier noReentrancy() {
        require(!locked, "reentrancy");
        locked = true;
        _;                 // 执行函数体
        locked = false;    // 结束解锁
    }

    function withdraw() public noReentrancy {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "nothing");
        balances[msg.sender] = 0;
        (bool ok, ) = msg.sender.call{value: amount}("");
        require(ok, "fail");
        // ★ 攻击者在回调里再调 withdraw,会撞上 locked == true,直接 revert
    }
}

白话:给柜员装个“正在办理中”的牌子,办完这单才摘下来。 你在他办理中再喊他,他直接说“请稍等”。

防御三:Pull Payment(拉式支付,让用户自己来“提”,而不是你主动“推”给他)

思路:不主动给用户转账(推),而是记账,让用户自己调一个函数来"领取"(拉)。
这样你的合约里就尽量少出现"转账给外部地址"的 call,攻击面大幅缩小。

contract PullPayment {
    mapping(address => uint256) public credits;
    function requestWithdraw() public { /* 记账 credits[msg.sender] = x */ }
    function withdraw() public {   // 用户自己来拉
        uint256 amount = credits[msg.sender];
        credits[msg.sender] = 0;    // 先扣
        payable(msg.sender).transfer(amount);  // 再给(transfer 只有 2300 gas,够用)
    }
}

★ 三者关系:CEI 是【思想】、ReentrancyGuard 是【保险】、Pull Payment 是【架构上的根本规避】。 生产项目通常 CEI + ReentrancyGuard 一起上,高风险路径再用 Pull Payment。

11.3.5 真实案例:The DAO(2016,损失约 6000 万美元 ETH)

这是智能合约安全史上最重要的一件事,面试提到重入必谈。

  • 背景:The DAO 是一个去中心化风投基金,用户往里面投 ETH 换投票权,也能“分拆”取回自己的 ETH。
  • 漏洞:splitDAO 函数在“给用户返还 ETH”和“扣除用户余额”之间,存在和上面一模一样的重入窗口。
  • 结果:攻击者反复调用,把合约里约 360 万 ETH 抽走(当时约 6000 万美元)。
  • 后续:社区最终决定对以太坊硬分叉,把资金追回——这也直接导致以太坊分裂成 ETH(分叉后的主链)和 ETC(坚持“代码即法律、不追回”的旧链)。

★ 面试升华点:The DAO 事件暴露的不只是代码 bug,而是一个哲学冲突—— “代码即法律”(不可篡改、不追回) vs “用户资产必须保护”(分叉追回)。 这个事件之后,“重入攻击”和“先扣账再转账”成了所有合约审计的红线。

11.4 整数溢出与下溢(Integer Overflow / Underflow)

11.4.1 什么是溢出,用大白话讲

一句话定义:一个数字超过了它这个类型能表示的最大值(或最小值),就"绕圈"回到另一边。

生活类比:老式机械里程表。
  它只有 6 位,最多显示 999999。再往前开 1 公里,就"咔哒"一声从 999999 跳回 000000。
  这叫"上溢"(加爆了)。
  反过来,从 0 再减 1,就绕回 999999,这叫"下溢"(减穿了)。

在 Solidity 里,uint256(无符号 256 位整数)能表示 0 ~ 2^256 - 1(一个天文数字)。 但如果一个数已经是最大值,再加 1,就会绕回 0;如果是 0,再减 1,就会变成最大值。

11.4.2 经典下溢漏洞:凭空造币

这是最经典的“下溢造币”场景,面试高频:

// SPDX-License-Identifier: MIT
pragma solidity ^0.4.18;   // ★ 注意:0.4.x 时代没有内置溢出检查

contract Token {
    mapping(address => uint256) public balances;
    uint256 public totalSupply;

    function transfer(address to, uint256 amount) public returns (bool) {
        // ★ 漏洞(0.4.x):如果 balances[msg.sender] < amount,减法会"下溢"
        // 得到一个接近 2^256 的天文数字,于是余额瞬间爆炸,代币凭空多出来
        balances[msg.sender] -= amount;
        balances[to] += amount;
        return true;
    }
}

白话:你账户里只有 5 块钱,却转账 10 块。老版本合约算 5 - 10, 里程表从 5 往前倒 10 格,直接绕到 “999…99”,你的余额瞬间变成天文数字。

11.4.3 Solidity 0.8.0 的分水岭

这是面试最爱问的版本差异点:

版本 溢出行为 防护方式
< 0.8.0 ★ 不会报错,静默溢出 必须手动用 SafeMath 库
>= 0.8.0 ★ 默认自动检查,溢出直接 revert 一般不需要 SafeMath,但可 unchecked 主动关闭
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Overflow8 {
    function test() public pure returns (uint256) {
        uint256 max = type(uint256).max;
        // 0.8.0+ 这一行会直接 revert(溢出检查)
        // uint256 bad = max + 1;

        // 如果你明确知道这里不会溢出、想省 gas,可以显式关闭检查:
        uint256 x = 1;
        unchecked { x = x + 1; }   // ★ 主动关掉溢出检查
        return x;
    }
}

★ 加分点:0.8.0 的自动检查是有 gas 成本的。高频循环里如果确定不会溢出, 用 unchecked 可以省 gas,但必须非常确定,这是“优化”和“埋雷”的一线之隔。

11.4.4 防御与审计要点

1. 老合约(<0.8.0):确认是否引入了 SafeMath,且所有 + - * 都用 .add/.sub/.mul
2. 新合约(>=0.8.0):默认安全,但要警惕那些【主动用 unchecked】的地方——逐个审查
3. 关注所有"由外部输入参与的四则运算",尤其是减法(下溢造币)和乘法(上溢归零)
4. 关注 for 循环计数器的自增,理论上 i++ 在极端情况下也可能上溢(循环次数不可能到 2^256,风险极低)
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24;

// SafeMath 时代的标准写法(0.4.x)
library SafeMath {
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "underflow");   // ★ 显式检查,防下溢
        return a - b;
    }
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "overflow");    // ★ 防上溢
        return c;
    }
}

contract SafeToken {
    using SafeMath for uint256;
    mapping(address => uint256) public balances;

    function transfer(address to, uint256 amount) public returns (bool) {
        balances[msg.sender] = balances[msg.sender].sub(amount);  // 下溢会 revert
        balances[to] = balances[to].add(amount);                   // 上溢会 revert
        return true;
    }
}

11.5 预言机与价格操纵(Oracle Manipulation)

11.5.1 预言机是什么,用大白话讲

一句话定义:预言机(Oracle)是把"链下真实世界的数据"喂给链上合约的桥梁。

生活类比:封闭房间里的"递纸条的人"。
  区块链是个没窗户的房间,合约看不到外面的世界(不知道 ETH 现在多少钱、今天几号)。
  预言机就是那个站在门口的人,每隔一会儿往房间里塞一张纸条:"现在 ETH = 3000 美元"。
  合约的所有"按价格决策"(抵押、清算、兑换),都信这张纸条。

★ 致命问题:如果这张纸条可以被【操纵】(比如有人故意把价格报成 1 美元),
  那么合约就会在错误的价格上做决策——这正是价格操纵攻击的本质。

11.5.2 为什么“现货价格”能被操纵(AMM 定价原理)

很多新手项目图省事,直接拿“去中心化交易所(DEX)里某个交易对的当前现货价格“当预言机。 问题是:DEX 的现货价格,是由池子里的两种资产比例决定的,而这个比例可以被一笔大额交易瞬间改变。

AMM(自动做市商)最经典的公式:x * y = k
  x = 池子里 A 币的数量
  y = 池子里 B 币的数量
  k = 恒定乘积(不变)

价格 = y / x(用 B 计价 A)。如果你往池子里【砸进巨量 B 币】,y 暴涨,
那么 A 的价格(y/x)瞬间暴涨——哪怕这只是你一手造成的短暂假象。

白话:一个小池塘,你往里倒一卡车水,水位当然瞬间暴涨。 但这个“水位”是被你一个人临时抬高的,不是真实价值。 合约如果信了这个假水位去做“抵押/清算”,就被你钻了空子。

11.5.3 价格操纵攻击完整演示

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IDex {
    function getPrice() external view returns (uint256);  // 池子的现货价格
    function swap(address tokenIn, uint256 amountIn) external;
}

// ★ 受害者:用"现货价格"判断抵押是否安全的借贷协议
contract VulnerableLending {
    IDex public dex;
    mapping(address => uint256) public collateral;  // 用户抵押的 ETH

    constructor(address _dex) { dex = IDex(_dex); }

    function deposit() public payable {
        collateral[msg.sender] += msg.value;
    }

    // 借款额度 = 抵押物价值 × 0.8(抵押率 80%)
    function borrow(uint256 amount) public {
        uint256 ethPrice = dex.getPrice();  // ★ 用现货价格
        uint256 borrowPower = collateral[msg.sender] * ethPrice * 8 / 10;
        require(amount <= borrowPower, "insufficient collateral");
        payable(msg.sender).transfer(amount);
    }
}

攻击者如何打(伪代码 + 讲解):

攻击者目标:用很少的抵押,借走协议里大量的钱。

步骤:
1. 攻击者先往 DEX 池子里【砸巨量钱】,把 ETH 现货价格抬高 100 倍
   (这是"自己抬起来的假价格")
2. 趁价格还高,攻击者调 borrow(),协议一看"你抵押的 ETH 现在值 100 倍",
   就允许他借走远超真实价值的钱
3. 攻击者再反向交易,把价格砸回正常,套利离场
4. 协议留下了"借出的钱 > 抵押真实价值"的坏账,无法清算,直接亏空

11.5.4 三种防御手段

防御一:TWAP(时间加权平均价格)

思路:不取"某一瞬间"的价格,而是取"过去一段时间"的加权平均。
  这样攻击者要用一笔巨款把价格持续顶住好几分钟,成本极高,往往得不偿失。

白话:不看"这一秒的水位",看"过去 30 分钟的平均水位"。
  你倒一车水能顶起一瞬间,但顶不起 30 分钟的平均值。

防御二:去中心化预言机网络(如 Chainlink)

思路:不用单一来源,而是多个独立节点(多个递纸条的人)各自从多个交易所聚合报价,
  取中位数/加权,并且有经济惩罚机制防止节点作恶。

★ 面试要点:Chainlink 不是"一个数据源",而是"预言机网络 + 聚合器 + 喂价合约"。
  它通过多节点、多数据源、离线聚合来抵抗单一来源操纵。

防御三:多种价格源交叉验证 + 异常熔断

思路:同时读多个价格(DEX 现货 + 预言机 + 中心化交易所),
  如果彼此偏差超过阈值(比如 5%),就暂停敏感操作(清算、借款),触发熔断。

★ 诚实提醒:没有一种方案是 100% 安全的。TWAP 能防“瞬时代价”,但挡不住“长期持续操纵”; Chainlink 能防单一节点作恶,但极端行情下的更新延迟仍可能被利用。 安全审计的要点是【识别“价格来源是否可被单笔交易影响”】,而不是迷信某个方案。

11.6 闪电贷攻击(Flash Loan Attack)

11.6.1 闪电贷是什么,用大白话讲

一句话定义:闪电贷是【在同一笔交易内】借出巨款、用完、立刻归还的零抵押贷款。

生活类比:银行允许你在"同一秒钟内"借 1 个亿,但要求这一秒结束时必须连本带息还清。
  只要你能在这一秒内"用这 1 个亿赚到钱并还上",这 1 个亿就随你支配。
  因为"借"和"还"发生在同一笔交易里,区块链的原子性保证了:
  如果你还不上,整笔交易回滚,等于从没发生过——所以放贷方【几乎零风险】。

关键点:

  • 零抵押:不需要你有任何本金(只需付一点手续费)
  • 原子性:借、用、还三件事在同一笔交易里,要么全成,要么全回滚
  • 它本身不是攻击:闪电贷是个中立工具(套利者用它做无风险套利), 但攻击者用它凭空获得巨量资金,去放大其它漏洞(价格操纵、重入等)

11.6.2 闪电贷攻击的典型套路

闪电贷几乎从不单独“攻击”,它是放大器,把一个小漏洞放大成天文数字的损失。 典型组合套路:

闪电贷 → 借巨款 → 砸进池子操纵价格 → 利用"价格操纵"漏洞套利 → 还贷 → 离场

或者:

闪电贷 → 借巨款 → 用巨款触发某个"按余额分配奖励/分红"的漏洞 → 抽走奖励 → 还贷

白话:闪电贷给了攻击者一把“无限子弹的枪”,但枪本身不伤人,伤人的是你的合约里那个 “会被大额资金影响决策”的漏洞。所以防闪电贷,本质是修好自己的价格/余额逻辑,而不是“禁掉闪电贷”(也禁不掉)。

11.6.3 一个简化的闪电贷攻击演示

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ★ 受害协议:按"合约余额"分配奖励的质押协议(有漏洞)
contract RewardsPool {
    mapping(address => uint256) public staked;
    uint256 public totalStaked;

    function stake() public payable {
        staked[msg.sender] += msg.value;
        totalStaked += msg.value;
    }

    // ★ 漏洞:奖励 = 合约余额 - totalStaked(即"多出来的钱全当奖励发")
    // 攻击者可以在同一笔交易里"临时存入巨款",把合约余额瞬间撑大,
    // 领走绝大部分"奖励",再把巨款撤走。
    function claimReward() public {
        uint256 reward = address(this).balance - totalStaked;
        require(reward > 0, "no reward");
        (bool ok, ) = msg.sender.call{value: reward}("");
        require(ok, "fail");
    }
}

攻击逻辑(讲解,无需完整合约代码):

1. 攻击者通过闪电贷借到 10000 ETH
2. 把这 10000 ETH 全部 stake 进 RewardsPool
   → 现在合约余额 = 10000 ETH + 原本的奖励池,totalStaked 也 = 10000 ETH + 原质押
   → 但"奖励"的算法是 address(this).balance - totalStaked,
      如果协议本身有"历史积累的未分配奖励",这个差值仍然存在
3. 攻击者 claimReward(),把"多出来的奖励"领走
4. 攻击者 unstake 撤走自己的 10000 ETH(如果协议允许),还掉闪电贷
5. 净赚:那些"历史未分配奖励"被攻击者用闪电贷的巨款一次性洗走

★ 这个例子的核心教训:任何“用当前余额做分母/做分配依据”的逻辑,都要假设余额可以被 一笔闪电贷临时撑大或抽干。正确的做法是“奖励按时间累积(如 block.timestamp 记账)”, 而不是“看瞬时余额差”。

11.6.4 防御闪电贷攻击的原则

1. 【别用瞬时余额做决策】—— 用时间累积的记账,而不是 address(this).balance 的瞬时值
2. 【价格用 TWAP / 预言机】—— 别用可被单笔交易操纵的现货价
3. 【奖励按区块/时间线性累积】—— 让"临时撑大余额"无法撬动分配比例
4. 【关键操作加冷却期】—— 存/取之间加时间锁,让闪电贷"一锤子"无从下手
5. 【重入防护】—— 闪电贷攻击常配合重入,先堵住重入这道门

★ 面试升华点:闪电贷攻击说明了一个更深层的安全理念——在可组合、资金可瞬时流动的链上世界里, “局部正确”不等于“全局安全”。你的合约单独看没问题,但和别人一组合、被一笔闪电贷一放大, 就崩了。这也是为什么合约审计越来越强调“协议级/经济级”审计,而不只是“代码级”审计。

11.7 MEV 与前置交易(Front-running / Sandwich)

11.7.1 MEV 是什么,用大白话讲

一句话定义:MEV(Miner/Maximal Extractable Value,矿工可提取价值)=
  交易排序者(矿工/验证者/机器人)通过【调整交易顺序、插入自己的交易】来额外赚取的那部分价值。

生活类比:排队买限量球鞋。
  你排在一个黄牛后面。你刚要下单,黄牛看到你的订单,抢先一步把你想要的鞋全买空,
  然后再高价卖回给你。黄牛赚的就是"排序优势"的钱。
  在链上,"黄牛"能看到 mempool 里你【尚未被打包】的交易,于是抢在你前面下单。

关键机制:链上交易不是“提交就立刻执行”,而是先进一个公开的待处理池(mempool), 再由打包者挑选、排序。这个“公开排队 + 有人能排序”的结构,天然产生了 MEV。

11.7.2 三明治攻击(Sandwich Attack)

这是最典型、最好讲的 MEV 攻击,面试高频:

你的交易:在 DEX 上买 100 个 A 币(会推高 A 币价格)

攻击者:
1. 在 mempool 里看到你的买单
2. 【抢在你前面】也买 A 币 → 价格被抬高
3. 你的买单执行 → 因为价格已更高,你买到更少的 A 币(你被"夹"了)
4. 【在你后面】攻击者把 A 币卖出 → 因为价格被你和他一起推高,他卖在高点
5. 净赚:你多付的钱 + 他低买高卖的差价

   ┌───────────┐
   │ 攻击者买入 │  ← 第一片"面包":抬高价格
   ├───────────┤
   │  你的交易  │  ← 中间的"肉":你高价成交,承受滑点
   ├───────────┤
   │ 攻击者卖出 │  ← 第二片"面包":高位套现离场
   └───────────┘

11.7.3 MEV 的几种常见形态

类型 白话解释
前置交易 Front-running 看到你的交易,抢在你前面执行相同/相关交易,赚差价
后置交易 Back-running 跟在你后面,利用你造成的价格变化反向操作
三明治 Sandwich 前 + 后一起夹你(上面讲的)
清算套利 Liquidation 抢着帮“该被清算”的仓位清算,赚清算奖励
套利 Arbitrage 两个池子价格不一致,低买高卖(这个本身是良性的)

★ 面试加分点:要区分“MEV 本身”和“MEV 攻击”。套利、清算这类 MEV 是市场的正常润滑剂, 甚至是好事(让价格回归);真正伤用户的是三明治、前置交易这类“以普通用户为代价”的提取。

11.7.4 防御手段(含用户侧和协议侧)

用户侧(作为普通用户能做的):
  1. 设【滑点容忍度】(slippage tolerance)—— 价格偏离超过 N% 就取消交易,
     让三明治攻击者无法把价格抬太高(抬太高你的单子会失败)
  2. 用【私有交易/Flashbots】—— 不把交易公开进 mempool,直接发给打包者,黄牛看不到

协议侧(作为开发者能做的):
  3. 关键操作加【提交-揭示(commit-reveal)】或【时间锁】—— 让攻击者无法即时抢先
  4. 用 TWAP 而非瞬时价 —— 提高操纵成本
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// 滑点保护的示意:设一个"最多能接受的价格"上限
contract SlippageGuard {
    uint256 public constant MAX_SLIPPAGE = 100; // 1% = 100 个基点

    function swapWithGuard(uint256 amountIn, uint256 expectedOut, uint256 minOut) public {
        // 调用者自己声明"最少要拿到多少",协议据此兜底
        uint256 minAcceptable = expectedOut * (10000 - MAX_SLIPPAGE) / 10000;
        require(minOut >= minAcceptable, "too much slippage");
        // ... 执行兑换,若实际得到 < minOut 则回滚
    }
}

白话:滑点保护就是“挂单时先设个止损价”——“如果价格被抬得太过分,这单我不做了”。 这样黄牛抬价抬过头,你的单子直接作废,他就白抬了。

11.8 代币安全(Token Security)

11.8.1 ERC20 的 approve / transferFrom 两段式,藏着什么坑

ERC20 标准的授权是“两段式”:先 approve(授权某地址花你的币),再 transferFrom(它替你花)。 这个设计带来一个经典问题——无限授权(unlimited approval)。

白话:你为了让 DEX 能自动帮你卖币,得先"签字授权"它动用你的币。
  很多钱包/前端为了省事,直接授权一个"无限额度"(2^256 - 1,天文数字)。
  这意味着:只要这个被授权的合约【将来出任何漏洞、被黑、或被替换成恶意合约】,
  攻击者就能把你钱包里【所有】这种币转走——而不需要你的私钥。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

contract ApproveHelper {
    // ★ 危险做法:无限授权
    function riskyApprove(IERC20 token, address spender) external {
        token.approve(spender, type(uint256).max);
    }

    // 安全做法:只授权本次需要的额度
    function safeApprove(IERC20 token, address spender, uint256 amount) external {
        token.approve(spender, amount);
    }

    // ★ 收尾:用完后把授权归零(尤其发现某合约有风险时,第一时间 revoke)
    function revoke(IERC20 token, address spender) external {
        token.approve(spender, 0);
    }
}

★ 面试加分点:

  1. approve 的“先归零再设新值”顺序问题——直接 approve(new) 在部分实现下可能因 旧额度未清零而被“覆盖失败”或产生竞争窗口,安全做法是先 approve(0) 再 approve(new)。
  2. 用户应对:定期用 revoke 工具(如 revoke.cash)检查并撤销不必要的授权。

11.8.2 貔貅盘 / 蜜罐 / 假代币(用户侧最常见的坑)

这些属于“用户被骗”的坑,但面试里“怎么识别一个恶意代币”也是常见话题:

1. 貔貅盘(Honeypot):只能买、不能卖。
   —— 合约里"卖"这个函数对普通用户是禁用/必定失败的,只有项目方自己能卖。
   识别:看代码里 transfer 是否对"卖方向"做了白名单限制。

2. 蜜罐(Honeypot 变体):看起来有漏洞(比如"能抽走池子的钱"),诱骗你打钱去"捡漏",
   结果你的钱进去就取不出来。

3. 假代币(Fake token):伪造知名代币的合约地址和符号,骗你在 DEX 里买到空气。

4. 无限增发(Unlimited mint):合约里 mint 函数没限权限,项目方能无限印币砸盘。

★ 审计口诀:看到一个代币,先问四个问题—— ① mint 有没有限权限? ② transfer/卖有没有隐藏限制? ③ 有没有黑名单/白名单能冻结你的币? ④ 税/手续费(fee-on-transfer)会不会吞掉你的转账金额?

11.8.3 转账扣税代币(Fee-on-Transfer)的兼容性坑

白话:有些代币每转一笔账,会扣一定比例当"手续费"(比如 2% 销毁或归项目方)。
  这本身是产品设计,但对【不知道这一点】的协议是灾难:

坑:协议按"amount"记账,但实际到账的是 amount * 98%。
  如果协议拿 amount 去算抵押/兑换,就会"账实不符",轻则用户吃亏,重则被套利。

防御:协议接新代币前,先测"实际到账余额差",不支持扣税代币就拒绝,
  或一律用"转账前后的余额差值"来确认真实到账数量,而不是信传入的 amount。

11.8.4 小数精度(Decimals)与舍入攻击

白话:代币有"精度"(decimals),比如 USDT 是 6 位小数、大多数是 18 位。
  合约里做除法、按比例分配时,如果先除后乘、或向下取整(floor),
  会产生"舍入误差"。攻击者可以通过精确构造金额,让每次舍入都"偏向自己",积少成多。

口诀:
  1. 【先乘后除】,尽量最后才做除法,减少中间精度损失
  2. 警惕"向下取整"累积——分配时要把"余数"处理掉(如归入协议或让利给最小单位)
  3. 金额计算用"最小单位"(整数),别用浮点(Solidity 根本没有浮点)

诚实提醒:代币安全是一个“深坑”,上面只是最常见四类。真实审计里还有 rebase 代币(余额会变)、重入式 ERC777 钩子、transfer 返回 false 却不 revert 等一堆兼容性问题。

11.9 钱包与私钥安全(用户侧攻防)

11.9.1 私钥与助记词:一条铁律 + 三个禁区

一句话定义(复习):私钥 = 资产的唯一钥匙;助记词 = 私钥的"人类可读备份"。
  谁拿到它们,谁就能【无需任何其他验证】地把资产全部转走,且无法追回。

铁律:not your keys, not your coins —— 私钥不在你手里,币就不是你的。
  (把币存在中心化交易所 = 把你的钥匙交给交易所保管,交易所被黑/跑路,你的币就没了)

三个禁区:
  1. 【绝不】把私钥/助记词截图、存网盘、发微信/邮件、抄在能被拍照的地方
  2. 【绝不】在任何网页、任何"客服"引导下输入助记词(正规钱包从不需要你"在线输入"助记词)
  3. 【绝不】把助记词导入一个"来路不明"的钱包 App(假钱包 App 会直接偷走你的助记词)

11.9.2 四类最常见的钓鱼手法

手法一:假网站 / 假钱包 App(Phishing)

原理:做一个和官方几乎一模一样的网站/App,诱导你输入私钥/助记词,或者诱导你"授权"。
识别:核对域名(如 uniswap.org vs uniswap-com.cn)、核对 App 来源、绝不点陌生链接里的"连接钱包"。

手法二:签名诈骗(Signature Phishing)

原理:骗你在钱包里"签一笔交易"(sign a message),你以为只是"登录/验证",
  实际上签的是"授权转走你所有币"或"把某币的授权给了攻击者"。
白话:对方递给你一份"文件",跟你说"签个字就行",你以为是签到表,其实是"财产转让同意书"。

★ 关键认知:很多钓鱼不需要你的私钥,只需要你【点一下"签名"】。
  因为"签名"= 你本人同意执行某个操作,攻击者把这个"同意"用在恶意交易上。

手法三:授权钓鱼(Approval Phishing)

原理:诱导你对一个恶意合约 approve(授权),授权之后攻击者就能 transferFrom 你的币。
识别:授权前看【授权对象地址】和【授权额度】,不明合约 + 无限额度 = 危险。
补救:立刻用 revoke 工具撤销授权。

手法四:假空投 / 假客服

原理:以"你中了空投""你的钱包有异常""帮你解冻"为由,诱导你"验证身份"= 交出助记词或签名。
铁律:真客服、真空投【永远】不会要你的助记词/私钥,也不会让你"先打钱"。

11.9.3 多签钱包(Multisig):把“一把钥匙”变成“N 把钥匙”

一句话定义:多签钱包要求一笔交易必须由【多个私钥中的 M 个】共同签名才能执行(M-of-N)。

生活类比:银行保险库需要【两把钥匙同时转】才能打开,行长一把、会计一把,缺一不可。
  这样即使一把钥匙被偷,攻击者也打不开。

安全价值:
  1. 单把私钥泄露 ≠ 资产丢失(还差其他人的签名)
  2. 防止"单人作恶"(内部人员监守自盗要串通多人)
  3. 适合项目方金库、DAO 资金池这类"大额 + 多人共管"的场景
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// 极简多签钱包示意(M-of-N)
contract SimpleMultiSig {
    address[] public owners;
    uint256 public required;                    // 至少需要 required 个签名
    mapping(address => bool) public isOwner;
    mapping(bytes32 => uint256) public signed;  // 每笔交易已有多少签名
    mapping(bytes32 => mapping(address => bool)) public hasSigned;

    constructor(address[] memory _owners, uint256 _required) {
        owners = _owners;
        required = _required;
        for (uint256 i = 0; i < _owners.length; i++) {
            isOwner[_owners[i]] = true;
        }
    }

    function sign(bytes32 txHash) external {
        require(isOwner[msg.sender], "not owner");
        require(!hasSigned[txHash][msg.sender], "already signed");
        hasSigned[txHash][msg.sender] = true;
        signed[txHash]++;
    }

    // 收集够签名后,任意 owner 可执行这笔交易
    function execute(bytes32 txHash, bytes calldata data) external {
        require(signed[txHash] >= required, "not enough signatures");
        (bool ok, ) = msg.sender.call(data);
        require(ok, "exec failed");
    }
}

白话:把“一把钥匙”拆成“N 把”,要凑够 M 把才能开门。 这是项目方资金安全的标配,也是面试里“如何保护大额资产”的标准答案之一。

11.9.4 私钥存储的最佳实践(从低到高)

第 0 级(危险):助记词明文存电脑/手机备忘录、截图、网盘、微信收藏 —— 随时可能被盗
第 1 级(基础):手抄在纸上,物理保存,多份异地备份 —— 但怕火灾、盗窃、被拍照
第 2 级(推荐):硬件钱包(如 Ledger/Trezor)—— 私钥【永不联网】,签名在硬件内完成
第 3 级(进阶):硬件钱包 + 多签 + 分片备份(把助记词拆成多份,异地存放,凑够才能还原)

★ 面试加分点:

  1. 硬件钱包的核心是“私钥离线 + 签名不出设备”,即使电脑中了毒也偷不到私钥
  2. 但硬件钱包也有风险:供应链被篡改、用户自己把助记词输进钓鱼网站、实物被盗
  3. 没有“绝对安全”,只有“把攻击成本拉高到不值得”

11.9.5 一条贯穿全节的审计心法

钱包安全的本质不是“技术多高深”,而是**“人”是最大的漏洞**。 再硬的硬件钱包,也挡不住用户自己把助记词截图发群里。 所以钱包安全的第一课永远是【安全教育】,其次才是技术手段(硬件钱包、多签、授权管理)。

11.10 面试题 K 组:Web3 与智能合约安全(30 题)

11.10.1 基础题(K1 ~ K12)

K1. 什么是智能合约?它和传统程序最本质的区别是什么?

★ 定义:部署在区块链上、自动执行、几乎无法修改的一段代码,直接管理链上资产。

生活类比:自动售货机——投钱自动出饮料,中间不需要人;但一旦摆上街就改不了,有 bug 也只能让它继续跑。

三个本质区别(必答):

  1. 不可篡改:部署后无法热修复,漏洞没法打补丁
  2. 直接管钱:出 bug 的后果是直接、不可逆的资金损失
  3. 代码公开:字节码全网可见,攻击者可以先读你的代码再打你

★ 加分点:补一句“代码即法律(Code is Law)”,并提 The DAO 事件对这条原则的冲击(分叉追回 vs 代码即法律)。

K2. 什么是重入攻击?请说它的根因和防御。

★ 定义:合约在“扣账之前”就调用外部合约转账,外部合约回调(重入)你的函数,趁状态未更新反复取钱。

生活类比:银行柜员“先给钱、后记账”,你接到钱发现账还没划掉,立刻再取,直到柜台被掏空。

根因:状态更新(Effects)晚于外部调用(Interactions)。

防御三条(都要说):

  1. CEI(检查-生效-交互):先改状态,最后才外部调用
  2. ReentrancyGuard:互斥锁禁止重入
  3. Pull Payment:改成“用户自己来提”,减少主动转账

★ 加分点:能说出四种变体(单函数/跨函数/跨合约/只读重入),并提 The DAO 损失 6000 万美元。

K3. 什么是整数溢出/下溢?Solidity 0.8.0 有什么变化?

★ 定义:数值超过类型上限绕回最小值(上溢),或低于 0 绕回最大值(下溢)。

生活类比:老式里程表,6 位显示,999999 再 +1 变 000000;0 再 -1 变 999999。

经典危害:下溢造币——余额 5 却转 10,5 - 10 下溢成天文数字,凭空多出代币。

版本分水岭:

  • < 0.8.0:静默溢出,必须手动 SafeMath
  • = 0.8.0:默认自动检查,溢出直接 revert,可用 unchecked 主动关闭

★ 加分点:unchecked 能省 gas 但必须非常确定,是“优化”和“埋雷”的一线之隔。

K4. transfer / send / call 三种转账方式的区别?

方式 失败行为 Gas 现代推荐
transfer 自动回滚 固定 2300 不推荐(2300 太少,易被拒)
send 返回 false 固定 2300 不推荐
call 返回 false,需手动检查 可指定 ★ 推荐(+ 手动检查返回值)

★ 加分点:transfer/send 的 2300 gas 上限是历史遗留(为了防重入), 但它导致“只要收款合约有稍复杂逻辑就转账失败”,所以现代改用 call + CEI + ReentrancyGuard。

K5. 什么是预言机?为什么“直接用现货价格”很危险?

★ 定义:预言机是把链下真实数据(价格等)喂给链上合约的桥梁。

生活类比:封闭房间里的“递纸条的人”,合约信纸条上的价格做决策。

危险:DEX 现货价格由池子资产比例决定,一笔大额交易就能瞬间改变它, 合约若用这个“可被单笔交易操纵”的价格做抵押/清算,就会被攻击者抬价套利。

防御:TWAP(时间加权平均价)、去中心化预言机网络(Chainlink)、多源交叉验证 + 熔断。

K6. 什么是闪电贷?它本身是攻击吗?

★ 定义:同一笔交易内“借巨款 → 用完 → 立刻还”的零抵押贷款。

生活类比:银行允许你同一秒内借 1 亿、赚到钱立刻还,还不上就整笔回滚。

关键:闪电贷本身是中立工具(套利者用它做无风险套利),但它给了攻击者“无限子弹”, 用来放大其它漏洞(价格操纵、余额分配漏洞等)。

★ 加分点:防闪电贷的本质是修好自己的价格/余额逻辑,而不是“禁掉闪电贷”(也禁不掉)。

K7. 什么是 MEV?什么是三明治攻击?

★ 定义:MEV = 交易排序者通过调整交易顺序、插入自己交易来额外提取的价值。

生活类比:排队买限量鞋,黄牛看到你的订单,抢在你前面买空再高价卖回给你。

三明治攻击:看到你的买单 → 抢在你前面买(抬价)→ 你高价成交 → 攻击者在你后面卖(套现), 你被“两片面包”夹在中间,多付了滑点。

★ 加分点:区分“良性 MEV”(套利、清算,让价格回归)和“恶性 MEV”(三明治、前置,坑普通用户)。

K8. ERC20 的 approve 有什么风险?

★ 定义:ERC20 授权是“两段式”(approve 授权 + transferFrom 花费), 很多场景授权了“无限额度”,一旦被授权合约出问题,攻击者能转走你所有币。

风险:无限授权 + 被授权合约有漏洞/被黑/被替换 = 资产被洗,无需私钥。

应对:只授权本次所需额度;用完 revoke 归零;定期用 revoke 工具撤销不必要授权。

★ 加分点:approve 的“先归零再设新值”顺序问题,避免旧额度未清零导致的覆盖失败/竞争窗口。

K9. 什么是多签钱包?它解决什么问题?

★ 定义:一笔交易需 N 个私钥中的 M 个共同签名才能执行(M-of-N)。

生活类比:银行保险库要两把钥匙同时转才能开。

价值:单钥泄露≠资产丢失;防单人作恶;适合项目方金库、DAO 资金池。

★ 加分点:多签是“如何保护大额资产”的标准答案之一,配合硬件钱包 + 分片备份更稳。

K10. 为什么不能把私钥/助记词截图存手机?

★ 定义:私钥/助记词是资产的唯一钥匙,拿到即可转走全部资产且无法追回。

铁律:not your keys, not your coins。

风险:截图/网盘/微信/备忘录都可能被云端泄露、被恶意 App 读取、被截图后盗用。

★ 加分点:正确答案是“手抄纸质 + 硬件钱包离线保存”,并强调“人”是钱包安全最大的漏洞。

K11. 访问控制缺失是什么?为什么它在合约里特别常见?

★ 定义:重要函数没加“只有管理员能调”的权限限制,任何地址都能调用。

为什么常见:合约函数默认对全网开放,和传统 Web“默认需要登录”的习惯相反, 新手容易漏掉“这个函数该不该限权限”这一步。

★ 加分点:审计第一动作 = 每个 function 过一遍,问“这是不是谁都能调?该不该 onlyOwner?”。

K12. tx.origin 和 msg.sender 的区别?为什么鉴权不能用 tx.origin?

★ 定义:tx.origin 是“最初发起交易的人”,msg.sender 是“当前这一层直接调用你的人”。

生活类比:tx.origin 是“最初按下按钮的人”,msg.sender 是“当前跟你说话的人”。

为什么不能用 tx.origin 鉴权:攻击者诱导 owner 调它的合约,在该合约里再调你的函数, 此时 tx.origin 仍是 owner(绕过鉴权),而 msg.sender 是攻击者合约。

★ 铁律:鉴权只用 msg.sender。

11.10.2 进阶题(K13 ~ K22)

K13. 请手写一个“先扣账再转账”的安全 withdraw,并说明为什么安全。

function withdraw() public {
    uint256 amount = balances[msg.sender];      // Checks
    require(amount > 0, "nothing");             // Checks
    balances[msg.sender] = 0;                   // Effects ★ 先扣
    (bool ok, ) = msg.sender.call{value: amount}(""); // Interactions 后转
    require(ok, "fail");
}

为什么安全:即使对方在 call 里重入 withdraw,第二次进来时 balances 已经是 0, require(amount > 0) 直接失败,重入被阻断。这就是 CEI 的完整体现。

K14. 只读重入(Read-only Reentrancy)和经典重入有什么不同?

★ 经典重入:通过“转账回调”重复调用,目的是重复取钱。 ★ 只读重入:即使加了 ReentrancyGuard 防住转账重入,攻击者仍能利用合约在“状态已更新但尚未结算”的 中间状态,从**只读函数(view)**读到不一致的数据,去误导另一个依赖该合约的协议。

区别:经典重入针对“资金”;只读重入针对“状态一致性/最终性(finality)”。

★ 加分点:防御是保证外部依赖读到的状态始终一致,或采用“先更新状态再暴露”的最终性设计。

K15. 如何审计一个“价格预言机”是否安全?

答(结构化四问):

  1. 价格从哪来?—— 单一 DEX 现货价(危险) vs 多源聚合(较安全)
  2. 能否被单笔交易影响?—— 现货价可以,TWAP/多源中位数难
  3. 有没有异常熔断?—— 价格偏离阈值时是否暂停清算/借款
  4. 更新延迟多大?—— 极端行情下旧价是否会造成套利窗口

★ 加分点:给出“TWAP + Chainlink + 熔断”的分层方案,并诚实说“没有 100% 安全”。

K16. 闪电贷攻击通常怎么和价格操纵组合?请描述完整攻击链。

完整链:闪电贷借巨款 → 砸进池子把价格抬/砸到目标值 → 触发目标协议的“价格决策”漏洞套利 → 反向交易恢复价格 → 还闪电贷 → 净赚离场。

关键:闪电贷提供“无限子弹”,价格操纵提供“错误决策”,两者叠加才致命。

★ 加分点:防御要落在“价格用 TWAP + 奖励按时间累积 + 关键操作加冷却期”。

K17. 什么是代理合约的存储冲突(Storage Collision)?

★ 定义:代理模式下,变量按“slot 位置”存储而非“名字”。升级后新逻辑合约若在相同 slot 放了不同类型/含义的变量,会覆盖或错读旧数据。

铁律:升级时变量声明顺序不能变,只能末尾追加。

★ 加分点:提 EIP-1967(实现地址放固定 slot)+ 透明代理(Transparent Proxy)。

K18. 什么是貔貅盘(Honeypot)?如何从代码识别?

★ 定义:只能买不能卖的恶意代币,“卖”对普通用户禁用/必失败,只有项目方能卖。

识别:看 transfer/卖相关函数是否对“卖方向”做了白名单限制、是否在卖出时强制 revert、 是否只有特定地址能成功卖出。

★ 加分点:审计一个代币先问四问——mint 权限?买卖限制?黑名单冻结?扣税?

K19. 如何防止三明治攻击?(用户侧和协议侧各说几点)

用户侧:设滑点容忍度(价格偏离过大就取消);用私有交易/Flashbots 绕开公开 mempool。 协议侧:关键操作提交-揭示/时间锁;用 TWAP 提高操纵成本。

★ 加分点:滑点保护的本质是“挂单先设止损价”,抬价过头单子就作废,黄牛白抬。

K20. 为什么“用 address(this).balance 做奖励分配”很危险?

★ 因为合约余额是“瞬时值”,可以被一笔闪电贷临时撑大或抽干。 用瞬时余额做分配依据,攻击者就能借巨款瞬间撑大余额、领走大部分奖励、再撤走。

防御:奖励按时间/区块累积记账,而非看瞬时余额差。

★ 加分点:可组合 + 资金可瞬时流动的世界里,“局部正确 ≠ 全局安全”。

K21. SafeMath 解决了什么问题?0.8.0 之后还需要吗?

SafeMath:在 <0.8.0 时代,把 + - * 包装成带溢出检查的 .add/.sub/.mul,溢出即 revert。

0.8.0 之后:编译器内置溢出检查,一般不再需要 SafeMath。

★ 加分点:但 unchecked 块会关闭检查,审计时反而要重点盯 unchecked 块。

K22. 如何保护一个项目的金库资金?(给一个分层方案)

分层方案:

  1. 多签钱包(M-of-N),大额需多人签名
  2. 硬件钱包离线保管私钥,冷热分离
  3. 关键操作加时间锁(Timelock),延迟执行留出“反悔/发现”窗口
  4. 定期 revoke 不必要授权 + 最小权限
  5. 应急预案:紧急暂停开关(但暂停开关本身要防滥用)

★ 加分点:时间锁(Timelock)是“防内部作恶 + 防被黑后瞬间卷款”的关键一环。

11.10.3 场景题(K23 ~ K27)

K23. 老板问你“我们项目要用区块链,安全吗?“你怎么回答?

答(三步法):

  1. 先澄清:区块链解决的是“信任/防篡改”,不解决“安全”。代码该有的漏洞一样有,而且更严重—— 因为不可篡改 + 直接管钱 + 代码公开。
  2. 给类比:上链 = 把带 bug 的钱包挂街上还贴了“里面有钱”。
  3. 给方案:安全要前置到写代码那一刻——合约审计、CEI、最小权限、预言机用 TWAP、金库多签。

★ 加分点:补一句“链上安全的第一责任人是写合约的人,不是链本身”。

K24. 你审计一个借贷协议,发现它用 DEX 现货价做清算价,怎么写报告?

答(报告要点):

  1. 风险定性:高危——现货价可被单笔交易操纵,导致错误清算/超额借款
  2. 攻击推演:攻击者闪电贷砸池子抬价 → 超额借款 → 砸回价格 → 坏账
  3. 修复建议:改用 TWAP / Chainlink 聚合价 + 多源交叉验证 + 异常熔断
  4. 附加:清算逻辑加冷却期,避免被闪电贷“一锤子”打穿

★ 加分点:给一个可量化的阈值建议(如“价格偏离 5% 暂停清算”)。

K25. 你的钱包收到一个陌生空投代币,还看到“xx 平台”让你“验证钱包领取奖励”,怎么办?

答:

  1. 不点、不签名、不授权——空投 + “验证钱包”是典型钓鱼话术
  2. 真空投不需要你输入助记词/私钥,也不需要你“先打钱”
  3. 若已授权过可疑合约,立刻 revoke
  4. 核对平台域名、App 来源,只信官方渠道

★ 加分点:强调“签名 = 同意执行某个操作”,很多钓鱼只需你点一下签名,不需要私钥。

K26. 项目被黑,攻击者还在陆续转钱,你作为安全负责人第一步做什么?

答(应急顺序):

  1. 先止血:能暂停的先暂停(紧急暂停开关/多签),冻结相关合约/撤销授权
  2. 保留证据:链上交易本就是公开的,立即拉取攻击交易哈希、时间线
  3. 通知:交易所/相关方冻结流入的赃款地址(部分中心化交易所能配合)
  4. 分析:定位漏洞(重入?价格操纵?私钥泄露?),确认影响面
  5. 复盘:修复 + 重新审计 + 公告

★ 加分点:诚实说明“链上转账不可逆”,所以第一优先级是“阻止进一步损失”而非“追回已转走的”。

K27. 面试官给你一段 withdraw 代码,让你找 bug,你的检查顺序是什么?

答(审计顺序):

  1. 看状态更新和外部调用的顺序(是否 CEI)
  2. 看转账方式(call 是否检查返回值,是否用 tx.origin 鉴权)
  3. 看有没有访问控制(谁都能调吗)
  4. 看算术(有没有 unchecked、有没有用瞬时余额/现货价)
  5. 看是否可被闪电贷/价格操纵影响

★ 加分点:能边看边把“每一处”对应到具体漏洞类别名(重入/溢出/ACL/tx.origin/预言机)。

11.10.4 追问链(K28 ~ K30)

K28. Web3 安全十连问

Q1: 智能合约为什么不能随便改? A: 部署到链上后代码(字节码)全网存储,无中心化“管理员”可改;要改只能预埋升级(代理),而升级本身又是攻击面。

Q2: 重入攻击的根因? A: 状态更新(扣账)晚于外部调用(转账),中间留出重入窗口。

Q3: CEI 三个字母分别是什么? A: Checks(检查)→ Effects(生效/改状态)→ Interactions(交互/外部调用)。

Q4: transfer 和 call 哪个现代更推荐?为什么? A: call(+手动检查返回值)。transfer 固定 2300 gas 太易被拒。

Q5: tx.origin 为什么不能做鉴权? A: 它代表“最初发起者”,攻击者可诱导 owner 调中间合约,绕过鉴权。

Q6: 0.8.0 之前的整数溢出怎么防? A: SafeMath(.add/.sub/.mul 带溢出检查)。

Q7: 预言机价格怎么防操纵? A: TWAP + 去中心化预言机网络(Chainlink)+ 多源交叉验证 + 熔断。

Q8: 闪电贷为什么能“零抵押”? A: 借和还发生在同一笔交易内,靠区块链原子性保证:还不上整笔回滚,放贷方零风险。

Q9: 三明治攻击夹的是谁? A: 普通用户的交易——攻击者前买后卖,让用户高价成交、承受滑点。

Q10: 钱包安全最大的漏洞是什么? A: 人——再硬的硬件钱包也挡不住用户自己把助记词截图发群里。

K29. 合约审计方法论十连问

Q1: 拿到一个合约,第一步做什么? A: 先读“它管什么钱、有哪些外部可调函数、状态变量有哪些”,建立全局模型。

Q2: 每个 function 必问的一句话? A: “这个函数是任何人都能调吗?该不该限权限(onlyOwner/onlyRole)?”

Q3: 看到 .call 转账,先看什么? A: 先看返回值是否检查,再看它在“扣账之前还是之后”。

Q4: 看到除法/比例分配,警惕什么? A: 精度丢失、先除后乘、向下取整累积,可能被舍入攻击。

Q5: 看到价格来源,问什么? A: 能不能被单笔交易影响?是现货价还是 TWAP/预言机?

Q6: 看到 address(this).balance 参与逻辑,警惕什么? A: 瞬时余额可被闪电贷撑大/抽干,做分配依据很危险。

Q7: 看到代理合约,警惕什么? A: 存储冲突(变量顺序不能变)+ 函数选择器遮蔽。

Q8: 看到 ERC20 交互,警惕什么? A: 扣税代币(到账≠传入 amount)、假币、无限授权。

Q9: 审计报告怎么分级? A: 按危害和可利用性分 Critical/High/Medium/Low,每条给“攻击推演 + 修复建议”。

Q10: 为什么说“局部正确 ≠ 全局安全”? A: 可组合 + 闪电贷放大,单个合约没问题,组合起来可能被套利/操纵打穿。

K30. 给非技术朋友讲“智能合约安全”八连问

Q1: 智能合约是什么? A: 跑在区块链上、自动执行、改不了的代码,就像街上的自动售货机。

Q2: 它和普通程序最大的不同? A: 改不了 + 直接管钱,出 bug 钱就没了,还追不回来。

Q3: 重入攻击是啥? A: 柜员先给钱后记账,你接到钱又立刻再取,直到柜台被掏空。

Q4: 整数溢出是啥? A: 里程表 999999 再走一格变 000000,余额算错账。

Q5: 闪电贷是啥? A: 同一秒内借 1 亿、赚完立刻还,还不上整笔作废——攻击者拿它当“无限子弹”。

Q6: 三明治攻击是啥? A: 黄牛看到你买单,抢你前面买、再高价卖回给你,你被夹中间。

Q7: 钱包被偷最多的原因? A: 不是被破解,是自己把助记词截图/发给假客服/点了假签名。

Q8: 普通人怎么自保? A: 助记词手抄离线存、用硬件钱包、不点陌生链接签名、定期 revoke 授权。

11.11 第十一章小结

这一章讲的是一个“代码直接管钱”的世界。它和前面十章的底层逻辑相通(最小权限、状态一致、审计), 但有两个颠覆性的新前提:不可篡改(出 bug 不能修)和资金可瞬时流动(闪电贷让“局部正确”失效)。 记住这两点,所有合约漏洞都能归结到“状态不一致”和“经济激励错位”两条主线上。

11.11.1 五条核心认知

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知一:代码即法律,但法律有 bug 时,代价是钱

   智能合约 = 无法修改 + 直接管钱 + 代码公开。
   传统程序有 bug 可以热修;合约有 bug 只能眼睁睁看钱被转走(除非硬分叉,如 The DAO)。
   ★ 推论:安全必须【前置】到写代码那一刻,审计是"上线前"而非"出事后"的事。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知二:重入攻击的根因是"状态更新晚于外部调用",一句话记住 CEI

   先检查(Checks)→ 再改状态(Effects)→ 最后才外部调用(Interactions)。
   这个顺序反了,就给了攻击者"趁你还没记账、再取一次"的窗口。
   CEI 不只是合约的规矩,它适用于任何"先改数据还是先做副作用"的场景。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知三:价格是合约的"眼睛",眼睛可以被蒙上

   合约看不到链下世界,只能信预言机。用"可被单笔交易操纵的现货价"当眼睛,
   攻击者就能蒙住它、骗它做出错误的抵押/清算决策。
   ★ 防御:TWAP、去中心化预言机网络、多源交叉验证 + 熔断。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知四:闪电贷不是攻击,是放大器;"局部正确 ≠ 全局安全"

   闪电贷本身是中立工具,但它给攻击者"无限子弹"。
   你的合约单独看没问题,但在可组合、资金可瞬时流动的链上,
   一个"用瞬时余额/现货价做决策"的小漏洞,会被一笔闪电贷放大成天文数字的损失。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知五:钱包安全最大的漏洞是"人",不是技术

   再硬的硬件钱包,也挡不住用户自己把助记词截图发群里、点假签名、授权给恶意合约。
   私钥/助记词一旦泄露 = 资产永久丢失,无法追回。
   ★ 所以钱包安全的第一课永远是安全教育,其次才是硬件钱包、多签、授权管理。

11.11.2 攻防速查卡:合约攻击手法 × 防御措施

# 攻击手法 一句话危害 一句话防御 详参
1 经典重入 反复取钱掏空合约 CEI + ReentrancyGuard 11.3
2 跨函数/跨合约重入 换函数继续重入 每个函数都 CEI 11.3
3 只读重入 误导依赖方 保证状态一致性/finality 11.3
4 整数上溢/下溢 凭空造币 0.8.0 自动检查 / SafeMath 11.4
5 未检查返回值 转账失败当成功 require 检查返回值 11.2
6 tx.origin 鉴权 授权被盗用 只用 msg.sender 11.2
7 访问控制缺失 谁都能抽钱 onlyOwner/onlyRole 11.2
8 代理存储冲突 升级覆盖数据 变量顺序不变 + EIP-1967 11.2
9 预言机/价格操纵 错误清算套利 TWAP + Chainlink + 熔断 11.5
10 闪电贷攻击 一锤子卷走千万 不用瞬时余额/现货价 11.6
11 三明治/前置交易 用户被夹、多付滑点 滑点保护 + 私有交易 11.7
12 无限授权 被授权合约出事洗走你币 最小授权 + revoke 11.8
13 貔貅盘/假币 只能买不能卖 审计买卖限制/mint 权限 11.8
14 扣税代币坑 到账≠传入金额 按余额差确认真实到账 11.8
15 助记词钓鱼/假签名 资产被洗、无法追回 安全教育 + 硬件钱包 11.9

11.11.3 智能合约安全成熟度自评表(L0 ~ L4)

级别 状态 典型表现 下一步
L0 裸奔 无审计、凭感觉 上线前没人审合约;用现货价做清算 先做一次完整代码审计
L1 起步 有基础规范 知道 CEI、用 0.8.x、用 Ownable 引入自动化审计工具(Slither)
L2 制度 流程化 上线前必审、有 check-list 价格用 TWAP/预言机 + 熔断
L3 自动化 工具 + 监控 Slither 进 CI、链上事件监控告警 上正式审计 + 漏洞赏金
L4 自适应 纵深防御 多签 + 时间锁 + 监控 + 应急预案 定期红蓝对抗、跟踪新攻击手法

★ 工具提示:Slither(静态分析)、Mythril(符号执行)、Echidna(模糊测试)是合约审计三件套, 面试提到“你用过哪些审计工具”时,能说出这三个 + 各自定位,会很加分。

11.11.4 五份可直接使用的清单

清单一:合约代码审计 check-list(上线前必过)

□ 每个 function:是否限了权限(该 onlyOwner 的有没有加)?
□ 每个 .call 转账:返回值是否检查?在"扣账之前还是之后"?
□ 每个算术:有没有 unchecked?有没有用瞬时余额/现货价?
□ 每个价格来源:能否被单笔交易影响?
□ 每个外部调用:会不会重入?有没有 ReentrancyGuard?
□ 每个代理:变量顺序是否锁定?有没有存储冲突风险?
□ 每个 ERC20 交互:是否兼容扣税代币?授权是否最小化?

清单二:价格安全 check-list

□ 价格来源是现货价还是 TWAP/预言机?
□ 是否多源交叉验证?偏差阈值多少?
□ 是否有异常熔断(价格偏离 N% 暂停清算)?
□ 预言机更新延迟是否会造成套利窗口?

清单三:资金金库安全 check-list

□ 是否多签(M-of-N)?
□ 是否硬件钱包冷热分离?
□ 是否有时间锁(Timelock)延迟执行?
□ 是否有紧急暂停开关(且暂停开关本身受限权)?
□ 是否定期 revoke 不必要授权?

清单四:用户钱包自保 check-list

□ 助记词是否手抄离线保存(不截图、不存网盘/微信)?
□ 是否使用硬件钱包?
□ 是否定期 revoke 不必要授权?
□ 是否只信官方渠道、核对域名?
□ 是否养成了"陌生链接不签名、不授权"的习惯?

清单五:合约审计工具链

□ 静态分析:Slither(找重入、未检查返回值、访问控制等常见问题)
□ 符号执行:Mythril(找可达路径上的漏洞)
□ 模糊测试:Echidna(用随机/定向输入测不变量)
□ 人工审计:以上工具 + 人工推演经济攻击(工具查代码,人查"经济逻辑")

11.11.5 五句话记忆法

一句话记【代码即法律】:自动售货机摆上街就改不了,有 bug 也只能让它继续卖。

一句话记【重入攻击】:柜员先给钱后记账,你接到钱又立刻再取,直到柜台被掏空。

一句话记【整数溢出】:里程表 999999 再走一格变 000000,余额算错账。

一句话记【闪电贷】:同一秒借 1 亿、赚完立刻还——攻击者拿它当"无限子弹"。

一句话记【钱包安全】:私钥是唯一钥匙,最大的漏洞不是锁,是拿钥匙的人。

11.11.6 本章与前后章节的关系

                          ┌─────────────────────────────────────┐
                          │  第十二章 数据合规与隐私保护         │
                          │  (从"技术攻防"转向"合规红线")      │
                          └─────────────────────────────────────┘
                                        ▲
  前九章(经典/云原生攻击面)            │  新的价值载体:链上资产
  ┌──────────────────────────┐  ┌──────────────────────────────┐
  │ 第一~五章:应用层        │→ │  第十一章 Web3 与智能合约安全  │
  │ 第六~七章:主机/内网     │  │  · 重入 / 溢出 / 访问控制     │
  │ 第八~九章:DFIR/供应链   │  │  · 预言机 / 闪电贷 / MEV      │
  │ 第十章:云原生与Serverless│ │  · 钱包 / 私钥 / 多签         │
  └──────────────────────────┘  └──────────────────────────────┘
        │                                │
        └────────── 共用的地基 ───────────┘
   (最小权限、状态一致、纵深防御、审计前置——这些基本功跨越所有技术栈)

一句话串起来:第十一章把“安全”从“防漏洞”上升到了“守资产”——当代码直接管钱、无法修改、资金可瞬时流动时, 安全不再是“上线后打补丁”,而是“写代码那一刻就该算清的经济博弈”。 下一章(第十二章)再换一个维度:不是“怎么被攻击”,而是“怎么不违法”——数据合规与隐私保护。

第十二章:数据合规与隐私保护(从“不被黑”到“不违法”)

本章定位:前面十一章都在讲“怎么不被攻击”,这一章换一个维度——怎么不违法、不踩红线。 对工程师来说,安全和合规是一体两面:安全是“防止数据被坏人拿走”,合规是“防止数据被你自己乱用”。 面试里,凡是涉及用户数据、跨境业务、金融/政务系统的岗位,“个保法”“等保”“数据出境”已经是必答题。

为什么普通工程师也要懂:

  1. 你写的每一个“收集用户手机号”的接口,背后都挂着一部《个人信息保护法》
  2. 数据出境、隐私政策、敏感个人信息,不再是法务一个人的事,而是架构设计的一部分
  3. “等保测评”“密评”是很多系统上线的硬性门槛,不懂连验收都过不了

本章口径说明:以下内容严格依据中国现行法律法规(《网络安全法》《数据安全法》《个人信息保护法》等) 及配套标准(等保 2.0、商用密码应用安全性评估等)的公开要求,做技术层面的客观介绍, 旨在帮助工程师理解“什么能做、什么不能做、该怎么做”,不构成法律意见。

12.1 数据合规全景与名词先行课

12.1.1 名词先行课:把合规术语讲成大白话

合规领域的术语又多又绕,先用生活类比把最核心的几个讲透,后面每一节都是它们的展开。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词一:个人信息 / PII(Personal Identifiable Information)

  一句话定义:能【直接或间接】识别到"具体某个自然人"的信息。

  生活类比:PII 就像一个人的"指纹"——单独一个"张三"可能不是(重名太多),
    但"张三 + 手机号"或者"张三 + 家庭住址"就能精准锁定"就是你"。

  分类(个保法的关键区分):
    · 一般个人信息:姓名、电话、邮箱……(能识别,但敏感性一般)
    · ★ 敏感个人信息:身份证号、银行账户、行踪轨迹、健康医疗、宗教信仰、
      不满 14 周岁未成年人信息……(一旦泄露/滥用,危害更大,规则更严)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词二:《个人信息保护法》(PIPL,个保法)

  一句话定义:中国专门管"个人信息怎么收集、怎么用、怎么保护"的一部法律(2021 年 11 月 1 日施行)。

  生活类比:给"个人信息"立了规矩的"交通法规"——规定了谁能收集(要有合法理由)、
    收集了能干嘛(目的明确)、不能干嘛(不能超范围用、不能随便卖)。

  几个关键词(后面 12.2 展开):
    · 告知-同意:收集前要明明白白告诉用户、征得同意
    · 最小必要:只收"提供服务所必需"的信息,不能多收
    · 单独同意:处理敏感个人信息、向第三方提供、跨境提供,要【单独】同意
    · 删除权/更正权:用户有权要求你删、改他的信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词三:GDPR(欧盟《通用数据保护条例》)

  一句话定义:欧盟的数据保护法规(2018 年生效),是"全球最严"的隐私法之一,
    对全球任何"处理欧盟居民数据"的企业都有管辖权。

  生活类比:欧盟给"欧洲人的数据"立的一部"域外效力的家规"——不管你的公司开在哪,
    只要你处理欧洲用户的数据,就得守它的规矩。

  几个关键点(后面 12.3 展开):
    · 数据主体权利:访问权、更正权、删除权(被遗忘权)、可携带权、反对权
    · 合法处理依据:同意、合同、法定义务、重大利益、公共利益、正当利益(六选一)
    · 处罚:最高可罚【全球年营业额 4% 或 2000 万欧元】取较高者
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词四:等保 2.0(网络安全等级保护 2.0)

  一句话定义:中国对"信息系统按重要程度分等级、按等级做安全保护"的强制性制度。

  生活类比:给大楼按"重要程度"分消防等级——普通住宅和银行金库的消防要求当然不一样。
    等保就是给信息系统分五级(一级最低、五级最高),等级越高,安全要求越严。

  关键点(后面 12.7 展开):
    · 定级 → 备案 → 建设整改 → 等级测评 → 监督检查,五个规定动作
    · 绝大多数企业系统是二级或三级
    · 三级以上要做"等保测评"(找有资质的测评机构),是很多系统上线的硬门槛
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词五:密评 / 国密(商用密码应用安全性评估 / 商用密码算法)

  一句话定义:密评 = 对"系统里用的密码技术是否合规、是否达标"的评估;
    国密 = 中国自主的商用密码算法(SM2 加密、SM3 哈希、SM4 分组加密、SM9 标识密码)。

  生活类比:国密就是"国产锁芯标准"——要求关键系统用国产的锁(算法),
    密评就是检查"你这把锁装得对不对、达不达标"。

  关键点(后面 12.7 展开):
    · 等保三级以上的系统、关键信息基础设施,通常要求"过密评"
    · SM2(公钥加密/签名,对标 RSA/ECDSA)、SM3(哈希,对标 SHA-256)、
      SM4(对称加密,对标 AES)是三个最常考的国密算法
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词六:数据出境(Cross-border Data Transfer)

  一句话定义:把在中国境内产生/收集的数据,传输到境外(或让境外主体可访问)的行为。

  生活类比:把"国内收集的客户资料"打包寄到国外分公司/用国外的云服务器存——这就叫"出境",
    不是"人出国",而是"数据出国"。

  关键点(后面 12.5 展开):
    · 出境要过"安全评估"或"标准合同"或"认证"等合规路径
    · 重要数据、达到一定量级的个人信息,出境前要申报安全评估
    · 用境外云服务器存国内用户数据,本身就是一种"数据出境"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
名词七:DPIA(数据保护影响评估 / 个人信息保护影响评估)

  一句话定义:在做一个"可能对个人权益有高风险"的数据处理活动【之前】,
    先评估它的风险、想好怎么降低,形成一份评估报告。

  生活类比:盖一栋可能影响居民采光的大楼之前,先做"环境影响评估"——
    提前把"会不会挡光、怎么补救"想清楚,而不是盖完了再被投诉。

12.1.2 为什么工程师要懂合规(三个理由)

理由一:合规是【架构约束】,不是"事后补材料"。
  数据要"最小必要"→ 影响你的接口设计(不能一次性收集一堆字段)
  数据要"可删除"→ 影响你的存储设计(要能按用户 ID 彻底删干净)
  数据要"可携带"→ 影响你的导出设计(要能导出结构化数据)
  这些都必须【在写代码之前】就想清楚,等系统上线了再改,成本极高。

理由二:合规是【上线门槛】。
  等保测评、密评、数据出境安全评估,都是"过不了就不能上线/不能接客户"的硬性要求。
  不懂这些,你在政务、金融、医疗、跨境业务里寸步难行。

理由三:合规是【面试考点】。
  "个保法和 GDPR 的区别""什么是敏感个人信息""数据出境怎么合规"
  已经是后端/架构/安全岗位的常见题。

12.1.3 中国数据合规法规体系全景

中国当前的数据合规,核心是“三法一条例 + 配套办法/标准”,先建立全局观:

┌─────────────────────────────────────────────────────────────────┐
│ 《网络安全法》(2017)   —— 网络运行安全 + 等级保护 + 关键信息基础设施 │
│ 《数据安全法》(2021)   —— 数据分类分级 + 数据出境 + 数据安全制度     │
│ 《个人信息保护法》(2021)—— 个人信息处理规则 + 用户权利 + 跨境        │
│ 《数据出境安全评估办法》等配套 —— 数据出境的具体路径                  │
├─────────────────────────────────────────────────────────────────┤
│ 配套标准:等保 2.0(GB/T 22239)· 密评 · 数据分类分级指南          │
└─────────────────────────────────────────────────────────────────┘

★ 记忆口诀(面试常用):“网安法管网络,数安法管数据,个保法管个人”。 三者层层递进:先保网络运行安全(网安法),再保数据本身安全(数安法), 最后落到最细的“个人信息”保护(个保法)。

12.1.4 合规 vs 安全:一体两面,但别搞混

维度 安全(Security) 合规(Compliance)
回答的问题 怎么不被攻击、不被偷 怎么不违法、不踩红线
保护对象 数据的机密性/完整性/可用性 数据主体的权利、法律的强制性要求
失败后果 被黑、数据泄露 行政处罚、罚款、下架、刑事责任
验证方式 渗透测试、漏洞扫描 等保测评、密评、监管检查

★ 关键认知:“合规”≠“安全”,但“合规”往往是“安全的最低标准”。 一个过了等保的系统照样可能被黑(等保是“底线要求”,不是“攻不破”); 但一个“技术上很安全”却违规收集数据的系统,照样会被罚。 优秀工程师两个都要懂,且要能分清“这件事是安全需求,还是合规需求”。

12.2 个人信息保护法(PIPL)技术落地

12.2.1 个保法的五条“铁规矩”

先把个保法最核心的规则提炼成工程师听得懂的五条:

规矩一:告知-同意(Consent)
  收集前,要【明明白白】告诉用户:你是谁、收什么、用来干嘛、存多久、怎么联系你,
  并取得用户【自愿、明确】的同意。
  ★ 技术含义:不能偷偷收集;隐私政策要"单独弹窗",不能藏在几十页里默认勾选。

规矩二:最小必要(Data Minimization)
  只收集"提供服务所必需"的信息。
  ★ 技术含义:能收手机号就别收身份证;能收昵称就别收家庭住址。
    接口字段、数据库字段都要"能少则少"。

规矩三:目的限制(Purpose Limitation)
  收集来的信息,只能用于当初告诉用户的那个目的,不能"顺便"拿去干别的。
  ★ 技术含义:营销、画像、二次利用,要重新告知并取得同意。

规矩四:单独同意(Separate Consent)
  处理敏感个人信息、向第三方提供、跨境提供,要【单独】同意,不能混在一揽子协议里。
  ★ 技术含义:这几个场景要有"独立的勾选框/独立弹窗",不能和主协议绑定。

规矩五:用户权利(Data Subject Rights)
  用户有权【查询、更正、删除、撤回同意、注销账户】。
  ★ 技术含义:系统要能按用户 ID 找到他的所有数据、改、删、导出。

12.2.2 敏感个人信息(最容易踩的坑)

一句话定义:一旦泄露或非法使用,容易导致自然人的人格尊严受损或人身、财产安全受危害的个人信息。

个保法明确列举的敏感个人信息:
  · 生物识别信息(人脸、指纹、声纹)
  · 宗教信仰、特定身份、医疗健康、金融账户、行踪轨迹
  · 不满十四周岁未成年人的个人信息

★ 处理敏感个人信息的特别要求:
  1. 要有【特定目的 + 充分必要性】,且采取严格保护措施
  2. 要取得【单独同意】(不能一揽子)
  3. 要告知"处理的必要性 + 对个人的影响"
  4. 处理不满 14 周岁未成年人信息,要取得【监护人同意】,且制定专门规则

白话:敏感个人信息就像“高危品”,普通物品走普通通道,高危品要单独申报、专人押运。 人脸、身份证、健康、金融这些,就是“高危品”。

12.2.3 个保法的技术落地(代码示例)

个保法的很多要求,最终都落到工程实现上。下面用代码演示三个最常见的落地点。

落地一:最小必要 + 字段分级(接口只返回必要字段)

# 用户信息表(示意):把字段按敏感性分级,接口按需返回
USER_FIELDS = {
    "basic":     ["user_id", "nickname"],                    # 基础:公开场合可展示
    "contact":   ["phone", "email"],                          # 联系:需授权才用
    "sensitive": ["id_card", "bank_account", "face_id"],      # 敏感:严格管控
}

def get_user_profile(user_id: str, need: str):
    """按'最小必要'原则,只返回调用方真正需要的字段层级"""
    allowed = {
        "basic": USER_FIELDS["basic"],
        "contact": USER_FIELDS["basic"] + USER_FIELDS["contact"],
        "sensitive": [],  # 默认绝不下发敏感字段
    }
    fields = allowed.get(need, USER_FIELDS["basic"])
    return query_user(user_id, fields)  # 只查这些字段

落地二:同意记录留痕(“告知-同意”要有证据)

# 每次收集敏感信息前,记录用户的"单独同意"作为合规证据
def collect_sensitive_info(user_id: str, field: str, purpose: str):
    if field not in USER_FIELDS["sensitive"]:
        raise PermissionError("敏感字段需单独同意")
    # 1. 记录同意时间、版本、目的
    record_consent(user_id=user_id,
                   field=field,
                   purpose=purpose,        # 明确告知的目的
                   policy_version="v2.3", # 当时的隐私政策版本
                   consented_at=now())
    # 2. 才去采集
    return collect(user_id, field)

落地三:删除权 / 撤回同意(要能“彻底删干净”)

# 用户行使删除权:不仅删主表,还要删所有关联副本、缓存、备份引用
def delete_user_data(user_id: str):
    # 1. 主表删除
    db.delete("users", where={"user_id": user_id})
    # 2. 关联表删除(订单、日志、画像、行为记录)
    for table in ["orders", "behavior_log", "user_profile", "consents"]:
        db.delete(table, where={"user_id": user_id})
    # 3. 缓存删除
    cache.delete(f"user:{user_id}")
    # 4. 标记"待物理删除",让备份/归档在保留期后同步清除
    db.insert("deletion_tasks", {"user_id": user_id, "status": "pending_purge"})

★ 面试加分点:删除权落地最大的坑是“只删了主表,没删副本”——日志、备份、数仓、缓存里还躺着数据, 严格来说不算“删除”。要设计“删除任务队列 + 备份保留期 + 定期物理清除”的闭环。

12.2.4 个保法的处罚力度

· 一般违法:责令改正、警告、没收违法所得;拒不改正的,可处 100 万元以下罚款
· 严重违法:可处【5000 万元以下】或【上一年度营业额 5% 以下】罚款,并可责令暂停业务、吊销执照
· 直接责任人:可处 10 万~100 万元罚款,并可能"禁业"
· 造成严重后果的,还可能涉及刑事责任(《刑法》侵犯公民个人信息罪)

★ 记忆点:个保法罚款是“5000 万 或 营业额 5% 取高”,和 GDPR 的“2000 万欧 或 4% 取高”是同一个量级思路, 都是“罚到肉疼”的顶格设计。

12.3 GDPR(欧盟通用数据保护条例)

12.3.1 GDPR 的三个“全球性”特点

特点一:域外效力(Extraterritorial)
  不管你的公司注册在哪国,只要你在【欧盟境内设立机构】,或【向欧盟居民提供商品/服务】、
  或【监控欧盟居民的行为】,GDPR 就管得到你。
  ★ 白话:一个中国公司做面向欧洲用户的 App,也得守 GDPR。

特点二:合法处理依据(六选一)
  处理个人数据,必须至少满足下面一条,否则就是违法:
    ① 数据主体同意  ② 履行合同所必需  ③ 法定义务  ④ 保护重大利益
    ⑤ 公共利益/公权力  ⑥ 控制者的正当利益(需利益平衡)
  ★ 对比:个保法更强调"同意"为主,GDPR 把"合同必需""正当利益"也作为独立依据。

特点三:天价罚款
  最高可处【2000 万欧元】或【全球年营业额 4%】,取较高者。

12.3.2 GDPR 的用户权利(比个保法更细)

权利 白话解释 技术落地
访问权 用户有权知道你存了他什么 提供“下载我的数据”
更正权 改错 提供资料编辑
删除权(被遗忘权) 彻底删 删除任务闭环
限制处理权 暂停用我的数据 冻结处理标记
可携带权 把我的数据带走 导出结构化格式(JSON/CSV)
反对权 反对你拿我数据做营销 退订/反对营销画像

★ 面试加分点:“可携带权”和“被遗忘权”是 GDPR 相对个保法更突出、面试更爱考的两条, 要能说清技术落地(可携带 = 导出标准化结构化数据;被遗忘 = 全链路删除)。

12.3.3 GDPR vs 个保法 对比表(面试高频)

维度 个保法(PIPL) GDPR
适用范围 在中国境内处理自然人信息 域外效力,覆盖欧盟居民
合法依据 以“同意”为核心,兼顾合同必需等 六种依据并列(同意/合同/法定义务/正当利益等)
敏感信息 “敏感个人信息”(列举 + 定义) “特殊类别数据”(种族、健康、性取向等)
删除权 删除权 删除权(被遗忘权),更强调
可携带权 有(条件性) 有,明确且强调
罚款上限 5000 万人民币 或 营业额 5% 2000 万欧元 或 全球营业额 4%
数据出境 安全评估/标准合同/认证等路径 充分性认定/标准合同条款(SCC)/约束性公司规则(BCR)

★ 一句话总结(面试可直接说):“个保法立足’境内处理 + 同意为核心’,GDPR 强调’域外效力 + 六种依据 + 被遗忘/可携带权’, 但两者的底层逻辑一致——都是’数据最小化 + 用户权利 + 重罚’。”

12.3.4 被遗忘权 / 可携带权的技术落地示例

import json

# 可携带权:把用户数据导出为机器可读的标准格式
def export_my_data(user_id: str) -> bytes:
    data = {
        "profile": query_user(user_id, USER_FIELDS["basic"] + USER_FIELDS["contact"]),
        "orders": query_table("orders", user_id),
        "exported_at": now(),
        "format_version": "1.0",       # 声明格式版本,方便接收方解析
    }
    return json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8")

# 被遗忘权:全链路删除(主表 + 关联 + 缓存 + 备份引用)
def right_to_be_forgotten(user_id: str):
    delete_user_data(user_id)               # 见 12.2.3 的删除闭环
    # 额外:通知第三方处理者同步删除(GDPR 要求控制者通知下游)
    notify_processors(user_id, action="erasure")

★ 诚实提醒:被遗忘权的落地难点在于“备份和日志里的残留数据”——GDPR 允许“为证明合规、履行法定义务” 而保留必要的最少记录,但这和“彻底删除”之间存在张力,落地时要和法务一起明确“保留什么、删什么、删到哪一层”。

12.4 数据分类分级

12.4.1 什么是数据分类分级,用大白话讲

一句话定义:数据分类分级 = 给数据"分门别类" + "定安全等级",然后【按等级配不同的保护措施】。

生活类比:档案室管理文件。
  · 分类:按"内容"分——人事档案、财务档案、技术档案(分类回答"这是什么")
  · 分级:按"重要/敏感程度"分——公开、内部、秘密(分级回答"丢了有多严重")
  · 落地:不同等级放不同柜子、不同人能看、不同借阅流程

★ 关键认知:分类分级是“数据安全治理的地基”。不知道哪些数据重要,就不知道把安全资源往哪投, 就会出现“不重要的数据花大钱保护、核心数据却裸奔”的错配。

12.4.2 中国的三级数据分级(《数据安全法》框架)

按"一旦被破坏、泄露、非法利用,对国家安全、公共利益、个人/组织合法权益的危害程度"分级:

  · 一般数据:泄露危害小(如公开的新闻、已脱敏的统计)
  · 重要数据:泄露可能危害国家安全、经济运行、社会稳定、公共利益(如能源、交通、人口健康大数据)
  · 核心数据:关系国家安全、国民经济命脉、重要民生、重大公共利益(危害程度最重)

★ 面试要点:“重要数据”和“核心数据”是《数据安全法》的特有概念,和个人信息保护法里的 “敏感个人信息”不是一回事——前者站在“国家/公共安全”角度,后者站在“个人权益”角度。 一个数据集可能同时是“重要数据”又包含“敏感个人信息”。

12.4.3 常见的分类维度(按业务定,别生搬硬套)

分类维度 举例
按数据主体 个人信息、企业数据、公共数据、政务数据
按业务 用户数据、交易数据、日志数据、配置数据
按敏感度 公开 / 内部 / 秘密 / 机密
按来源 自采数据、第三方提供、公开爬取

★ 务实建议:分类分级没有“全国统一的一张表”,而是“行业有指引 + 企业自己定标准”。 金融、医疗、政务、汽车等行业各有指南,落地时要先找本行业的分类分级指引,再结合自身业务细化。

12.4.4 数据分类分级的技术落地

落地一:给数据打标签(元数据管理)

# 数据分级标签(示例,企业自定义)
DATA_LEVEL = {
    "L1_PUBLIC":  "公开数据,可自由流通",
    "L2_INTERNAL": "内部数据,限企业内部",
    "L3_SENSITIVE": "敏感数据,需授权 + 脱敏 + 审计",
    "L4_CORE":    "核心数据,最高管控,原则上不出域",
}

def tag_asset(asset_id: str, level: str, category: str, owner: str):
    """给每个数据资产打上'等级 + 分类 + 责任人'标签"""
    meta_store.set(asset_id, {
        "level": level,          # 如 L3_SENSITIVE
        "category": category,    # 如 "个人信息/交易数据"
        "owner": owner,          # 数据责任人
        "tagged_at": now(),
    })

落地二:分级访问控制(不同等级不同权限)

# 访问数据前,校验"请求者的权限级别 >= 数据等级要求"
def access_data(user: dict, asset_id: str):
    asset = meta_store.get(asset_id)
    required = asset["level"]
    user_level = user["clearance"]   # 用户的"可访问等级"

    if rank(user_level) < rank(required):
        audit_log(f"DENIED: {user['id']} -> {asset_id} ({required})")
        raise PermissionError("无权限访问该等级数据")
    # 敏感/核心数据:额外要求"脱敏"或"审计"
    if required in ("L3_SENSITIVE", "L4_CORE"):
        audit_log(f"ACCESS: {user['id']} -> {asset_id} ({required})")
    return read(asset_id)

落地三:分级存储与加密(不同等级不同保护强度)

· L1 公开:明文存,正常备份
· L2 内部:访问控制 + 传输加密(TLS)
· L3 敏感:加密存储(如 AES/SM4)+ 脱敏展示 + 访问审计 + 最小权限
· L4 核心:国密算法加密 + 密钥分级管理 + 不出域 + 双人复核 + 全程审计

★ 面试加分点:“分类分级是手段,差异化保护才是目的”——分级不是贴个标签就完事, 而是要驱动后续的【访问控制、加密、脱敏、审计、备份、出境】等一整套差异化措施。

12.5 数据出境

12.5.1 什么是数据出境,用大白话讲

一句话定义:数据出境 = 在中国境内运营中收集和产生的数据,被传输、存储到境外,
  或虽存储在境内但【境外主体可以访问/处理】。

生活类比:不是"人出国",是"数据出国"。
  · 把国内用户的资料存到国外的云服务器 → 出境
  · 国内存数据,但国外分公司能远程访问 → 也算出境("向境外提供")
  · 用境外 SaaS 工具处理国内客户数据 → 同样涉及出境

★ 最容易忽略的坑:很多人以为"服务器在国内"就没出境,其实只要【境外能访问到】,就是出境。

12.5.2 数据出境的三条合规路径

路径一:数据出境安全评估(国家网信部门组织的评估)
  适用:重要数据出境;或个人信息处理者达到一定规模(如处理 100 万人以上个人信息、
        或累计向境外提供 10 万人以上个人信息/1 万人以上敏感个人信息)等法定情形。

路径二:个人信息出境标准合同
  适用:未达到"必须安全评估"门槛的个人信息出境,与境外接收方签标准合同并备案。

路径三:个人信息保护认证
  适用:通过国家认可的认证机构,证明境外接收方具备相应的保护水平。

★ 底线原则:无论走哪条路径,都要满足"告知-同意 + 最小必要 + 出境前影响评估 + 安全保障"。

12.5.3 触发“必须申报安全评估”的典型情形(记忆版)

满足【任一】即须申报数据出境安全评估:
  ① 向境外提供"重要数据"
  ② 关键信息基础设施运营者向境外提供个人信息
  ③ 处理 100 万人以上个人信息者,向境外提供个人信息
  ④ 自上年 1 月 1 日起,累计向境外提供 10 万人以上个人信息 或 1 万人以上敏感个人信息

★ 面试要点:这几个数字(100 万 / 10 万 / 1 万)是高频考点,记清楚“谁、什么数据、达到多少量”。

12.5.4 数据出境的技术落地

落地一:数据本地化 + 境内存储优先

# 存储路由:核心/重要数据强制境内,普通数据才允许走跨境通道
STORAGE_REGION = {
    "L1_PUBLIC":   "any",          # 公开数据,哪里都行
    "L2_INTERNAL": "cn",           # 内部数据,境内
    "L3_SENSITIVE": "cn_only",     # 敏感数据,仅境内,禁止出境
    "L4_CORE":     "cn_only",      # 核心数据,仅境内
}

def route_storage(asset: dict):
    region = STORAGE_REGION.get(asset["level"], "cn")
    if region == "cn_only":
        return china_storage.write(asset)   # 强制境内
    return default_storage.write(asset)

落地二:跨境传输的安全保障(加密 + 审计 + 最小化)

# 必须出境的场景:加密后传输,记录审计日志,只传必要字段
def cross_border_transfer(user_id: str, fields: list, destination: str):
    # 1. 最小化:只传境外业务真正必需的字段
    minimal = [f for f in fields if f in USER_FIELDS["contact"]]  # 敏感字段一律不下发
    data = query_user(user_id, minimal)

    # 2. 加密(用国密 SM4 或 AES)后传输
    ciphertext = sm4_encrypt(json.dumps(data), key_from_kms())

    # 3. 全程审计
    audit_log(f"cross_border: {user_id} -> {destination}, fields={minimal}")

    return send(destination, ciphertext)

★ 面试加分点:数据出境的技术落地点是“三个最小 + 两个保障”—— 最小字段、最小范围、最短保留,加上传输加密 + 全程审计。 同时要和法务确认“走了哪条合规路径(安全评估/标准合同/认证)”,技术只是合规链条上的一环。

12.6 个人信息保护影响评估(DPIA / PIA)

12.6.1 什么时候必须做 DPIA

一句话定义:在开展"可能对个人权益有高风险"的数据处理活动【之前】,
  先评估风险、想好降险措施,形成书面评估报告。

个保法明确【必须】做影响评估的情形(记忆版):
  ① 处理敏感个人信息
  ② 利用个人信息进行自动化决策(如算法评分、画像)
  ③ 委托处理、向第三方提供、公开个人信息
  ④ 向境外提供个人信息
  ⑤ 其他对个人权益有重大影响的情形

白话:DPIA 就是“动工前的安全交底”——先想清楚“我要拿用户数据干嘛、会不会出事、出事怎么兜底”, 而不是等出了事再补救。

12.6.2 DPIA 的标准流程(五步)

第一步:描述处理活动
  处理什么数据?什么目的?什么范围?涉及多少人?存多久?谁在管?

第二步:评估必要性 & 合规性
  这个处理是否"最小必要"?是否已取得同意?是否有合法依据?

第三步:识别风险
  泄露、滥用、过度收集、跨境、自动化决策歧视……各有什么风险?概率多大?影响多大?

第四步:设计降险措施
  脱敏、加密、访问控制、最小化、留存期限、应急预案……

第五步:形成报告 + 持续跟踪
  记录评估结论;当处理活动变化时,重新评估。

12.6.3 DPIA 报告模板(可直接套用)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
个人信息保护影响评估报告(摘要模板)

1. 处理活动概述:________(如"用于风控的自动化评分")
2. 涉及数据类型:□一般个人信息 □敏感个人信息(____)□未成年人信息
3. 数据规模/范围:________人,境内□ 出境□
4. 合法依据:□同意 □合同必需 □法定义务 □其他____
5. 风险识别:
   · 风险1:________(概率:高/中/低;影响:高/中/低)
   · 风险2:________
6. 降险措施:
   · 措施1:脱敏/加密/访问控制/最小化________
7. 评估结论:□可开展 □附条件开展(需完成____) □不可开展
8. 复核人 / 日期:________
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

★ 面试加分点:DPIA 的核心是“事前、书面、留痕、可追溯“——它是证明”你已经尽到合规义务“的关键证据, 出问题时,一份合格的 DPIA 报告能显著降低处罚风险。

12.7 等保 2.0、密评与国密算法

12.7.1 等保 2.0:五步 + 定级

等保 2.0 的五个规定动作(顺序不能乱):
  定级 → 备案 → 建设整改 → 等级测评 → 监督检查

定级(五个等级,绝大多数企业是二级或三级):
  一级:损害公民/组织合法权益(最低)
  二级:损害公民/组织合法权益,或对社会秩序造成轻微危害
  三级:损害国家安全、社会秩序、公共利益(★ 多数金融/政务/重要业务在此)
  四级:对社会秩序、公共利益造成特别严重危害
  五级:对国家安全造成特别严重危害(最高,特殊场景)

★ 三级及以上:必须委托【有资质的测评机构】做等级测评,是上线的硬门槛。

白话:等保就像“给大楼定消防等级、装对应设施、定期验收”。定级越高,防火墙、加密、审计、灾备的要求越严。

12.7.2 等保 2.0 的技术要求框架(面试要点)

等保 2.0 的要求分"安全通用要求 + 安全扩展要求",技术层面核心是"一个中心、三重防护":

  一个中心:安全管理中心(统一的管理、审计、策略下发)
  三重防护:
    ① 安全通信网络(网络架构、边界防护、传输加密)
    ② 安全区域边界(边界防护、入侵防范、恶意代码防范)
    ③ 安全计算环境(身份鉴别、访问控制、数据加密、数据备份、审计)

★ 常考的具体要求(二级/三级通用):
  · 身份鉴别:口令复杂度 + 登录失败锁定 + 双因素(三级)
  · 访问控制:最小权限、三权分立(管理员/审计员/操作员分离)
  · 安全审计:日志留存不少于 6 个月(重要系统)
  · 数据完整性与保密性:传输/存储加密、备份、异地容灾
  · 入侵防范:部署 IDS/IPS、防病毒、边界防火墙

12.7.3 密评(商用密码应用安全性评估)

一句话定义:密评 = 对"系统里用没用合规的密码技术、用得好不好"进行评估。

什么时候要过密评:
  · 等保三级及以上的系统,通常要求同步过密评
  · 关键信息基础设施(关基)
  · 政务、金融、能源、交通等重要领域的系统

密评查什么:
  · 密码算法是否合规(是否用了国密算法)
  · 密码产品/服务是否合规(用了有资质的密码产品)
  · 密码应用是否到位(加密、签名、密钥管理是否规范)

12.7.4 国密算法(SM2 / SM3 / SM4)+ 代码示例

三个最常考的国密算法,记住"对标关系":

  SM2  —— 非对称(公钥加密/数字签名),对标 RSA / ECDSA
  SM3  —— 哈希(摘要),对标 SHA-256
  SM4  —— 对称(分组加密),对标 AES
# 用 Python 的 gmssl 库演示国密算法的基本用法(示意)
from gmssl import sm2, sm3, sm4

# ---- SM3 哈希(对标 SHA-256)----
def sm3_hash(text: str) -> str:
    crypt_sm3 = sm3.CryptSM3()
    return crypt_sm3.hash(text.encode("utf-8"))

# ---- SM4 对称加密(对标 AES,密钥 16 字节)----
def sm4_encrypt(plaintext: bytes, key: bytes) -> bytes:
    crypt_sm4 = sm4.CryptSM4()
    crypt_sm4.set_key(key, sm4.SM4_ENCRYPT)
    return crypt_sm4.crypt_ecb(plaintext)   # 生产建议用 CBC/GCM 模式

# ---- SM2 公钥加密(对标 RSA 加密,密钥 64 字节十六进制私钥)----
def sm2_encrypt(public_key: str, data: bytes) -> bytes:
    crypt_sm2 = sm2.CryptSM2(private_key="", public_key=public_key)
    return crypt_sm2.encrypt(data)

★ 面试加分点:要能说出“为什么用国密”——自主可控 + 合规要求。在政务、金融、关基场景, 等保/密评会要求关键环节使用国密算法;同时要能说出“国密算法的安全性对标国际算法, 但生态/性能/兼容性还在追赶”这种客观判断。

12.7.5 等保、密评、关基 三者的关系(面试高频)

  网络安全等级保护(等保 2.0)── 通用基础制度,绝大多数系统都要做
        │
        ├── 三级以上系统 ──► 同步要求 密评(商用密码应用安全性评估)
        │
  关键信息基础设施(关基)── 等保基础上"再加码"的重点保护对象
        │(能源、交通、金融、水利、公共服务、国防科技等一旦破坏即危及国家安全)
        │
  数据安全法 / 个保法 ── 从"系统安全"延伸到"数据安全 + 个人信息保护"

★ 一句话串起来:等保管"系统",密评管"密码",关基管"重点系统",
  数安法/个保法管"数据和个人信息",它们共同构成中国的网络安全与数据合规体系。

12.8 面试题 L 组:数据合规与隐私保护(30 题)

12.8.1 基础题(L1 ~ L12)

L1. 什么是个人信息(PII)?什么算“敏感个人信息”?

★ 定义:PII = 能直接或间接识别到具体自然人的信息。

生活类比:PII 像“指纹”——“张三”可能重名,“张三 + 手机号”就锁定是你。

敏感个人信息:一旦泄露/滥用易损害人格尊严或人身、财产安全——身份证号、银行账户、行踪轨迹、 健康医疗、生物识别(人脸指纹)、宗教信仰、不满 14 周岁未成年人信息等。

★ 加分点:敏感个人信息的处理要“单独同意 + 特定目的 + 充分必要 + 严格保护”。

L2. 个保法的核心原则有哪些?(至少说 5 条)

答:告知-同意、最小必要、目的限制、单独同意(敏感/第三方/跨境)、用户权利(查询更正删除撤回注销)、 安全保护义务、公开透明。

★ 加分点:能对应到技术落地——“最小必要”对应接口字段裁剪,“删除权”对应全链路删除闭环。

L3. 什么是“告知-同意”?技术怎么落地?

★ 定义:收集前明明白白告诉用户“谁、收什么、干嘛用、存多久”,并取得自愿明确同意。

技术落地:隐私政策单独弹窗(不能默认勾选)、敏感信息单独勾选框、同意记录留痕(时间+版本+目的)。

★ 加分点:留痕是关键——出问题时,同意记录是证明“已尽告知义务”的证据。

L4. GDPR 和个保法最大的区别是什么?

答(三点):

  1. 适用范围:GDPR 有域外效力(管欧盟居民),个保法立足境内处理
  2. 合法依据:GDPR 六种并列(同意/合同/法定义务/正当利益等),个保法以同意为核心
  3. 特色权利:GDPR 更强调被遗忘权、可携带权

★ 加分点:两者底层逻辑一致——数据最小化 + 用户权利 + 重罚(GDPR 4%/2000万欧,个保法 5%/5000万)。

L5. 什么是数据分类分级?为什么它很重要?

★ 定义:给数据“分门别类 + 定安全等级”,按等级配差异化保护。

生活类比:档案室管理——按内容分类(人事/财务/技术),按重要程度分级(公开/内部/秘密)。

为什么重要:不知道哪些数据重要,安全资源就会错配(不重要的花大钱,核心的裸奔)。

★ 加分点:分级是手段,差异化保护(访问控制/加密/脱敏/审计/出境)才是目的。

L6. 《数据安全法》里的“重要数据”和“核心数据”是什么?

★ 定义:按“泄露/非法利用对国家安全、公共利益等的危害程度”分级。 重要数据 = 可能危害国家安全、经济运行、社会稳定、公共利益; 核心数据 = 关系国家安全、国民经济命脉、重大公共利益(危害最重)。

★ 加分点:和“敏感个人信息”不是一回事——前者站在国家/公共安全角度,后者站在个人权益角度。

L7. 什么是数据出境?“服务器在国内”就安全了吗?

★ 定义:境内收集产生的数据,被传输/存储到境外,或境内存储但境外可访问。

生活类比:不是“人出国”,是“数据出国”;用境外 SaaS 处理国内客户数据也算出境。

★ 加分点:最容易忽略的坑——服务器在国内,但境外能远程访问,也构成出境。

L8. 数据出境有哪几条合规路径?

答(三条):① 数据出境安全评估(重要数据/达到规模);② 个人信息出境标准合同;③ 个人信息保护认证。

★ 加分点:无论哪条路径,都要满足“告知-同意 + 最小必要 + 出境前影响评估 + 安全保障”。

L9. 触发“必须申报数据出境安全评估”的情形有哪些?

答(记忆数字):① 出境重要数据;② 关基运营者出境个人信息;③ 处理 100 万人以上个人信息者出境个人信息; ④ 累计出境 10 万人以上个人信息 或 1 万人以上敏感个人信息。

★ 加分点:100 万 / 10 万 / 1 万 这三个数字是高频考点。

L10. 什么是 DPIA?什么时候必须做?

★ 定义:开展高风险数据处理活动之前,先评估风险、设计降险措施、形成书面报告。

必须做的情形:处理敏感个人信息、自动化决策、委托/第三方/公开、出境、其他重大影响。

★ 加分点:核心是“事前、书面、留痕、可追溯”,是证明“已尽义务”的关键证据。

L11. 什么是等保 2.0?五个规定动作是什么?

★ 定义:对信息系统按重要程度分等级、按等级做安全保护的强制性制度。

五个动作:定级 → 备案 → 建设整改 → 等级测评 → 监督检查。

★ 加分点:三级及以上必须找有资质机构测评,是上线的硬门槛。

L12. 什么是国密算法?SM2/SM3/SM4 分别对标什么?

答:国密 = 中国自主商用密码算法。SM2(非对称加密/签名,对标 RSA/ECDSA)、SM3(哈希,对标 SHA-256)、 SM4(对称加密,对标 AES)。

★ 加分点:用国密是“自主可控 + 合规”要求,政务/金融/关基场景常见。

12.8.2 进阶题(L13 ~ L22)

L13. “最小必要”原则在接口设计上怎么落地?

答:接口按“字段层级”返回——基础字段/联系字段/敏感字段分档,默认只返回业务必需的最小集合, 敏感字段绝不下发;采集时“能收手机号就不收身份证”。

★ 加分点:结合代码说“get_user_profile(need) 按需返回字段层级”这种落地。

L14. 用户的“删除权”怎么彻底落地?有什么坑?

答:不只删主表,要删关联表(订单/日志/画像)、缓存、并标记备份/归档待物理清除,形成删除任务闭环。

坑:日志、备份、数仓、缓存里还躺着数据,严格来说不算“删除”。

★ 加分点:设计“删除任务队列 + 备份保留期 + 定期物理清除”闭环。

L15. 敏感个人信息在技术上有哪些特殊保护要求?

答:单独同意、特定目的+充分必要、严格保护措施(加密存储、脱敏展示、最小权限、访问审计)、 处理未成年人信息要监护人同意。

★ 加分点:给“敏感字段默认不下发 + 展示脱敏 + 访问留痕”的代码思路。

L16. 数据分类分级后,怎么驱动“差异化保护”?

答:不同等级配不同措施——L1 明文、L2 访问控制+TLS、L3 加密+脱敏+审计+最小权限、L4 国密+不出域+双人复核。

★ 加分点:强调“分级是手段,差异化保护才是目的”,不是贴标签就完事。

L17. 数据出境的技术保障措施有哪些?

答:三个最小(最小字段、最小范围、最短保留)+ 两个保障(传输加密、全程审计); 核心/重要数据强制境内存储。

★ 加分点:技术只是合规链条的一环,要和法务确认“走了哪条路径(评估/标准合同/认证)”。

L18. 等保 2.0 的“一个中心、三重防护”是什么?

答:一个中心 = 安全管理中心;三重防护 = 安全通信网络、安全区域边界、安全计算环境。

★ 加分点:能说出具体措施(身份鉴别双因素、最小权限、审计日志留 6 个月、传输存储加密、备份容灾)。

L19. 等保、密评、关基 三者什么关系?

答:等保管“系统”(通用基础)、密评管“密码”(三级以上同步要求)、关基管“重点系统”(等保上加码)。

★ 加分点:串成“等保 + 密评 + 数安法/个保法”共同构成中国网络安全与数据合规体系。

L20. 合规和安全有什么区别?为什么“合规”不等于“安全”?

答:安全回答“怎么不被攻击”,合规回答“怎么不违法”;合规往往是安全的最低标准。

★ 加分点:过了等保照样可能被黑(等保是底线不是攻不破);技术安全却违规收集照样被罚。

L21. 自动化决策(算法评分)有哪些合规风险?

答:个保法要求自动化决策透明 + 结果公平公正,不得对个人实行不合理的差别待遇; 涉及重大影响的自动化决策,个人有权要求说明,并有权拒绝“仅由自动化决策”作出的决定。

★ 加分点:DPIA 必做 + 提供人工复核渠道 + 决策可解释。

L22. 一份合格的 DPIA 报告要包含哪些要素?

答:处理活动概述、数据类型与规模、合法依据、风险识别(概率×影响)、降险措施、评估结论、复核人。

★ 加分点:核心是“事前、书面、留痕、可追溯”。

12.8.3 场景题(L23 ~ L27)

L23. 老板说“我们收集用户信息越多越好,以后有用”,你怎么回应?

答(三步):

  1. 合规风险:违反“最小必要”,个保法最高罚营业额 5%/5000 万
  2. 安全风险:多存一份敏感数据 = 多一个泄露点 + 多一份保护成本
  3. 给方案:先明确业务真正需要哪些字段,只收必需的;用“字段分级 + 按需返回”落地

★ 加分点:把“合规”翻译成“成本和风险”,让老板听得懂利害。

L24. 你发现数据库里存了明文身份证号,怎么处理?

答:

  1. 定性:身份证是敏感个人信息,明文存储不合规且风险高
  2. 整改:加密存储(如 SM4/AES)+ 展示脱敏 + 最小权限 + 访问审计
  3. 收尾:存量数据清洗(历史明文加密回迁),更新 DPIA,确认是否“单独同意”到位

★ 加分点:区分“存量整改”和“新增管控”两条线,存量往往更难(要兼容老数据)。

L25. 公司要把数据放到境外云服务器,作为技术负责人你第一步做什么?

答:

  1. 先确认是否构成“数据出境”(境外存储/境外可访问都算)
  2. 确认数据类型(是否重要数据/敏感个人信息)和规模(是否触发安全评估门槛)
  3. 拉法务确认合规路径(评估/标准合同/认证)
  4. 技术侧:最小字段 + 传输加密 + 审计 + 境内保留必要副本

★ 加分点:技术负责人第一动作是“识别是否出境 + 拉法务”,而不是闷头做技术迁移。

L26. 面试官问你“等保三级系统,你会重点做哪些安全建设?”

答(按三重防护展开):

  1. 身份鉴别:口令复杂度 + 失败锁定 + 双因素
  2. 访问控制:最小权限、三权分立
  3. 审计:日志留存 6 个月以上、集中审计
  4. 数据安全:传输/存储加密、备份、异地容灾
  5. 边界防护:防火墙 + 入侵防范 + 恶意代码防范
  6. 密码应用:密评达标(国密算法)

★ 加分点:主动补一句“还要同步准备密评”,体现体系化认知。

L27. 用户投诉“你们把我的数据泄露了”,你怎么应对?

答:

  1. 先核实:是否真的泄露、泄露了什么、影响多少人
  2. 止损:封堵漏洞、冻结相关数据、联系可能泄露方
  3. 合规动作:按个保法要求“立即补救 + 通知用户 + 报告主管部门”(重大泄露有报告义务)
  4. 留痕:记录时间线、处置过程,配合调查

★ 加分点:个保法要求发生泄露时“立即采取补救措施 + 及时通知个人 + 按规定报告”, 这是法定义务,不能隐瞒。

12.8.4 追问链(L28 ~ L30)

L28. 数据合规十连问

Q1: 三法一条例分别是什么? A: 网安法、数据安全法、个保法 + 数据出境安全评估办法等配套。

Q2: 网安法/数安法/个保法各管什么? A: 网安法管网络运行安全,数安法管数据安全,个保法管个人信息。

Q3: 敏感个人信息举 5 个例子? A: 身份证号、银行账户、行踪轨迹、健康医疗、生物识别(人脸指纹)。

Q4: 最小必要是什么? A: 只收提供服务所必需的信息,不能多收。

Q5: 单独同意在哪些场景必须? A: 敏感个人信息、向第三方提供、跨境提供。

Q6: 数据出境的三种路径? A: 安全评估、标准合同、保护认证。

Q7: 触发安全评估的个人信息量级? A: 处理 100 万人以上 / 累计出境 10 万个人信息 / 1 万敏感个人信息。

Q8: GDPR 罚款上限? A: 2000 万欧元 或 全球营业额 4%,取高。

Q9: 等保五步? A: 定级、备案、建设整改、等级测评、监督检查。

Q10: SM2/SM3/SM4 对标什么? A: SM2→RSA/ECDSA,SM3→SHA-256,SM4→AES。

L29. 个保法技术落地十连问

Q1: 告知-同意怎么落地? A: 隐私政策单独弹窗 + 敏感字段单独勾选 + 同意记录留痕。

Q2: 最小必要怎么落地? A: 接口字段分级、按需返回,采集字段能少则少。

Q3: 删除权怎么落地? A: 全链路删除:主表 + 关联表 + 缓存 + 备份引用 + 物理清除闭环。

Q4: 可携带权怎么落地? A: 导出标准化结构化数据(JSON/CSV)。

Q5: 敏感信息怎么保护? A: 加密存储 + 脱敏展示 + 最小权限 + 访问审计。

Q6: 自动化决策要满足什么? A: 透明 + 公平 + 可拒绝仅自动决策 + 提供说明。

Q7: 数据泄露了要做什么? A: 立即补救 + 通知个人 + 报告主管部门。

Q8: 数据分类分级怎么落地? A: 元数据打标签(等级+分类+责任人)+ 分级访问控制 + 分级存储加密。

Q9: 跨境传输技术要点? A: 最小字段 + 传输加密 + 全程审计。

Q10: 合规落地最大的坑? A: 只做“表面合规”(贴标签、写政策),没落到代码和架构里。

L30. 给非技术朋友讲“数据合规”八连问

Q1: 数据合规是啥? A: 就是“收集、使用别人数据时,守规矩、不越界”。

Q2: 个保法是啥? A: 给“个人信息怎么收集怎么用”立的交通法规。

Q3: 什么算个人信息? A: 能认出“就是你”的信息——名字+手机号就能锁定你。

Q4: 敏感个人信息是啥? A: 身份证、银行卡、人脸、健康、行踪这些“高危品”。

Q5: 数据出境是啥? A: 不是人出国,是数据出国——存到国外服务器、让国外能访问。

Q6: 等保是啥? A: 给系统按重要程度定消防等级、装对应设施、定期验收。

Q7: 国密是啥? A: 国产锁芯标准,关键系统要用国产的加密算法。

Q8: 普通用户怎么保护自己? A: 授权时看清要什么、能少给就少给、定期撤销不用的授权、不点陌生链接。

12.9 第十二章小结

这一章把视角从“怎么不被黑”切换到“怎么不违法”。安全是“防止数据被坏人拿走”,合规是“防止数据被自己乱用”。 两者一体两面,但落地方式完全不同:安全靠技术(加密、访问控制、审计), 合规靠“制度 + 技术 + 留痕”三者结合。对工程师而言,最关键的一句话是—— 合规不是法务的事,而是架构设计的一部分,必须前置到写代码之前。

12.9.1 五条核心认知

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知一:合规是"架构约束",不是"事后补材料"

  "最小必要"影响接口设计,"删除权"影响存储设计,"可携带权"影响导出设计,
  "数据出境"影响部署架构。这些都要在写代码前想清楚,上线后再改成本极高。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知二:三法一条例,各管一段

  《网安法》管网络运行安全 → 《数安法》管数据安全 → 《个保法》管个人信息,
  加上数据出境等配套办法,共同构成中国网络安全与数据合规体系。
  口诀:"网安法管网络,数安法管数据,个保法管个人。"

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知三:个人信息处理的核心是"告知-同意 + 最小必要 + 用户权利"

  收集前明明白白告知并同意、只收必需的、用户能查能改能删能带走。
  敏感个人信息要"单独同意",未成年人信息要"监护人同意"。
  这三条是几乎所有个保法落地场景的"公分母"。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知四:数据分类分级是"差异化保护"的地基

  不知道哪些数据重要,安全资源就会错配。分级之后,用不同的访问控制、
  加密、脱敏、审计、备份、出境策略去保护——分级是手段,差异化保护才是目的。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
认知五:"合规"≠"安全",但合规是安全的最低标准

  过了等保照样可能被黑;技术很安全却违规收集照样被罚。
  优秀工程师两个都要懂,且能分清"这是安全需求还是合规需求"。

12.9.2 合规速查卡

主题 核心要求 一句话记忆
个人信息处理 告知-同意 + 最小必要 + 目的限制 先告知、再同意、只收必需的
敏感个人信息 单独同意 + 特定目的 + 严格保护 高危品要单独申报
用户权利 查询/更正/删除/撤回/注销/可携带 用户的数据,用户说了算
数据分类分级 分类 + 定级 + 差异化保护 分好类、定好级、差别保护
数据出境 评估/标准合同/认证 + 安全保障 数据出国,先办手续
等保 2.0 定级→备案→整改→测评→监督 按等级装设施、定期验收
密评 密码算法/产品/应用合规 关键系统用国密
GDPR 六种依据 + 用户权利 + 重罚 管全球欧盟居民数据

12.9.3 数据合规成熟度自评表(L0 ~ L4)

级别 状态 典型表现 下一步
L0 无意识 无制度、无意识 明文存身份证、随便收集、不知道个保法 做一次数据盘点
L1 有意识 知道但不系统 有隐私政策,但没落到代码 建数据分类分级 + 敏感数据清单
L2 制度化 有流程有留痕 有 DPIA、有同意记录、有删除流程 合规要求落到架构(加密/脱敏/访问控制)
L3 技术化 合规进代码 字段分级、加密存储、审计留痕自动化 上数据出境合规 + 密评/等保
L4 闭环 持续合规 定期评估、跨境合规、应急响应完备 持续跟踪法规变化,定期复审

★ 判断口诀:L0 是“不知道”,L1 是“知道了没做”,L2 是“做了没进代码”,L3 是“进代码了没闭环”,L4 是“闭环了能持续”。 多数团队卡在 L1→L2,根因是“把合规当法务的事,没当成架构的事”。

12.9.4 五份可直接使用的清单

清单一:个人信息处理合规自查

□ 收集前是否告知(谁/收什么/干嘛用/存多久/联系方式)?
□ 是否取得明确同意(非默认勾选)?
□ 是否最小必要(能少收就少收)?
□ 敏感信息是否单独同意?未成年人是否监护人同意?
□ 是否有同意记录留痕(时间+版本+目的)?

清单二:用户权利落地自查

□ 用户能否查询自己的数据?
□ 用户能否更正?
□ 用户能否删除(全链路,含缓存/备份引用)?
□ 用户能否撤回同意、注销账户?
□ 用户能否导出数据(可携带权)?

清单三:数据分类分级自查

□ 是否做了数据资产盘点?
□ 是否给每个资产打了等级 + 分类 + 责任人标签?
□ 是否区分了"敏感个人信息 / 重要数据 / 核心数据"?
□ 是否按等级配了差异化访问控制 / 加密 / 审计?

清单四:数据出境合规自查

□ 是否识别出所有"境外存储 / 境外可访问"的场景?
□ 是否确认数据类型(重要数据/敏感个人信息)和规模(是否触发评估门槛)?
□ 是否已确认合规路径(评估 / 标准合同 / 认证)?
□ 是否落实了最小字段 + 传输加密 + 全程审计?

清单五:等保/密评准备自查

□ 是否完成定级与备案?
□ 三级以上是否已委托测评机构?
□ 身份鉴别是否达标(口令复杂度/失败锁定/双因素)?
□ 访问控制是否最小权限、三权分立?
□ 日志是否留存 6 个月以上?
□ 关键环节是否使用国密算法(SM2/SM3/SM4)?是否准备密评?

12.9.5 五句话记忆法

一句话记【个保法】:先告知、再同意、只收必需的,用户数据用户说了算。

一句话记【敏感信息】:身份证、银行卡、人脸、行踪是高危品,要单独申报。

一句话记【数据出境】:不是人出国,是数据出国,出国先办手续。

一句话记【等保】:按等级装设施、定期验收,三级以上是硬门槛。

一句话记【合规本质】:合规不是法务的事,是写代码之前就该想清楚的架构约束。

12.9.6 本章与前后章节的关系

                          ┌─────────────────────────────────────┐
                          │  第十三章 综合实战:六道大题的攻防推演 │
                          │  (把前十二章的知识串起来打实战)      │
                          └─────────────────────────────────────┘
                                        ▲
  前十一章(技术攻防)                  │  从"不被黑"到"不违法"
  ┌──────────────────────────┐  ┌──────────────────────────────┐
  │ 第一~五章:应用层        │→ │  第十二章 数据合规与隐私保护  │
  │ 第六~九章:主机/供应链   │  │  · 个保法 / GDPR            │
  │ 第十章:云原生           │  │  · 分类分级 / 数据出境        │
  │ 第十一章:Web3           │  │  · 等保 / 密评 / 国密         │
  └──────────────────────────┘  └──────────────────────────────┘
        │                                │
        └────────── 安全与合规是一体两面 ───────────┘
   (安全防"被偷",合规防"被乱用";都靠最小权限、审计、留痕这些基本功)

一句话串起来:第十二章补上了整个安全知识体系的最后一块拼图——技术之外,还有法律和制度的红线。 一个完整的安全工程师,既要会“攻防”,也要懂“合规”,还要能把两者都翻译成工程落地。 下一章(第十三章)就是大考:用六道综合实战题,把前面十二章的知识串起来,做完整的攻防推演。

第十三章:综合实战——六道大题串起全书

本章定位:前面十二章是“分科”——一章一个专题。这一章是“综合大考”: 用六道贴近真实场景的大题,把分散的知识点串成完整的攻防推演。 面试里,高级岗位最爱考这种“给一个场景,看你能不能系统性地分析、决策、落地”的题。

怎么用这一章:每道题先自己想一想,再对照答案。重点不是“记住答案”,而是 学会这套**“识别风险 → 推演攻击 → 分层防御 → 落地检查”**的分析框架。

13.1 综合实战题一:一个电商系统的全链路攻防

场景

你接手一个电商系统:Java 后端 + MySQL + Redis + 前端 Vue + Nginx,对外有登录、下单、支付、订单查询。 老板问:“我们这个系统,攻击者会怎么打进来?我们该怎么防?” 请给出一份“攻击链推演 + 分层防御”的完整分析。

攻击链推演(攻击者视角)

第一步:信息收集(打点)
  · 扫描暴露端口、识别框架指纹(Nginx 版本、Java 中间件)
  · 找子域名、GitHub 搜源码/密钥、看错误页面泄露的堆栈信息

第二步:找入口(突破)
  · 登录接口:撞库、弱口令、SQL 注入
  · 支付/下单接口:越权、参数篡改(改金额、改数量)
  · 订单查询接口:水平越权(改 orderId 看别人订单)
  · 评论/搜索:存储型 XSS、注入

第三步:提权与内网横向
  · SQL 注入拿数据库 → 拖用户表(含手机号、密文密码、可能明文敏感字段)
  · 服务器 RCE(反序列化、上传 WebShell)
  · 内网横向:Redis 未授权、SSRF 打内网服务

第四步:扩大战果
  · 拖库 → 撞库其他平台 → 卖给黑产
  · 勒索:加密数据 + 删备份
  · 埋后门:持久化,长期窃取

分层防御(防守者视角,对应全书章节)

攻击环节 对应防御 详参
信息收集 收敛暴露面、隐藏版本、错误页不泄露堆栈 第一~五章
注入 预编译参数化、WAF、输入校验 第二章
越权/篡改 服务端校验金额、对象级授权、防水平越权 第二~五章
XSS 输出编码、CSP、HttpOnly Cookie 第三章
认证会话 强密码、多因素、会话管理、防撞库(限速/验证码) 第四章
数据库安全 最小权限、加密敏感字段、审计 第六章
供应链 依赖漏洞扫描(SCA)、SBOM 第九章
应急响应 日志留存、备份、应急预案、遏制流程 第八章

面试答题框架(记住这四步)

1. 先定性:把系统拆成"入口层 / 应用层 / 数据层 / 基础设施层"
2. 逐层推演攻击:每层最可能的攻击手法是什么
3. 分层给防御:每个攻击手法对应什么防御措施
4. 给优先级:不是所有防御一起上,而是按"被攻击概率 × 损失"排序,先堵最高危的

★ 加分点:结尾补一句“安全不是一次做完,而是先做’拖库、越权、支付篡改’这三件最容易出事、损失最大的事, 再逐步覆盖。分清 P0/P1/P2 优先级,比列一堆工具清单更能体现工程思维。”

13.2 综合实战题二:一次 SSRF 引发的云上横向渗透

场景

你的 Web 应用有个“用户输入 URL → 服务端抓取该 URL 内容”的功能(比如抓网页标题/图片预览)。 部署在云上(AWS 或国内云),服务有访问云存储、云数据库的权限。 面试官问:“这个功能有什么风险?攻击者能走到哪一步?怎么防?”

攻击链推演

第一步:确认 SSRF 存在
  · 输入 http://127.0.0.1:8080 看能否访问到内网服务(端口探测)

第二步:打元数据服务(云上的"隐藏后门")
  · 请求 http://169.254.169.254/... (各云元数据地址)
  · 拿到本机角色的【临时访问密钥】(IAM/云凭据)

第三步:用偷来的密钥横向
  · 列存储桶 → 拖走敏感文件
  · 访问数据库 → 拖库
  · 如果密钥权限过大(如 s3:* / admin)→ 甚至提权、删桶、创建资源

第四步:持久化 + 扩大
  · 创建新的高权限密钥留后门
  · 跨服务横向,甚至打到其他账号

关键知识点串联(第十、二章)

1. SSRF 本质(第二章):服务端代替客户端发请求,攻击者控制了"请求的目标"
2. 元数据服务(第十章 10.2.3):169.254.169.254 是云主机内置的"身份证查询窗口"
3. 权限过大(第十章 10.2.2):偷到的密钥权限有多大,损失就有多大

完整防御(三层都要做)

# 第一层:应用层 SSRF 白名单 + DNS 二次解析校验
import ipaddress, socket

BLOCKED = ipaddress.ip_network("169.254.0.0/16")   # 元数据段
PRIVATE = [
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
    ipaddress.ip_network("192.168.0.0/16"),
    ipaddress.ip_network("127.0.0.0/8"),
]

def is_safe_url(url: str) -> bool:
    host = parse(url).hostname
    ip = ipaddress.ip_address(socket.gethostbyname(host))   # 第一次解析
    # 再解析一次,防 DNS rebinding(两次解析结果不一致 = 攻击)
    if socket.gethostbyname(host) != str(ip):
        return False
    if ip in BLOCKED or any(ip in net for net in PRIVATE):
        return False
    return True
第二层:云平台层
  · 开启 IMDSv2(要求 PUT token 才能读元数据)
  · 容器 hop limit 设 1
  · 最小权限:应用角色只给"真正需要"的权限,绝不 s3:* / admin

第三层:检测层
  · 监控"对 169.254.169.254 的异常访问"
  · 云审计日志开数据事件,告警异常密钥使用

面试答题框架

1. 定性:这是"SSRF → 元数据服务 → 云凭据泄露"的经典链,是云上最危险的 SSRF 变种
2. 推演:SSRF → 169.254.169.254 → 偷临时密钥 → 凭密钥权限横向
3. 防御三层:应用层白名单 + 云平台层(IMDSv2 + 最小权限)+ 检测层(审计告警)
4. 诚实补一句:IMDSv2 不能 100% 防 SSRF(DNS rebinding 等),但能把攻击成本拉高一个数量级

★ 加分点:能说出“SSRF 在云上比传统环境危险得多”——传统 SSRF 最多打打内网服务, 云上 SSRF 能直接偷到角色密钥、接管云资源,所以“元数据服务”是云安全里最被低估的攻击面。

13.3 综合实战题三:一次供应链投毒事件的完整溯源

场景

你的 CI 流水线某天开始,构建产物里出现了一个可疑的域名外联请求,而且只在生产环境触发。 排查发现是一个上游 npm 依赖被投毒了。面试官问:“你怎么溯源这件事?怎么防它再次发生?”

溯源推演(应急响应 + 供应链取证)

第一步:止血(遏制)
  · 锁定受影响的构建版本,回滚到干净版本
  · 下架受污染产物,切断可疑外联

第二步:定位(溯源)
  · 从"可疑外联域名"反查 → 是哪个依赖引入的
  · 比对 package-lock / lock 文件,找出"哪个依赖在哪个时间点被换成了恶意版本"
  · 检查是否是:Typosquatting(拼写相似的假包)?账号劫持?版本回滚投毒?

第三步:定范围(影响面)
  · 哪些构建、哪些版本、哪些环境用了这个坏依赖
  · 恶意包做了什么(窃取密钥?留后门?外传数据?)

第四步:根因(为什么进来了)
  · 依赖是否固定了版本?还是用了 ^ / latest 拉到了最新毒包?
  · 是否用了私有源?私服是否校验了完整性?

第五步:复盘(防再发)
  · 锁依赖版本 + 锁 hash
  · 引入 SBOM + 依赖漏洞扫描(SCA)进 CI
  · 关键依赖做人工评审 + 多源校验

关键知识点串联(第九章)

1. 依赖投毒五种姿势(9.2):Typosquatting / 依赖混淆 / 账号劫持 / 生命周期脚本 / 版本回滚
2. 供应链防线(9.3~9.5):锁文件、SBOM、Sigstore 签名、私有源、CI 安全
3. 溯源工具(9.8):依赖 diff、lock 文件比对、恶意包行为特征(外联、读密钥、生命周期脚本)

防御落地(可复用的流水线加固)

# 示例:CI 里的依赖安全门禁(伪代码,示意)
steps:
  - name: 锁文件校验
    run: |
      # 必须存在 lock 文件,且和 package.json 一致,禁止拉最新
      test -f package-lock.json || exit 1
      npm ci   # 严格按 lock 安装,不用 npm install

  - name: 依赖漏洞 + 恶意包扫描
    run: |
      npm audit --audit-level=high      # 已知漏洞
      # 扫描可疑生命周期脚本、可疑外联域名
      ./scan_malicious_deps.sh

  - name: 生成 SBOM
    run: cyclonedx-bom -o sbom.cdx.json

面试答题框架

1. 先讲溯源思路:止血 → 定位(反查外联域名 + 比对 lock 文件)→ 定范围 → 找根因 → 复盘
2. 再讲根因:大概率是"依赖没锁版本,拉到了最新毒包"或"拼写相似假包"
3. 最后讲防线:锁依赖 + SBOM + SCA 进 CI + 私有源 + 关键依赖人工评审

★ 加分点:强调“供应链攻击利用的是信任,不是漏洞”——你信任了上游,上游被攻破,你就连带中招。 所以防御的核心是“信任最小化”:锁版本、锁 hash、签名验证、私有源、关键依赖评审。

13.4 综合实战题四:一个 DeFi 协议被“闪电贷 + 价格操纵”打穿

场景

一个借贷协议,用 DEX 现货价格作为抵押品定价,且奖励按“合约瞬时余额”分配。 面试官问:“这个协议有哪些致命漏洞?攻击者如何组合攻击?怎么修?”

攻击链推演(第十一章知识串联)

第一步:借(闪电贷)
  · 攻击者通过闪电贷借出巨额资金(零抵押、同一笔交易内还)

第二步:操纵(价格操纵)
  · 把巨款砸进 DEX 池子,瞬间抬高抵押品现货价格

第三步:套利(触发漏洞)
  · 协议一看"抵押品现在值很多钱",允许超额借款
  · 攻击者借走远超真实价值的资金

第四步:收割(余额分配漏洞)
  · 协议按"瞬时余额"发奖励,攻击者用闪电贷临时撑大余额,领走大部分奖励

第五步:还贷离场
  · 反向交易恢复价格,还掉闪电贷,净赚离场

完整修复(对应两个根因)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// 修复后的协议(关键两点:价格用 TWAP + 奖励按时间累积)
contract FixedLending {
    // 修复一:价格用时间加权平均(TWAP),不用现货价
    uint256 public lastPrice;
    uint256 public lastUpdateTime;

    function getTWAP() public view returns (uint256) {
        // 简化示意:取过去一段时间的平均价,而非瞬时价
        return oracle.getTimeWeightedPrice(30 minutes);
    }

    // 修复二:奖励按时间累积记账,不用瞬时余额差
    mapping(address => uint256) public staked;
    mapping(address => uint256) public rewardDebt;
    uint256 public accRewardPerToken;

    function claimReward() public {
        uint256 earned = staked[msg.sender] * accRewardPerToken - rewardDebt[msg.sender];
        rewardDebt[msg.sender] = staked[msg.sender] * accRewardPerToken;
        if (earned > 0) payable(msg.sender).transfer(earned);
    }
}

面试答题框架

1. 定性:两个致命漏洞——"现货价定价"(可被单笔交易操纵)+ "瞬时余额分配"(可被闪电贷撑大)
2. 攻击链:闪电贷 → 操纵价格 → 超额借款 → 领走奖励 → 还贷离场
3. 修复:价格用 TWAP/预言机 + 奖励按时间累积 + 关键操作加冷却期
4. 升华:可组合 + 资金可瞬时流动的世界里,"局部正确 ≠ 全局安全"

★ 加分点:点出“闪电贷本身不是攻击,是放大器”——修好价格和余额逻辑才是根本, “禁闪电贷”既做不到也没意义。

13.5 综合实战题五:一次数据泄露事件的应急响应与合规处置

场景

某天安全监控告警:数据库出现异常大量查询,疑似拖库。初步判断泄露了约 20 万用户的 姓名、手机号、部分身份证号。面试官问:“作为安全负责人,你从第一分钟开始怎么做?”

处置时间线(技术应急 + 合规义务双线并行)

┌─ 技术线(第八章 应急响应)─────────────────────────────────────────┐
│ 0~15 分钟:止血(遏制)                                            │
│   · 封堵拖库入口(临时下线可疑接口 / 隔离服务器)                    │
│   · 修改/禁用泄露的数据库凭据,限制异常来源 IP                       │
│   · 保留现场:拉取日志、数据库慢查询、网络流量(证据保全)           │
│                                                                     │
│ 15 分钟~2 小时:定位 + 定范围                                       │
│   · 确认攻击入口(SQL 注入?越权?凭据泄露?)                        │
│   · 确认泄露了哪些字段、多少人、是否含敏感个人信息                    │
│                                                                     │
│ 2 小时~:根除 + 恢复 + 复盘                                         │
│   · 修复漏洞、清理后门、恢复服务                                     │
│   · 写复盘报告,落实整改                                            │
└─────────────────────────────────────────────────────────────────────┘
┌─ 合规线(第十二章 个保法义务)──────────────────────────────────────┐
│ 立即:采取补救措施,阻止泄露扩大                                    │
│ 及时:通知【受影响的个人】(告知泄露了什么、可能后果、已采取措施)     │
│ 报告:按规定向【主管部门】报告(重大泄露有报告义务)                  │
│ 留痕:全程记录时间线、处置过程,作为合规证据                        │
└─────────────────────────────────────────────────────────────────────┘

关键知识点串联(第八、十二章)

1. 应急响应六阶段(8.9):准备→检测→遏制→根除→恢复→复盘
2. 证据保全(8.1):日志、流量、快照,保证可追溯
3. 个保法泄露义务(12.2):立即补救 + 通知个人 + 报告主管部门
4. 敏感信息(12.2):身份证号属于敏感个人信息,泄露后果更重,处置要求更严

面试答题框架

1. 先亮"双线并行":技术线(止血→定位→根除→复盘)+ 合规线(补救→通知→报告→留痕)
2. 技术线按时间轴展开,强调"先止血再查因"
3. 合规线强调"通知个人 + 报告主管部门"是法定义务,不能隐瞒
4. 加分:身份证号是敏感个人信息,要评估是否触发"重大泄露"的更高报告要求

★ 加分点:很多人只会答“技术应急”,漏了“合规义务”。能同时答出“通知个人 + 报告主管部门”, 说明你懂“安全”和“合规”是一体两面,这正是高级岗位要的。

13.6 综合实战题六:从零设计一个“安全 + 合规”双达标的新系统

场景

公司要做一个面向 C 端用户的新 App(收集手机号、身份证用于实名认证,数据可能跨境, 部署在云上,K8s + 微服务)。面试官问:“作为安全负责人,你从设计阶段开始,怎么把它建成一个 既安全又合规的系统?”

设计框架(安全 + 合规双线,贯穿全书)

第一层:架构设计阶段(先想清楚,再写代码)
  · 数据最小化:实名认证只收"手机号 + 身份证号"必需字段,不多收
  · 敏感信息:身份证号加密存储(SM4/AES)+ 脱敏展示 + 单独同意
  · 数据出境:确认是否跨境,若是,提前走合规路径(评估/标准合同/认证)
  · 云架构:IAM 最小权限、对象存储默认私有、IMDSv2

第二层:开发阶段(把安全写进代码)
  · 输入校验 + 参数化查询(防注入)
  · 对象级授权(防越权)
  · 密钥不硬编码,用密钥管理服务(KMS/密钥管理)
  · 依赖锁版本 + SCA 扫描(防供应链投毒)

第三层:部署阶段(云原生安全)
  · K8s:RBAC 最小权限、NetworkPolicy 默认 deny、Secret 用 External Secrets
  · 准入控制(PSA/Kyverno)+ 容器镜像扫描 + 非 root 运行
  · IaC 安全检查(Checkov)进 CI

第四层:运行阶段(检测 + 响应)
  · 云审计日志(CloudTrail)+ 数据事件 + 告警
  · 运行时检测(Falco)+ 日志进 SIEM
  · 应急预案 + 数据泄露处置流程

第五层:合规闭环(等保 + 密评 + 个保法)
  · 定级备案(等保三级大概率要做)
  · 关键环节用国密算法,准备密评
  · DPIA(处理敏感个人信息必做)
  · 隐私政策 + 单独同意 + 用户权利(删除/导出/撤回)落地

关键知识点串联(全书覆盖)

设计层 用到的章节
敏感信息 + 最小必要 + 出境 第十二章
注入/越权/会话 第一~五章
供应链 第九章
云 IAM + K8s + IaC 第十章
应急响应 第八章

面试答题框架

1. 先给"五层框架":架构 → 开发 → 部署 → 运行 → 合规闭环
2. 每层点 2~3 个最关键的落地动作,别堆砌
3. 强调"安全 + 合规双线并行",敏感信息(身份证)贯穿始终
4. 收尾:安全设计要前置,等上线了再补,成本翻十倍

★ 加分点:能把“实名认证收身份证”这种具体业务,落到“加密存储 + 脱敏展示 + 单独同意 + DPIA”, 说明你不是背概念,而是真能把合规翻译成工程落地。这是这道题想考察的核心能力。

13.7 第十三章小结

13.7.1 六道题的共同框架

六道题场景各不相同,但底层是同一个分析框架,值得反复用:

识别风险 → 推演攻击 → 分层防御 → 落地检查

1. 识别风险:这个系统/功能,最可能被怎么打?(定性)
2. 推演攻击:攻击者具体会怎么一步步走?(攻击链)
3. 分层防御:每一环用什么措施挡?(对应全书章节)
4. 落地检查:这些措施怎么落到代码/配置/流程?(可执行)

13.7.2 六道题的知识点地图

题 场景 串联的章节
13.1 电商全链路攻防 一~六、九章
13.2 SSRF 云上横向 二、十章
13.3 供应链投毒溯源 八、九章
13.4 DeFi 闪电贷+价格操纵 十一章
13.5 数据泄露应急+合规 八、十二章
13.6 安全+合规新系统设计 全书

下一章(第十四章)是全书收尾:把 12 个章节、几百道面试题做一次全量索引 + 高频 TOP50 + 答题方法论, 帮你从“会做题”升级到“会应对任何题”。

第十四章:全书总汇——356 题总索引与答题方法论

本章定位:这是全书收尾。前面十二章已经产出了 356 道面试题(A~L 组)。 这一章做三件事:① 给你一张全量索引,方便检索;② 提炼 50 道最高频题; ③ 给你一套“应对任何题”的答题方法论 + 自测评分表。

14.1 全书面试题总索引(A ~ L 组,共 356 题)

组 章节 主题 题数 核心考点
A 第一章 AI / LLM 应用安全 28 Prompt 注入、模型数据泄露、RAG 投毒、越狱
B 第二章 API 安全 30 对象级/功能级越权、认证缺失、速率限制
C 第三章 GraphQL 与 gRPC 安全 30 深度查询 DoS、内省泄露、gRPC 元数据
D 第四章 移动 App 与小程序 28 反编译、脱壳、证书校验、越权
E 第五章 实时通信与 IoT 30 WebSocket 劫持、MQTT 未授权、协议漏洞
F 第六章 主机与操作系统安全 30 提权、持久化、SUID、内核漏洞
G 第七章 内网横向与域渗透 30 Kerberoast、Pass-the-Hash、黄金票据
H 第八章 数字取证与应急响应 30 证据链、时间线、遏制、复盘
I 第九章 软件供应链安全 30 依赖投毒、SBOM、CI/CD、供应链溯源
J 第十章 云原生与 Serverless 30 IAM、IMDS、K8s、容器逃逸、IaC
K 第十一章 Web3 与智能合约 30 重入、溢出、预言机、闪电贷、MEV
L 第十二章 数据合规与隐私 30 个保法、GDPR、分类分级、数据出境、等保

★ 检索技巧:题目都是“组 + 序号”编号(如 J24、K13),在文档里 Ctrl+F 搜编号即可直达。 全书题目都遵循统一结构:定义 → 生活类比 → 原理 → 代码 → 加分点。

14.2 高频 TOP50(跨章节精选)

面试时间有限,如果你只能复习一部分,优先吃透这 50 道。按主题归类,标注难度。

14.2.1 应用层(A~E 组,20 题)

# 题目 难度 一句话答案锚点
1 Prompt 注入攻击是什么?怎么防? ★★☆ 把用户输入当“指令”而非“数据”,输入输出隔离 + 权限最小化
2 RAG 检索增强有什么安全风险? ★★☆ 知识库投毒 → 检索到毒 → 生成被污染
3 对象级越权(BOLA)是什么? ★★★ 每个对象都校验“当前用户是否拥有”,不能只验登录
4 功能级越权(BFLA)是什么? ★★☆ 每个接口校验角色/权限,不能只靠前端隐藏按钮
5 API 速率限制缺失有什么危害? ★☆☆ 撞库、刷接口、DDoS、薅羊毛
6 GraphQL 深度查询 DoS 怎么防? ★★☆ 查询深度限制 + 复杂度预算 + 超时
7 GraphQL 内省(Introspection)泄露风险? ★★☆ 生产环境关闭内省,否则泄露 schema
8 移动 App 反编译能拿到什么? ★★☆ 硬编码密钥、接口地址、逻辑,配合加固/混淆
9 移动 App 证书校验绕过怎么防? ★★☆ SSL Pinning + 双向校验
10 WebSocket 劫持与 CSWSH 怎么防? ★★☆ Origin 校验 + 鉴权 token + 心跳
11 MQTT 未授权访问的危害? ★★☆ 订阅敏感主题、伪造消息,需账号密码 + ACL + TLS
12 XSS 三种类型及防御? ★★☆ 存储/反射/DOM,输出编码 + CSP + HttpOnly
13 CSRF 原理与防御? ★★☆ 浏览器自动带 Cookie,用 Token/SameSite/校验 Referer
14 SQL 注入原理与防御? ★★☆ 拼接 SQL,用预编译参数化 + 最小权限
15 SSRF 原理与防御? ★★☆ 服务端代发请求,白名单 + 禁内网 + 二次解析
16 越权 vs 未授权访问的区别? ★☆☆ 越权=有账号但越权访问;未授权=没账号也能访问
17 JWT 常见安全坑? ★★☆ 弱签名、alg=none、密钥泄露、不设过期
18 会话固定攻击怎么防? ★★☆ 登录后重新生成 sessionId
19 密码存储的正确姿势? ★★☆ bcrypt/argon2 加盐慢哈希,绝不明文/可逆
20 文件上传漏洞怎么防? ★★☆ 类型白名单 + 重命名 + 存隔离 + 不解析

14.2.2 主机与内网(F~H 组,12 题)

# 题目 难度 一句话答案锚点
21 Linux 常见提权手法? ★★☆ SUID、sudo 配置错、内核漏洞、计划任务、PATH 劫持
22 SUID 提权原理? ★★☆ 以文件属主权限运行,找 root 属主 SUID 可滥用
23 持久化常见手法? ★★☆ 计划任务、SSH 公钥、启动项、内核模块
24 Pass-the-Hash 原理? ★★★ 用 NTLM Hash 直接认证,无需明文密码
25 Kerberoasting 攻击? ★★★ 请求服务票据离线爆破,弱口令可解
26 黄金票据 vs 白银票据? ★★★ 黄金=伪造 TGT(任意权限);白银=伪造服务票据(限服务)
27 横向移动常用手法? ★★☆ PsExec、WMI、SMB、Pass-the-Hash
28 应急响应六阶段? ★★☆ 准备→检测→遏制→根除→恢复→复盘
29 取证第一原则? ★★☆ 易失性优先(内存→网络→进程→磁盘)
30 证据链(Chain of Custody)是什么? ★★☆ 证据从采集到庭审全程可追溯、未被篡改
31 内网横向最常用的探测命令? ★☆☆ 找域控、找共享、找凭据、找可达主机
32 黄金票据的防御? ★★☆ 定期改 krbtgt 密码两次、监控异常票据

14.2.3 供应链 / 云原生 / 新兴(I~K 组,12 题)

# 题目 难度 一句话答案锚点
33 依赖混淆攻击? ★★★ 私包名撞公有源,公有源同名包被拉取
34 SBOM 是什么? ★★☆ 软件物料清单,SPDX/CycloneDX,列出所有依赖
35 CI/CD 安全风险? ★★☆ 密钥泄露、脚本注入、Runner 污染
36 云责任共担模型? ★★☆ 云管底层,你管云上配置
37 IAM 最小权限怎么落地? ★★☆ 按需授予、定期审查、权限边界兜底
38 IMDS / SSRF 攻击链? ★★★ SSRF → 169.254.169.254 → 偷角色密钥
39 混乱代理人(Confused Deputy)? ★★★ 用 ExternalId + 精确信任策略
40 K8s RBAC 最小权限? ★★☆ 按命名空间/资源/动词最小化,禁通配符
41 容器逃逸手法? ★★★ 特权容器、docker.sock、内核漏洞
42 K8s Secret 为什么只是 base64? ★★☆ base64 不是加密,需 etcd 加密 + External Secrets
43 重入攻击? ★★★ 先转账后记账,用 CEI + ReentrancyGuard
44 整数溢出/下溢? ★★☆ 数字绕圈,0.8.0 自动检查 / SafeMath
45 预言机价格操纵? ★★★ 现货价可被操纵,用 TWAP/Chainlink
46 闪电贷攻击? ★★★ 借巨款放大漏洞,防价格/余额逻辑漏洞
47 三明治攻击? ★★★ 前买后卖夹用户,滑点保护 + 私有交易
48 IaC 安全风险? ★★☆ 硬编码密钥、tfstate 明文、漂移

14.2.4 合规(L 组,2 题)

# 题目 难度 一句话答案锚点
49 个保法核心原则? ★★☆ 告知-同意 + 最小必要 + 用户权利 + 单独同意
50 数据出境三条路径? ★★☆ 安全评估 / 标准合同 / 保护认证

★ 使用建议:先自测这 50 题,能流利答出 40 题以上,说明你已经建立起完整的安全知识骨架; 剩下的 300 多题是“深化 + 覆盖边缘场景”,按目标岗位的侧重点挑着补即可。

14.3 答题方法论:如何应对任何安全面试题

14.3.1 通用五步答题法

面试题千变万化,但都能套用这个五步框架。这套框架贯穿全书每一道题:

第一步:先给一句话定义(定性)
  "这是一个 XX 攻击/机制,本质是……"
  先让面试官知道"你懂这是什么",为后面铺路。

第二步:给一个生活类比(让人秒懂)
  "打个比方,就像……"
  类比的目的是证明你"真理解"了,而不是死记硬背。

第三步:讲原理/流程(show 深度)
  攻击链怎么走?关键机制是什么?核心参数是什么?
  这是区分"背过"和"真懂"的分水岭。

第四步:给代码/配置(show 落地)
  能写一段关键代码、一条命令、一个配置,瞬间拉开差距。

第五步:加分点(show 认知)
  补一个"局限/坑/升华"——"但这里有个坑……""诚实地讲,它防不住……"
  这一句往往决定你是"合格"还是"优秀"。

14.3.2 五种题型的应对侧重

题型 侧重 示例
概念题 定义 + 类比 + 一句话危害 “什么是重入攻击?”
原理题 流程/时序 + 关键机制 “SSRF 怎么打到元数据服务?”
场景题 定性 → 推演 → 分层防御 → 优先级 “这个系统怎么防?”
手写题 关键代码 + 为什么这样写 “写一个安全的 withdraw”
开放题 框架 + 取舍 + 诚实局限 “如何建设安全体系?”

14.3.3 三条加分话术(贯穿全书)

话术一:诚实说明局限(最加分,没有之一)
  "IMDSv2 不能 100% 防 SSRF……" / "Falco 只能检测不能阻断……"
  / "脱敏不等于匿名化……" / "等保是底线不是攻不破……"
  ★ 面试官最反感"什么都懂、什么都绝对",最欣赏"懂边界"。

话术二:给出优先级和取舍
  "不是所有防御一起上,而是按'被攻击概率 × 损失'排序,先堵拖库、越权、支付篡改这三件……"
  ★ 体现工程思维,而非堆砌工具清单。

话术三:把概念翻译成工程落地
  "最小必要 → 接口字段分级" / "删除权 → 全链路删除闭环"
  / "单独同意 → 敏感字段独立勾选 + 同意留痕"
  ★ 证明你能把抽象要求变成可执行方案。

14.4 自测评分表(评估你的安全水平)

按“能答对多少题 + 答到什么深度”给自己打分:

级别 表现 说明
L1 入门 能说出名词定义,但说不出原理 概念题 60% 能答,原理题卡壳
L2 熟悉 定义 + 原理 + 能举例 高频题能流利答,代码题勉强
L3 熟练 能写关键代码 + 讲攻击链 手写题、场景题能拿下
L4 精通 能讲局限 + 给优先级 + 落地 开放题能系统性作答,有取舍判断

自测方法:随机抽 TOP50 里的 10 题,用“五步答题法”完整作答并录音/录像。 10 题里能流利答出 8 题(含加分点),你就处于 L3 水平,可以应对绝大多数安全岗位面试。

14.5 连环追问链模板(模拟面试官)

面试官常常从一道题“往下追问”,检验你是不是真懂。下面给三条高频追问链:

追问链一(SSRF):
  Q1 什么是 SSRF? → Q2 它和普通 SSRF 在云上有什么不同?
  → Q3 怎么打到 169.254.169.254? → Q4 IMDSv2 能完全防住吗?
  → Q5 那 DNS rebinding 怎么绕过?怎么防?

追问链二(重入攻击):
  Q1 什么是重入? → Q2 根因是什么? → Q3 CEI 三个字母?
  → Q4 只用 ReentrancyGuard 够吗? → Q5 只读重入是什么?

追问链三(数据合规):
  Q1 个保法核心原则? → Q2 敏感个人信息有哪些?
  → Q3 数据出境怎么合规? → Q4 触发安全评估的量级?
  → Q5 数据泄露了要做什么?

★ 使用建议:把每条追问链的“最后一问”当成重点——那才是区分度所在。 能答到“最后两问”的人,才真正掌握了这个主题。

14.6 全书总结与学习路线

14.6.1 全书脉络一句话回顾

全书 12 章 + 1 章实战,是一条"从应用层到基础设施、再到新兴与合规"的完整攻击面地图:

  应用层(一~五章)→ 主机内网(六~七章)→ 出事后(八章)→
  供应链(九章)→ 云原生(十章)→ Web3(十一章)→ 合规(十二章)→ 综合实战(十三章)

14.6.2 全书反复出现的“四个基本功”

无论哪一章,最终都回到这四个不变的基本功:

1. 最小权限 —— 每个身份、每个账号、每个角色,只给"刚好够用"的权限
2. 状态一致 / 先改后调 —— 先改状态再外部调用(CEI),先扣账再转账
3. 审计与留痕 —— 一切关键操作可追溯,出事后能还原时间线
4. 纵深防御 —— 不依赖单一防线,层层设卡,一处失守还有下一处

14.6.3 建议学习路线(按目标岗位)

通用安全工程师:一~五章(应用层)→ 六~七章(主机内网)→ 八章(应急)→ 高频 TOP50
云安全工程师:一~二章(基础)→ 九章(供应链)→ 十章(云原生,重点)→ 十二章(合规)
安全研发/架构:一~五章(应用层)→ 九章(供应链)→ 十二章(合规)→ 十三章(综合)
区块链安全:十一章(Web3,重点)→ 二章(API)→ 十三章(综合实战)

★ 最后一句话:安全知识的价值不在“记住多少攻击手法”,而在“能不能在任何新场景下, 快速识别风险、推演攻击、给出分层防御、并诚实地说出它的边界”。 这套分析框架,才是本书真正想交给你的东西。

14.6.4 全书完

感谢你读到这里。356 道题、12 个专题、6 道综合实战,加上开篇的名词速查, 构成了这份《新兴攻击面与专项安全-实战专题》。 建议配合同目录的《00-名词速查手册.md》交叉查阅——遇到看不懂的术语,Ctrl+F 回手册查白话解释。 祝你面试顺利,把安全思维带进每一个你写的系统里。