Articles
Collections
Write Article
Create Collection
Import from medium
Loading...
Sign in
POSTED 03 May 2018 02:54

Setting Up a Bug Bounty Smart Contract

OWNER
Zeppelin

Introduction

The effect of having a market cap north of $10 Billion USD is that a growing number of blockchain enthusiasts trust putting their Ether only in smart contracts that have been vouched and fine-combed by security professionals.

Among the emerging best practices, the most recommended are to write extensive unit tests, have the code audited by specialists and set up a bug bounty program. I won’t elaborate on the first two recommendations as they deserve entire blog posts on their own, but in this blog post you will learn about how to setup bug bounty programs for your smart contracts.

The advantage of setting up bug bounty program is that security researchers from all over the world are incentivized to have a go at hacking your smart contract for a period of time. If the smart contract is broken into, the researcher collects Ether and you learn where the vulnerability lies in your code.

Another upside is that the process is able to be fully automated on the Ethereum blockchain and with the Bounty smart contract from the OpenZeppelin library, any blockchain developer is able to set up a bug bounty program within a reasonable amount of time. Let’s see how this is achieved.

Guide


First off, let’s bootstrap the project using Truffle.
$ mkdir bug-bounty-example && cd bug-bounty-example && truffle init
Next step is to add the OpenZeppelin library.
$ npm install zeppelin-solidity --save
This adds the zeppelin-solidity folder to node_modules and in it you will find all smart contract templates from the OpenZeppelin library.

For this project we only need Bouncty.sol, SafeMath.sol, Ownable.sol, PullPayment.sol and Destructable.sol. If you just want to jump to the code, this is the repo where it resides: https://github.com/gustavoguimaraes/bug-bounty .

Moving on.

To set up a bug bounty program we need a smart contract that others will try to break. Let’s code up a smart contract that locks a user’s ether for any amount of time in the future.
$ touch contracts/LockYourEther.sol
And include the following contract:
pragma solidity ^0.4.11;

import 'zeppelin-solidity/contracts/math/SafeMath.sol';
import { Target } from "zeppelin-solidity/contracts/Bounty.sol";

contract LockYourEther is Target {
  uint256 public totalLockedWei;
  address[] public lockedAddresses;

  struct User {
    uint256 releaseDate;
    uint256 value;
  }

  mapping (address => User) public balances;

  // params _releaseDate as timestamp
  function lockFundsUntil (uint256 _releaseDate) payable {
    require(_releaseDate > now);

    totalLockedWei = SafeMath.add(totalLockedWei, msg.value);
    balances[msg.sender].value = msg.value;
    balances[msg.sender].releaseDate = _releaseDate;
    lockedAddresses.push(msg.sender);
  }

  function withdraw () {
    require(now > balances[msg.sender].releaseDate && balances[msg.sender].value > 0);

    uint256 payout = balances[msg.sender].value;
    uint256 totalLockedWei = SafeMath.sub(totalLockedWei, payout);

    balances[msg.sender].value = 0;
    balances[msg.sender].releaseDate = 0;
    msg.sender.transfer(payout);
  }

  function checkInvariant() returns(bool) {
    uint256 totalAmountLockedInBalances;

    for (uint256 i = 0; i < lockedAddresses.length; i++) {
      address balanceAddress = lockedAddresses[i];

      totalAmountLockedInBalances = SafeMath.add(balances[balanceAddress].value, totalAmountLockedInBalances);
    }

    return totalLockedWei == totalAmountLockedInBalances;
  }
}
The LockYourEther contract keeps track of the User in a mapping hash function which has access to a value and releaseDate . A User is able to lockFundsUntil a date in the future when it is able to withdraw the Ether from the contract (and hope that Ether will triple in value by then).

Now onto setting up the bug bounty program. This is the high level procedure:

  1. Create a bounty contract that inherits from OpenZeppelin’s Bounty.sol and have a deployContract() function which has to return a new contract address every time. In our case, we will return the LockYourEther contract address.
  2. Have LockYourEther inherit from Target contract found in OpenZeppelin’s Bounty.sol . Add another function to LockYourEther called checkInvariant() which checks state variables within the contract. These must be true at all times. That is to say that you need to check if the contract state remains as intended. Example of implementations of checkInvariant() could be: checking if the total supply of all tokens equals the number of tokens that handed out on a token crowdsale, whether the timestamp of a variable in the contract is > now and so on and so forth.
  3. Deploy the contracts to the blockchain.
  4. Set the Bounty reward.
Let’s create the Bounty contract:

Firstly, we override the Bounty contract function deployContract function with our own so as to instantiate LockYourEther version for the bounty program for us.
pragma solidity ^0.4.11;

import "./LockYourEther.sol";
import { Bounty } from "zeppelin-solidity/contracts/Bounty.sol";

contract LockYourEtherBounty is Bounty {
  function deployContract() internal returns(address) {
    return new LockYourEther();
  }
}
Next, we include checkInvariant() in LockYourEther and ensure that it derives from the Target contract found at Bounty.sol.
pragma solidity ^0.4.11;

import 'zeppelin-solidity/contracts/math/SafeMath.sol';
import { Target } from "zeppelin-solidity/contracts/Bounty.sol";

