Skip to content

Latest commit

 

History

History
71 lines (42 loc) · 2.13 KB

039.md

File metadata and controls

71 lines (42 loc) · 2.13 KB

Tall Burgundy Nightingale

High

Privilege Escalation Through Blacklist Token Seizure

Summary

The direct transfer of blacklisted user's tokens to msg.sender will cause complete loss of funds for users as any BLACKLISTER_ROLE can claim tokens for themselves, allowing malicious blacklisters to steal user funds through the blacklisting process.

Root Cause

https://github.com/sherlock-audit/2024-11-telcoin/blob/main/telcoin-audit/contracts/stablecoin/Stablecoin.sol#L123-L125

In Stablecoin.sol in the _onceBlacklisted function, the choice to send tokens to msg.sender instead of a controlled treasury or the contract itself is a mistake as it enables direct theft of user funds by any address with BLACKLISTER_ROLE.

Internal pre-conditions

User needs to have a token balance greater than 0

Attacker needs to have BLACKLISTER_ROLE

User must not be currently blacklisted

External pre-conditions

None

Attack Path

Malicious actor notices User A has 1,000,000 tokens

Malicious actor with BLACKLISTER_ROLE calls addBlackList(userA)

_onceBlacklisted is triggered

All 1,000,000 tokens are transferred directly to the malicious actor (msg.sender)

Malicious actor now owns all of User A's tokens

Malicious actor can immediately transfer these tokens to another address or sell them

Even if User A is later unblacklisted, their tokens remain stolen

Impact

Users suffer 100% loss of their token holdings when blacklisted. The attacker gains immediate ownership of all seized tokens, effectively turning the blacklist feature into a mechanism for direct theft by any address with BLACKLISTER_ROLE.

PoC

No response

Mitigation

Transfer to treasury or contract instead of msg.sender

contract Stablecoin is ERC20PermitUpgradeable, Blacklist {
    address public immutable TREASURY;
    
    function _onceBlacklisted(address user) internal override {
        uint256 balance = balanceOf(user);
        if(balance > 0) {
            // Transfer to treasury or contract instead of msg.sender
            _transfer(user, TREASURY, balance);
            // OR
            _transfer(user, address(this), balance);
        }
    }
}