Ethereum Solidity Security - Inter-Contract Security

in #utopian-io5 years ago

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

Sort:  

I thank you for your contribution. Here are my thoughts. Note that, my thoughts are my personal ideas on your post and they are not directly related to the review and scoring unlike the answers I gave in the questionnaire;

  • Structure

    • Overall structure of the post is very good. It's easy to determine which part does what, so I appreciated that.
  • Language

    • I personally think the usage of the first person reduces the efficiency of the post. I advise you to consider using less first person. For example, instead of using "we" to describe what you are showing, you can refer to it as the third person like "A developer should consider this" etc. Still, this is a personal thought, don't get me wrong! :)
  • Content

    • Content is relatively simple, nonetheless it is useful and a good one, thanks for sharing with us!

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Chat with us on Discord.

[utopian-moderator]

Thank you for your review, @yokunjon! Keep up the good work!

Valeu! Parabéns pela iniciativa de compartilhar estas aulas no GitHub via Utopian. Como estou testando a rede social Cent, que remunera com Ethereum, tomei a liberdade de divulgar seu link por lá: https://beta.cent.co/+chhq6z Qq comentário ou sugestão é só avisar. Sucesso e boa sorte mais uma vez!!

Posted using Steeve, an AI-powered Steem interface

Congratulations! Your post has been selected as a daily Steemit truffle! It is listed on rank 1 of all contributions awarded today. You can find the TOP DAILY TRUFFLE PICKS HERE.

I upvoted your contribution because to my mind your post is at least 10 SBD worth and should receive 109 votes. It's now up to the lovely Steemit community to make this come true.

I am TrufflePig, an Artificial Intelligence Bot that helps minnows and content curators using Machine Learning. If you are curious how I select content, you can find an explanation here!

Have a nice day and sincerely yours,
trufflepig
TrufflePig

Parabéns Igor, feliz em ver trabalhos de grande qualidade como o seu por aqui!

Posted using Steeve, an AI-powered Steem interface

Hi, @igormuba!

You just got a 0.24% upvote from SteemPlus!
To get higher upvotes, earn more SteemPlus Points (SPP). On your Steemit wallet, check your SPP balance and click on "How to earn SPP?" to find out all the ways to earn.
If you're not using SteemPlus yet, please check our last posts in here to see the many ways in which SteemPlus can improve your Steem experience on Steemit and Busy.

Hi @igormuba!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

Hey, @igormuba!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Coin Marketplace

STEEM 0.19
TRX 0.14
JST 0.030
BTC 60115.50
ETH 3192.77
USDT 1.00
SBD 2.45