class: middle, title-slide # Smart Contract Vulnerabilities:
Does Anyone Care? ## Daniel Perez and Benjamin Livshits ### Smart Contracts Security Technical Workshop
July 13th 2019 #### Slides available at: http://bit.ly/eth-contracts-vulns --- class: larger-fonts # Overview .spaced-list[ 1. Background * Smart contracts basics * Common vulnerabilities 2. Overview of the research * Data used * Finding exploits in the wild 3. Takeaways and discussion 4. Questions ] --- class: middle, title-slide # Background ## A word about smart contracts and potential vulnerabilities --- # Ethereum Smart Contracts .pull-left-50[ .spaced-list[ * Programs deployed on the Ethereum blockchain * Usually written in Solidity, compiled into EVM bytecode * Can transfer money to other addresses (including contracts) * Each instruction execution consumes gas ] ] .pull-right-50[![Smart Contracts](./smart-contracts.png)] --- # Solidity .pull-left-40[ * High-level language targeting the EVM * Looks vaguely like JavaScript * Strongly typed, with a fairly simple type-system * Contains smart contract related primitives ] .pull-right-60[ ```solidity contract Coin { address public minter; mapping (address => uint) public balances; constructor() public { minter = msg.sender; } function mint(address receiver, uint amount) public { require(msg.sender == minter); require(amount < 1e60); balances[receiver] += amount; } function send(address receiver, uint amount) public { require(amount <= balances[msg.sender]); balances[msg.sender] -= amount; balances[receiver] += amount; } } ``` .caption[ Contract implementing a simple coin ] ] --- # Ethereum Virtual Machine .pull-left-70[ * Stack-based virtual machine * Very low-level * No functions/block, only jumps * No types * Has regular VM instructions * `ADD`, `SUB`, `PUSH`, `POP` * Has instructions to interact with environment * `SENDER`, `CALL`, `BALANCE` * Has both ephemeral and permanent storage * Uses 256-bits words ] .pull-right-30[ ``` PUSH1 0x00 PUSH1 0x00 MSTORE JUMPDEST PUSH1 0x0a PUSH1 0x00 MLOAD PUSH1 0x01 ADD DUP1 PUSH1 0x00 MSTORE LT PUSH1 0x05 JUMPI ``` .caption[ Simple loop from 0 to 10 using EVM instructions ] ] --- # Smart Contracts: What could go wrong? .pull-left-50[ ## TheDAO hack (2016) .spaced-list[ * TheDAO raised *~$150M in ICO* * Soon after, it got hacked *~$50M* * Price of Ether halved * Ethereum community decided to hard-fork * Attacker used a *re-entrancy* vulnerability ] ] .pull-left-50[ ## Parity Wallet bug (2017) .spaced-list[ * Parity wallet library was used to manage multisig wallet contracts * Parity *wallet* has been *removed* due to a "bug" * *Dependent contracts* became unable to send funds * Around *$280M frozen* ] ] --- # Common vulnerabilities / bugs .spaced-list[ * Re-entrancy * Can allow an attacker to drain funds * Unhandled exceptions * Can result in lost funds * Dependency on destructed contract * Can result in locked funds * Transaction order dependency * Can allow an attacker to manipulate prices * Integer overflow * Can result in locked fund ] --- # Re-entrancy .pull-left-50[ * Vulnerable contract sends money before updating state * Attacker contract’s fallback function is called * Attacker contract makes re-entrant call to attacker ] .pull-right-50[ ```solidity function payMe(address account) public { uint amount = getAmount(account); // XXX: vulnerable if (!account.send(amount)) throw; balance[account] -= amount; } ``` .caption[ Vulnerable contract ] ```solidity function () { victim.payMe(owner); } ``` .caption[ Attacker contract ] ] --- # Unhandled exception .pull-left-50[ * In Solidity, not all failed "calls" raise an exception * If the failed call returns a boolean, it must be checked correctly * Failure to do this could result in inconsistent state or even locked funds ] .pull-right-50[ .middle-block[ ```solidity // allows user to withdraw funds function withdraw(address account) public { uint amount = getAmount(account); balance[account] -= amount; account.send(amount); // could silently fail } ``` .caption[ Problematic contract ] ] ] --- # Dependency on destructed contract .pull-left-40[ * Contracts can use other contracts as library * If the library contract gets destructed, the call becomes a no-op * If the only way for a contract to send money is to use the library, Ether can be locked ] .pull-right-60[ ```solidity function sendEther(address recipient, uint value) public onlyOwner { address.send(value) } ``` .caption[ Library contract ] ```solidity address public library = 0xdcc703c0E500B653Ca82273B7BFAd8045D85a470; function sendEther(address recipient, uint value) public { bytes4 sig = bytes4(sha3("sendEther(address, uint)")); library.delegatecall(sig, recipient, value); } ``` .caption[ Contract using library ] ] --- # Transaction Order Dependency .pull-left-50[ * Result can change depending on the order of the transactions * Miners are free to choose the miner order of the transactions in a block * There can be financial incentives to perform such manipulations ] .pull-right-50[ ```solidity function solve() { if (msg.sender == owner) {// update reward owner.send(reward); reward = msg.value; } else if (puzzleSolved(msg.data)) { msg.sender.send(reward); //send reward solution = msg.data; } } ``` .caption[ Contract vulnerable to transaction order dependency ] ] --- # Integer Overflow .pull-left-50[ .spaced-list[ * Solidity has many different numeric types * `int8` to `int256` and `uint8` to `uint256` * Types are encoded in EVM using bit manipulations * If `a` is `uint8`, a `AND a 0xff` would be generated * Variables may therefore overflow or underflow during execution ] ] .pull-right-50[ .middle-block[ ```solidity function overflow(uint fee) { uint amount = 100; // underflows if fee > 100 amount -= fee; // tries to send a large value // and fails on underflow msg.sender.send(amount); } ``` .caption[ Contract vulnerable to integer overflow ] ] ] --- # Smart Contract analysis tools .pull-left-40[ * Usually static analysis and/or symbolic execution * Work either on Solidity or on the EVM bytecode * Check for known vulnerabilities/patterns ] .pull-right-60[ ![Securify](./securify.png) .caption[ Securify web interface
https://securify.chainsecurity.com ] ] --- # Smart Contract exploitation .pull-left-50[ ## According to analysis tools * Thousands of vulnerable contracts * Hundreds of millions USD at risk ] .pull-right-50[ ## In the real-world * TheDAO and Parity wallet bug were impressive * Otherwise, a few attacks here and there ]
⇒ We analyze the Ethereum blockchain to look for *actual exploitation* --- class: middle, title-slide # Overview of the Research ## https://arxiv.org/abs/1902.06710 --- # Dataset .pull-left-40[ * Data received from the paper authors * Received replies from 5 out of 8 of the authors we contacted * Ether at stake computed at time of the report * Total of around *3M ETH at stake* ] .pull-right-60[ .right[ .left[Name] | Contracts analyzed | Vulnerabilities found | Ether at stake ---------------------|--------------------|-----------------------|--------------- .left[Oyente] | 19,366 | 7,527 | 1,289,177 .left[Zeus] | 1,120 | 855 | 729,376 .left[Maian] | NA | 2,691 | 14.13 .left[Securify] | 29,694 | 9,185 | 719,567 .left[MadMax] | 91,800 | 6,039 | 1,114,692 .caption[ Dataset overview ] ] ] --- # Detection overview .pull-left-40[ 1. Retrieve all transactions 2. Retrieve execution traces for all transactions 3. Encode execution traces to Datalog 4. Query Datalog for vulnerabilities ] .pull-right-60[![Smart Contracts](./system-overview.png)] --- # Checking for re-entrancy .pull-left-50[ 1. Record all calls 2. Check for mutually recursive calls between contracts
```prolog call_flow(A, B) :- call(A, B). call_flow(A, B) :- call(A, C), call_flow(C, B). reentrant_call(A, B) :- call_flow(A, B), call_flow(B, A), A != B. ``` .caption[ Datalog rules ] ] .pull-right-50[ ```solidity function payMe(address account) public { uint amount = getAmount(account); // XXX: vulnerable if (!account.send(amount)) throw; balance[account] -= amount; } ``` .caption[ Vulnerable contract ] ```solidity function () { victim.payMe(owner); } ``` .caption[ Attacker contract ] ] --- # Checking for unhandled exceptions .pull-left-50[ 1. Record all call results * Top value on the stack after a call 2. Check if the return value is used in a condition (`JUMPI`) ```prolog influences_condition(A) :- used_in_condition(A). influences_condition(A) :- depends(B, A), used_in_condition(B). unhandled_exception(A) :- failed_call(A), ~influences_condition(A). ``` .caption[ Datalog rules ] ] .pull-right-50[ .middle-block[ ```solidity // allows user to withdraw funds function withdraw(address account) public { uint amount = getAmount(account); balance[account] -= amount; account.send(amount); // could silently fail } ``` .caption[ Problematic contract ] ] ] --- # Checking for dependency on destructed contract 1. Find `DELEGATECALL` instructions 2. Check the program counter after the call 3. If the PC was incremented by 1, the call was a no-op .center-block.width-50[ ```prolog empty_delegate(A) :- call_entry(V, A), call_exit(V, V2), successor(V2, V). ``` .caption[ Datalog rule ] ] --- # Checking for transaction order dependency .pull-left-50[ 1. Find transactions in the same block 2. Check if two calls read and write to the same storage key ```prolog tod(Block, Tx, Tx2, Idx) :- tx_sstore(Block, Tx, Idx), tx_sload(Block, Tx2, Idx), Tx != Tx2. ``` .caption[ Datalog rule ] ] .pull-right-50[ ```solidity function solve() { if (msg.sender == owner) {// update reward owner.send(reward); reward = msg.value; } else if (puzzleSolved(msg.data)) { msg.sender.send(reward); //send reward solution = msg.data; } } ``` .caption[ Contract vulnerable to transaction order dependency ] ] --- # Checking for integer overflow .pull-left-50[ 1. Infer variable types from bytecode * `SIGNEXTEND` means the variable is signed * `AND n 0xff` means it is a `uint8` 2. Compare typed and untyped results .italic[The approach can lead to some false-positives] ] .pull-right-50[ .middle-block[ ```solidity function overflow(uint fee) { uint amount = 100; amount -= fee; msg.sender.send(amount); } ``` .caption[ Contract vulnerable to integer overflow ] ] ] --- # Detection results .right.results-table[ .left[Vulnerability] | Vulnerable contracts | Total Ether at Stake | Contracts exploited (with Ether) | Exploited Ether | % of Exploited Ether --------------|----------------------|----------------------|----------------------------------|-----------------|--------------------- .left[Re-entrancy] | 4,336 | 1,027,585 | 113 (6) | 6,075 | **0.59%** .left[Unhandled Exception] | 11,426 | 208,528 | 268 (6) | 169 | **0.081%** .left[Destructed contract dep.] | 7,271 | 1,135,313 | 0 (0) | 0 | **0%** .left[Transaction order dependency] | 1,877 | 207,926 | 57 (14) | 189 | **0.0091%** .left[Integer overflow] | 2,472 | 508,750 | 141 (23) | 2,661 | **0.52%** .left[Total] | 21,270 | 3,088,102 | 504 (49) | 9,094 | **0.30%** ] In total, *at most 0.30%* of the 3M Ether at stake has been *exploited* --- class: middle, title-slide # Takeaways and conclusions --- # Takeaways * **Re-entrancy** seems to be the *most dangerous* issue. Good target for static analysis. Better prevention at the EVM level could be helpful. * **Unhandled exceptions** are *handled well enough* by current tooling. * **Dependency on destructed contract** is mostly a *non-issue*. * **Transactions order dependency** seems to be *very rare* in practice * **Integer overflow** is hard to prevent and consequences *hard to predict*. Also good target for static analysis. --- # Contracts wealth distribution .pull-left-50[ * Combined value: *~2 million ETH* * Only *~2,000* out of 21,270 contracts *held Ether* * Less than 100 contracts had more than 10 Ether * The *top 6 contracts held 83%* of the total Ether ] .pull-right-50[ ![Cumulative distribution of Ether](./cumulative-ether.png) .caption[ Cumulative Ether held in contracts holding more than 10 ETH ] ] --- # Vulnerable but not exploitable .pull-left-50[ * Many cases where "vulnerable" contracts are not exploitable * High-value contracts flagged vulnerable are typically not exploitable * Usually limitation due to the nature of static analysis ] .pull-right-50[ ```solidity function removeOwner(address owner) onlyWallet { isOwner[owner] = false; // Could in Theory run out of gas for (uint i=0; i
350K ETH at address .small[`0x7da82C7AB4771ff031b66538D2fB9b0B047f6CF9`] ] ] --- class: middle # Summary * Analyzed 21k contracts with *3M Ether at risk* * *At most 0.3%* of this Ether, less than 10k ETH, was *exploited* * Overall, *high-value contracts* seem to be quite *secure*