contract LockYourEther is Target {
 
 ...

  function checkInvariant() returns(bool) {
    uint256 totalAmountLockedInBalances;

    for (uint256 i = 0; i < lockedAddresses.length; i++) {
      address balanceAddress = lockedAddresses[i];

      totalAmountLockedInBalances = SafeMath.add(balances[balanceAddress].value, totalAmountLockedInBalances);
    }

    return totalLockedWei == totalAmountLockedInBalances;
  }
}
Here we are checking if the total Ether value from balances must match the state variable totalLockedWei . We know that if these are not equal the contract is not storing the ether or withdrawing as it should.

Now for the deployment:

In migrations/2_deploy_contracts.js flesh it out with the JavaScript that lets Truffle deploy the contracts:
var LockYourEther = artifacts.require("./LockYourEther.sol");
var LockYourEtherBounty = artifacts.require("./LockYourEtherBounty.sol");

module.exports = function(deployer) {
  deployer.deploy(LockYourEther);
  deployer.deploy(LockYourEtherBounty);
};
Run the Truffle command to the desired Ethereum blockchain of your choice: kovan or ropsten which are test nets (For n00bs out there, these are examples of test nets that we can deploy and test our contracts on decentralized environments), or main net for the actual bug bounty program.

Lastly, set the reward for the bounty program.

Using truffle console:
> LockYourEtherBounty.deployed().then(inst => {bounty = inst})
> address = 0xb9f68f96cde3b895cc9f6b14b856081b41cb96f1; // your account address
> bountyAddress = bounty.address
> reward = 20 // reward to pay to a researcher who breaks contract
> bounty.sendTransaction({ from: address, value: web3.toWei(reward, "ether") })
> bountyValue = web3.eth.getBalance(bountyAddress).toNumber()
> web3.fromWei(bountyValue, 'ether')
'20'
Voilá, the bug bounty program is setup.

I purposely left a bug in the LockYourEther. Are you able to find it and get the bounty reward?

Give it a try, I will give you a few minutes …


If you have found it, great! You got an eye for security breaches in smart contracts (this is a link for you). If you jumped to this section, no worries. The more exposure you get to this, the better it becomes.

So let's find the bug in LockYourEther contract and collect the bounty. For this, we are going to interact with the contract.

From truffle console:

Set ourselves as researchers of this bug bounty program and setup the contract instance we will be hacking:
> researcher = '0xa96c979aa675d8c63d03b53e1f06cfa33c65721e' // the researcher address
> bounty.createTarget({ from: researcher }).then(result => targetContractAddress = result.logs[0].args.createdAddress)
> LockYourEther.deployed().then(inst => abi = inst.abi)
> contract = web3.eth.contract(abi)
> lockedAddressInstance = contract.at(targetContractAddress)
Then, interact with the target contract.
> theFuture = 1537401600 // 09/20/2018 @ 12:00am (UTC)
> etherToLock = 3
> lockedAddressInstance.lockFundsUntil.sendTransaction(theFuture, { from: researcher, value: web3.toWei(etherToLock, "ether"), gas: 1000000 })
Check if all is okay with the contract:
> lockedAddressInstance.checkInvariant.call()
true
So far, so good. Let’s send it some more Ether with the same researcher address:
> etherToLock = 6
> lockedAddressInstance.lockFundsUntil.sendTransaction(theFuture, { from: researcher, value: web3.toWei(etherToLock, "ether"), gas: 1000000 })
Now calling checkingInvariant once more:
> lockedAddressInstance.checkInvariant.call()
false
Ha! We found the bug. The contract permits users to override their data. If you pay attention to the lockFundsUntil() function , line 19, you see that we are only checking if _releadeDate > now. A user could just keep calling this function over and over and modifying its balances value. To fix this bug we would need to create another check such as require(balance[msg.sender.value == 0) so that an address is of balance 0 before locking any funds.

We deserve some Ether. Let’s claim the reward.
> bounty.then(inst => inst.claim(targetContractAddress, { from: researcher }))
> bounty.then(inst => inst.claimed())
true
> web3.eth.getBalance(bountyAddress).toNumber()
20
> bounty.then(inst => inst.withdrawPayments({ from: researcher }))
> web3.eth.getBalance(bountyAddress).toNumber()
0
There we go.

If there happened to have no researcher to breach the contract, the creator of the contract could retrieve the bounty reward as such:
bounty.then(inst => inst.destroy({ from: creatorAddress }))

Wrapping up


We saw here how to setup a bug bounty contract using the OpenZeppelin smart contract library. Traditionally bounty sites are non standardized, and its process is manual and can cause disputes such as who claimed first and whether the claim is legitimate. With smart contracts and the help of the OpenZeppellin library, smart contract owners can post their smart contract (target contract) as well as another contract which checks whether the contract can be hackable or not (invariant contract) with some rewards of Ether if it is hackable. Any security researchers can submit their exploit contract to prove that it can hack the contract. Once the hack is proved, the security researcher can get reward automatically. Or if no bug is found the creator can destroy the bounty contract and recover the invested reward.

The code for the smart contracts can be found at: https://github.com/gustavoguimaraes/bug-bounty

Hope this has helped you understand more about smart contract security.

Thanks for Francisco GiordanoManuel Aráoz and Demian Brener for reviewing this blog post before publication.
Outline
  • Introduction

  • Guide

  • Wrapping up