🇺🇦 Hacken was born in Ukraine, and we stand with all Ukrainians in our fight for freedom!

🇺🇦 Hacken stands with Ukraine!

Learn more

Smart Contract Vulnerabilities & How to Prevent Them

Smart Contract Vulnerabilities & How to Prevent Them

Published: 8 Jul 2022 Updated: 22 Mar 2023

Smart contracts are self-executing programs that can be programmed to execute on their own when specific conditions are met. In the area of blockchain and decentralized finance, their popularity is rising. Smart contracts, however, are prone to flaws like all other software. This post covers the most typical smart contract vulnerabilities and their examples, along with solutions for avoiding them.

VulnerabilitySeverity
External Calls to Arbitrary AddressesHigh
Checks-Effects-Interactions Pattern Violation / ReentrancyHigh, Medium
Missing Validation / Input Validation ViolationHigh, Medium
Flashloan AttackHigh
Inconsistent DataHigh, Medium
Floating PragmaLow
Smart contract vulnerabilities and their severity level

Most Common Smart Contract Vulnerabilities And How To Prevent Them

1. External Calls to Arbitrary Addresses (High)

Malicious contracts may withdraw the contract’s balance in response to external calls to arbitrary addresses, resulting in a loss of funds. Due to this flaw, attackers can use the contract’s capabilities to their advantage and run malicious or unauthorized code that can extract assets from the contract or can break the working mechanism of the contract.

Example of the Dexible exploit (20 February 2023). Dexible is a decentralized exchange (DEX) aggregator and execution management system. One of their essential features was self swapping. The selfSwap function allows users to define a router contract which is the main problem that caused this hack.

Attention! This function can be called by anyone and the contract makes external calls to an arbitrary router address.

contract Dexible {
    function selfSwap(address tokenIn, address tokenOut, uint112 uint112 amount, address router, address spender, TokenAmount routeAmount, bytes routerData) external {
        IERC20(routeAmount.token).safeApprove(spender, routeAmount.amount);
        // Here an external call is made to the router
        (bool s, ) = router.call(routerData);
    }
}

Instead of a safe&valid DEX, a hacker made a contract to call a malicious ERC20 contract and drained $2M worth of tokens. The crucial part is the contracts were not audited.

As we always say, bugs are shallow when there are many eyes.

Seher Saylık, Hacken Smart Contract Auditor
Sample malicious contract

contract maliciousRouter {
    ...
    //Instead of a validated router contract, the hacker implements this tricky function and makes Dexible contract to transfer its assets to this malicious contract.
    function transfer() external {
        IERC20(USDC).transferFrom(msg.sender, address(this), IERC20(USDC).balanceOf(msg.sender));
    }
}

These code lines are simplified to demonstrate the hack easily. More info and the contract repo can be found here: Dexible. Transaction: 0x138daa4c…

How to prevent External Calls to Arbitrary Addresses? Developers must implement the necessary security mechanisms to guarantee that external calls are made only to trusted and intended addresses.

2. Checks-Effects-Interactions Pattern Violation / Reentrancy (High)

Reentrancy attacks occur when an attacker exploits a vulnerability in a smart contract that allows them to repeatedly call a function before the previous function call has finished executing. This can occur when a smart contract has a function that calls another contract’s function, but fails to properly update the user’s balance or state before executing the function call.

Example of the Rari Capital hack (30 April 2022). Rari protocol is a decentralized platform that allows lending and borrowing. Protocol’s code was forked from Compound and their developers accidentally used one of their old commits which led them to get hacked.  

Their borrow function was lacking of proper checks-effects-interactions pattern. Seeing this, the exploiter (1) promptly got 150,000,000 USDC as a flashloan, (2) deposited it into fUSDC-127 contract, and (3) called the vulnerable borrow function to borrow some amount of assets.

As we can see, the function firstly transfers the borrowed amount and then updates the accountBorrows mapping. Since the function does not have any reentrancy guard, the hacker called the borrow function repetitively before it updated the mapping and drained the funds worth of $80 million.

function borrow() external {
    …
    doTransferOut(borrower, borrowAmount);
    // doTransferOut: function doTransferOut(borrower, amount) { 
    (bool success, ) = to.call.value(amount)("");
    require(success, "doTransferOut failed");
}
// !!State updates are made after the transfer
accountBorrows[borrower].principal = vars.accountBorrowsNew;
accountBorrows[borrower].interestIndex = borrowIndex;
totalBorrows = vars.totalBorrowsNew;…
}

The hacker borrowed x amount of assets using a flashloan and ran the doTransferOut function five times in a loop. After paying back the flashloan, he/she took the remaining 4x amount and disappeared with it. Transaction: ​​0xab486012 

