Skip to content

Latest commit

 

History

History
917 lines (684 loc) · 22.9 KB

File metadata and controls

917 lines (684 loc) · 22.9 KB

AGENTS.md - 区块链合约安全研究 Agent 指南

0x00 角色定义

你是一个专业的区块链安全研究员 / 智能合约审计专家 / 漏洞研究员. 你的核心职责是:

  1. 分析和复现 DeFi 安全事件
  2. 识别智能合约中的安全漏洞
  3. 编写漏洞利用 PoC (Proof of Concept)
  4. 提供安全加固建议

0x01 工作空间结构

合约安全研究/
├── DeFiHackLabs/                    # 674+ DeFi 安全事件 PoC
│   ├── src/test/                    # 按年月组织的漏洞 PoC (2017-2025)
│   ├── src/test/interface.sol       # 通用接口定义
│   ├── src/test/basetest.sol        # 测试基类
│   ├── script/                      # PoC 模板
│   └── academy/                     # 安全教程
│       ├── onchain_debug/           # 链上调试教程
│       └── solidity/                # 审计教程
├── WTF-Solidity/                    # Solidity 教程
│   ├── S01-S17/                     # 安全专题 (17个漏洞类型)
│   ├── 01-57/                       # 基础语法教程
│   └── Topics/                      # 进阶专题
└── Web3-Project-Security-Practice-Requirements/  # 慢雾安全规范

0x02 漏洞速查手册

2.1 重入攻击 (Reentrancy)

漏洞原理: 合约在外部调用时将执行权交给接收方, 若状态更新在调用之后, 攻击者可在回调中重复调用.

漏洞代码特征:

function withdraw() external {
    uint256 balance = balances[msg.sender];
    require(balance > 0);
    (bool success, ) = msg.sender.call{value: balance}("");  // 危险点
    require(success);
    balances[msg.sender] = 0;  // 状态更新太晚
}

触发方式:

  • call{value:}() - ETH 转账触发 receive()/fallback()
  • safeTransfer/safeTransferFrom - ERC721/1155 的 onERC721Received 回调
  • ERC777 的 tokensReceived 回调

变种类型:

类型 描述 攻击点
单函数重入 重复调用同一函数 同函数内状态未更新
跨函数重入 从函数A重入到函数B 函数B没有加锁且共享状态
跨合约重入 多合约系统各自有锁 锁状态不互通
只读重入 利用 getter 返回过期状态 依赖过期状态的第三方合约

修复方案:

// CEI 模式
balances[msg.sender] = 0;  // 先更新
(bool success, ) = msg.sender.call{value: balance}("");

// 重入锁
modifier nonReentrant() {
    require(_status != ENTERED);
    _status = ENTERED;
    _;
    _status = NOT_ENTERED;
}

// 全局重入锁 (多合约系统)
// 独立合约存储锁状态, 所有合约共享

案例: DeFiHackLabs/src/test/2021-08/ | WTF-Solidity/S01_ReentrancyAttack/


2.2 NFT 重入 (NFT Reentrancy)

漏洞原理: ERC721/ERC1155 的安全转账函数会调用接收方的回调函数, 可被利用进行重入.

危险函数:

  • ERC721: safeTransferFrom(), _safeMint() -> 调用 onERC721Received()
  • ERC1155: safeTransferFrom(), safeBatchTransferFrom() -> 调用 onERC1155Received()

漏洞代码特征:

function mint() external {
    require(mintedAddress[msg.sender] == false);
    totalSupply++;
    _safeMint(msg.sender, totalSupply);  // 危险: 回调在状态更新前
    mintedAddress[msg.sender] = true;  // 太晚
}

攻击合约:

function onERC721Received(...) external returns (bytes4) {
    if (nft.balanceOf(address(this)) < 10) {
        nft.mint();  // 重入
    }
    return this.onERC721Received.selector;
}

案例: WTF-Solidity/S16_NFTReentrancy/


2.3 选择器碰撞 (Selector Clash)

漏洞原理: 函数选择器只有 4 字节, 可暴力碰撞出相同选择器.

漏洞代码特征:

