Written by Gustavo Guimaraes
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.
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 Bounty.sol
, SafeMath.sol
, Ownable.sol
, PullPayment.sol
and Destructible.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:
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:
deployContract()
function which has to return a new contract address every time. In our case, we will return theLockYourEther
contract address.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 betrue
at all times. That is to say that you need to check if the contract state remains as intended. Example of implementations of checkIvanriant()
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.Let’s create the Bounty contract:
Firstly, we override theBounty
contract function deployContract
function with our own so as to instantiate LockYourEther
version for the bounty program for us.
Next, we includecheckInvariant()
inLockYourEther
and ensure that it derives from theTarget
contract found at Bounty.sol.
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:
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(balances[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 }))
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 Giordano, Manuel Aráoz and Demian Brener for reviewing this blog post before publication.