Skip to content
#11 largest Access control 2017

The Parity Multisig Wallet hack — ~$150M lost

Loss~$150M
DateJul & Nov 2017
ChainEthereum
Failure classAccess control
In assets153,037 ETH stolen · 513,774 ETH frozen
Targetthe same bug, twice
1

What happened

Parity's wallets were thin proxies: any call they did not recognise was forwarded to one shared library with delegatecall, meaning the library's code ran against your wallet's storage. The library's initWallet had no guard against being called twice, so anyone could re-initialize any wallet and make themselves the sole owner. In July, 153,037 ETH left three ICO wallets. The replacement library shipped the next day still had no guard — and in November a user called initWallet on the library itself, became its owner, and called kill(). Every wallet built on it now delegates into an empty address. 513,774 ETH have been unreachable ever since.

2

How the attack ran

  1. Call the wallet’s fallbackAnything unmatched is delegatecalled into the library
  2. Call initWallet([me], 1)Re-initialise it and become the only owner
  3. No initializer guard — on the library eitherSo the library itself can be claimed, then kill()ed
  4. Stolen, then bricked153,037 ETH taken · 513,774 ETH frozen forever
3

The code

Wallet.sol + WalletLibrary.sol
// Wallet.sol — anything unmatched runs in OUR storage
function() payable {
    if (msg.value > 0) Deposit(msg.sender, msg.value);
    else if (msg.data.length > 0) _walletLibrary.delegatecall(msg.data);
}

// WalletLibrary.sol
function initWallet(address[] _owners, uint _required, uint _daylimit) {
    initDaylimit(_daylimit);
    initMultiowned(_owners, _required);
    // ❌ no modifier. no "already initialised" flag. public to the world.
}

// July 2017 — against a WALLET:
  wallet.initWallet([attacker], 1, ...)   → sole owner → execute() → 153,037 ETH
// November 2017 — against the LIBRARY:
  library.initWallet([anon], 1, ...)      → owner of the library
  library.kill(anon)                     → SELFDESTRUCT → 513,774 ETH frozen forever
4

What would have caught it

What an audit looks for: every initializer needs an initializer modifier, and every logic/library contract needs to be initialized at deployment so nobody else can claim it. A delegatecall fallback turns every public function in the library into a public function of your wallet — including the ones you forgot about.
6

Sources

Every figure on this page comes from the post-mortems above, not from us. Losses are US dollars at the time of the incident.

Check your own contract for this

Access control is one of the 203 classes the SaferICO scanner checks for. It will not review your signing process — but it will read your Solidity.

Run the scanner See how it is attacked Read the docs