function executeCrossChainTx(bytes memory _method, bytes memory _bytes) public {
    (success, ) = address(this).call(
        abi.encodePacked(
            bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))),
            abi.encode(_bytes)
        )
    );
}

碰撞示例:

transferFrom(address,address,uint256) => 0x23b872dd
gasprice_bit_ether(int128)           => 0x23b872dd

修复方案:

  • 不使用用户输入拼接函数选择器
  • 关键函数白名单校验

案例: DeFiHackLabs/src/test/2021-08/ ($611M) | WTF-Solidity/S02_SelectorClash/


2.4 中心化风险 (Centralization)

漏洞原理: 合约控制权过于集中, 单点失败可导致全部资金损失.

漏洞代码特征:

// 危险: owner 可无限铸币
function mint(address to, uint256 amount) external onlyOwner {
    _mint(to, amount);
}

// 危险: 可随意修改关键参数
function setPrice(uint256 newPrice) external onlyOwner {
    price = newPrice;
}

风险场景:

  • 单一 EOA 控制合约 owner
  • 多签但签名人为一致行动人
  • 私钥泄露 / 钓鱼攻击

修复方案:

  • 多签钱包 (真正的分散控制)
  • 时间锁 (Timelock)
  • 权限分离

案例: Ronin Bridge ($624M), Harmony Bridge ($100M) | WTF-Solidity/S03_Centralization/


2.5 整数溢出 (Integer Overflow/Underflow)

漏洞原理: 整型变量超出范围后回绕.

漏洞代码特征:

// Solidity < 0.8.0 或 unchecked 块
unchecked {
    require(balances[msg.sender] - _value >= 0);  // 永远为真
    balances[msg.sender] -= _value;  // 下溢变成巨大数字
}

修复方案:

  • 使用 Solidity >= 0.8.0
  • 旧版本使用 SafeMath
  • 避免 unchecked 块中用户可控运算

案例: DeFiHackLabs/src/test/2018-04/ | WTF-Solidity/S05_Overflow/


2.6 签名重放 (Signature Replay)

漏洞原理: 同一签名被多次使用或跨链使用.

漏洞代码特征:

function badMint(address to, uint amount, bytes memory signature) public {
    bytes32 _msgHash = keccak256(abi.encodePacked(to, amount));
    require(verify(_msgHash, signature), "Invalid!");
    _mint(to, amount);  // 无 nonce, 可重放
}

修复方案:

// 记录已使用签名
mapping(address => bool) public mintedAddress;

// 包含 nonce 和 chainid
bytes32 _msgHash = keccak256(abi.encodePacked(to, amount, nonce, block.chainid));
nonce++;

// 校验签名长度
require(signature.length == 65);

案例: DeFiHackLabs/src/test/2022-06/ (Wintermute $20M) | WTF-Solidity/S06_SignatureReplay/


2.7 坏随机数 (Bad Randomness)

漏洞原理: 链上数据可被预测.

漏洞代码特征:

uint256 randomNumber = uint256(keccak256(abi.encodePacked(
    blockhash(block.number - 1),
    block.timestamp
))) % 100;

攻击合约:

function attack(address nft) external {
    // 同一交易中计算相同"随机数"
    uint256 luckyNumber = uint256(keccak256(abi.encodePacked(
        blockhash(block.number - 1), block.timestamp
    ))) % 100;
    INft(nft).luckyMint(luckyNumber);
}

不安全随机源: block.timestamp, blockhash(), block.number, block.prevrandao

修复方案: Chainlink VRF / Commit-Reveal

案例: WTF-Solidity/S07_BadRandomness/


2.8 绕过合约检查 (Contract Check Bypass)

漏洞原理: 合约在构造函数执行时 extcodesize 为 0, 可绕过 isContract 检查.

漏洞代码特征:

function isContract(address account) public view returns (bool) {
    uint size;
    assembly {
        size := extcodesize(account)
    }
    return size > 0;
}

function mint() external {
    require(!isContract(msg.sender), "No contracts!");  // 可被绕过
    _mint(msg.sender, 1);
}

攻击方式: 将攻击逻辑写在构造函数中

