The research behind the Ethereum security scanner has been presented at 10+ major
academic and Ethereum conferences, including DevCon3,
EdCON, d10e, Crypto Summit 2018, Crypto Valley Conference 2018, and
Transaction Order Affects Execution of Ether Transfer
The processing order of transactions may affect whether an ether transfer is executed.
In this example, users must guess an input such that the sha3 hash of this input equals 0xDEADBEEF. The first user who provides the correct input receives 10 ether. The processing order of transactions may affect whether this ether transfer takes place or not. This is because if two users concurrently submit transactions with correct inputs, only the first processed transaction would result in a transfer of ether while the second user would not be rewarded.
Transaction Order Affects Ether Receiver
The processing order of transactions may change the receiver of the ether.
In this example, users must guess an input such that the sha3 hash of this input equals 0xDEADBEEF. The user who provides such an input is selected as a winner and can claim a reward by calling the getReward() function. In this example, the winner field could be over-written if a second user also provides a correct input. The receiver of the ether transfer when calling the getReward() function may change if another winning user submits a correct input. This behavior is undesirable and should be avoided.
Transaction Order Affects Ether Amount
The processing order of transactions may affect the amount of transferred ether.
The contract keeps track of the token balances of users. A user can sell the tokens at the price specified in the smart contract, by calling the sellTokens() function. The amount of ether the user receives for the tokens however may change, because the owner can call the setPrice function and changes the token price to a new one. This behavior is undesirable, as users cannot know at what price their tokens would be sold.
A contract is exposed to gas-dependent reentrancy if: (1) the amount of transferred ether depends on state that gets updated only after the transfer is made and (2) the amount of gas handed to the caller depends on amount of available gas. The call instruction for ether transfer grants control over the transaction execution to the ether receiver while handing all available gas, and this allows the receiver to call the contract again (before its state is updated) to receive the same amount of ether again. An attacker can repeat this process until the contract's balance is drained.
The balance of the wallet is set to zero out only after it is transferred to the transaction sender. This contract is vulnerable to a gas-dependent reentrancy because it uses the call.value() statement, which transfers all available gas to msg.sender and also it updates the state of the contract (setting the balance to zero) after the call statement is executed.
Reentrancy with constant gas
A contract is exposed to constant-gas reentrancy if: (1) the contract sends a constant amount of gas to the receiver (i.e., it uses either send or transfer) and (2) the contract state is updated after the call instruction.
The balance of the wallet is set to zero out only after it is transferred to the transaction sender. This contract is vulnerable to a gas-dependent reentrancy because it uses the call.value() statement, which transfers a constant number of gas to msg.sender and also it updates the state of the contract (setting the balance to zero) after the call statement is executed.
Reentrant method call
A contract is exposed to a reentrant method call if it makes state changes after calling another contract.
Unrestricted write to storage
Unrestricted writes indicate parts in the contract storage that are universally writable by all users. This can be extremely dangerous if the writes are to sensitive fields of the contract, such as owner.
The owner field in the contract above is universally writable by any user. This is because any user can submit a transaction that invokes the transferOwnership function and pass as an argument an arbitrary address. An identical bug cause a major security exploit of Parity's Multisig wallet in July 2017, which allowed an attacker to hijack ownership over wallets and steal their ether.
Unused write to storage
Writes to storage should be used by the contract. If the stored values are meant to be read by external actors, then it is recommended to use events.
The variable marketStatus is written in function setMarketStatus(). However, the contract logic does not rely on the value of the marketStatus.
Call and Send statements returns a non-zero value if an exception occurs during the execution of the instruction (e.g., out-of-gas or stack overflow). A contract must check the return value of these instructions and throw an exception.
This contract on ignores the value returned by the send statement. Consequently, the balance of the transaction sender is zeroed out even if the transfer of ether via the call instruction fails.
Division Before Multiplication
Using division before multiplication is dangerous as it often results in unexpected result. This is because the only numerical type in Solidity is integer, and the result of integer division is rounded down to the nearest integer.
This contract attempts to apply a reward percentage to the tokens of a particular user. To do so, the contract computes the percentage of the reward, by dividing reward (an integr between 0 and 100) by 100 and then multiplying the result with the balance of the user. However, due to rounding of integer division, the result of reward / 100 will always be zero. To prevent this undesirable behavior, the contract must first multiply the reward with the balance of the user and then divide the result by 100.
Division influences Transfer Amount
Integer division results in rounding the result down to the nearest integer. This often results in unexpected calculations and developers must be extra cautious when using division to compute the amount in ether transfers.
This contract computes a reward for a given user and sends it to that user. The reward is computed by dividing the argument reward by 100, then multiplying the result by the balance of the user and finally sending the reward to the user. The result of reward / 100 is however always 0 due to rounding, resulting in unexpected calculation of the user reward.
Selfdestruct statements remove the contract from the blockchain and return all its balance to the address provided as argument. The execution of the selfdestruct statement must be restricted to only those users that are authorized to destroy the contract.
This contract does not restrict the execution of the selfdestruct statement in any way. That is, any use can call the function kill which will remove the contract and will send all of its ether to the transaction sender. Note that this immediately enables any user to steal the contract's ether by simply destroying the contract.
Missing Input Validation
Unexpected method arguments may result in insecure contract behaviors. To avoid this, contracts must check whether all transaction arguments meet their desired preconditions.
This contract does not check the amount argument provided as input to the method withdraw. This may result in transferring more ether than the transaction sender actually has (as stored in balances[msg.sender]). This introduces a critical vulnerability, because the value stored in balances[msg.sender] could underflow since for unsigned integers we have 0 - 1 = 2**256 - 1.
Use Of Origin
The statement tx.origin walks all the way up the call stack and returns the address of the contract that originated the call. Authorization checks based on the tx.origin can result in dangerous callback attacks.
This contract compares the owner address to the one returned by tx.origin, which makes the code vulnerable to callback attacks where calls originating from the UserWallet contract are redirected to the send method to drain the wallet.
Unrestricted ether flow
The execution of ether transfers often should be restricted to an authorized set of users. The scanner reports any ether transfer instructions that can be triggered by an arbitrary user.
This wallet contract allows any user to deposit ether into it by calling the default fallback function. To function withdraw transfers all ether deposited into the contract to the wallet's owner. However, the execution of this transfer is not restricted, which allows any user to trigger the transfer. To fix this, the wallet's owner may want to include a check that would allow only the owner to call the withdraw function.
The locked Ether bug occurs in contracts that can receive ether but do not allow users to extract ether from them (nor to destroy them).
This contract is vulnerable to the locked ether vulnerability because it allows users to deposit ether by calling the deposit function and it does not contain any function that would allow users to withdraw the deposited ether.
Unsafe Call to Untrusted Contract
The target of a call instruction and the data provided as argument must be carefully checked to avoid the execution untrusted code on behalf of the contract.
This contract allows an arbitrary user to execute untrusted code on behalf of the contract. This is because any user can submit an arbitrary address _spender and arbitrary data _data which are provided as argument to the method. The address spender determines the target of the call instruction, while _data determines the function called in the untrusted contract.
Unsafe Dependence On Block Information
Unsafe inputs such as those stored in the block should not be used in security critical operations such as SHA3, which is often used to generate random numbers, and SSTORE, which stores values in the contract's storage.
This contract stores a seed. To generate a random number, it computes the hash of the block number and the seed. This, however, is not a secure way to generate random numbers as the block number can be predicted by attackers.
Unsafe Dependence On Block Gas
Information such as the gas price can be controlled by the transaction sender and must therefore not be used in security critical operations such as SHA3, which is often used to generate random numbers, and SSTORE, which stores values in the contract's storage.
This contract stores a seed. To generate a random number, it computes the hash of the gas price and the seed. This, however, is not a secure way to generate random numbers as the gas price can be chosen by the transaction sender.
Delegatecall dependent on user input
The delegatecall statement enables the contract to execute code of external contracts in the context of the current contract. It is therefore critical for the contract to sanitize which external contract's code is executed and what data is provided to it as an argument.
The function delegate of this contract allows any user to execute arbitrary code in the context of the contract. This is because the user can pass the address of any contract as argument lib and any data as part of the transaction data. Both the external contract lib and the data msg.data must be sanitized before passing them as arguments to the delegatecall command.
Ether transfers whose execution can be manipulated by other transactions must be inspected for unintended behavior.