区块链安全—详谈合约攻击(三)
字数 1196 2025-08-22 18:37:15

区块链安全:智能合约Call函数攻击详解

一、Call函数基础

1. Call函数定义

call()是Solidity中的一个底层接口,用于向合约发送消息。基本语法为:

<address>.call(...) returns (bool)

2. 调用方式

  • 直接调用:test.call("abc", 123)
    • test:调用此call方法的用户
    • abc:调用的方法名称
    • 123:传入的参数值
  • 字节调用:<address>.call(bytes)

3. 特性

  • 支持传入任意类型的任意参数
  • 将参数打包成32字节并拼接后发送
  • 调用过程中会修改msg全局变量
    • msg.sender变为调用者地址
    • 执行环境为被调用者的运行环境

二、Call函数安全风险

1. 参数处理问题

Solidity的call函数会自动忽略多余参数:

function test(uint256 a) public {
    aa = a;
}

function callFunc() public {
    this.call(bytes4(keccak256("test(uint256)")), 10, 11, 12); // 仍然能成功调用
}

2. 权限绕过模型

典型身份验证函数:

function isAuth(address src) internal view returns (bool) {
    return (src == address(this) || src == owner);
}

攻击方式:

通过call函数调用内部函数,使msg.sender变为合约地址,绕过验证:

function callFunc(bytes data) public {
    this.call(data); // msg.sender变为合约地址
}

function withdraw(address addr) public {
    if(!isAuth(msg.sender)) throw;
    addr.transfer(this.balance);
}

攻击者构造data为withdraw(攻击者地址)即可绕过验证。

3. 代币方法器注入攻击

当只能控制函数名参数时:

function CPcall(address _to, uint _value, bytes data, string fallback){
    assert(_to.call(bytes4(keccak256(fallback)),msg.sender, _value, _data))
}

攻击者设置fallback为"transfer",即使参数数量不匹配也能执行转账函数。

三、实际攻击案例:ATN代币增发

1. 漏洞背景

ATN代币合约同时使用了ERC223和ds-auth库,单独使用时安全,但组合使用时出现漏洞。

2. 关键代码

ERC223转账函数:

function transferFrom(address _from, address _to, uint256 _amount, bytes _data, string _custom_fallback) public returns (bool) {
    // ...省略验证代码...
    if (isContract(_to)) {
        ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);
        receiver.call.value(0)(bytes4(keccak256(_custom_fallback)), _from, _amount, _data);
    }
    // ...
}

ds-auth权限验证:

function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
    if (src == address(this)) {
        return true;
    } else if (src == owner) {
        return true;
    }
    // ...
}

3. 攻击步骤

  1. 攻击者调用transferFrom(),设置:

    • _from:攻击者钱包地址
    • _to:ATN合约地址
    • _custom_fallback:"setOwner"
  2. 合约执行call调用时:

    • msg.sender变为合约本身地址
    • 绕过isAuthorized验证
    • 成功将owner改为攻击者地址
  3. 攻击者完成非法操作后,再次调用setOwner还原权限

四、防御建议

1. 开发原则

  • 简单性:使用尽可能少的操作码和数据类型
  • 确定性:确保规范无歧义,明确计算步骤和Gas消耗
  • 空间节省:保持EVM组件紧凑
  • 安全性:易于计算智能合约运行的燃料成本

2. 具体措施

  1. 避免直接使用原始call函数,使用封装的安全调用方法
  2. 对用户输入的函数名和参数进行严格验证
  3. 实现多重权限验证机制,不单纯依赖msg.sender
  4. 使用最新版本的Solidity编译器,启用所有安全检查
  5. 对关键函数添加事件日志,便于监控异常调用

3. 安全编码示例

// 安全的调用方式
function safeCall(address target, bytes data) internal {
    require(target != address(0), "Invalid target address");
    require(data.length >= 4, "Invalid calldata");
    
    // 限制可调用的函数白名单
    bytes4 funcSig = bytes4(data);
    require(isAllowedFunction(funcSig), "Function not allowed");
    
    (bool success, ) = target.call(data);
    require(success, "Call failed");
}

// 函数白名单验证
function isAllowedFunction(bytes4 funcSig) internal pure returns (bool) {
    return funcSig == bytes4(keccak256("safeFunction()")) || 
           funcSig == bytes4(keccak256("anotherSafeFunction(uint256)"));
}

五、总结

Call函数作为Solidity中的底层调用接口,虽然功能强大但存在严重安全隐患。开发者必须充分理解其工作原理和安全风险,避免直接暴露给不可信输入。通过案例分析可以看出,即使是标准库的组合使用也可能产生意想不到的漏洞。智能合约开发应遵循最小权限原则,实施深度防御策略,并在上线前进行充分的安全审计。

区块链安全:智能合约Call函数攻击详解 一、Call函数基础 1. Call函数定义 call() 是Solidity中的一个底层接口,用于向合约发送消息。基本语法为: 2. 调用方式 直接调用: test.call("abc", 123) test :调用此call方法的用户 abc :调用的方法名称 123 :传入的参数值 字节调用: <address>.call(bytes) 3. 特性 支持传入任意类型的任意参数 将参数打包成32字节并拼接后发送 调用过程中会修改 msg 全局变量 msg.sender 变为调用者地址 执行环境为被调用者的运行环境 二、Call函数安全风险 1. 参数处理问题 Solidity的call函数会自动忽略多余参数: 2. 权限绕过模型 典型身份验证函数: 攻击方式: 通过call函数调用内部函数,使 msg.sender 变为合约地址,绕过验证: 攻击者构造data为 withdraw(攻击者地址) 即可绕过验证。 3. 代币方法器注入攻击 当只能控制函数名参数时: 攻击者设置 fallback 为"transfer",即使参数数量不匹配也能执行转账函数。 三、实际攻击案例:ATN代币增发 1. 漏洞背景 ATN代币合约同时使用了ERC223和ds-auth库,单独使用时安全,但组合使用时出现漏洞。 2. 关键代码 ERC223转账函数: ds-auth权限验证: 3. 攻击步骤 攻击者调用 transferFrom() ,设置: _from :攻击者钱包地址 _to :ATN合约地址 _custom_fallback :"setOwner" 合约执行call调用时: msg.sender 变为合约本身地址 绕过 isAuthorized 验证 成功将owner改为攻击者地址 攻击者完成非法操作后,再次调用 setOwner 还原权限 四、防御建议 1. 开发原则 简单性 :使用尽可能少的操作码和数据类型 确定性 :确保规范无歧义,明确计算步骤和Gas消耗 空间节省 :保持EVM组件紧凑 安全性 :易于计算智能合约运行的燃料成本 2. 具体措施 避免直接使用原始call函数,使用封装的安全调用方法 对用户输入的函数名和参数进行严格验证 实现多重权限验证机制,不单纯依赖 msg.sender 使用最新版本的Solidity编译器,启用所有安全检查 对关键函数添加事件日志,便于监控异常调用 3. 安全编码示例 五、总结 Call函数作为Solidity中的底层调用接口,虽然功能强大但存在严重安全隐患。开发者必须充分理解其工作原理和安全风险,避免直接暴露给不可信输入。通过案例分析可以看出,即使是标准库的组合使用也可能产生意想不到的漏洞。智能合约开发应遵循最小权限原则,实施深度防御策略,并在上线前进行充分的安全审计。