ORT Token被攻击事件分析

  • 小帕菜
  • 更新于 2023-01-18 17:22
  • 阅读 335

攻击者利用StakingPool合约中没有对意料之外的值做处理,循环利用获得大量Token,最后通过Pancake砸盘获利。

基本信息

  • 黑客EOA地址:0x9bbd94506398a1459f0cd3b2638512627390255e
  • 攻击合约:0x0effeca3dbcbcda4d5e4515829b0d42181700606、0xdd87d807774c8aa9d70fc6af5912c97fadbf531b
  • 受攻击合约Proxy:0x6f40a3d0c89cffdc8a1af212a019c220a295e9bb
  • 受攻击合约:0x26bc1245b8476086e85553e60ee5e3e59fed9be0

攻击细节

攻击者部署了2个合约,2个攻击合约相似度高,以其中一个攻击合约为例,攻击者主要的攻击步骤为三步:

  1. 攻击者使用EOA账户去调用攻击合约的invest方法
  2. 攻击者使用EOA账户去调用攻击合约的Withdraw方法
  3. 攻击者使用EOA账户去调用攻击合约的WithdrawToken方法

20230118151005.png

20230118143557.png

分别对应选取一笔交易进行分析,先来看调用invest方法的这笔交易

https://bscscan.com/tx/0x63e8185c867de4c0d09b8df7813647d0aca0596a0e786cd8c6f4202053edc0bc

其实际调用了被攻击合约的invest方法: 20230118144553.png

参照对应的StakingPool中的invest合约代码,攻击者给形参end_date传入了0,给形参qty_ort传入了1,过了require(qty_ort > 0, "amount cannot be 0")的判断,之后由于end_date输入了意外之外的值0,直接过了if...eles if...,并调用内部_Check_reward方法开始计算奖励,并写入结构体中。

    function invest(uint256 end_date, uint256 qty_ort) external whenNotPaused {
        require(qty_ort > 0, "amount cannot be 0");
        //transfer token to this smart contract
        stakeToken.approve(address(this), qty_ort);
        stakeToken.safeTransferFrom(msg.sender, address(this), qty_ort);
        //save their staking duration for further use
        if (end_date == 3) {
            end_staking[msg.sender] = block.timestamp + 90 days;
            duration[msg.sender] = 3;
        } else if (end_date == 6) {
            end_staking[msg.sender] = block.timestamp + 180 days;
            duration[msg.sender] = 6;
        } else if (end_date == 12) {
            end_staking[msg.sender] = block.timestamp + 365 days;
            duration[msg.sender] = 12;
        } else if (end_date == 24) {
            end_staking[msg.sender] = block.timestamp + 730 days;
            duration[msg.sender] = 24;
        }
        //calculate reward tokens  for further use
        uint256 check_reward = _Check_reward(duration[msg.sender], qty_ort) /
            1 ether;
        //save values in array
        tokens_staking.push(
            staking(
                msg.sender,
                qty_ort,
                end_staking[msg.sender],
                duration[msg.sender],
                true,
                check_reward
            )
        );
        //save array index in map
        uint256 lockId = tokens_staking.length - 1;
        userStake[msg.sender].push(lockId);

        emit Invest(
            msg.sender,
            lockId,
            end_staking[msg.sender],
            duration[msg.sender],
            qty_ort,
            check_reward
        );
    }

跟进计算奖励的_Check_reward方法,内部写了一个if...else if...来计算奖励,但由于攻击者传入的end_date为0,因此duration[msg.sender]对应的值也为0,导致_Check_reward直接返回了total_percent,从Events事件可以看到total_percent值为8930620160000000000000000:

20230118145219.png

以上就是调用invest方法的分析,接着再来看下withdraw的这笔交易: https://bscscan.com/tx/0xd90f2ccfcd0fdbd002af7b21b51732a7a08d0bd3af2d298ec275062a11eb3019

20230118150126.png

实际是调用了被攻击合约的withdrawAndClaim方法,其会调用内部2个方法_withdraw_claim,需要传入的是lockId,lockId,在invest交易的Log日志中有输出。

    function withdrawAndClaim(uint256 lockId)
        external
        nonReentrant
        whenNotPaused
        onlyStakeOwner(lockId)
    {
        _withdraw(lockId);
        _claim(lockId);
    }

分析_withdraw方法其作用是用于领取之前质押进去的Token。

    function _withdraw(uint256 lockId) internal {
        require(tokens_staking[lockId].balances > 0, "not an investor");
        require(
            block.timestamp >= tokens_staking[lockId].end_staking,
            "too early"
        );
        //Change status of invester for reward
        uint256 invested_balance = 0;
        invested_balance = tokens_staking[lockId].balances;
        tokens_staking[lockId].changeClaimed = false;
        tokens_staking[lockId].balances = 0;
        tokens_staking[lockId].duration = 0;
        tokens_staking[lockId].end_staking = 0;
        //transfer back amount to user
        stakeToken.safeTransfer(msg.sender, invested_balance);

        emit Withdraw(msg.sender, lockId, invested_balance);
    }

分析_claim方法其作用是用于领取奖励。

    function _claim(uint256 lockId) internal {
        require(tokens_staking[lockId].check_claim > 0, "No Reward Available");
        require(
            tokens_staking[lockId].changeClaimed == false,
            "Already claimed"
        );
        //change status of invested user
        tokens_staking[lockId].changeClaimed = true;
        uint256 claimed_amount = 0;
        claimed_amount = tokens_staking[lockId].check_claim;
        tokens_staking[lockId].check_claim = 0;
        //mint new ort token
        rewardToken.mint(msg.sender, claimed_amount);

        emit Claim(msg.sender, lockId, claimed_amount);
    }

最后调用withdrawToken方法将攻击合约中的Token全部取出。

https://bscscan.com/tx/0x7aac05fecd19b19c28929b29262fb848eb00323d9f2b496d422e795fd9869f70

20230118150225.png

攻击者循环利用3步,将获得的Token通过Pancake砸盘获利。 https://bscscan.com/tx/0xa68442bd5ae35711e4e7f26d02787a3d2aed9db7084f48e3b7ed722a3487c629 20230118153355.png

参考链接

https://twitter.com/BeosinAlert/status/1615197760546037760

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
小帕菜

1 篇文章, 26 学分