欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

从一道CTF题看智能合约的安全问题

来源:本站整理 作者:佚名 时间:2018-07-21 TAG: 我要投稿

belluminarbank 是俄罗斯战队在 WCTF 上出的一道 EVM 题目,其中用到了很多 ETH 中经典的漏洞。虽然难度都不是很大,但是如果对 EVM 相关特性不了解的话还是有一定难度的,本文以这个题目为例详细介绍一下 ETH 智能合约中的安全性问题 。
题目是一个存储交易类合约,用户可以通过给合约发送 ether 实现将 ether 存储在合约中的目的。攻击者的目标就是将存储在这个合约里的所有 ether 全部取走。
合约的代码如下 :
pragma solidity ^0.4.23;contract BelluminarBank {
    struct Investment {
        uint256 amount;
        uint256 deposit_term;
        address owner;
    }
    Investment[] balances;
    uint256 head;
    address private owner;
    bytes16 private secret;
    function BelluminarBank(bytes16 _secret, uint256 deposit_term) public {
        secret = _secret;
        owner = msg.sender;
        if(msg.value > 0) {
            balances.push(Investment(msg.value, deposit_term, msg.sender));
        }
    }
    function bankBalance() public view returns (uint256) {
        return address(this).balance;
    }
    function invest(uint256 account, uint256 deposit_term) public payable {
        if (account >= head && account else {
            if(balances.length > 0) {
                require(deposit_term >= balances[balances.length - 1].deposit_term + 1 years);
            }
            investment.amount = msg.value;
            investment.deposit_term = deposit_term;
            investment.owner = msg.sender;
            balances.push(investment);
        }
    }
    function withdraw(uint256 account) public {
        require(now >= balances[account].deposit_term);
        require(msg.sender == balances[account].owner);
    msg.sender.transfer(balances[account].amount);
}
function confiscate(uint256 account, bytes16 _secret) public {
    require(msg.sender == owner);
    require(secret == _secret);
    require(now >= balances[account].deposit_term + 1 years);
    uint256 total = 0;
    for (uint256 i = head; i delete balances[i];
    }
    head = account + 1;
    msg.sender.transfer(total);
    }
}
可以看到合约的代码逻辑非常简单,invest 函数负责将传入的 ether 保存进 bank;withdraw函数负责取出保存的 ether;confiscate 函数可以将某些用户的账户全部转走。简单分析可以看出攻击者最终可以通过函数confiscate将所有的 ether 都转走。
经典的 Integer OverFLow
想要调用函数confiscate需要满足几个 require 条件,首先的一个就是要求require(now >= balances[account].deposit_term + 1 years); 调用时间必须在设定的 deposit_term 一年之后。这个条件没有对溢出进行判断,可以通过传入一个超长的 deposit_term 绕过判断。 这里展示了一个智能合约中典型的整数溢出问题。 EVM 使用的存储单位 uint256,即其中的栈、storage、memory 都是以 0×20 个字节为单位存储的,其可以表示的数据范围为 0 到 2^256 ,0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 可以看的其能够表示的范围是很大的,但是即使是这么大的范围依然存在溢出的可能性,当运算操作的结果大于 0×20 byte 所能表示的范围时,就会造成溢出导致判断失败。 在这个例子中 1 year 是 Solidity 语法中的时间单位,可以通过下面的单位换算转换成 uint :
1 years == 31536000
1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks == 7 days
1 years == 365 days
如果我们将 deposit_term 设置为 超过0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - 31536000 那么在计算 require(now >= balances[account].deposit_term + 1 years); 时就会产生溢出,导致加法运算的结果很小从而可以通过检测。 整数溢出问题在智能合约当中非常常见,合约的编写者或由于考虑不周或由于编写失误导致合约计算产生问题,进而影响整个合约。一个著名的例子 https://etherscan.io/address/0x55f93985431fc9304077687a35a1ba103dc1e081#code。 为了应对这问题有很多合约使用 SafeMath 来进行运算操作,一旦在运算过程中产生溢出便会回滚。但是依然有很多合约没有使用 SafeMath 或者使用了 SafeMath 但是忽略了某些操作。

[1] [2]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载