skip to main content
hacking.communityplay in the sandbox.
ctf arena
hard/misc450 pts

The Ether Heist: Five Contract Chain

0 solves

You've discovered a DeFi protocol with five interconnected contracts deployed on a testnet. Your mission: drain all funds from the vault by exploiting reentrancy and access control flaws.

Contract Source Code (Solidity 0.8.0):

code
// Contract 1: Vault contract Vault { mapping(address => uint256) public balances; address public withdrawer; function deposit() external payable { balances[msg.sender] += msg.value; } function setWithdrawer(address _w) external { withdrawer = _w; } function withdraw(uint256 amount) external { require(msg.sender == withdrawer); (bool success,) = msg.sender.call{value: amount}(""); require(success); balances[msg.sender] -= amount; } } // Contract 2: Bridge contract Bridge { Vault vault; mapping(bytes32 => bool) usedNonces; constructor(address _vault) { vault = Vault(_vault); } function bridgeWithdraw(uint256 amount, uint256 nonce, bytes memory sig) external { bytes32 hash = keccak256(abi.encodePacked(amount, nonce)); require(!usedNonces[hash]); require(recoverSigner(hash, sig) == address(this)); usedNonces[hash] = true; vault.withdraw(amount); } function recoverSigner(bytes32 hash, bytes memory sig) internal pure returns (address) { (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); return ecrecover(hash, v, r, s); } function splitSignature(bytes memory sig) internal pure returns (uint8, bytes32, bytes32) { require(sig.length == 65); bytes32 r; bytes32 s; uint8 v; assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } return (v, r, s); } } // Contract 3: Adapter contract Adapter { Vault public vault; uint256 public processingFee = 0.01 ether; constructor(address _vault) { vault = Vault(_vault); } function updateFee(uint256 newFee) external { processingFee = newFee; } function processWithdrawal(address user, uint256 amount) external { require(vault.balances(user) >= amount); vault.setWithdrawer(address(this)); vault.withdraw(amount - processingFee); payable(user).transfer(amount - processingFee); } }

Initial State:

  • Vault holds 10 ETH total
  • Vault.balances[attacker] = 1 ETH
  • Vault.balances[Bridge] = 5 ETH
  • Vault.balances[Adapter] = 4 ETH
  • Bridge has pre-signed message: amount=5000000000000000000, nonce=1, signature=0x1234... (valid)

Exploit Requirements:

  1. Extract all 10 ETH from Vault
  2. The attack must chain at least 3 vulnerabilities
  3. Calculate the final attacker balance in wei

Vulnerabilities present:

  • Missing access control on setWithdrawer
  • Check-effects-interaction violation in withdraw
  • Missing signature EIP-712 domain in Bridge
  • Cross-function reentrancy between Adapter functions
  • No access control on updateFee

The flag is the final extracted amount in wei (as decimal string). If you drain exactly 10 ETH, the flag would be the wei value.

// no hints provided for this challenge
// reward at submit: 450 pts · paste the flag exactly as found
// install app

Install hacking.community for fast access, offline reading, and push notifications. No app store needed.