修复方案: 不要依赖 isContract 检查, 或使用 msg.sender == tx.origin (但不推荐)

案例: WTF-Solidity/S08_ContractCheck/


2.9 拒绝服务 (DoS)

漏洞原理: 恶意行为使合约无法正常运行.

漏洞代码特征:

function refund() external {
    for(uint256 i; i < players.length; i++) {
        (bool success, ) = players[i].call{value: balances[players[i]]}("");
        require(success, "Refund failed!");  // 一个失败全部失败
    }
}

攻击合约:

fallback() external payable {
    revert("DoS Attack!");
}

修复方案:

  • Pull 模式让用户自行领取
  • 不依赖外部调用成功

案例: DeFiHackLabs/src/test/2022-04/ (11,539 ETH) | WTF-Solidity/S09_DoS/


2.10 貔貅合约 (Honeypot)

漏洞原理: 代币只能买入不能卖出.

漏洞代码特征:

function _update(address from, address to, uint256 amount) internal override {
    if(to == pair) {  // 卖出时
        require(from == owner(), "Cannot sell");
    }
    super._update(from, to, amount);
}

常见伪装:

  • 转账不 revert 但状态不变
  • emit 假事件误导钱包
  • 白名单/时间锁

识别工具: Token Sniffer, Ave Check, Tenderly 模拟卖出

案例: WTF-Solidity/S10_Honeypot/


2.11 抢跑 (Front-running)

漏洞原理: 攻击者监听 mempool, 通过提高 gas 抢先执行交易获利.

攻击类型:

  • 三明治攻击: 在目标交易前后插入买卖单
  • 抢跑铸造: 抢先 mint NFT
  • 抢跑清算: 抢先清算获取奖励

修复方案:

  • Commit-Reveal 方案
  • 使用 Flashbots 等私有交易池
  • 设置合理滑点

案例: WTF-Solidity/S11_Frontrun/


2.12 访问控制 (Access Control)

漏洞代码特征:

// 缺失权限检查
function setPrice(uint256 newPrice) external {
    price = newPrice;
}

// 使用 tx.origin (可被钓鱼)
require(tx.origin == owner);

// initialize 未保护
function initialize() external {
    owner = msg.sender;
}

修复方案: 使用 msg.sender, OpenZeppelin AccessControl, initializer 修饰器

案例: WTF-Solidity/S04_AccessControlExploit/ | WTF-Solidity/S12_TxOrigin/


2.13 未检查的低级调用 (Unchecked Call)

漏洞原理: call/send/delegatecall 失败不会自动 revert, 需手动检查返回值.

漏洞代码特征:

function withdraw() external {
    uint256 balance = balances[msg.sender];
    balanceOf[msg.sender] = 0;
    bool success = payable(msg.sender).send(balance);  // 未检查!
    // 转账失败但余额已清零
}

修复方案:

(bool success, ) = msg.sender.call{value: balance}("");
require(success, "Transfer failed");

案例: King of Ether | WTF-Solidity/S13_UncheckedCall/


2.14 时间操纵 (Time Manipulation)

漏洞原理: PoS 前矿工可操纵 block.timestamp.

漏洞代码特征:

function luckyMint() external {
    if(block.timestamp % 170 == 0) {  // 可被操纵
        _mint(msg.sender, totalSupply);
    }
}

修复方案: 不依赖 block.timestamp 做关键判断, 使用 Chainlink VRF

案例: WTF-Solidity/S14_TimeManipulation/


2.15 预言机操纵 (Oracle Manipulation)

漏洞原理: 使用可被操纵的即时价格.

漏洞代码特征:

// 使用瞬时储备
function getPrice() public view returns (uint256) {
    (uint112 reserve0, uint112 reserve1, ) = pair.getReserves();
    return reserve0 / reserve1;  // 可被闪电贷操纵
}

// 使用 balanceOf
function getTokenPrice() public view returns (uint256) {
    return USDC.balanceOf(pool) / TOKEN.balanceOf(pool);
}

攻击流程:

1. 闪电贷借入大量代币
2. DEX 大量买入/卖出操纵价格
3. 利用被操纵价格获利
4. 归还闪电贷

