Common Smart Contract Vulnerabilities and Attack Techniques

·

Smart contracts power decentralized applications across blockchain ecosystems, enabling trustless automation and value exchange. However, their immutable nature and direct access to funds make them prime targets for exploitation. Understanding common vulnerabilities is essential for developers, auditors, and users alike. This article explores key attack vectors in Ethereum-based smart contracts, explains how they work, and provides best practices for mitigation — all while aligning with current security standards and SEO-friendly technical content guidelines.

Integer Overflow and Underflow

One of the most fundamental vulnerabilities in early Solidity contracts is integer overflow and underflow. Prior to Solidity version 0.8, arithmetic operations did not include built-in checks for overflows. Since uint (unsigned integer) types wrap around when exceeding their maximum or minimum values, attackers could manipulate balances by causing intentional overflows.

👉 Discover how secure arithmetic prevents costly exploits in DeFi protocols.

Consider a simple token contract where the transfer function fails to validate arithmetic safety:

function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
}

An attacker can supply a _value greater than their balance, causing balances[msg.sender] - _value to underflow into a very large positive number due to unsigned integer wrapping. This results in an inflated balance after the transfer.

Solution: Use SafeMath libraries such as those from OpenZeppelin. Starting with Solidity 0.8, overflow checks are enabled by default, eliminating this risk in modern codebases.

Array Length Underflow Leading to Arbitrary Storage Writes

A more dangerous variation of integer underflow involves manipulating dynamic array lengths. If a contract allows decreasing an array's length without proper validation, it can be exploited to overwrite arbitrary storage slots.

For example:

function retract() contacted public {
    codex.length--;
}

When codex.length reaches zero and is decremented again, it wraps to 2^256 - 1, effectively granting access to nearly the entire storage space. Since array data is stored starting at keccak256(slot), an attacker can calculate the storage position of other variables and use functions like revise(uint i, bytes32 _content) to overwrite critical state data — such as ownership or sensitive credentials.

This technique was famously used in the Alien Codex challenge on Ethernaut, demonstrating how low-level storage layout knowledge enables full contract compromise.

Reentrancy Attacks

Reentrancy remains one of the most notorious smart contract vulnerabilities, famously responsible for the 2016 DAO hack.

In a typical reentrancy scenario:

function withdraw(uint256 amount) public {
    require(balances[msg.sender] >= amount);
    msg.sender.call.value(amount)("");
    balances[msg.sender] -= amount; // Updated *after* external call
}

The problem lies in the order of operations: funds are sent before the balance is updated. During the .call, the recipient can trigger a fallback function that recursively calls withdraw() again — each time bypassing the balance check because the state hasn’t been updated yet.

Mitigation strategies:

👉 Learn how real-world DeFi platforms prevent recursive call exploits.

Exploiting Pseudo-Randomness

True randomness does not exist on deterministic blockchains. Contracts relying on predictable sources for random selection open themselves to manipulation.

Block Variables as Random Seeds

Using block.timestamp, block.number, or block.coinbase for randomness is insecure because miners influence these values. In games or lotteries, attackers can predict outcomes based on known block parameters.

Examples include:

Blockhash Limitations

While blockhash(blockNumber) seems safer, Ethereum only retains recent 256 block hashes. Any attempt to retrieve older hashes returns zero — a critical flaw if contracts depend on historical blocks for randomness.

Additionally, rollback attacks occur when prediction and payout happen in the same transaction. An attacker can revert the transaction upon receiving an unfavorable result, effectively "retrying" until success — at no cost.

Flash Minting (Sybil / "Farming" Attacks)

Also known as "薅羊毛" (literally “pull wool”), this attack exploits poorly scoped incentives. A contract may offer one-time rewards (e.g., token airdrops), but fail to restrict eligibility beyond basic address checks.

Example:

function airdrop() public {
    require(!airdropped[msg.sender]);
    airdropped[msg.sender] = true;
    balances[msg.sender] += 100;
}

Attackers create multiple smart contracts — each appearing as a unique address — to claim rewards repeatedly. Funds are then drained back to a central wallet.

Defense: Use identity verification (e.g., ERC-721 ownership), proof-of-humanity systems, or decentralized reputation models to limit sybil attacks.

Reading "Private" State Variables

Despite being labeled private, Solidity variables are not truly hidden. They reside in predictable storage slots and can be read directly from the blockchain.

For instance:

While private prevents direct function calls, it offers no data confidentiality. For true secrecy, off-chain solutions or zero-knowledge proofs (ZKPs) are required.

Dangerous Delegatecall Usage

delegatecall executes code from another contract in the context of the calling contract — meaning it uses the caller’s storage, balance, and state.

Vulnerable pattern:

function setFirstTime(uint _timeStamp) public {
    timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}

If the called library modifies state variables at specific storage slots, and those slots map to critical fields (like owner or function pointers), an attacker can hijack control flow.

In Preservation, updating a time variable overwrites a library address due to matching storage layout — turning delegatecall into a privilege escalation vector.

Similarly, Puzzle Wallet shows how improper storage alignment during delegatecalls leads to ownership takeover.


Frequently Asked Questions

Q: Can integer overflow still happen in modern Solidity?
A: No — starting with Solidity 0.8, arithmetic operations automatically check for overflows and underflows, making SafeMath largely obsolete unless using older versions.

Q: How can I securely generate random numbers in smart contracts?
A: Avoid on-chain randomness. Instead, use Chainlink VRF (Verifiable Random Function) or commit-reveal schemes with off-chain entropy.

Q: Is delegatecall ever safe to use?
A: Yes — but only when calling trusted, immutable libraries with known storage layouts. Never allow user-controlled addresses in delegatecall targets.

Q: Can private variables be encrypted on-chain?
A: No — all blockchain data is public. Use off-chain encryption or advanced cryptography like ZKPs for confidentiality.

Q: What is the best way to prevent reentrancy?
A: Follow the Checks-Effects-Interactions pattern and use nonReentrant modifiers from audited libraries like OpenZeppelin.

Q: How do I protect against flash minting attacks?
A: Implement rate-limiting per identity, use NFT-based eligibility, or integrate decentralized identity protocols.

👉 Explore secure contract development tools and resources today.


Core Keywords

By understanding these common flaws and applying defensive programming practices, developers can build more resilient decentralized applications. As blockchain adoption grows, so must security rigor — ensuring user trust and ecosystem integrity.