paint-brush
Build Your Bug Bounty: Smart Contract Pentesting Overviewby@immunefi
2,627 reads
2,627 reads

Build Your Bug Bounty: Smart Contract Pentesting Overview

by ImmunefiAugust 8th, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Immunefi is the bug bounty platform for smart contracts and DeFi projects. It provides bug bounty hosting, consultation, bug triaging, and program management services to blockchain and smart contract projects. This guide will help you set up a local environment and reproduce the Fei Protocol exploit. It will also teach you how to test your own exploits against the mainnet. The rest of this guide will assume you’re using Ubuntu, though it’s unlikely commands will be much different in other systems.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coins Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Build Your Bug Bounty: Smart Contract Pentesting Overview
Immunefi HackerNoon profile picture

Bug bounty programs are open invitations to security researchers to discover and disclose potentially vulnerabilities in projects’ smart contracts and applications, thereby protecting projects and their users. For their good work, security researchers receive a reward based on the severity of the vulnerability, as determined by the project affected.

Launched on December 9, 2020, focused on blockchain and smart contract security, Immunefi provides bug bounty hosting, consultation, bug triaging, and program management services to blockchain and smart contract projects.

This guide, written by whitehat Lucash-dev for Immunefi, will help you set up a local environment and reproduce the Fei Protocol exploit, so that you can get up to speed on setting up a proper tooling environment for finding bugs and claiming the world’s largest bounties at Immunefi.

Join our whitehat community and get notified when new bounties launch on the platform. Learn new info!

Get started here or join our Discord!

By the end of this guide you will have:

  • Created a simple HardHat project
  • Compiled PoC smart contract code
  • Created a local fork of the Ethereum mainnet
  • Run a script that deploys and executes the PoC contract

And that’s supposed to teach you:

  • How to test your own exploits against the mainnet
  • That hacking Ethereum contracts isn’t that complicated

Assumptions

  • You understand the basics of distributed ledger (aka blockchain)
  • You understand the basics of Ethereum smart contracts and Solidity
  • You’ve read and understood in general the exploit described in the Fei postmortem (linked above)
  • You have some basic skills in JavaScript and node.js (not a lot needed)
  • You have a Unix OS (Linux/MacOS) or a Unix compatible shell (like git-bash in Windows). The remainder of this guide will assume you’re using Ubuntu, though it’s unlikely commands will be much different in other systems

Linux Dependencies

Before we get started let’s ensure you have the following dependencies installed on your system:

  • node.js
  • npm
  • yarn

Installing them in Ubuntu is simple:

sudo apt update
sudo apt install nodejs npm
sudo npm install --global yarn

If you have a different system or the above commands don’t work, please refer to the project documentations.

HardHat project

HardHat is a framework for the development, test, and deployment of Ethereum smart contracts. It will make our life easier by making it easy to run a local fork Ethereum network, so that we don’t perform tests against the actual mainnet (expensive and dangerous!). It also provides libraries and utilities to automate scripts to perform all of the tasks for our tests.

To create an empty HardHat project:

mkdir fei-poc
cd fei-poc
yarn add hardhat
npx hardhat init

If HardHat prompts you for answers, you should select “create a sample project” and then “yes” to all other questions.

You’ll observe that a bunch of files have been created, and 

yarn
dependencies added.

You should see a file named 

hardhat.config.js
 that looks like this (some content omitted):

require("@nomiclabs/hardhat-waffle");
task("accounts", "...", async () => {
    ...
});
module.exports = {
  solidity: "0.7.3",
};

You should also see a file 

contracts/Greeter.sol
 and 
scripts/sample-scripts.js
.

Those are a sample smart contract and a sample script.

To check that your setup is working you can run:

npx hardhat run scripts/sample-script.js

You should see a message in the console like Greeter deployed to:

0x...

What the command above just did:

  • Compiled the sample contract
  • Started a local Ethereum network node (empty)
  • Deployed the sample contract to it

If you see any error messages at this point, there’s likely something wrong with your installation, and you should troubleshoot it.

Compiling the PoC Code.

Copy Exploit Code

Now that we have a HardHat project, let’s copy the PoC exploit code to it, so that we can compile and deploy locally.

First, grab the two files for the exploit and copy them to the contractsdirectory in your project.

Now, compiling it should be as simple as running:

npx hardhat compile

Except this won’t work yet! If you try the command above, all you’ll see is an error message about an imported file not being found.

The reason is we’re missing the code for the interfaces of the contracts the PoC code interacts with.

Grabbing Code for Interfaces

Our life would be much easier if the hacker who created the PoC had provided a full project we can just build locally, but they haven’t. When you’re hacking deployed contracts, this will often be the case.

For the Fei PoC most of the interfaces can be easily found in the Fei codebase in GitHub, but that’s not always going to be the case. You can definitely search GitHub, and often find what you’re looking for, but sometimes mixing code written in different versions of Solidity might be problematic and require manual fixing.

As a last resort, you can always look for the contract code in Etherscan, if you know the contract address. Sometimes it will have the code for the interface ready. Other times, you might at least find the ABI. One easy trick to get interfaces, even when you don’t have the source code for the contracts, is to find the ABI for the contract in Etherscan and then use a tool to create the Solidity interface code for it. Though this trick is nice, it don’t always work. For example, the tool has trouble with complex types (

struct
).

For this PoC contract, though, you should be able to easily find all interfaces between the Fei codebase and the following resources:

Add each to its own “.sol” file, matching the names of the files in the “import” statements in the PoC code.

