Solidity Pitfalls and Hazards Part Two CS 1951

  • Slides: 60
Download presentation
Solidity Pitfalls and Hazards (Part Two) CS 1951 L Spring 2019 26 February 2019

Solidity Pitfalls and Hazards (Part Two) CS 1951 L Spring 2019 26 February 2019 Maurice Herlihy Brown University

Today’s Pitfalls: Default Visibility False Randomness Unchecked return values Parameter Attack 2

Today’s Pitfalls: Default Visibility False Randomness Unchecked return values Parameter Attack 2

Default Visibility Attack External: only from outside Private: only current not derived contracts Internal:

Default Visibility Attack External: only from outside Private: only current not derived contracts Internal: only current or derived contracts Publ ic: anyone can call 3

Default Visibility Attack External: only from outside Private: only current not derived contracts Internal:

Default Visibility Attack External: only from outside Private: only current not derived contracts Internal: only current or derived contracts Public: anyone can call default 4

Fallback Function Remember, transaction data includes: Function selector (hashed name) Function arguments 5

Fallback Function Remember, transaction data includes: Function selector (hashed name) Function arguments 5

Fallback Function contract Example { uint x = 0; function foo() public { x

Fallback Function contract Example { uint x = 0; function foo() public { x = 1; } function () public payable { x = 2; } } 6

Fallback Function contract Example { uint x = 0; function foo() public { x

Fallback Function contract Example { uint x = 0; function foo() public { x = 1; } function () public payable { x = 2; } } named regular function 7

Fallback Function contract Example { uint x = 0; function foo() public { x

Fallback Function contract Example { uint x = 0; function foo() public { x = 1; } function () public payable { x = 2; } } named regular function nameless fallback function Often used to receive payments 8

Function Call Contract A Contract Bnot me! call “bar” fn foo() fn bar() me!

Function Call Contract A Contract Bnot me! call “bar” fn foo() fn bar() me! fn () A calls B’s “bar” function … 9

Fallback Function Contract A Contract B call. value(100) fn foo() fn bar() fn ()

Fallback Function Contract A Contract B call. value(100) fn foo() fn bar() fn () I handle pure ETH transfers A sends ETH to B 10

Wallet Library Does most of the work Delegatecall: updates wallet internal state only Wallet

Wallet Library Does most of the work Delegatecall: updates wallet internal state only Wallet Library fn transfer() fn credit() fn init () Also, init() function called internally by constructor to initialize owners, etc. 11

Wallet Library Security Audit contract Wallet. Library { address owner; // called by constructor

Wallet Library Security Audit contract Wallet. Library { address owner; // called by constructor function init. Wallet(address _owner) { owner = _owner; //. . . more setup. . . } … } 12

Wallet Library Security Audit contract Wallet. Library { address owner; // called by constructor

Wallet Library Security Audit contract Wallet. Library { address owner; // called by constructor function init. Wallet(address _owner) { owner = _owner; //. . . more setup. . . } … } Initialization code called only by constructor. 13

Wallet Library Security Audit contract Wallet. Library { … function change. Owner(address _new_owner) external

Wallet Library Security Audit contract Wallet. Library { … function change. Owner(address _new_owner) external { if (msg. sender == owner) { owner = _new_owner; } } } Owner can be changed only by current owner 14

Wallet Library Security Audit contract Wallet. Library { … function () payable { //.

Wallet Library Security Audit contract Wallet. Library { … function () payable { //. . . receive money, log events, . . . } } Anyone can deposit money in wallet 15

Wallet Library Security Audit contract Wallet. Library { … function withdraw(uint amount) external returns

Wallet Library Security Audit contract Wallet. Library { … function withdraw(uint amount) external returns (bool success) { if (msg. sender == owner) { return owner. send(amount); } else { return false; } } Only owner can withdraw money 16

Composite Wallet call “transfer” fn transfer() Wallet Library fn transfer() fn credit() … fn

Composite Wallet call “transfer” fn transfer() Wallet Library fn transfer() fn credit() … fn init () fn () Most calls just forwarded to library 17

Wallet Fallback Function function() payable { // just being sent some cash? if (msg.

Wallet Fallback Function function() payable { // just being sent some cash? if (msg. value > 0) Deposit(msg. sender, msg. value); else if (msg. data. length > 0) _wallet. Library. delegatecall(msg. data); } 18

Wallet Fallback Function function() payable { Mostly // just being sent some cash? used

Wallet Fallback Function function() payable { Mostly // just being sent some cash? used to if (msg. value > 0) send Deposit(msg. sender, msg. value); cash to else if (msg. data. length > 0) wallet _wallet. Library. delegatecall(msg. data); } 19

Wallet Fallback Function function() payable { // just being sent some cash? if (msg.

Wallet Fallback Function function() payable { // just being sent some cash? if (msg. value > 0) Deposit(msg. sender, msg. value); else if (msg. data. length > 0) _wallet. Library. delegatecall(msg. data); } If not cash deposit, just forward message to library … 20

Wallet Fallback Function All public functions in library callable by anyone! function() payable {

Wallet Fallback Function All public functions in library callable by anyone! function() payable { // just being sent some cash? if (msg. value > 0) Deposit(msg. sender, msg. value); else if (msg. data. length > 0) _wallet. Library. delegatecall(msg. data); } If not cash deposit, just forward message to library … 21

Uh, Oh contract Wallet. Library { address owner; // called by constructor function init.

Uh, Oh contract Wallet. Library { address owner; // called by constructor function init. Wallet(address _owner) { owner = _owner; //. . . more setup. . . } … } Oh wait, where is this function’s visibility declared? It isn’t. It has default public visibility! 22

This Happened Someone exploited exactly this opening Started pumping money out … “White hat”

This Happened Someone exploited exactly this opening Started pumping money out … “White hat” hackers notice, exploit same opening to pump money out to save victims Final Score Black hats: 153 K ETH, White Hats: 377 K ETH 23

Prevention Solidity now requires visibility declarations to be explicit Moral Designing languages for mission-critical

Prevention Solidity now requires visibility declarations to be explicit Moral Designing languages for mission-critical apps is not for amateurs. 24

False Randomness Attack Gambling is very popular in Ethereum Gambling requires randomness Blockchains require

False Randomness Attack Gambling is very popular in Ethereum Gambling requires randomness Blockchains require determinism Check your wallet 25

What are the Chances? Clients bet or RED or BLACK Last block hash even:

What are the Chances? Clients bet or RED or BLACK Last block hash even: RED Last block hash odd: BLACK What could go wrong? 26

Mining bet on RED 27

Mining bet on RED 27

Mining Try nonce = 1 bet on RED Try nonce = 2 Try nonce

Mining Try nonce = 1 bet on RED Try nonce = 2 Try nonce = 3 Eureka! I mined a block! But hash is odd Suppress that block, try again … 28

Examined 3, 649 smart contracts 78 unique PNRG implementations 43 identified as vulnerable A.

Examined 3, 649 smart contracts 78 unique PNRG implementations 43 identified as vulnerable A. Reutov, Predicting Random Numbers in Ethereum Smart Contracts 29

Block Variables block. number block. coinbase block. gaslimit block. difficulty block. timestamp Not just

Block Variables block. number block. coinbase block. gaslimit block. difficulty block. timestamp Not just miners can predict … Exploit contract makes delegatecall to target contract, gets same “random” values 30

Hash of Current Block function random(uint 64 upper) public returns (uint 64 random. Number)

Hash of Current Block function random(uint 64 upper) public returns (uint 64 random. Number) { _seed = uint 64( sha 3( blockhash(block. number), _seed), now)); return _seed % upper; } 31

Hash of Current Block function random(uint 64 upper) public returns (uint 64 random. Number)

Hash of Current Block function random(uint 64 upper) public returns (uint 64 random. Number) { _seed = uint 64( sha 3( blockhash(block. number), _seed), now)); return _seed % upper; } start with hash of current block … 32

Hash of Current Block function random(uint 64 upper) public returns (uint 64 random. Number)

Hash of Current Block function random(uint 64 upper) public returns (uint 64 random. Number) { _seed = uint 64( sha 3( blockhash(block. number), _seed), now)); return _seed % upper; } … hash it a few more times something is wrong here … 33

Hash of Current Block block hash of block being created is function random(uint 64

Hash of Current Block block hash of block being created is function random(uint 64 upper) public returns (uint 64 random. Number) { (obviously) unknown to EVM _seed = uint 64( sha 3( blockhash(block. number), _seed), now)); return _seed % upper; } This expression always evaluates to 0! 34

Hash of Earlier Block blockhash(block. number - 1) Attacker can delegatecall contract to predict

Hash of Earlier Block blockhash(block. number - 1) Attacker can delegatecall contract to predict its “random” number 35

Hash of Future Block Let’s play a game Player makes bet, house stores transaction’s

Hash of Future Block Let’s play a game Player makes bet, house stores transaction’s block. number Player later requests house announce winning number House computes blockhash from saved block. number, then generates pseudo-random number 36

Hash of Future Block “The block hashes are not available for all bocks for

Hash of Future Block “The block hashes are not available for all bocks for scalability reasons. You can only access the hashes of the most 256 blocks, all other values will be zero. ” Player requests winning number 256 blocks later House computes blockhash 0, then generates totally predictable pseudo-random number 37

This Happened Huge ICO “provably fair” lottery Issued a “hackathon challenge” Hacked by 256

This Happened Huge ICO “provably fair” lottery Issued a “hackathon challenge” Hacked by 256 -block delay described Arguably, the challenge was a success! 38

Blockhash with Private Seed From the Slotthereum Lottery bytes 32 _a = blockhash(block. number

Blockhash with Private Seed From the Slotthereum Lottery bytes 32 _a = blockhash(block. number - pointer); for (uint i = 31; i >= 1; i--) { if ((uint 8(_a[i]) >= 48) && (uint 8(_a[i]) <= 57)) { this variable declared private so can’t return uint 8(_a[i]) - 48; be read by another contract } } Like all blockchain data, trivial to read from off-chain 39

Prevention Use External Oracle for Randomness (trusted by all parties) 40

Prevention Use External Oracle for Randomness (trusted by all parties) 40

Prevention Algorithms we will discuss later 41

Prevention Algorithms we will discuss later 41

Unchecked CALL Return Values Inconsistent interfaces for ether transfer: If transfer() fails, caller reverts

Unchecked CALL Return Values Inconsistent interfaces for ether transfer: If transfer() fails, caller reverts If send() or call() fails, caller does not revert, instead gets Boolean return code Unchecked return codes as attack vector 42

If something goes terribly wrong … Out of gas Assertion violated stack overflow The

If something goes terribly wrong … Out of gas Assertion violated stack overflow The function call reverts Rolling back that transaction’s effects 43

Normally if a function call reverts: contract. long. Call(); // runs out of gas

Normally if a function call reverts: contract. long. Call(); // runs out of gas The entire call-chain reverts Exceptions: call(), delegatecall(), codecall() Return Booleans instead of propagating 44

Gas Trap if (! some. Addr. call. value(100)()) { // some failure code }

Gas Trap if (! some. Addr. call. value(100)()) { // some failure code } Might take all your gas if (! some. Addr. send(100)) { // some failure code } Max 2300 gas (not much) 45

Prevention If possible, use transfer() instead of send() Otherwise, check return codes! Use withdrawal

Prevention If possible, use transfer() instead of send() Otherwise, check return codes! Use withdrawal pattern, where clients claim funds via their function calls 46

This Happened Suppose claim price for throne is 10 ETH You want to be

This Happened Suppose claim price for throne is 10 ETH You want to be King/Queen, so send 10 ETH Kot. ET sends your 10 ETH minus comission to previous monarch You are now monarch Claim Price goes up to 15 ETH You get paid when someone else becomes K/Q 47

This Happened Payments were sent by unchecked sends send() did not include enough gas

This Happened Payments were sent by unchecked sends send() did not include enough gas for payment processing by wallet accounts Recipient reverted, and payment was returned to contract Kot. ET contract did not revert, nor check return code Proceeded without paying previous monarch 48

Stack Depth Attack Stack Depth cannot exceed 1024 Artificially create stack depth of 1023,

Stack Depth Attack Stack Depth cannot exceed 1024 Artificially create stack depth of 1023, then pay to become king. Refund payments to predecessor guaranteed to fail at depth 1025 Unchecked send()not just bug, but attack vector 49

Stack Depth Attack Stack Depth attack no longer feasible New rule: call cannot consume

Stack Depth Attack Stack Depth attack no longer feasible New rule: call cannot consume more then 63/64 of parent’s gas If start with g gas, after n calls you have at most g (63/64)n gas available 50

Parameter Attack Parameters to calls encoded by “ABI”specification Possible to send shorter than expected

Parameter Attack Parameters to calls encoded by “ABI”specification Possible to send shorter than expected encodings EVM silently pads 0 s to the end of short parameters 51

Generate an Ethereum adress with trailing 0 On average, takes 256 tries Find an

Generate an Ethereum adress with trailing 0 On average, takes 256 tries Find an exchange with 25, 600 tokens Send 100 tokens to my account Withdraw 100 tokens, leave off last byte in my address 52

Dear Exchange, Please Transfer 100 Tokens address 0 xcafecafecafecafecafe 0000 number tokens 100 order

Dear Exchange, Please Transfer 100 Tokens address 0 xcafecafecafecafecafe 0000 number tokens 100 order of arguments: address then amount function transfer(address to, uint tokens) public returns (bool success); 53

encoding of message to contract functionaddressamount a 9059 cbb 000000000000 cafecafecafe 0000000000000000000000000000056 bc 75

encoding of message to contract functionaddressamount a 9059 cbb 000000000000 cafecafecafe 0000000000000000000000000000056 bc 75 e 2 d 63100 000 54

Attacker drops one Byte Attacker sends address: 0 xcafecafecafecafecafe 00 instead of: 0 xcafecafecafecafecafe

Attacker drops one Byte Attacker sends address: 0 xcafecafecafecafecafe 00 instead of: 0 xcafecafecafecafecafe 0000 Exchange does not validate parameters 55

Message Padded with 00 Encoded as a 9059 cbb 000000000000 cafecafecafe 0000000000000056 bc 75

Message Padded with 00 Encoded as a 9059 cbb 000000000000 cafecafecafe 0000000000000056 bc 75 e 2 d 6310000000 instead of: a 9059 cbb 000000000000 cafecafecafecafe 0000000000000056 bc 75 e 2 d 63100000 This missing byte… Replaced with this 56

Dear Contract, Please Transfer 25600 Tokens address 0 xcafecafecafecafecafe 0000 number tokens 256 X

Dear Contract, Please Transfer 25600 Tokens address 0 xcafecafecafecafecafe 0000 number tokens 256 X 100 57

Prevention Validate inputs before sending anything to a blockchain 58

Prevention Validate inputs before sending anything to a blockchain 58

Ideas we covered in this lecture Default Visibility False Randomness Unchecked return values Parameter

Ideas we covered in this lecture Default Visibility False Randomness Unchecked return values Parameter Attack 59

60

60