Detailed explanation of Ethereum smart contract vulnerabilities:Code Execution Vulnerability
Detailed explanation of Ethereum smart contract vulnerabilities, Welcome to follow us and discuss
Author: hzqd@lunaray security team
0x01. Vulnerability introduction
Code Execution Vulnerability aka Cross-Contract Call Vulnerability，It is maliciously constructed data caused by the Call series of functions. During the development of smart contracts, mutual calls of contracts often occur.
In order to implement certain functions, developers use functions of another contract. However, in the actual development process, developers often use functions with any public attributes in order to take into account the flexibility of the code. The addresses and character sequences in the contract are all determined by the user. If you pass it in, you can use a function with any address.
0x02. Call Series function introduction
First, learn about the mutual use of contracts in Solidity. There are two main ways to use contracts in Solidity.
- Use the encapsulation method to encapsulate the contract address as a contract object to call the function on it
- Use functions directly to call other contracts.
Solidity provides three functions: call(), delegatecall(), and callcode() to implement calls and interactions between contracts. Because of the existence of these flexible calls, these functions are abused by contract developers, and even unscrupulously provide the function of arbitrary calls. lead to various security vulnerabilities and risks.
The following is the call(), delegatecall(), callcode() function call model in Solidity:
During the calling process of the above three functions, the built-in variable msg in Solidity will change with the initiation of the call, and msg stores a lot of information about the caller, such as the amount of the transaction, the sequence of calling function characters, and the calling function. Initiator’s address information, etc.
- call()：The most commonly used calling method, the external calling context of call is the callee contract, that is, the execution environment is the runtime environment of the callee, and the value of the built-in variable msg will be changed to the caller after the call.
- delegatecall()：The external calling context of delegatecall is the caller’s contract, that is, the execution environment is the caller’s runtime environment, and the value of the built-in variable msg will not be changed to the caller after the call.
- callcode()：The external calling context of call is the caller’s contract, that is, the execution environment is the caller’s runtime environment. After the call, the value of the built-in variable msg will be changed to the caller.
In addition, due to the similarity of these three external calling functions, the differences are easy to be confused, leading to abuse. The possible security problems include:
- An attacker can directly steal the currency in the vulnerable contract.
- An attacker can set himself up as the contract owner.
0x03. Delegatecall function call injection attack
Delegatecall function vulnerability analysis
In the delegatecall() function, the external calling context of the delegatecall() is the caller contract, that is, the execution environment is the caller’s execution environment, and the value of the built-in variable msg will not be modified to the caller after the call.
When using delegatecall normally to call the specified function of the specified contract, the function id used by the function selector should be fixed to lock the function to be called, but in fact, for flexibility, some developers will use msg.data directly as a parameter , such as the following contract:
The above demonstration is generally not encountered. Of course, the parameter does not have to be msg.data. Even if the calling function name and parameters are written to death, we can also create an attack contract to meet the conditions, which is still very harmful.
Then let’s look at the more complicated case:
It seems that there is no issue, but the position of the variable b of the two contracts is different from that of the variable c, let’s take a look at the result of the execution.
when value is not called
Value after call:
It may seem a little unexpected that the variable that is changed in the second contract is not b but c, which is not the same as the code we called,
It may seem a little unexpected that the variable that is changed in the second contract is not b but c, which is not the same as the code we called,However, in fact, it involves the memory access mechanism when using delegatecall, which can prove that the delegatecall() function has a vulnerability of variable overwriting.
Delegatecall function vulnerability code example
There are two delegatecall calls here, the targets are the setTime() functions of different libraryContract contracts. It can be seen that the original meaning is to use these two functions to update the storedTime of this contract. Of course, a library contract is directly replaced here for convenience. Through the knowledge obtained above, we find that the storedTime updated here is also a storage variable in the library contract, which means that we can borrow it to cover the corresponding position in the main contract variable, here we can override the timeZone1Library variable, it is not difficult to find that after operating it, we are relatively manipulating the address of the called contract, so that we can create a contract ourselves and execute arbitrary functions.
Next, let’s do a simple test, first deploy two libraryContract contracts, fill in their addresses into our main contract, and then deploy the main contract to prepare for testing. The contract used for the attack is as follows:
Because the final target we want to control is that the storage bit corresponding to the owner of the main contract is 3, we need to put two variables for placeholders in front, and then overwrite the address of the Attack contract to the timeZone1Library of the main contract, and directly put It can be passed as a parameter to the setSecondTime function.
It can be seen that timeZone1Library has been modified to our attack contract at this time, and the owner variable can be successfully changed by calling the setFirstTime function with the attacker’s account.
0x04. call function call injection attack
Call Vulnerability Basics knowledge
Let’s start at the basics involved in call-related vulnerabilities. The call() function can call a contract or a method of a local contract in the following two ways.
call(method selector,arg1，arg2, ….)
You can pass method selectors and parameters by passing parameters, or you can pass a byte array directly (of course, you have to construct msg.data yourself). The call() function injection vulnerability, as the name suggests, is that the outside world can directly control the parameters of the call() function call in the contract. According to the injection location analysis, there are the following three scenarios.
• Controllable parameter list
• Method selector controllable
The code snippet in question
In the contract code, there is an approveAndCallcode method, this method allows to call some methods of the _spender contract or pass some data, by introducing _spender.call() to complete this function.
If _spender is controllable, you can specify _spender as the contract’s own address, and then you can call some methods that non-owners can call. For example, we use the identity of the contract to call the transfer number. The code of the transfer function is as follows:
Pay attention to where the vulnerability may appear: we specify the spender as the contract’s own address, and then construct the extraData by ourselves, such as specifying the _to parameter of transfer as our own account address. In this way, you can directly transfer all the tokens in the contract account to your own account, because through call injection, the execution environment is in the transfer method of the callee. In the view of transfer, msg.sender is actually the caller’s own contract. address, allowing hackers to steal the contract’s ether.
Method selector injection
The code snippet in question:
In this contract, there is a logAndcail() method. We have control over the _fallback parameter, that is, we can specify any method that calls the _to address. There are three parameters msg.sender , _value , and _data in this contract, and their types are Address , uint256 and Bytes . So, can we only call these three types of methods? the answer is negative.
Here we will mention a feature of the EVM when dealing with calldata. When EVM obtains parameters, it does not check the number of parameters. Therefore, as long as the parameters required by the method are found, other parameters will be ignored and will not have any effect; attackers often use this to attack.
Suppose we use the above method to call the following approve function. The approve method here has two parameters, and the types are address and uint256, so the call can be successful. This will authorize the tokens in the contract account to our own account.
Call function vulnerability code example
On May 11, 2018, ATN technicians received an abnormal monitoring report showing that the supply of ATN Token was abnormal. The technicians quickly intervened and found that the Token contract was attacked due to a loophole.
The ATN Token contract uses ERC-223, which is an extension of the traditional ERC-20 Token contract and uses the DS-AUTH library in it. There is no problem using the ERC-223 or DS-AUTH library alone, but there are mixed vulnerabilities in the ERC-223 method and the DS-AUTH library. If the two are used in combination, an attacker can call the setOwner() method through a callback function. Get advanced permissions.
The following is the vulnerability contract. If the code is too long, only the link is attached.
We use Remix IDE (remix.ethereum.org) to deploy the vulnerability contract to reproduce the vulnerability.
The contract performs a lot of calculations, and the default Gas Limit may fail to be deployed. Therefore, we add a 0 to the original value before deploying.
First look at the core vulnerable code snippet in the transferFrom() function.
By observing the parameters of the transferFrom() function, it can be found that these parameters are controllable, which means that when we use the transferFrom() function, this contract can call any function of other contracts, and the parameters are controllable to a certain extent.
Because the _to parameter is also controllable, and there is no restriction on the _to parameter in the code, it only determines whether it is a smart contract, so you can control _to to be the contract itself, and call any public function of the contract itself;
Let’s take another look at the contract setOwner() function to modify the contract owner;
It can be found that setOwner() only receives an address parameter.
Let’s look again at the function modifier auth for this function.
We found that when the caller is the contract itself, it can pass the authentication and can directly use setOwner() to modify the contract owner.
Then we can directly call setOwner to modify the contract owner through the transferFrom() function constructor call to attack.
The transferFrom() function with the _custom_fallback parameter, fill in the corresponding parameter information, as shown in the figure below, and then execute.
_from :The parameter should be the attacker’s contract address
_to :The parameter should be the current contract address
_amount :parameter should be 0
_data :The parameter should be 0x000000
_custom_fallback :Parameters should be: setOwner() function (ie setOwner(address) ).
After the execution is completed, it can be found that the execution is successful, and no exceptions are found. Theoretically speaking, the owner of the contract is now the attacker account, execute the owner() function to check, we find that the owner permission and become the attacker account, as shown below.
Next, you can use the owner permission to execute any privileged function that has been implemented (for example, execute the mint() function to issue coins to yourself, as shown in the figure below);
So far, the coin has been issued successfully.
0x05. Vulnerability Prevention
It is not difficult to find from the above analysis that the key to the cross-contract call vulnerability lies in the three functions call(), delegatecall(), and callcode(). Although these three functions provide great convenience for calling between contracts, when we analyze their parameter passing and calling implementation mechanisms, we will also find that, in fact, these three functions are convenient to implement and have security risks themselves.
Advice for preventing improper use of delegatecall
The problem of delegatecall is mainly caused by two aspects. On the one hand, the data sent when the call is made or the called contract address is controllable, which may lead to the execution of malicious functions and cause great harm. For this kind of vulnerability, developers still need to develop Implement the delegatecall correctly according to the safe writing method to avoid malicious use; on the other hand, it is the variable coverage that may be caused when the storage variable is involved in this complex context. For this kind of vulnerability, avoid using the delegatecall directly to make calls , you should use library to achieve code reuse, which is also a safer way to reuse code in Solidity.
Advice for prevention of call injection
For sensitive operations, you should check that sender is this; use private and intetnal to restrict access, like this:
Project Security Advice
The contract security audit should be conducted by a third-party professional security audit agency before the contract is deployed online.