Skip to content

Micro Neon Beaver - Blacklisted Users Can Receive mTokens Through Mint-on-Behalf #1471

@sherlock-admin3

Description

@sherlock-admin3

Micro Neon Beaver

Medium

Blacklisted Users Can Receive mTokens Through Mint-on-Behalf

Summary

The blacklist check in beforeMTokenMint only validates the minter, not the receiver, allowing blacklisted users to receive mTokens through third-party minting

Root Cause

In src/Operator/Operator.sol:589, the beforeMTokenMint() function only checks ifNotBlacklisted(minter), but the actual mToken recipient is the receiver parameter passed to __mint() at src/mToken/mToken.sol:716.

Operator.sol - Only checks minter:

function beforeMTokenMint(address mToken, address minter)
    external view override 
    onlyAllowedUser(minter) 
    ifNotBlacklisted(minter)  // BUG: Doesn't check receiver
{
    // ...
}

mToken.sol - Receiver gets the tokens:

function __mint(address minter, address receiver, uint256 mintAmount, ...) {
    IOperatorDefender(operator).beforeMTokenMint(address(this), minter);
    // ...
    
    // RECEIVER gets the mTokens, not minter
    accountTokens[receiver] = accountTokens[receiver] + mintTokens;
    emit Transfer(address(this), receiver, mintTokens);
}

Note: mintExternal() is not vulnerable as it overwrites receiver.

Internal Pre-conditions

  1. User needs to be blacklisted by the protocol
  2. Another non-blacklisted user needs to call mint() with blacklisted user as receiver

External Pre-conditions

None

Attack Path

  1. Alice gets blacklisted for malicious activity
  2. Alice cannot directly call mint() due to blacklist check on minter
  3. Bob (non-blacklisted) calls mToken.mint(amount, Alice, minOut)
  4. beforeMTokenMint() only checks Bob's blacklist status
  5. Alice successfully receives mTokens: accountTokens[receiver] = accountTokens[receiver] + mintTokens
  6. Alice now holds mTokens despite being blacklisted

Impact

Blacklisted users can bypass restrictions and keep using the protocol through other users, breaking the blacklist system.

  • Blacklisted users get mTokens through third-party minting
  • They cannot transfer mTokens due to blacklist check in beforeMTokenTransfer()
  • But they still earn MALDA rewards based on their mToken balance
  • They can claim all earned MALDA rewards since claimMalda() has no blacklist check
  • Blacklist only blocks direct actions but not reward earning and claiming

PoC

// Add to test/unit/mErc20/mErc20_mint.t.sol

function test_BlacklistBypass_MintOnBehalf() external whenMarketIsListed(address(mWeth)) {
    address alice = address(0xa11ce);
    address bob = address(0xb0b);
    
    // Setup: Give Bob tokens
    _getTokens(weth, bob, LARGE);
    vm.prank(bob);
    weth.approve(address(mWeth), LARGE);
    
    // Blacklist Alice
    blacklister.blacklistUser(alice);
    assertTrue(blacklister.isBlacklisted(alice));
    
    // Alice cannot mint directly
    vm.prank(alice);
    vm.expectRevert(OperatorStorage.Operator_UserBlacklisted.selector);
    mWeth.mint(SMALL, alice, SMALL);
    
    // But Bob can mint FOR Alice
    uint256 aliceBalanceBefore = mWeth.balanceOf(alice);
    
    vm.prank(bob);
    mWeth.mint(SMALL, alice, SMALL); // receiver = alice
    
    uint256 aliceBalanceAfter = mWeth.balanceOf(alice);
    
    // Alice received mTokens despite being blacklisted
    assertGt(aliceBalanceAfter, aliceBalanceBefore);
    assertGt(aliceBalanceAfter, 0);
}

Mitigation

Check both minter and receiver in beforeMTokenMint():

function beforeMTokenMint(address mToken, address minter, address receiver) 
    external override 
    onlyAllowedUser(minter) 
    ifNotBlacklisted(minter) 
+   ifNotBlacklisted(receiver)
{
    // ...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions