paint-brush
Understanding and Preventing Honeypot Attacks in Smart Contracts 🔐by@codingjourneyfromunemployment
475 reads
475 reads

Understanding and Preventing Honeypot Attacks in Smart Contracts 🔐

by codingJourneyFromUnemploymentDecember 4th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this article, we explore the vulnerability of smart contracts to Honeypot attacks, using a real-world example involving a Bank, Logger, and Honeypot contract. The attacker exploits the lack of re-entrancy protection in the Bank contract. Additionally, we dissect the interface consistency check, revealing its imperfections. The article concludes with practical strategies to reduce such risks, emphasizing careful examination of external addresses, code obfuscation, reliance on trusted libraries, thorough auditing, and continuous monitoring. Stay informed and secure in the dynamic realm of smart contract development! 👩‍💻🔐
featured image - Understanding and Preventing Honeypot Attacks in Smart Contracts 🔐
codingJourneyFromUnemployment HackerNoon profile picture

Honeypot attacks in smart contracts essentially exploit the check for interface consistency during contract interactions. Although simple, these attacks involve security checks that are necessary when a contract interacts with others and are not impregnable, making such vulnerabilities still common today. 🧐


This reminds us of the need for careful examination of interfaces when writing smart contracts, especially when calling external interfaces. Attackers can use contracts disguised as legitimate interfaces to execute attacks. In this article, I will demonstrate a simple Honeypot attack example and how to prevent such attacks. 🕵️‍♂️


First, Let’s Describe the Process of a Honeypot Attack

Alice deploys 3 simple contracts: a Bank contract, a Logger contract, and a HoneyPot contract. The HoneyPot contract's file is separate, so its code is not visible externally. The Bank contract's main function is to deposit and withdraw ethers, and the Logger contract mainly serves as an event logger, recording information about withdrawal events. This is common in complex contract scenarios, where different functions are written in different modules for decoupling.


The code is as follows: 👩‍💻

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Bank {
    mapping(address => uint) public balances;
    Logger logger;

    constructor(Logger _logger) {
        logger = Logger(_logger);
    }

    function deposit() public payable {
        balances[msg.sender] += msg.value;
        logger.log(msg.sender, msg.value, "Deposit");
    }

    function withdraw(uint _amount) public {
        require(_amount <= balances[msg.sender], "Insufficient funds");

        (bool sent, ) = msg.sender.call{value: _amount}("");
        require(sent, "Failed to send Ether");

        balances[msg.sender] -= _amount;

        logger.log(msg.sender, _amount, "Withdraw");
    }
}

contract Logger {
    event Log(address caller, uint amount, string action);

    function log(address _caller, uint _amount, string memory _action) public {
        emit Log(_caller, _amount, _action);
    }
}

// Let's say this code is in a separate file so that others cannot read it.
contract HoneyPot {
    function log(address _caller, uint _amount, string memory _action) public {
        if (equal(_action, "Withdraw")) {
            revert("It's a trap");
        }
    }

    // Function to compare strings using keccak256
    function equal(string memory _a, string memory _b) public pure returns (bool) {
        return keccak256(abi.encode(_a)) == keccak256(abi.encode(_b));
    }
}


The attacker, Eve, notices the Bank and Logger contracts. The Bank contract's withdrawal function lacks measures against Re-Entrancy, making it vulnerable to liquidity drainage as shown in [this example](https://hackernoon.com/what-is-a-re-entrancy-attack-in-smart-contracts-and-how-to-avoid-it).


Hence, she deploys a simple Attack contract, identical to the one in our previous example link, which triggers recursive calls to the Bank contract's withdraw function via the Attack contract's fallback function upon receiving ether.


The code is as follows: 💻

// Hacker tries to drain the Ethers stored in Bank by reentrancy.
contract Attack {
    Bank bank;

    constructor(Bank _bank) {
        bank = Bank(_bank);
    }

    fallback() external payable {
        if (address(bank).balance >= 1 ether) {
            bank.withdraw(1 ether);
        }
    }

    function attack() public payable {
        bank.deposit{value: 1 ether}();
        bank.withdraw(1 ether);
    }

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}


A complete Honeypot attack process:


  1. Alice first deposits 10 ether into the Bank contract.


  2. Eve calls the Attack.attack function intending to perform a Re-Entrancy attack on the Bank contract. This function first deposits 1 ether into the Ethereum Bank contract, then calls the Bank's withdraw to take out the just-deposited 1 ether.


  3. When the Bank contract sends 1 ether back to the Attack contract, without a receive function, it falls into the fallback function, which recursively calls the Bank contract's withdraw function until all of Alice's 10 ethers are sent to the Attack contract.


  4. Since EVM code execution is synchronous, all subsequent code is blocked until now. Only after the Bank contract sends all 10 ethers to the Attack contract does it execute the logger.log(msg.sender, _amount, "Withdraw"); statement.


  5. Here's the crux. When Alice deployed the Bank contract, the _logger parameter in the constructor wasn't the real Logger contract's address but the address of the HoneyPot contract. The HoneyPot contract has a log function with the same function name, parameters, and return values as the Logger contract's log function, meaning these interfaces are identical. Thus, the object instantiated in the Bank contract's constructor logger = Logger(_logger); is actually the HoneyPot contract, and calling its log function would pass the interface consistency check.


  6. Therefore, executing logger.log(msg.sender, _amount, "Withdraw"); actually runs the HoneyPot contract's log function.


  7. This function checks if the string passed in the Bank.withdraw function's statement logger.log(msg.sender, _amount, "Withdraw"); is "Withdraw". Clearly, it is, so HoneyPot.log executes the revert("It's a trap"); statement.


  8. All transactions that occurred in the Bank.withdraw function call are then rolled back. Thus, not only can the attacker Eve not get Alice's deposited ether, but her own deposited ether is also locked in the contract.

How to Prevent Such Honeypot Attacks

This attack is straightforward, but it shows that interface consistency checks in function calls between contracts are not perfect, being merely checks of function signatures.


Based on the principle of the attack, we can consider some ways to reduce the risk of such vulnerabilities:


  1. Attackers might use malicious contracts disguised as legitimate ones. Therefore, when a contract interacts with external addresses, especially when calling critical functions, these external addresses and their internal code should be carefully examined.


  2. In the smart contract environment, code transparency is a double-edged sword. Although it provides auditability and trust, it also exposes the internal logic of the contract. When designing defenses against Honeypot attacks, consider hiding key logic or verification steps in contracts that are not easily analyzed (like the HoneyPot contract in the example).


  3. Use libraries like openzeppelin that have been verified over time. These libraries reduce code and logic errors and provide community-verified best practices. If someone else's contract does not use these simple methods to reduce risk, we need to know why before calling them.


  4. Adequate auditing and testing before contract deployment. Continuous monitoring and updating after launch, and timely upgrades or fixes if such vulnerabilities are discovered.


That's all about the HoneyPot vulnerability and related security strategies. Since you've read this far, help me out with a like! 👍