Ethereum Solidity Security - Inter-Contract Security

Repository

https://github.com/igormuba/EthereumSolidityClasses/tree/master/MultiContractExploit

What Will I Learn?

Requirements

  • Internet connection
  • Code editor
  • Browser

Difficulty

Tutorial Contents

I am writing this tutorial as a standalone piece. Many people will benefit from being mindful of the exploits while working with multiple contracts. For the people that have been following this series, I have focused on making mutable contracts and turning pieces of code into modules.

If you work with contracts on that way, you are adding more features, power, and upgradeability to your code, but also, you are opening many doors for possible exploits. Overflow and underflow exploits look like a children's game after you realize how hard it is to connect multiple contracts without allowing bad people to hijack the functions.

Calling data and functions from another contract

Starting simple, this is the multi-contract interaction I have covered the most and is the simplest one.

Here is an example of unsafe code, right below it I will explain why it is unsafe and how can it be fixed.
Callee (called) contract:

pragma solidity ^0.5.0;
//this contract is designed to be called by someone else
contract callee{
    mapping (address=>uint) balances;//balance of tokens of an user
    function addTokens(uint _tokens) public {//receives how many tokens to be "minted"
        balances[msg.sender]+=_tokens;//add tokens to the caller
    }
}

Caller contract:

pragma solidity ^0.5.0;

import "browser/callee.sol";//imports the code of the callee for internal reference
//this contract calls the other one
contract caller{
    function mintTokens(address _contract) public{//what contract will it call the function from
        callee called = callee(_contract);//creates the contract object pointing to the given address
        called.addTokens(10);//calls the function from the contract/address
    }
}

The exploit here is very obvious and the code very simple to show in the clearest way possible how the exploit occurs here, a real-world case would be more subtle.

In this case, we expected that only the owner of the contract, or the caller contract, could call the function and mint tokens for themselves, but what actually happens is that anyone can call the function and generate tokens for themselves. Let's first see the exploit working, and then patch and see the patched contract denying "hackers" from calling what they are not supposed to call.

Exploit test

If, on the caller contract, I pass the address of the Calle contract:
Captura de Tela 20190124 às 00.49.10.png

What happens is that the callee will the caller address as msg.sender, on this line:

balances[msg.sender]+=_tokens;

And the caller tells it to mint 10 tokens to the caller contract, as on the line:

called.addTokens(10);

And that is exactly what we wanted to happen.

But, we are allowing anyone to call the function from the callee, and that is not what we wanted to happen. So, when an unintentional third person, not the owner not the caller contract, calls the function to "mint" tokens, he can give any number to the callee and get those tokens generated to himself. What is the worth of a token if anyone can get as much of it as desired. We will need to add some authorization and requirements here!

Patching

There are many approaches to fixing this exploit. The simplest one is to, on the callee contract, set, on a constructor function, the address of the creator of the contract. And then allow the creator, and only him, to "whitelist" a caller address.

Here is how it works:

//address of the creator, set on the constructor
address private creator;
//address of the caller, set by the creator only
address private caller;
//modifier that checks if msg.sender is the creator
modifier isCreator{
    require(msg.sender==creator);
    _;
}
//sets the creator when deploying the contract
constructor() public{
    creator=msg.sender;//creator is the deployer of the contract
}
//sets the address of a whitelisted caller
function setCaller(address _caller) public isCreator{
    caller=_caller;
}

It is noticeable now that the contract has tangible restrictions. Only the creator can set the caller. The next step is to allow only either the creator or the whitelisted caller to mint tokens.

And that is as simple as one line of code:

require(msg.sender==creator || msg.sender==caller);

The code above should be added BEFORE the code minting the tokens to the creator, to avoid exploits of the EVM asynchrony.

That will throw an error if the caller is neither the creator or the whitelisted caller:

How it works after patched

The contract is supposed to work identically as before the fix. Anyone can call the function from the caller because all that does is mint more tokens to the caller, a contract that we expect to be owned by the creator, so there is no problem on that since other people can't access those tokens anyways, they would be just spending gas.

But, if someone that is not the creator, and is not calling the callee through the caller, that is, someone tries to mint tokens for themselves on the callee, this is the error he will face:

Captura de Tela 20190124 às 01.08.02.png

Delegate call exploit

This one is a more subtle, and harder to work with, exploit. I have been teaching mostly the technique above to work with multiple contracts.

