Pentesting Ethereum dApps

An Ethereum decentralized application (dApp) is an application that interacts with a consensus protocol behind it. In our case, we examine one of the most common use cases of a dApp: a regular web application that interacts with one or several smart contracts.

When you visit a dApp over the web (with an extension like MetaMask), you can interact with the site with your private key and sign transactions through its web interface.

Here’s an example of a dApp, where I use its web interface and my Chrome extension containing my Ethereum wallet to buy a Cryptokitty:

Buying a kitty

When your browser interacts with a regular web application, the web app might speak to other internal servers, databases, or a cloud. In the end, the interaction is simple:

Standard web app

In a dApp, most interactions are the same. But there’s a third element: the smart contract, which is publicly accessible.

Public blockchain

Some interactions with the web application will lead to either a read or a write to one or multiple smart contracts on the Ethereum blockchain.

Standard dapp

Multi-pronged approach

The dApp exists, in part, to make interacting with its smart contracts easier for the end-user. But there is no rule that states we must interact with the dApp’s smart contracts through the dApp’s web interface. Because smart contracts are publicly accessible, we can interact with them directly, unimpeded by the web server logic that might limit what transactions we can issue.

So far, we have a two-pronged approach to our pentest:

  1. A standard web application pentest exploiting authentication, access controls, and session management.
  2. A smart contract audit.

Two-pronged approach

In other words, we check for logical errors in both the web application and the smart contract logic.

Because of modifiers, though, there’s actually a third prong to our attack we can consider.

Modifiers

In Ethereum, you can write functions that only execute if called from a specific Ethereum address. onlyOwner is a common example of a modifier that, when implemented properly, only allows the owner of the contract to run certain functions.

contract mortal {
    /* Define variable owner of the type address */
    address owner;

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function writeData(bytes32 data) public onlyOwner returns (bool success) {
        // will only run if owner sent transaction
    }
    ...
}

Though we can interact directly with the smart contract, we can’t execute functions when modifiers like onlyOwner are properly implemented. However, when dealing with a dApp, the private keys to these privileged addresses almost certainly exist on the web server. And the web application almost certainly has logic that takes user input over the web and calls a privileged function in the smart contract using one of those keys.

Since the dApp does have access to those privileged Ethereum addresses, the third prong becomes: “how can we get the dApp to write to privileged functions in the smart contract on our behalf?”

Three-pronged approach

All this considered, we have our attack surface:

  1. A standard web application assessment (authentication, access control, session management). This may not involve smart contracts at all. Horizontal/vertical privilege escalation, database injection, XSS, etc.

  2. A smart contract audit. Permission issues, overflows/underflows, race conditions, etc.

  3. Attempting to forge privileged writes to the smart contract through the web interface. Can you get the web application to interact with the smart contract in a way it didn’t expect?

Intercepting requests with Burp

When you go to sign up for an account on Cryptokitties, the web application logic extracts your Ethereum address you expose from your MetaMask extension. It then asks you to enter your email address and nickname for your account.

Sign up

The next part is key: because dApps deal with your Ethereum accounts, they are based on public key authentication. Not password authentication.

To this effect, Cryptokitties asks you to sign a message (“Cryptokitties”) to ensure that you own the private key associated with your address.

Sign a message

If we intercept the request, we see:

Intercept sign message

In theory, Cryptokitties would verify that the data in the sign parameter (your signed “Cryptokitties” message) corresponds to the address parameter, your Ethereum address.

This validation takes place in the web application’s logic. I have encountered several dApps that do not validate the signature properly, allowing me to swap out my signup Ethereum address with one that does not match my signature, like so:

Intercept sign message

Cryptokitties validates signatures properly, but when a dApp does not, I have produced denial of service for the owner of the spoofed Ethereum address, and forged their identity on that application.

Signing in

Here’s an example of a dApp handling authentication. After signing up for an account, any subsequent login from your Ethereum address requires you to sign a detailed message containing your intention (I am signing in), your email address, and the current time.

Sign in

Why does this dApp break down the signature in these fields?

  1. Intention: Signing something and sending that signature somewhere is dangerous if the user doesn’t understand what they are signing. It makes the intention of the signature clear in the message text.
  2. Email address: It recovers the email address from your signature and sees if that email address was used to sign up with the Ethereum address that signed the message. If this is a match, the login attempt is valid (so far).
  3. Timestamp: This prevents a replay attack. If the current time was not included in the signature, an attacker who saw the signature could replay it at any time to authenticate as that user. Instead, it only considers the signature valid if the web application receives it within a couple minutes of the signature’s stated timestamp.

Bloom intercept login

Altering any of these fields should produce an error.

Smart contract vulnerabilities

We discussed one of the prongs of our attack as auditing the smart contract for vulnerabilities directly. Let’s examine a few vulnerabilities seen in the wild.

batchOverflow

This one was dubbed batchOverflow. See if you can find how the attackers exploited this vulnerability before looking at some of these answers.

A more detailed analysis can be found here.

Reinitializing wallet owner

Parity suffered a major loss by not implementing a proper modifier as we discussed earlier. Anyone could call the initWallet function, which allowed them to set their own address as the owner of that wallet.

Typically, the owner of a wallet (or contract) is set in the contract’s constructor, which is only called once. Any subsequent change to that address would take place in a function that required the signature from the initial owner. Here, no such modifier existed, and initWallet could be called whenever.

Reinitialize wallet

A more detailed analysis can be found here.

Smart contract auditing tools

Powerful open source tools exist for auditing smart contract code. Among them are Manticore by Trail of Bits and Mythril by ConsenSys. I will leave the finer details of those tools for another post.

Wrapping up

Hopefully this post gave you a better understanding about the attack surface of a dApp, and how it differs from a standard web application.

For more content on Ethereum and blockchain security, follow my Twitter, where I post about those topics frequently.