Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: medium

Unprotected Internal _mint() Exposure in Derived Contracts

Author Revealed upon completion

Root + Impact

Description

  • The _mint() function in Token-0x's base implementation is correctly marked as internal, requiring derived contracts to implement proper access control when exposing minting functionality. However, derived contracts can expose this function publicly without any access controls, allowing anyone to mint unlimited tokens.


  • This creates a critical vulnerability when developers inherit from Token-0x without implementing proper access control patterns.

// Base ERC20Internals.sol - correctly internal
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0xec442f05))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
// ... minting logic
}
}
// Vulnerable derived contract - no access control
contract VulnerableToken is ERC20 {
constructor() ERC20("Vuln", "V") {}
@> function mint(address to, uint256 amount) external { _mint(to, amount); }
}

Risk

Likelihood:

  • Any derived contract that exposes _mint() publicly without access controls will be vulnerable

  • Developers inheriting from Token-0x may not realize they need to implement access control

  • The vulnerability manifests immediately upon deployment of such contracts

Impact:

  • Unlimited token minting by anyone, completely destroying token economics

  • Total loss of value for all legitimate token holders

  • Protocol failure and potential financial losses

Proof of Concept

The test demonstrates that a contract exposing _mint() without access control allows any address to mint unlimited tokens. The attacker address successfully mints 1 million tokens without any restrictions.

/ Added to test/Token.t.sol
contract VulnerableToken is ERC20 {
constructor() ERC20("Vuln", "V") {}
function mint(address to, uint256 amount) external { _mint(to, amount); }
}
function test_UnauthorizedMintVulnerability() public {
VulnerableToken baseToken = new VulnerableToken();
address attacker = makeAddr("attacker");
// Anyone can call mint() - no access control
vm.prank(attacker);
baseToken.mint(attacker, 1000000e18);
// Attacker successfully minted tokens
assertEq(baseToken.balanceOf(attacker), 1000000e18);
assertEq(baseToken.totalSupply(), 1000000e18);
}

Recommended Mitigation

Derived contracts must implement proper access control when exposing minting functions. The base contract itself is secure - the responsibility lies with implementers.

// Secure derived contract pattern
contract SecureToken is ERC20 {
address public immutable owner;
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner");
_;
}
+ function mint(address to, uint256 amount) external onlyOwner {
+ require(to != address(0), "Mint to zero");
+ require(amount > 0, "Zero amount");
_mint(to, amount);
}
}

Support

FAQs

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

Give us feedback!