修复方案:

  • TWAP (时间加权平均价格)
  • Chainlink 等去中心化预言机
  • 多价格源校验

案例: DeFiHackLabs/src/test/2022-08/ | WTF-Solidity/S15_OracleManipulation/


2.16 精度损失 (Precision Loss)

漏洞代码特征:

// 先除后乘
uint256 shares = (amount / totalAssets) * totalSupply;

// 首次存款攻击
return assets * totalSupply / totalAssets;  // totalAssets 可被操控

首次存款攻击:

1. 攻击者存入 1 wei, 获得 1 share
2. 直接转入大量资产 (不通过 deposit)
3. 后续用户存入时精度截断获得 0 shares

修复方案: 初始化 mint 死亡 shares, 使用虚拟偏移

案例: DeFiHackLabs/src/test/2024-05/


2.17 闪电贷回调验证

漏洞代码特征:

function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external {
    // 缺少验证
}

修复方案:

require(msg.sender == address(pair), "Invalid caller");
require(sender == address(this), "Invalid sender");

2.18 任意外部调用 (Arbitrary Call)

漏洞代码特征:

function execute(address target, bytes calldata data) external {
    target.call(data);  // 可调用任意合约任意函数
}

修复方案: target 白名单, 限制函数选择器

案例: DeFiHackLabs/src/test/2022-03/


0x03 漏洞分类索引

按攻击类型

攻击类型 相关漏洞
资金窃取 重入, 访问控制, 签名重放, 任意调用
价格操纵 预言机操纵, 精度损失, 闪电贷
逻辑绕过 选择器碰撞, 整数溢出, 访问控制, 合约检查绕过
拒绝服务 DoS, 意外自毁
欺诈 貔貅合约, Rug Pull, 中心化风险
MEV 抢跑, 三明治攻击

按代码模式

代码模式 关注漏洞
call{value:} 重入攻击, 未检查调用
_safeMint() NFT 重入
balanceOf() 用于计价 预言机操纵
循环中外部调用 DoS
缺少权限修饰器 访问控制
tx.origin 钓鱼攻击
unchecked 整数溢出
block.timestamp 坏随机数, 时间操控
delegatecall 存储冲突, 恶意实现
签名验证无 nonce 签名重放
除法运算 精度损失
extcodesize 合约检查绕过
onlyOwner 铸币/提款 中心化风险

0x04 PoC 代码结构

4.1 标准 PoC 模板

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~$XXX
// Attacker : https://etherscan.io/address/0x...
// Attack Contract : https://etherscan.io/address/0x...
// Vulnerable Contract : https://etherscan.io/address/0x...
// Attack Tx : https://etherscan.io/tx/0x...

// @Analysis
// Post-mortem : https://...
// Twitter : https://...

contract Exploit is Test {
    function setUp() public {
        vm.createSelectFork("mainnet", BLOCK_NUMBER);
        vm.label(address(TARGET), "TARGET");
    }

    function testExploit() public {
        emit log_named_decimal_uint("[Start] Balance", TOKEN.balanceOf(address(this)), 18);
        
        // 攻击逻辑
        
        emit log_named_decimal_uint("[End] Balance", TOKEN.balanceOf(address(this)), 18);
    }
}

4.2 闪电贷攻击结构

contract Exploit is Test {
    uint256 borrow1;
    uint256 borrow2;

    function testExploit() public {
        borrow1 = 2000 * 1e18;
        USDT_WBNB_LPPool.swap(borrow1, 0, address(this), "0000");
    }

    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public {
        if(keccak256(data) == keccak256("0000")) {
            // 第一层闪电贷回调 -> 发起第二层
            borrow2 = IERC20(usdt).balanceOf(address(EGD_USDT_LPPool)) * 9999 / 10000;
            EGD_USDT_LPPool.swap(0, borrow2, address(this), "00");
            // 还款第一层
            IERC20(usdt).transfer(address(USDT_WBNB_LPPool), borrow1 * 1003 / 1000);
        } else {
            // 第二层闪电贷回调 - 执行攻击
            // 还款第二层
            IERC20(usdt).transfer(address(EGD_USDT_LPPool), amount1 * 1003 / 1000);
        }
    }
}

4.3 重入攻击结构

contract Attack {
    IVictim victim;
    uint256 count;
    
    function attack() external payable {
        victim.deposit{value: msg.value}();
        victim.withdraw();
    }
    
    receive() external payable {
        if (count < 10 && address(victim).balance >= 1 ether) {
            count++;
            victim.withdraw();
        }
    }
}

4.4 DoS 攻击结构

contract Attack {
    function attack(address gameAddr) external payable {
        IGame(gameAddr).deposit{value: msg.value}();
    }
    
    fallback() external payable {
        revert("DoS!");
    }
}

0x05 分析工具

交易调试

工具 URL 特点
Phalcon explorer.phalcon.xyz 可视化调用流, 资金流向
Tx Tracer openchain.xyz/trace 存储变化
Tenderly dashboard.tenderly.co 模拟交易, Debug
eigenphi tx.eigenphi.io MEV 分析

签名解码

工具 URL
4byte 4byte.directory
sig.eth openchain.xyz/signatures
etherface etherface.io/hash

辅助工具

工具 URL 用途
ABI to Interface gnidan.github.io/abi-to-sol 生成接口
Dedaub library.dedaub.com/decompile 反编译

0x06 运行命令

# 运行单个 PoC
forge test --contracts ./src/test/2022-08/EGD_Finance_exp.sol -vvv

# 指定 EVM 版本
forge test --contracts ./src/test/... -vvv --evm-version shanghai

# Fork 测试
forge test --fork-url https://rpc.ankr.com/eth --fork-block-number 15000000 -vvv

# Gas 报告
forge test --gas-report --contracts ./src/test/... -vvv

0x07 安全事件信息源

来源 URL
SlowMist Hacked hacked.slowmist.io
DeFiLlama Hacks defillama.com/hacks
De.Fi Rekt de.fi/rekt-database
BlockSec app.blocksec.com/explorer

0x08 Web3 项目安全实践要求 (慢雾)

来源: Web3-Project-Security-Practice-Requirements/README_zh_CN.md

8.1 开发准备

需求分析文档:

  • 包含项目的详尽描述
  • 包含项目解决的问题
  • 包含安全/隐私风险评估

开发设计文档:

  • 包含项目的架构设计图
  • 包含代码中函数的功能描述
  • 包含合约之间的关联关系描述

业务流程文档:

  • 包含每个业务流程的描述
  • 包含详尽的业务流程图
  • 包含详尽的资金链路图

8.2 智能合约安全编码

基础规范:

  • 基于 OpenZeppelin 等知名 library 开发
  • 使用 SafeMath 或 0.8.x+ 编译器避免溢出
  • 遵循 Solidity Style Guide 命名规范
  • 函数和变量可见性显性声明
  • 函数返回值显性赋值
  • 函数功能和参数注释完备

外部调用:

  • 外部调用正确检查返回值 (transfer, transferFrom, send, call, delegatecall)
  • interface 的参数类型返回值实现正确
  • call 等 low level 调用的目标地址和函数是预期内的
  • 使用 call 时根据业务需要限制 Gas

状态管理:

  • 设置合约关键参数时进行鉴权并使用事件记录
  • 可升级合约新旧实现的数据结构兼容
  • 编码遵循: 先判断, 后写入变量, 再进行外部调用 (CEI)

精度与计算:

  • 算数运算充分考虑精度问题, 避免先除后乘
  • 避免使用大量循环对 storage 变量赋值/读取

权限控制:

  • 避免权限过度集中, 做权限分离
  • 关键参数修改采用治理/timelock/多签管理

随机数:

  • 避免使用链上区块数据作为随机数种子
  • 随机数获取和使用充分考虑回滚攻击
  • 使用 Chainlink VRF 获取可靠随机数

价格预言机:

  • 避免使用第三方合约 token 数量直接计算价格
  • 获取价格时避免单一来源, 建议至少 3 个价格源

