Unvalidated user-provided wrapped token address in pair registration enables binding legitimate tokens to malicious contracts
Token-pair registration is permissionless and accepts a user-provided wrappedToken address with no validation. An attacker can permanently bind a legitimate original token (e.g., USDC) to an attacker-controlled ERC20, drain all subsequent wraps, and block correct registration forever.
Description
Any user can register a token pair with a malicious wrapped token via
registerKeyPairWithTransfer, permanently binding a legitimate original
token (e.g., USDC or other not yet registered token) to an
attacker-controlled ERC20. This enables complete theft of all wrapped
funds for that token and permanently blocks the correct pair from ever
being registered.
Root Cause
Two missing safeguards in the token pair registration logic inside
registerKeyPairWithTransfer:
- No access control on token pair registration. The
newTokenPairsfield inRegisterKeyPairContextis processed for any caller during any key inception or rotation. There is noonlyOwnercheck. - User-provided wrapped token address is accepted without validation.
When
pair.wrappedToken != address(0)(line 494), the address is stored directly intotokenPairswithout verifying it was deployed by the bridge's beacon or that it enforcesonlyBridgeaccess control onmint/burn.
Vulnerable Scenario
Prerequisites:
- The target original token (e.g., USDC) has not yet been registered as a pair on the bridge.
- The attacker deploys a standard ERC20 with no access control on
mint()andburn().
- Register malicious pair. The attacker generates a fresh wallet and
calls
registerKeyPairWithTransferfor key inception, includingnewTokenPairsthat maps USDC to their malicious ERC20. No special privileges are required. - Pair is permanently locked. Any subsequent attempt to register USDC
with the correct wrapped token reverts with
TokenPairExists. There is no removal or update function. - Victim wraps real tokens. A legitimate user wraps USDC through the
bridge during their own key rotation. The bridge transfers real USDC
from the victim into itself and mints the malicious ERC20 (which has
no
onlyBridgerestriction) to the victim's next key. The victim receives worthless tokens. - Attacker mints fake tokens. The attacker freely mints any amount of
the malicious ERC20 to themselves, since
mint()has no access control. - Attacker drains real tokens. The attacker generates another fresh
wallet, does key inception with an
unwrappermit for the malicious token. The bridge burns the worthless malicious tokens and releases real USDC from its reserves to the attacker. The bridge is fully drained for the affected token.
Impact
- Fund theft. Attacker can drain 100% of the bridge's reserves for any affected token.
- Permanent denial of service. The legitimate token pair can never be registered without a contract upgrade.
Recommendation
Separate token pair registration from key rotation and enforce access control:
1. Extract token pair registration into an onlyOwner function:
function registerTokenPair(address originalToken,string memory tokenName,string memory tokenSymbol) external onlyOwner {if (tokenPairs[originalToken].wrappedToken != address(0)) revert TokenPairExists();bytes memory initData = abi.encodeWithSelector(WrappedToken.initialize.selector,tokenName,tokenSymbol,address(this),address(keyLogRegistry));WrappedTokenProxy proxy = new WrappedTokenProxy(wrappedTokenBeacon, initData);address wrappedToken = address(proxy);tokenPairs[originalToken] = TokenPairData(originalToken, wrappedToken);tokenPairs[wrappedToken] = TokenPairData(originalToken, wrappedToken);supportedOriginalTokens.push(originalToken);}
2. Remove newTokenPairs from RegisterKeyPairContext and
registerKeyPairWithTransfer.
3. Always deploy wrapped tokens via the beacon. Never accept a
user-provided wrappedToken address. This guarantees every wrapped
token enforces onlyBridge on mint/burn/transfer.
Additional Recommendations
- Add a
removeTokenPairorupdateTokenPairfunction so the owner can correct mistakes or respond to attacks. - Remove the
wrappedTokenfield from theTokenPairstruct passed by users, since the bridge should always deploy it.
Resolution
YadaCoin, Confirmed.
Zealynx, Fixed.

