Go to course navigation

8 - Security I

Tutorial objectives

  • Introduction into security auditing
  • Increase skill in recognizing known security flaws
  • Ability to write unit tests in Wake

Tutorial pre-requisites

pip3 install eth-wake
curl -L https://foundry.paradigm.xyz | bash # insecure install, you know
source ~/.bashrc && foundryup

Hacking approach

There are several different techniques to find vulnerabilities in smart contracts. To achieve the best possible output, it is recommended to combine them together.

  • The first essential part is understanding the target contract’s main purpose. For example, DeFi applications can be very complex, with different approaches to the same problems as reward logic, token distribution, etc. Read the documentation if it exists and try to understand the idea behind the project.
  • Use the tools with automated vulnerability detectors (e.g. Wake, Slither, etc.). This way, finding the most common vulnerabilities is possible, but it is necessary to be aware of many false positives.
  • The code review is the most important and challenging part of the process. An auditor can check with a confidence +- 100 lines of code daily. While browsing the code, use the tooling to extract helpful information (call graph, inheritance graph, state variables, etc.). The first goal is to understand the purpose of every single line. Sometimes the line does not make much sense - bug or feature? With a good understanding of the code, we can focus on the crucial parts:
    • Access control - Who can call the function? Why is there no modifier?
    • Requirements and conditions - Is the operator < = strict enough? Any way to bypass it?
    • State variables - Can a regular user make a state changing transaction? Does the change affect other users? Does the change happen in the correct order (reeantrancy)?
    • Inputs - Think about every input as a payload. Is there any payload that can allow me to access a critical line of code? Look for edge cases.
    • Solidity version - Does the contract use the old version? Is there any known vulnerability in this version?
  • Interact with the contract when suspicious behavior is found. Deploy the project, simulate the real-world behavior, and write tests. If a project does not provide tests with good coverage, write them yourself. Unit tests, property-based tests, fuzzing, etc. Well-written tests can be used to verify the contract behavior and also break the code logic with randomized outputs.

Common vulnerabilities

Just a few common vulnerabilities are described. Most of the issues cannot be easily categorized. Some of the known vulnerabilities can be found in SWC Registry of known issues.

Integer under/overflow

When an unsigned integer reaches its maximal/minimal value, and then, it is incremented/decremented. E.g., uint8 has range 2^8 (0-255).

uint8 balance = 255;
balance++; // balance = 0
balance--; // balance = 255

The issue no more exists for solidity version >= 0.8.0.

Reentrancy

A dangerous situation when calling an external contract address. If the called contract is malicious, it can take control of the control flow. This type of bug can have many forms, but the basic idea stays the same. If there is a call to the external address before the state change, there is a possibility of making a reentrancy attack. Reentrancy can be in the same function but also cross-function, cross-contract, and even cross-chain thanks to the cross-chain bridges.

mapping (address => uint) private userBalances;

function withdrawBalance(uint amount) public {
    require(userBalances[msg.sender] >= amount, "insufficient funds")
    uint amountToWithdraw = userBalances[amount];
    msg.sender.call.value(amountToWithdraw)(""); // Caller's code can be executed, and can call withdrawBalance again
    userBalances[msg.sender] -= amount;  // State variable is updated after the call, attacker can drain the contract
}

Denial of Service

Make the contract unusable for future use.

Block Gas Limit: Each block has an upper bound on the amount of gas that can be spent and thus the amount of computation that can be done. This is the Block Gas Limit. If the gas spent exceeds this limit, the transaction will fail. This leads to a couple of possible Denial of Service vectors. A simple example is when users can store an array with unbounded length. Whenever the contract loop over the array and do some computation, it is possible to reach the block gas limit and make the transaction fail.

Front Running

Since all transactions are visible in the mempool for a short while before being executed, network observers can see and react to an action before it is included in a block. An example of how this can be exploited is with a decentralized exchange where a buy order transaction can be seen, and second order can be broadcasted and executed before the first transaction is included.

Flash Loan attack

Some protocols offer the possibility to borrow a large amount of tokens for a short time. The time is limited to one transaction. It means a borrower must create a smart contract with the logic of borrowing money, doing some activities with it, and then returning the loan. This approach can be used in algorithmic arbitrage trading, but it can also be weaponized to attack the protocol.

E.g. DAO (Decentralized Autonomous Organization) is a smart contract that can be used as a governance contract. Users stake their tokens, and based on the staked amount, they receive the corresponding voting power. DAO can be used to govern the staking pools and control a fee amount or an address of a fee receiver.

Attack scenario:

  • Attacker uses a flash loan,
  • stakes tokens to the DAO contract,
  • because of the flash loan he has a lot of voting power (more than 50%) to change the fee receiver to his own address,
  • unstake
  • return the loan.

It can all be done in one transaction using the customized smart contract.

Wake

Python-based development and testing framework for Solidity.

Everything about the tool can be found here: https://ackeeblockchain.com/wake/docs/latest/

Useful commands

  • wake init
  • wake init pytypes
  • wake compile
  • wake test

Useful sources