Dangerous Repeated Calls to Untrusted Contracts

ChainSecurity
ChainSecurity
Published in
3 min readFeb 8, 2019

On February 6 the Genesis Alpha DAO of DAOstack was hacked. The attacker used a single transaction to perform the exploit, walking away with ~$15k. For an explanation of the vulnerability specific to the Genesis Alpha DAO see here. This post explains the vulnerability in a more general sense. To secure smart contracts against this vulnerability in the future, we extended Securify to check for this new type of vulnerability pattern. At the end of this article we describe how users can check if their contracts may be vulnerable. Further, we will include it in the Smart Contract Weakness Classification Registry, which lists common vulnerabilities for smart contracts.

Vulnerability

The vulnerability is caused by repeated function calls to an untrusted contract inside a single transaction, assuming that these return the same value each time. This can be easily overlooked especially in the case of seemingly benign getter functions (such as token.decimals()), which are often expected to return the same value. However, an attacker may craft a malicious contract that returns different values depending on how many times the function has been called, as illustrated below.

The attacker assumes that InvestorRegistry has already been deployed on the blockchain. The attacker deploys the Attack contract, which inside its constructor deploys a new Investor contract. This contract has the correct interface as expected by InvestorRegistry, however the implementation of addr() has been updated to exploit the assumption that addr() will return the same value every time it’s called inside 1 transaction. The Attack constructor then calls InvestorRegistry.register multiple times. This will run the exploit and add the same address to the list of registered investors, something assumed to be impossible. At the end the InvestorRegistry.payout() function is called to let the address that was added x times be paid out x times.

Avoiding the vulnerability

To avoid this vulnerability, the untrusted contract function should be called once, saving the return value in a variable and using that variable in the rest of the function.

Here, the fix is to store the result of investor.addr() to variable investorAddress. This way, the getter function addr() is called only once and therefore the check require(!isRegistered[investorAddress]) can be assumed to hold when updating the mapping isRegistered and the array registered.

Detecting the vulnerability

We have added a security pattern, called Repeated Calls, to Securify that matches subsequent function calls to untrusted contracts with identical arguments. More formally, Securify evaluates the following two conditions for any CALL instruction in the contract:

  1. It checks whether the address of the called contract can be manipulated by an attacker, and is therefore considered untrusted.
  2. It checks if there is another CALL instruction that is executed earlier in the trace and invokes the same function with the same arguments.

If both conditions hold then Securify marks the CALL instruction as a potentially dangerous repeated call. We remark that to precisely check these conditions, Securify relies on its data-flow analysis to evaluate condition (1) and constant-propagation to evaluate condition (2).

In addition to CALL instructions, Securify also checks STATICCALL instructions which may also be vulnerable to this security issue. Although STATICCALL instructions forbid state-changes (which are used in the exploit described above), an attacker may use other information (such as the amount of available gas) to craft an exploit.

How to check if my contract is secure?

If you are unsure whether your contract is secure, we suggest developers to use our latest update of Securify in GitHub (we will update our front-end soon).

The easiest way to run this is by using the public docker image (make sure to use the latest version):

docker pull chainsecurity/securifydocker run --rm -v $(pwd):/project chainsecurity/securify

For any questions related to the vulnerability you can contact us at contact@chainsecurity.com or discuss with fellow Securify-users on Discord.

Unlisted

--

--

ChainSecurity
ChainSecurity

ChainSecurity provides security audits and conducts research and development for blockchain platforms.