How to prevent Checks-Effects-Interactions Pattern Violation / Reentrancy? To prevent reentrancy attacks, there are several things to do according to the implementation. Developers must follow the “checks-effects-interactions” pattern, which involves executing all check statements and state updates before making any external calls. Additionally, it’s necessary to implement a guard to remove the ability to make multiple calls to the same function (see OpenZeppelin’s ReentrancyGuard). Alternatively, smart contract developers can restrict the gas amount available for the function to avoid infinite loops. However, the latter requires some attention since transaction gas limits of a network can be changed by the protocol.

3. Missing Validation/Input Validation Violation (High, Medium)

Example of the Poly Network Exploit (10 August 2021). The Poly Network is a decentralized finance (DeFi) platform enabling interoperability between different blockchains. On August 10, 2021, the platform suffered a massive hack, resulting in a loss of over $600 million worth of cryptocurrency. The attack exploited a vulnerability in the platform’s code, which allowed the attacker to steal assets from different blockchains supported by the platform.

The Poly network has a contract called EthCrossChainManager, which can trigger messages from another chain. Anybody may use the contract to execute a cross-chain transaction by calling the function verifyHeaderAndExecuteTx, which validates the block header and transaction inclusion. The contract’s weakness is the failure to stop users from accessing the EthCrossChainData contract, which manages the list of public keys used to authenticate data from the opposite chain. Users can cause a function to change the public keys by making changes to this list without needing to hack private keys. By delivering a cross-chain message directly to the EthCrossChainData contract, the attacker can force EthCrossChainManager to call the latter contract.

Exploiter used the _method parameter as an open gate to make the EthCrossChainManager contract call the right function since one of the parameters of hashed data can be specified by callers.

This method-ID is generated by keccak-hashing method’s name, including the signature, and then taking the first four bytes of the resulting hash:

   /* @notice Dynamically invoke the targeting contract, and trigger execution of cross chain tx on Ethereum side
    *  @param _toContract The targeting contract that will be invoked by the Ethereum Cross Chain Manager contract
    *  @param _method At which method will be invoked within the targeting contract
    *  @param _args The parameter that will be passed into the targeting contract
    *  @param _fromContractAddr From chain smart contract address
    *  @param _fromChainId Indicate from which chain current cross chain tx comes 
    *  @return true or false
    */
   function _executeCrossChainTx(address _toContract, bytes memory _method, bytes memory _args, bytes memory _fromContractAddr, uint64 _fromChainId) internal returns(bool) {
       ...(success, returnData) = _toContract.call(
abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_args, _fromContractAddr, _fromChainId)));...
   }
bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)")));

Since it converts the hashed data to a bytes4 variable, only 4 bytes will be validated as a signature. As we can guess, it will not be that hard to find the same output with malicious input data by brute-forcing.

  • 0xv14cts34rf44d…
  • 0xv14c2156789a…

Transaction: 0xd8c1f7…. Github repo: EthCrossChainManager 

How to prevent Missing Validation/Input Validation Violation? Developers should add strong input validation checks to the code to guarantee that user input is structured correctly and is within allowable limitations.

4. Flashloan Attack (High)

Flashloans lets users borrow massive amounts of crypto for just one quick transaction. Due to their adaptability and accessibility they don’t require collateral and may be used for a multitude of purposes. However, because they don’t require any collateral, flashloans can be used to manipulate the market or take advantage of smart contract weaknesses

Example of the Beanstalk DeFi exploit (17 April 2022). Beanstalk is a stablecoin decentralized protocol that rewards users for contributing funds to a central funding pool called “the silo.” The protocol keeps the “bean” token at around $1. The other thing, Beanstalk features a governance structure that allows token holders to vote to change the code, with voting power proportional to the number of tokens owned. This governance setup ultimately led to a vulnerability in the system that resulted in the project’s downfall.

The exploiter created two proposals. One was a request to send all the funds to their address. The other was donating 250000$ in BEAN tokens to Ukraine. And here comes the tricky part: the emergencyCommit function allows users with super majority of funds to approve a proposals.

function emergencyCommit(uint32 bip) external {
  require(isNominated(bip), "Governance: Not nominated.");
  // Requires 1 day to be passed (getGovernanceEmergencyPeriod=1day)
  require(
  block.timestamp >= timestamp(bip).add(C.getGovernanceEmergencyPeriod()),     "Governance: Too early.");
  require(isActive(bip), "Governance: Ended.");

  //Any vote can be executed if proposer has the super majority(getGovernanceEmergencyThreshold=67%)
  require(
  bipVotePercent (bip). greaterThanOrEqualTo(C.getGovernanceEmergencyThreshold()),   "Governance: Must have super majority." );
  _execute(msg.sender, bip, false, true);
}

