Common Smart Contract Vulnerability Attacks

·

Abstract
A breakdown of classic and prevalent smart contract vulnerabilities and exploitation techniques

Integer Overflow

The uint type in Solidity is widely used but inherently vulnerable to overflow since it's an unsigned integer. Prior to Solidity 0.8, there was no overflow protection, making contracts susceptible to manipulation.

Example: Token Balance Exploit

contract Token {
    mapping(address => uint) balances;
    uint public totalSupply;

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

Exploit: Transferring values exceeding the balance causes an underflow, inflating the recipient's balance to an extreme value.

Solution: Use OpenZeppelin’s SafeMath for arithmetic operations with overflow checks.

Dynamic Array Length Underflow

Allowing users to manipulate array lengths can lead to underflows, enabling arbitrary storage writes.

Example:

contract AlienCodex {
    bytes32[] public codex;
    function retract() public { codex.length--; } // Underflow risk
}

Impact: An underflow resets the array length to 2^256-1, allowing attackers to overwrite any storage slot.


Reentrancy Attacks

A classic exploit where recursive calls drain funds before state updates.

Vulnerable Code:

function withdraw(uint amount) public {
    require(balances[msg.sender] >= amount);
    (bool sent, ) = msg.sender.call{value: amount}(""); // External call
    balances[msg.sender] -= amount; // State update after transfer
}

Attack: A malicious contract’s fallback function re-calls withdraw(), bypassing checks due to delayed balance updates.

Prevention:

  1. Follow Checks-Effects-Interactions pattern.
  2. Use OpenZeppelin’s ReentrancyGuard.

👉 Learn how to secure your contracts against reentrancy


Pseudorandom Number Exploits

Block-Dependent Variables

Using block.timestamp or blockhash for randomness is predictable within the same block.

Example:

uint random = uint(keccak256(abi.encodePacked(block.timestamp))) % 2;

Fix: Use verifiable randomness oracles like Chainlink VRF.

Blockhash Limitation

blockhash only works for the last 256 blocks; older blocks return zero.


Airdrop Exploitation

Attackers deploy multiple contracts to claim tokens repeatedly:

contract Token {
    mapping(address => bool) airdropped;
    function airdrop() public {
        require(!airdropped[msg.sender]);
        balances[msg.sender] += 100;
    }
}

Exploit: New contracts bypass the airdropped check, syphoning tokens.


Private Variable Reading

private variables are accessible via storage slot inspection:

contract Vault {
    bytes32 private password; // Slot 0
}

Tool: Use web3.eth.getStorageAt(contractAddress, slotIndex).


Delegatecall Hijacking

delegatecall executes external code in the caller’s context, risking storage collisions.

Example:

contract Preservation {
    address public owner; // Slot 2
    function setFirstTime(uint _time) public {
        timeZone1Library.delegatecall(abi.encodeWithSignature("setTime(uint256)", _time));
    }
}

Attack: Modify timeZone1Library to point to a malicious contract, then overwrite owner.

👉 Secure delegatecall usage in your projects


FAQ

How can I prevent integer overflows?

Use OpenZeppelin’s SafeMath or Solidity ≥0.8 with built-in overflow checks.

Are private variables truly private?

No—they’re only hidden from public view but readable via storage slots.

What’s the safest way to generate randomness?

Off-chain oracles (e.g., Chainlink VRF) or commit-reveal schemes.

Why is delegatecall dangerous?

It allows external contracts to modify your contract’s storage unpredictably.

How do reentrancy attacks work?

Recursive calls exploit delayed state updates to drain funds.