Mono Audit logo

Common Vulnerabilities List

Smart contract vulnerabilities can lead to catastrophic and often irreversible consequences once deployed. Exploitation of these weaknesses represents a major challenge to blockchain security, resulting in billions of dollars in stolen assets and undermining user confidence. Achieving robust smart contract security is therefore paramount. It requires more than just deploying code to the blockchain. It demands rigorous secure coding practices, thorough testing, and often, independent smart contract audits to identify issues before they can be exploited. Understanding common pitfalls, from reentrancy attacks like the notorious DAO hack to subtle logic errors and Ethereum vulnerabilities like integer overflows, is the first critical step towards mitigation. This page provides an essential overview of frequent smart contract vulnerabilities to enhance awareness and promote safer development in the decentralized ecosystem.

  1. Reentrancy
  2. Unexpected Ether
  3. DoS
  4. Overflow
  5. tx.origin Authentication
  6. Access Control
  7. Weak Randomness
  8. Hash Collision
  9. Precision Loss
  10. msg.value in loop
  11. ecrecover
  12. Replay
  13. Inheritance Order
  14. MEV
Link

Reentrancy

Reentrancy is one of the earliest and most devastating smart contract vulnerabilities, responsible for significant historical exploits like The DAO hack, which led to an Ethereum hard fork. It remains a critical threat with potentially horrific consequences, capable of draining contracts of all funds.

The vulnerability stems from a common coding anti-pattern, often carried over from Web2 habits: performing external interactions (like transferring Ether or calling another contract) before updating the contract's internal state (e.g., user balances). When a contract sends Ether or calls an external contract, it temporarily transfers execution control. An attacker controlling the receiving contract can exploit this by immediately calling back into the original contract, often via the receive or fallback function triggered by the Ether transfer, or through token standard hooks.

Because the original contract's state (e.g., the attacker's balance) hasn't been updated yet, the re-entrant call passes the initial checks, allowing the attacker to repeat the action (like withdrawing funds) multiple times within the same transaction. This recursive loop continues until funds are drained or gas limits are hit.

While the classic attack involves a single function, more complex variations exist, such as cross-function reentrancy (exploiting shared state between functions) and read-only reentrancy (manipulating state reads via view functions).

The primary defense is the Checks-Effects-Interactions (CEI) pattern: perform all necessary checks, then update state variables (effects), and only after these updates, interact with external contracts.

Link

Unexpected Ether

Balance Manipulation Through Unexpected Ether vulnerability arises because Solidity smart contracts can receive Ether through mechanisms that bypass their defined receive and fallback functions. The primary methods for this "unexpected" Ether injection are the selfdestruct opcode, which forcibly transfers a dying contract's balance to a designated address, and coinbase transactions (block rewards).

The core issue is that these forced transfers directly increase the contract's Ether balance (address(this).balance) without triggering the contract's programmed logic for handling incoming funds. This breaks a common, often implicit, assumption or invariant: that the contract's actual balance accurately reflects the sum of funds processed through its intended payable functions or accounted for internally.

Contracts that incorrectly use address(this).balance for critical logic checks become vulnerable. For example, contracts might check that address(this).balance == expectedAmount. An attacker can exploit this by using selfdestruct to send a small amount of Ether, manipulating the balance. This can lead to:

Denial of Service (DoS): Strict equality checks can be permanently broken, rendering functions unusable.

Logic Manipulation: Thresholds can be met prematurely, triggering unintended state changes or payouts.

Assert Violations: In rare cases, the unexpected balance might lead to internal state inconsistencies that cause an assert() to fail, consuming all transaction gas.

The fundamental mitigation is to never rely on address(this).balance for contract logic. Instead, contracts must maintain accurate internal accounting using dedicated state variables, updating them only within legitimate fund-handling functions. All critical checks and state transitions must be based on these reliable internal variables.

Link

DoS

Denial of Service (DoS) vulnerabilities in Solidity smart contracts aim to disrupt or completely halt the contract's intended functionality, preventing legitimate users from accessing its services. The "service" being denied is the contract's ability to execute its functions as programmed and expected by its users.

Attackers achieve this by exploiting flaws in the contract's code logic, manipulating resource limitations (primarily gas), or leveraging external dependencies.

Common patterns include:

Gas Exhaustion: Crafting transactions or manipulating state to make function execution costs exceed the block gas limit or transaction gas limit. This often involves triggering computationally expensive operations or, very commonly, iterating over unbounded arrays whose size can grow indefinitely. As the data grows, the loop's gas cost surpasses limits, rendering the function unusable.

Unexpected Reverts: Causing critical functions to revert unexpectedly. This can be triggered by forcing failed external calls (e.g., sending funds to a contract that rejects them), manipulating state to fail require conditions, or other unhandled exceptions. If a contract relies on an external call that fails (perhaps maliciously induced), the entire operation can be blocked.

The immutable nature of smart contracts makes DoS particularly severe. A successful attack can lead to permanently locked funds or irrecoverable functionality, as the contract code cannot be easily fixed post-deployment.

Mitigation relies on secure coding practices: using bounded operations instead of unbounded loops, preferring pull-payment patterns over pushing funds to users (which isolates transfer failures), robustly handling external call failures (e.g., using try/catch where appropriate), and careful gas management.

Link

Overflow

Integer overflow and underflow vulnerabilities represent a persistent threat in the domain of smart contract security, stemming from the fundamental limitations of fixed-size integer arithmetic. An overflow occurs when an arithmetic operation's result exceeds the maximum value for its type and an underflow occurs when the result falls below the minimum value.

Historically, in Solidity versions before 0.8.0, these arithmetic errors caused the value to silently "wrap around". For instance, adding 1 to the maximum uint8 (255) would result in 0, and subtracting 1 from 0 would result in 255. This predictable but unchecked behavior was a major source of exploits, allowing attackers to manipulate token balances (e.g., minting huge amounts or draining funds by causing balances to wrap), bypass critical security checks, corrupt contract state, or cause denial-of-service.

Solidity version 0.8.0 introduced a crucial security enhancement: standard arithmetic operations (+, -, *, /) now perform overflow and underflow checks by default. If an operation would result in wrapping, the transaction reverts instead, preventing silent state corruption.

However, the risk is not entirely eliminated. Developers can explicitly bypass these default checks using unchecked blocks, often for gas optimization. Code within an unchecked block reverts to the dangerous silent wrapping behavior of pre-0.8.0 versions and requires extremely careful validation. Similarly, low-level EVM assembly code does not benefit from Solidity's checks, demanding manual safety management for arithmetic operations. Additionally, shift operations (<<, >>) remain unchecked even in default >=0.8.0 mode and will truncate results. Improper type casting (e.g., converting a large uint256 to a smaller type like uint8) can also lead to value truncation, potentially causing unexpected behavior in subsequent calculations.

Therefore, while significantly mitigated by default in modern Solidity, integer overflow/underflow remains a relevant vulnerability, especially in legacy contracts, code utilizing unchecked blocks or assembly, and through specific unchecked operations or careless type conversions.

Link

tx.origin Authentication

The use of tx.origin for authorization logic in Solidity smart contracts represents a critical security vulnerability. It stems from a fundamental misunderstanding of its behavior compared to msg.sender. While tx.origin consistently identifies the EOA that initiated the transaction, msg.sender identifies the immediate caller. Using tx.origin for permission checks fails to validate the entity directly interacting with the contract.

This flaw opens the door to phishing-style attacks where a malicious intermediary contract, invoked by a privileged user, can successfully call protected functions on the target contract. The tx.origin check incorrectly validates the original user, allowing the malicious contract to execute actions with the user's authority. The consequences range from direct theft of Ether and tokens to unauthorized manipulation of contract state, potentially causing significant financial losses and irreparable damage to the protocol's integrity and reputation.

Link

Access Control

Insufficient Access Control is a critical vulnerability in Solidity smart contracts where restrictions on who can execute which functions are missing or improperly implemented. Since Solidity lacks built-in permission models, developers must manually add checks, often using modifiers like onlyOwner or implementing Role-Based Access Control (RBAC). Failure to do so correctly creates security holes.

This vulnerability typically manifests as unprotected functions. Functions intended for administrative or sensitive operations - such as transferring contract ownership (changeOwner), withdrawing funds (withdraw), pausing the contract, minting tokens, or even destroying the contract (selfdestruct) - might be left callable by any external account. This often occurs due to missing access control modifiers or incorrect function visibility (e.g., functions default to public if not specified). Exposed initialization functions, which should only run once, can also be a vector if they remain callable after deployment, potentially allowing attackers to reset ownership or critical parameters.