The difference between the technique form the previous sections of this tutorial and the delegatecall() is that the above-explained execute the code of the functions on the context of the callee, while the delegatecall() executes the code on the context of the caller.

You might think that, since the context is the one of the callers, it is more secure, and at a first glance you are not wrong.

The delegatecall() function known to be a very low level, almost assembly, code, that is difficult to understand and more difficult to implement correctly. Here I will describe how a bad implementation of that function caused the Parity wallet to lose millions of dollars from their users!

Here is the code I have developed to exemplify the exploit, again, it is too obvious, but the intention is to really be that obvious, so we can focus on what and why, instead of how, because there are too many "hows".

Callee:

pragma solidity ^0.5.0;
//the callee is supposed to be an unused library that will be developed later
//but the exploit happens because it is actually a contract
contract callee{
//mapping of balances for inter contract compatibility of delegatecall
    mapping(address=>uint) balances;

//function that was supposed to be on a library, not a contract
    function calledFunction() public{
        balances[msg.sender]+=10;
    }
    function getBalance() public view returns(uint){
        return balances[msg.sender];
    }
}

Caller:

pragma solidity ^0.5.0;

import "browser/callee.sol";

//the real contract, or real wallet
contract caller{
    mapping(address=>uint) balances;
    
//this is supposed to be a library for further upgrades to the contract
    address pseudoLibrary;

//sets the library that was not supposed to be used
    constructor(address _pseudoLibrary) public{
        pseudoLibrary=_pseudoLibrary;
    }
    
    function() external payable {
        if (msg.value > 0){
            balances[msg.sender]+=msg.value;
        }
//if there is data do this
        else if (msg.data.length > 0){
//extremely obvious exploit, more on that below
            pseudoLibrary.delegatecall(abi.encodePacked(bytes4(keccak256("calledFunction()"))));
        }
  }
  
    function getBalance() public view returns(uint){
        return balances[msg.sender];
    }
  
}

On the code above, it is too obvious that when sending data to the fallback function we will call the function from the callee to generate tokens on the context of the caller. But the hacker of the Parity wallet exploited that it was actually like this:

pseudoLibrary.delegatecall(msg.data);

So he wrote the data himself exploiting it, but to be obvious enough, we are hard-coding the exploit on the contract.

If you want to see the transactions and the calls the hacker made to parity, here is the transaction he made to the fallback function to delegate the call:
https://etherscan.io/tx/0xeef10fc5170f669b86c4cd0444882a96087221325f8bf2f55d6188633aa7be7c

Testing the exploit

What was expected to happen is that when a user sends Ethereum to the contract it adds the credit to the users' balance, to allow him to withdraw it in the future. But actually, if the transaction sent has data, the data will be processed.

On our example, if we send 10 Ethereum to the contract, it records we have 10 Ethereum in balance:
Captura de Tela 20190124 às 01.46.09.png
(There are lots of zeroes in there because the unit is measured in Wei, the smallest fraction of ETH)

But, now, from a wallet with zero balance, if I send any data at all, even if just 1 letter or number or bit:
Captura de Tela 20190124 às 01.48.02.png

For each time the transaction is called 10 Wei will be added to the users' credit balance. Of course, this is an illustrative example and the hacker would lose money from transaction fees because 10 Wei is very small.

The fix

The fix is to make the callee contract a library. We want to allow ourselves to implement a function to it in the future. The mistake of Parity was setting the callee as a contract and putting unsafe logic into it. so the fix is not just that, but it is about the safety of the logic inside of the callee to, through SafeMathLib, etc.

As said, the deletagecall is too low level and complex. But here is how I fixed the exploit on our example. I made the callee like this:

pragma solidity ^0.5.0;

library callee{

    function calledFunction() public{
        //it is supposed to implement logic
        //and return a result in the future
    }

}

So, at the same time, we allow ourselves to add new logic to the library in the future, we aren't doing that unnecessarily now.

Also, do not commit the mistake Parity did, and do not pass to delegate call the msg.data, unless you are very sure of why you are doing so and the other variables around it, like requirements before.

Curriculum

This is supposed to be a standalone tutorial to help anyone interested in multi-contract security, however, if you are interested in mastering solidity, here is the main Ethereum smart contract development series:

First tutorial

Latest tutorial

Beneficiaries

This post has as beneficiaries

using the SteemPeak beneficiary tool

Captura de Tela 20190122 às 16.07.11.png

H2
H3
H4
Upload from PC
Video gallery
3 columns
2 columns
1 column
8 Comments