其他:

  • 确保业务上交互的外部合约互相兼容 (通缩/通胀代币, ERC-777, ERC-677 等)
  • 外部调用充分考虑重入风险
  • 合约继承关系保持线性, 确保继承的合约业务确实需要
  • 关键业务流程中使用事件记录执行状态
  • 预留全局与核心业务紧急暂停开关

8.3 测试要求

  • 业务流程/函数功能可用性测试
  • 单元测试覆盖率 95%+, 核心代码 100%
  • 输出单元测试覆盖率报告

8.4 基础安全配置

CI/CD:

  • 使用有效的 CI/CD Pipeline

账号安全:

  • 官方邮箱使用 Gmail 等知名服务商
  • 邮箱账号强制开启 MFA
  • 云平台管理账号使用强口令并开启 MFA

域名安全:

  • 使用知名域名服务商 (GoDaddy, NameSilo, NameCheap)
  • 域名服务商平台账号开启 MFA
  • DNS 配置开启 DNSSec
  • 开启域名隐私保护

CDN/网络:

  • 使用优秀 CDN 服务商 (Akamai, Cloudflare)

终端安全:

  • 全员设备使用杀毒软件 (卡巴斯基, AVG)

8.5 Web 前端安全

  • 全站 HTTPS
  • 配置 HSTS 防止中间人攻击
  • 配置 X-FRAME-OPTIONS 防止 Clickjacking
  • 配置 X-Content-Type-Options 对抗浏览器 sniff
  • 配置 CSP 策略防止 XSS
  • Cookie 配置 HttpOnly, Secure, Expires, SameSite
  • 不同业务子域严格划分
  • 第三方资源使用 integrity 属性 (SRI)
  • 正确配置 CORS
  • addEventListener/postMessage 检查 origin 和 target

8.6 后端环境安全

  • 选用优秀云服务商 (AWS, Google Cloud)
  • 服务器安全加固: HIDS, SSH Key 登录, SSH 登录 alert, google-auth
  • 使用专业软件监控服务器可用性 (APM, Zabbix)
  • 开启日志统一采集管理 (Splunk)
  • 通过 IP 白名单和网段划分做网络访问限制

8.7 发布流程

代码冻结:

  • 上线前 2 天冻结代码

测试要求:

  • 单元测试覆盖率 95%+, 核心代码 100%
  • 上线前 1 天执行回归测试
  • 上线前 0.5 天完成测试报告

安全审计:

  • 培养内部安全团队进行 code review
  • 代码冻结后进入整体安全回归
  • 至少 3 个团队独立审计 (1 内部 + 2 外部)
  • 发现严重/高危/中危漏洞推迟上线

8.8 运行时安全

监控:

  • Netlify/Vercel 开启 audit log
  • 设置 DNS 变更监控 (Better Stack)
  • 合约关键权限/参数变更事件监控
  • 合约资金变化监控
  • 周期性链上事件对账

漏洞赏金:

  • 发布漏洞赏金计划
  • 入驻知名平台: BugRap, code4rena, immunefi

应急小组:

  • 成立应急小组并对外提供联系方式

8.9 应急处置

处置流程:

  • 制定完备应急处置流程
  • 周期性安全应急响应演练 (SEAL Drills)

止损:

  • 及时通过紧急暂停开关止损
  • 通知社区避免用户继续交互

追踪:

  • 分析黑客获利地址
  • 留存访问日志/木马文件
  • 服务器快照保留现场
  • 联系安全团队追踪: MistTrack, Chainalysis

修复:

  • 与专业安全团队讨论修复方案
  • 实施修复并请安全团队验证

复盘:

  • 披露验尸报告
  • 同步问题本质原因, 影响范围, 损失, 修复情况, 追踪进展

8.10 安全意识培训

培养:

  • 团队成员阅读《区块链黑暗森林自救手册》(darkhandbook.io)
  • 在线安全意识测试: Google Phishing Quiz, Phishing.org
  • 新成员入职安全意识培训和测验
  • IT 岗位人员代码开发/系统运维安全培训

关注:

  • 关注 ScamSniffer, Wallet Guard 等团队动态
  • 关注社区/生态安全事件并同步团队

演练:

  • 周期性安全意识考察
  • 模拟钓鱼/木马投放测试