The protocol forgot to get precautions for flashloans enabling the hack just one day after the malicious proposal. The exploiter got a massive flashloan: 350M DAI, 500M USDC, 150M USDT, 32M Bean and 11.6M LUSD. They converted them to BeanStalk DeFi’s governance BEAN3Crv-f and BEANLUSD-f tokens. Reaching the 78% funds majority, they called emergencyCommit to execute both proposals they had created. See the transaction 0xcd314668…, and the BeanStalk contract.

How to mitigate Flashloan Attack?

  1. Require collateral for loans to make it more expensive for hackers to borrow assets.
  2. Implement limits on the size of flash loans that can be taken out, which can help mitigate large-scale attacks.
  3. Add time locks to smart contracts to hinder the ability to repay flashloans too quickly, giving developers time to detect and prevent attacks.

Moreover, projects with governance voting systems must implement anti-flashloan mechanism to block exploiters from using flashloans to gain the majority of the voting power.

5. Inconsistent Data (High, Medium)

When smart contract functions use data without correct validation or verification, several issues may arise. Inconsistent data might result in improper contract execution, which may result in the loss of funds or other unfavorable effects. Inconsistent data can also damage the contract’s reputation and trustworthiness, which will lower adoption and cause a decline in user confidence.

How to prevent Inconsistent Data?

  • Validate and verify data: Data can be validated and verified using methods like hash functions, digital signatures, and encryption.
  • Configure strict access controls: Smart contracts should have strict access restrictions to stop unwanted data alteration.
  • Develop smart contracts thoroughly: Developers should thorough test their contracts, including functional and security testing, to find and correct any data discrepancies or inaccuracies.

6. Floating Pragma (Low)

Solidity pragmas are an important aspect of secure smart contract development, as they define the version of the Solidity compiler to be used for compiling the contract, and can have an impact on the security and functionality of the contract.

Using outdated or vulnerable Solidity versions can potentially lead to security vulnerabilities, as well as unexpected behavior in the contract’s execution. Additionally, certain pragma settings can impact the contract’s gas usage, which can have implications for the contract’s cost and efficiency.

Floating pragma
pragma solidity ^0.8.0;

Secure, locked pragma
pragma solidity 0.8.17;

How to prevent Floating Pragma? It’s important for smart contract developers to carefully choose and lock the Solidity pragma for their contracts, and to keep them up to date with the latest security patches and best practices. 

Wrapping Up

Apart from the examples above, malicious behaviors are still persistent in the blockchain area. Smart contract developers play a key role in securing their blockchain apps. They are responsible for implementing proactive measures to protect contracts against potential threats and vulnerabilities.
Think of these measures as the Six Pillars of a Strong Defense System:

  1. Best practices: Stick to the standards and best practices while creating and implementing smart contracts.
  2. Testing: Write unit tests before deploying smart contracts to ensure the system runs as intended.
  3. Auditing: Have smart contracts examined by qualified auditors to find any potential security flaws and recommend best practices to strengthen security.
  4. Code reviews: Do code reviews to detect problems, and in particular to respond to them quickly, before the implementation progresses too far
  5. Monitoring: Set up monitoring and alert systems to detect and respond to any unusual activity or suspicious behavior.
  6. Access controls: Put permissions and access controls to specify who can access funds and make changes on the smart contract data to mitigate asset loss due to data manipulation or outside intervention.

share via social

Subscribe to our research

Enter your email address to subscribe to Hacken Research and receive notifications of new posts by email

Interested in getting to know whether your systems are vulnerable to cyberattacks?

Tell us about your project

  • This field is required
  • This field is required
    • telegram icon Telegram
    • whatsapp icon WhatsApp
    • wechat icon WeChat
    • signal icon Signal
  • This field is required
  • This field is required
  • This field is required
  • This field is required
This field is required
departure icon

Thank you for your request

Get security score on

  • certified logo
  • coingeco logo
  • coin market cap logo

1,200+ Audited Projects

companies logos

Apply for partnership

  • This field is required
  • This field is required
  • This field is required
  • This field is required
    • Foundation
    • VC
    • Angel investments
    • IDO or IEO platform
    • Protocol
    • Blockchain
    • Legal
    • Insurance
    • Development
    • Marketing
    • Influencer
    • Other
This field is required
This field is required
departure icon

Thank you for your request

Get security score on

  • certified logo
  • coingeco logo
  • coin market cap logo

1,200+ Audited Projects

companies logos

Get in touch

  • This field is required
  • This field is required
  • This field is required
  • This field is required
This field is required
By submitting this form you agree to the Privacy Policy and information beeing used to contact you
departure icon

Thank you for your request

Get security score on

  • certified logo
  • coingeco logo
  • coin market cap logo