一、前言

在上一篇文章中,我们详细地讲述了solidity中的整数溢出漏洞。而在本文中,我们将重点放在真实事件中,从0开始针对某些真实环境中的CVE进行复现操作。一步一步带领大家去了解合约内部的秘密。

本文中涉及的漏洞内容均为整数溢出的CVE,我们会对源代码进行详细的分析,并在分析漏洞的过程中讲述合约的搭建内容。

二、cve漏洞介绍

这次分析漏洞CVE-2018-11811。由于上次的文章我们详细的讲述了整数溢出漏洞的原理以及一些合约实例。所以这次我们趁热打铁,针对真实生产环境中的cve漏洞进行分析、复现。

这个漏洞是于今年6月12日由清华-360企业安全联合研究中心的张超教授团队披露出来的,安比(SECBIT)实验室针对这些漏洞,对以太坊上已部署的23357个合约进行了分析检测,发现共有866个合约存在相同问题。而今天我们讲述的这个漏洞共影响了288个智能合约。

合约源码如下:https://etherscan.io/address/0x0b76544f6c413a555f309bf76260d1e02377c02a

在详细讲述合约漏洞前,我们将合约的具体内容交代清楚。

这个合约发行了一套自己的token,并且可以用以太币对这些token进行交换。即包括:用户可以使用以太币兑换token,也可以出售token去获得以太币。而具体实现的函数如下:

这是owner对购买与卖出价格进行设定的函数:

function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
  sellPrice = newSellPrice;
  buyPrice = newBuyPrice;
}

下面是用户对token进行购买的函数:

function buy() payable {
  uint amount = msg.value / buyPrice;               // calculates the amount
  _transfer(this, msg.sender, amount);              // makes the transfers
}

这是用户出售token去换取以太币的函数:

function sell(uint256 amount) {
  require(this.balance >= amount * sellPrice);      // checks if the contract has enough ether to buy
  _transfer(msg.sender, this, amount);              // makes the transfers
  msg.sender.transfer(amount * sellPrice);          // sends ether to the seller. It's important to do this last to avoid recursion attacks
}

而问题就出在上面的三个函数中。最终达成的效果就是我平台的搭建者可以令用户用高价购买token却在卖出的时候以很便宜的价格卖出。从而达成对平台使用者的欺诈行为。

三、cve详细分析

1 合约详解

这个合约是多继承于其他合约而部署的。所以在分析的时候我们要有针对性。

首先,我们需要看主要的合约内容:

contract INTToken is owned, token {
  uint256 public sellPrice;
  uint256 public buyPrice;

  mapping (address => bool) public frozenAccount;

  /* This generates a public event on the blockchain that will notify clients */
  event FrozenFunds(address target, bool frozen);

  /* Initializes contract with initial supply tokens to the creator of the contract */
  function INTToken(
      uint256 initialSupply,
      string tokenName,
      uint8 decimalUnits,
      string tokenSymbol
  ) token (initialSupply, tokenName, decimalUnits, tokenSymbol) {}

  /* Internal transfer, only can be called by this contract */
  function _transfer(address _from, address _to, uint _value) internal {
      require (_to != 0x0);                               // Prevent transfer to 0x0 address. Use burn() instead
      require (balanceOf[_from] > _value);                // Check if the sender has enough
      require (balanceOf[_to] + _value > balanceOf[_to]); // Check for overflows
      require(!frozenAccount[_from]);                     // Check if sender is frozen
      require(!frozenAccount[_to]);                       // Check if recipient is frozen
      balanceOf[_from] -= _value;                         // Subtract from the sender
      balanceOf[_to] += _value;                           // Add the same to the recipient
      Transfer(_from, _to, _value);
  }

  /// @notice Create `mintedAmount` tokens and send it to `target`
  /// @param target Address to receive the tokens
  /// @param mintedAmount the amount of tokens it will receive
  function mintToken(address target, uint256 mintedAmount) onlyOwner {
      balanceOf[target] += mintedAmount;
      totalSupply += mintedAmount;
      Transfer(0, this, mintedAmount);
      Transfer(this, target, mintedAmount);
  }

  /// @notice `freeze? Prevent | Allow` `target` from sending & receiving tokens
  /// @param target Address to be frozen
  /// @param freeze either to freeze it or not
  function freezeAccount(address target, bool freeze) onlyOwner {
      frozenAccount[target] = freeze;
      FrozenFunds(target, freeze);
  }

  /// @notice Allow users to buy tokens for `newBuyPrice` eth and sell tokens for `newSellPrice` eth
  /// @param newSellPrice Price the users can sell to the contract
  /// @param newBuyPrice Price users can buy from the contract
  function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
      sellPrice = newSellPrice;
      buyPrice = newBuyPrice;
  }
点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