Once you do that, compiling should work:

npx hardhat compile

Forking the Mainnet

For testing the PoC code, we are going to create a local fork of the Ethereum mainnet. That means that the initial state of all contracts already deployed are coming from the real blockchain, but all changes are only made locally.

That is a very powerful tool that HardHat (well, actually ganache behind the scenes) for testing. We can mimic exactly what would happen in a real attack, without affecting any real mainnet state and without spending huge money on transaction fees.

It will also allow us to simulate the blockchain at any given point in the past, as we can choose the block number to start the fork, and let us impersonate any account for testing (simulating attacks by privileged users for example, or by whales).

To do that, you’ll need to sign up for an account with Alchemy (there are other ways to do that without their services, but not practical).

Sign up for Alchemy API

Before forking the blockchain you’ll need to sign up for using the Alchemy API at: https://auth.alchemyapi.io/signup

The free account is probably enough for all your hacking needs. Once you finish signing up, you’ll need to add an “app” in your dashboard. You can call it “test”. After creating an app you should be able to find the API URL, with your secret key.

It should look like:

https://eth-mainnet.alchemyapi.io/v2/k1asdfa2fasdfa-asdf_asdf

Take note of the URL and keep it secret. You’ll need it to configure your local fork.

Configure and Test Local Fork

Now that you have your Alchemy API URL, add configuration to fork the mainnet to your 

hardhat.config.js
 (in your project root directory).

module.exports = {
  defaultNetwork: 'hardhat',
  networks: {
    hardhat: {
      forking: {
        // Replace with your actual API URL
        url: "https://eth-mainnet.alchemyapi.io/v2/k1asdfa2fasdfa-asdf_asdf"
      }
    }
  },
  
  solidity: "0.7.3"
 }

You can run the sample script in your local fork. It shouldn’t make any difference for now, as the sample script doesn’t interact with any mainnet contracts, but it’s a good way to test your setup.

npx hardhat run scripts/sample-script.js

Create and Run the Test Script

To test the Fei attack, you can use the script below:

const hre = require("hardhat");
async function main() {
  
  // We reset the local chain to a fork of mainnet
  // so that the state is always pristine.
  await hre.network.provider.request({
  method: "hardhat_reset",
  params: [{
    forking: {
      // Replace with your actual API URL
      jsonRpcUrl: "https://eth-mainnet.alchemyapi.io/v2/asdfasdfasdfasdf"
      //,blockNumbeR: 12500000 // after fix
      ,blockNumber: 12350000 // before fix
      }
    }]
  })
  // We'll use this contract to check the WETH balance of the 
  // PoC contract.
  const WETH = await hre.ethers.getContractAt("IERC20",'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2');
  // Deploy the PoC contract.
  const Exploit = await hre.ethers.getContractFactory("Exploit");
  const exploit = await Exploit.deploy();
  console.log("Exploit deployed to:", exploit.address);
  // Let's run the exploit PoC!
  const balance0 = await WETH.balanceOf(exploit.address);
  console.log("Balance before exploit",balance0/1e18,"ETH");
  console.log("starting exploit");
  // I had a bit of trouble finding the optimal values using the
  // the Python script, values didn't seem to work.
  // Found these parameters by trial and error.
  d = "207569000000000000000000"
  b = "092430000000000000000000"
  await exploit.start(d, b);
  const balance1 = await WETH.balanceOf(exploit.address);
  console.log("If the balance is positive the exploit worked!");
  console.log("Balance after exploit",balance1/1e18,"ETH");
}
main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });

If you follow the comments, it should be clear what each command does.

The most important detail to notice here is that we’ll use a fork of the mainnet at a specific block before the vulnerability was fixed.

Save it as 

fei.js
 and run with the command:

npx hardhat run scripts/fei.js

You should see several debug messages and something like this in the end of the output:

If the balance is positive the exploit worked!
Balance after exploit 9635.065665081976 ETH

If the balance is displayed as above, congratulations! You just simulated stealing a few million dollars from Fei Protocol smart contracts.

The ability to go back in time and replay an attack is definitely a great help for learning hacking!

Testing Fix to the Bug

We’re not 100% done yet. What if someone actually executed this attack in the real world? We can test that the issue has been fixed by trying to replay the attack, but using a fork of the mainnet at a block after the vulnerability was fixed.

Change the lines that reset the network to look like:

// We reset the local chain to a fork of mainnet
  // so that the state is always pristine.
  await hre.network.provider.request({
  method: "hardhat_reset",
  params: [{
    forking: {
      // Replace with your actual API URL
      jsonRpcUrl: "https://eth-mainnet.alchemyapi.io/v2/asdfasdfasdfasdf"
      blockNumbeR: 12500000 // after fix
      //,blockNumber: 12350000 // before fix
      }
    }]
  })

Run the script again. It should fail now, proving the attack doesn’t work anymore, and that the fix deployed indeed removed the vulnerability.

You could further test the deployed contracts for a regression of the issue by changing the block number to a number closest to the present blockchain height (number of the last block).

Conclusion

By following this guide, you should’ve been able to reproduce the Fei vulnerability in your local fork of the Ethereum mainnet.

Now, you should try modifying the test script and the PoC contract, pointing to other mainnet contracts, perhaps in other projects, and observing the results.

My recommendation for learning hacking is to start from the target code base, and when you find something that looks suspicious, try to interact with the contract using the local fork.

Hope you found this guide useful! Happy hacking!