The consequences are severe, ranging from unauthorized users gaining administrative privileges to the complete theft of funds managed by the contract or the irreversible destruction of the contract itself. Real-world exploits, like the Parity Wallet incidents and the LAND Token hack, demonstrate the devastating potential of insufficient access control.

Mitigation involves rigorously applying access control checks to all sensitive functions, adhering to the Principle of Least Privilege, using established patterns like Ownable or RBAC (often via libraries like OpenZeppelin's), and conducting thorough testing and audits.

Link

Weak Randomness

Generating secure randomness on the Ethereum Virtual Machine is challenging due to its deterministic nature, which is essential for network consensus. This creates an "entropy illusion" where seemingly random values derived purely from on-chain data are actually predictable.

Developers often misuse readily available block variables like block.timestamp, blockhash, and the post-Merge prevrandao (accessed via block.difficulty) as sources of pseudo-randomness. These variables are insecure because they can be predicted or influenced.

Miners (in Proof-of-Work) or validators (in Proof-of-Stake) can manipulate these values to some extent to gain an unfair advantage, often as part of MEV strategies. Crucially, even regular users or attacker contracts can often predict outcomes if the randomness logic relies only on inputs known before or during transaction execution, enabling exploits like front-running. This predictability undermines the fairness of applications like lotteries, games, and NFT mints.

Link

Hash Collision

Hash Collision via abi.encodePacked with Multiple Dynamic Types arises not from weaknesses in the underlying Keccak-256 hash function, but from the way data is encoded before being hashed, specifically when using Solidity's abi.encodePacked function. Unlike the standard abi.encode, which pads arguments to 32 bytes and includes length prefixes for dynamic types, abi.encodePacked creates a compact, non-standard encoding by concatenating arguments using the minimum bytes required, omitting padding for small static types and, crucially, omitting length information for dynamic types like string, bytes, or dynamic arrays.

The core issue occurs when abi.encodePacked is used with two or more adjacent dynamic-type arguments. Because the length of each dynamic argument isn't encoded, the boundary between them becomes ambiguous in the resulting byte string. This ambiguity makes it possible, often trivially, to craft different sets of logical inputs that produce the exact same packed byte sequence. For instance, abi.encodePacked("a", "bc") yields the same byte output as abi.encodePacked("ab", "c").

When this identical byte output is subsequently hashed (e.g., keccak256(abi.encodePacked(...))), it results in the same hash value, an encoding-induced hash collision.

This encoding collision vulnerability can be exploited in several ways if the resulting hash is used in a security-sensitive context:

Signature Verification Bypass: An attacker can take a valid signature created for one set of parameters and reuse it with a different, malicious set of parameters that produce a colliding hash. The contract's signature verification (ecrecover) will succeed, granting unauthorized execution.

State Corruption via Mapping Key Collisions: If the collision-prone hash is used as a key in a mapping (mapping(bytes32 =>...)), an attacker can craft inputs to generate a key that collides with a legitimate user's key, potentially overwriting their data, bypassing access controls, or causing denial of service.

Message Authentication Issues: The vulnerability undermines checks that rely on the hash to ensure data integrity, as different logical messages can appear identical after hashing.

The consequences of successful exploitation can be severe, including Unauthorized Access to functions or data, direct Fund Theft, critical State Corruption, and Denial of Service (DoS).

Link

Precision Loss

Precision loss vulnerabilities stem from reliance on integer arithmetic and the lack of native support for floating-point numbers. This design prioritizes deterministic execution but requires developers to manually manage fractional values, creating opportunities for errors.

The core issue is integer division truncation: Solidity discards remainders and rounds division results towards zero. This predictable behavior can be exploited, often through patterns like:

Division Before Multiplication: Calculating (a / b) c instead of (a c) / b truncates the intermediate result a / b, amplifying precision loss.

Rounding Down to Zero: If the numerator A is less than the denominator B (and both are positive), A / B always results in 0. This is risky for calculations involving small fees, rewards, or token conversions.

Attackers exploit these mathematical properties to manipulate contract logic for financial gain. Common strategies include:

State/Price Manipulation: Triggering rounding errors to distort critical protocol values like exchange rates, pool reserves, vault share prices, or collateral ratios, which can then be exploited in subsequent transactions.

Targeting Edge Cases: Using transactions with very small inputs or inputs designed to interact with large internal values to maximize the impact of truncation, often causing calculations to result in zero.

Successful precision loss attacks can lead to significant negative consequences:

Reduced Costs: Attackers pay lower or zero fees/costs.

Inflated Gains: Attackers illegitimately receive more tokens, shares, or rewards.

Arbitrage Opportunities: Creating artificial price differences within the protocol for attackers to exploit.

Circumventing Risk Mechanisms: Bypassing liquidations or other safety checks due to inaccurate calculations.

Gradual Fund Depletion: Siphoning value through repeated transactions exploiting tiny rounding errors ("1 wei attacks").

Mitigation involves careful arithmetic handling, such as performing multiplications before divisions, using numerical scaling (simulating fixed-point math), employing specialized math libraries, and implementing appropriate rounding logic. Standard overflow checks (like SafeMath or Solidity >=0.8) do not prevent precision loss from division.

Link

msg.value in loop

This vulnerability occurs when a smart contract improperly uses the msg.value within a loop. The core issue stems from the fact that msg.value remains constant throughout the entire execution context of a transaction. If a loop iterates multiple times, performing checks or actions based on this initial msg.value in each iteration without correctly tracking the cumulative value processed or spent across those iterations, it creates an exploit opportunity.

An attacker can exploit this by sending a specific amount of Ether to trigger the vulnerable function. Inside the loop, checks like require(msg.value >= amount_per_item) might pass repeatedly, or state updates might incorrectly use the full initial msg.value multiple times. This happens because the contract logic fails to account for the value effectively 'spent' or allocated in previous iterations of the same loop.

This flaw allows an attacker to trigger actions (like Ether transfers or internal balance credits) whose total value significantly exceeds the Ether they actually sent with the transaction.

Link

ecrecover

ecrecover is an essential EVM precompile allowing smart contracts to recover the signer's address from a message hash and an ECDSA signature (v, r, s). This enables crucial functionalities like verifying off-chain signed messages for meta-transactions or permit functions. However, its direct use presents significant, often underestimated, security risks if not handled carefully.

Zero-Address Return Vulnerability: A critical issue stems from ecrecover's unique error handling. When presented with an invalid or mathematically impossible signature, it does not revert the transaction. Instead, it silently fails and returns the zero address. Contracts that call ecrecover but suffer from a missing zero address check are highly vulnerable. An attacker can intentionally submit invalid signature data, causing ecrecover to return address(0). If this output isn't explicitly checked and rejected, the contract might incorrectly proceed, treating address(0) as the legitimate signer. This can lead to severe consequences like unauthorized state changes, incorrect event emissions, or granting permissions, especially if the zero address has special privileges or meaningful state within the contract's specific logic. Robust code must always validate recoveredAddress!= address(0) immediately after the ecrecover call.

Signature Malleability Vulnerability: The second major risk arises from an inherent property of the ECDSA algorithm itself: signature malleability. For any given message and private key, multiple distinct but cryptographically valid signature representations can exist (specifically, a signature using component `s` can often be transformed into a valid signature using `n-s`, where `n` is the curve order). This becomes a vulnerability if contracts incorrectly assume that a signature for a message is unique. Attackers can exploit this by bypassing uniqueness checks. For instance, if a contract uses the hash of the signature itself as a nonce to prevent replays (a flawed pattern), an attacker can take a valid signature, compute its malleable counterpart, and submit it to execute the action again, as the signature hashes will differ. It can also cause unexpected behavior or replay in systems expecting specific signature form if external systems or parts of the contract logic were not designed to handle both valid signature forms. Effective mitigation involves enforcing signature canonicalization, a check robustly implemented in standard libraries like OpenZeppelin's ECDSA, which should be preferred over direct ecrecover usage.

Link

Replay

Cross-Chain Replay Attack (CCRA): A transaction validly executed on one EVM chain is captured and successfully re-submitted on a different EVM chain. This exploits similarities in transaction formats and signatures across chains, especially when transactions lack unique chain identifiers (Chain IDs). The Ethereum/Ethereum Classic hard fork is a classic example where this risk materialized. EIP-155 was introduced to mitigate this by embedding the Chain ID into standard transaction signatures, making them chain-specific. However, smart contracts using custom signature verification must also explicitly check the Chain ID. The $20 million Optimism exploit against Wintermute resulted from such a missing Chain ID check in a cross-chain deployed contract.

Smart Contract-Level Replay (Same-Chain): A signed message or transaction is replayed against the same smart contract, or potentially another contract on the same chain. This typically exploits vulnerabilities within the contract's own logic, particularly in custom signature verification schemes used for features like meta-transactions or ERC-20 permit functions. The most common flaw is the absence or improper implementation of an application-level nonce. A nonce ("number used once") is a unique counter associated with the signer that must be included in the signed message digest and tracked on-chain by the contract to ensure each specific signature authorizes only one action.

Link

Inheritance Order

Solidity's support for multiple inheritance allows a contract to inherit features from several parent contracts simultaneously. While powerful for code reuse, this introduces potential ambiguity, known as the "Diamond Problem": if two or more base contracts define a function with the same name and parameters.

To resolve this, Solidity employs the C3 linearization algorithm to establish a single, deterministic Method Resolution Order (MRO) for each contract. This MRO dictates the precise sequence in which base contracts are checked when resolving function calls.

The vulnerability arises directly from how this MRO is determined. The crucial factor is the order in which the developer lists the base contracts in the `is` statement. Solidity requires listing contracts from "most base-like" to "most derived". This specified order directly influences the final MRO generated by the C3 algorithm.

The vulnerability occurs when the developer provides an inheritance order that does not match the intended logical hierarchy or priority. If a more general contract is listed after a more specific one, or the order otherwise causes the MRO to prioritize an unintended function implementation, the contract can behave unexpectedly. For example, a call might resolve to a base function lacking critical security checks or updated logic that was implemented in an intended, but incorrectly ordered, derived contract override.

Consequences of executing the wrong function due to incorrect inheritance order include bypassing access controls, executing outdated or incorrect business logic, state corruption, and potential financial loss. Essentially, the contract's actual execution flow deviates from the developer's design, undermining security and functionality.

Link

MEV

Maximal Extractable Value represents the profit block producers and specialized searchers can capture by manipulating the inclusion and ordering of transactions within a block, beyond standard block rewards and gas fees. Originally termed "Miner Extractable Value", the name evolved to "Maximal" to reflect its greed for reward.

MEV arises because pending transactions often sit in a public waiting area called the mempool, visible to anyone. Block producers have the authority to decide the final order of transactions in a block. Automated bots, run by "searchers", constantly monitor the mempool, simulate potential outcomes, and exploit profitable opportunities by strategically ordering transactions, often using gas bidding (Priority Gas Auctions - PGAs) to ensure preferential placement.

Common MEV strategies, often viewed as attacks, include:

Front-Running: Placing an attacker's transaction before a victim's transaction (e.g., a large DEX trade) to profit from the anticipated price impact.

Sandwich Attacks: A combination of front-running and back-running a victim's DEX trade to capture the price difference (slippage) caused by the manipulation.

JIT Liquidity: Temporarily adding and removing liquidity around a large swap on concentrated liquidity DEXs to capture fees.

Oracle Manipulation: Exploiting price oracle updates or inaccuracies for profit, often impacting lending protocols.

Other types include NFT sniping and dust attacks.

The consequences for users include increased transaction costs due to gas wars, worse execution prices (slippage) on trades, exacerbated impermanent loss for liquidity providers, and unfairly triggered liquidations.

Mitigation strategies aim to reduce MEV's negative impact. Sending transactions via private mempools or relays (like Flashbots Protect, or MEV Blocker) hides them from public view. Application-level designs like Commit-Reveal schemes obscure transaction details until ordering is fixed, while using Time-Weighted Average Prices (TWAPs) for oracles can reduce manipulation risks.

Smart Contract Security Consulting

Get expert blockchain consulting before development begins. We help you design secure, efficient smart contracts and protocols, avoid costly mistakes, and optimize performance. Faster and more affordable than hiring in-house. Book a free consultation today!