Web3 Security Auditor's 2024 Rewind

Co-authors: FRANK LEI & YUGUANG IPSEN

Welcome to the Web3 Security Auditor's 2024 Rewind, a collection of succinct technical breakdowns of notable security incidents and vulnerabilities from the past year.

Previously known as the Ethereum Smart Contract Auditor's Rewind, the article series was originally authored by Patrick Drotleff, an Information Security and Privacy advocate, independent security researcher and mentor in the Secureum community. After connecting at TrustX Istanbul, Patrick entrusted us with continuing this tradition while he shifts focus to new endeavors.

We've adhered to the original format, delivering concise, actionable insights for each incident—highlighting the vulnerabilities and offering just enough context to identify these issues during security reviews or bug hunting. To increase the diversity of issues, this year's compilation expands beyond exploits to include bugs uncovered through traditional audits, contests, and bug bounty platforms. Additionally, as auditing increasingly encompasses off-chain infrastructure, we've included vulnerabilities from Rust and Golang systems like blockchain nodes or CEX on-ramping infrastructure.

Table of Contents

The Highlights

The Usual


The Highlights

EIP-1153: Transient Storage

The Ethereum Cancun-Deneb upgrade introduced transient storage, a short-lived data space similar to persistent storage but lasting only through a transaction. Developers can declare a variable transient or leverage tstore and tload assembly instructions to interact with transient storage, benefiting from its cost-efficiency. At roughly 100 gas per operation, it is significantly cheaper than traditional storage, which typically requires around 20,000 gas for reads and writes. However, with increased complexity comes increased risk, and transient storage is no exception:

  • Due to the low gas cost of accessing transient storage, reentrancy attacks with low gas stipends (like the 2300 gas allowance through address.transfer) are now possible. If a code block does not follow the checks-effects-interactions pattern nor uses ReentrancyGuard, security researchers should ensure that transient storage variables are safe from reentrancy-based modifications, as such changes could lead to malicious or unintended contract behaviors.

  • Currently, transient storage supports only reading and writing value types. Because data structures are reference-types, developers that want to use them must implement workarounds that sacrifice type-safety. This Solidity Underhanded Contest submission demonstrates how the absence of type-safety can lead to accidental decoding of data into a structure with a similar layout but different meaning, which can introduce subtle and potentially exploitable errors.

  • Lastly, while transient storage is automatically cleared at the end of a transaction, developers should explicitly clear it to prevent possible interactions with the contract in a "dirtied" transient state. The following article details how reverts or incorrect behavior can occur during multicalls or complex interactions between contracts within the same transaction.


Uniswap V4 Double-Entrypoint Tokens Critical

In 2024, Uniswap V4 has gone through several thorough audits, one of which uncovered a critical issue involving the deployment of the protocol on chains where the native tokens also have an ERC20 representation.

In Uniswap V4, the settle function manages debt by increasing the account delta for the specified token. It uses msg.value for native token deltas and the balance change since the last sync or settle call for ERC-20 tokens. This mechanism can be exploited on chains where native tokens, like CELO on Celo, also have ERC-20 representations: After syncing to load current balances, an attacker can first call settle with msg.value > 0 to increase the delta for the native token, then call settle with the ERC-20 token's address. Since the balance of the ERC-20 token has increased from the first call, the attacker can inflate the delta without transferring actual tokens in the second call, allowing them to later withdraw more tokens than they deposited, effectively draining the pool.

Rewind 4

The fix ensures that the settle call must settle the previously synced token, otherwise the transaction will revert.


Polygon PoS Voting Takeover and Infinite Mint Bug

The following research article uncovers how the Polygon bridge could have been drained by combining a rogue validator with two issues found in the Heimdall validator code.

The Polygon Proof-of-Stake network is made of three layers:

  1. Ethereum Contracts: Manage staking, checkpoint submissions and validator rewards.

  2. Consensus Layer (Heimdall): Determines block producers based on Ethereum's staking state and pushes checkpoint updates.

  3. Execution Layer (Geth fork): Produces new blocks.

When users stake MATIC on Ethereum, a StakeUpdate(uint256 validatorId, uint256 nonce, uint256 newAmount)event is emitted. Off-chain components notify Heimdall, which increases the validator's voting power for consensus. To verify the legitimacy of such events, Heimdall queries Ethereum to ensure the event indeed exists, the field values match and the nonce aligns with the one stored on Polygon.

The first issue arises because Heimdall fails to validate event types. If the Ethereum contracts emit a similarly structured event like SignerChange(uint256 validatorId, address oldSigner, address newSigner), Heimdall could misinterpret it as StakeUpdate. In this scenario, Heimdall would parse oldSigner as nonce and newSigner as newAmount. A rogue validator could emit a SignerChange event with an oldSigner address matching their Polygon nonce and a newSignerthat parses into a massive newAmount, artificially inflating their voting power.

Rewind 1 & 2

The second issue lies in how Heimdall validates nonce. The event's 256-bit nonce is truncated to uint64, dramatically lowering the computational difficulty of generating an oldSigner address matching the nonce on Polygon.

Rewind 3

An attacker needs only the last 64 bits of oldSigner to align. Although quite limited, the attacker can further reduce the necessary computational effort by altering their nonce through on-chain actions.

With overwhelming voting power, the attacker could have manipulated consensus to accept fake deposit events, arbitrarily minting funds on Polygon. By bridging the funds back to Ethereum, they could have drained the Polygon bridge. Fortunately, the bug was responsibly disclosed and fixed without any incident and loss of funds.


Compound DAO Governance Attack

In July, the Compound DAO faced an attack aiming to pass a malicious governance proposal. This proposal sought to transfer 499K COMP tokens — 5% of the total supply, valued at $25 million at the time — from the treasury to an investment vault. Besides the transfer, the tokens' voting power would also be delegated to the proposers. Given that Compound's voting turnout is usually 4-5% of the total token supply, the attackers could have controlled the governance process by passing this proposal. By being able to pass any malicious proposals, the Compound TVL and treasury funds would be put at risk.

The attack involved three proposals:

  • Proposal 247 (May 6th): was created directly on-chain, without a corresponding forum post detailing it, process which is customary within the Compound community. The proposal meant to send 92K COMP tokens from the treasury to a multisig owned by the “GoldenBoyz”, promising the tokens would be further invested into a vault that deposits them for yield into Balancer. This raised suspicions within OpenZeppelin and one of our colleagues promptly notified the community about the possibility of malicious activity. The proposal was subsequently cancelled after being heavily voted down.

  • Proposal 279 (July 15th): This time accompanied by a forum post, it intended to transfer funds via a TrustSetup smart contract directly into the vault, bypassing the "GoldenBoyz" multisig. While this was an improvement, new concerns were raised that the tokens' voting power was delegated to the "GoldenBoyz" multisig before investment. This proposal was heavily voted down and did not pass.

  • Proposal 289 (July 24th): Identical to the secondary proposal, except for the amount now being 499k tokens instead of 92k. Despite the community being promptly made aware, the "GoldenBoyz" mustered enough votes to pass the proposal. It is suspected that the first two proposals were only created to gauge the usual voting power of the community, only to accumulate more funds in the meantime and overwhelm the opposition at the vote of the third proposal. Moreover, most "for" votes were cast right at the end of the voting period (See "Timeline" in Tally), restricting the community's capacity to respond.

Given the previous voting power of the "GoldenBoyz" and the newly granted 499K COMP tokens, the succesful execution of Proposal 289 could have given the attackers control over Compound Governance, rendering other large delegates powerless against any malicious proposals.

The community counteracted with Proposal 290, aiming to transfer the timelock admin to the Community Multisig and restrict the "GoldenBoyz" from executing malicious actions against the DAO treasury and the protocol. Thanks to rapid action by Compound delegates in collaboration with OpenZeppelin and other parties, this emergency proposal neutralized the threat by preventing the misuse of the newly acquired voting power.

Ultimately, the "GoldenBoyz" reached an agreement with the DAO and canceled Proposal 290 before its execution. While they and affiliated accounts retain significant voting power, it currently doesn't pose an immediate threat to the DAO but warrants ongoing vigilance.

As an additional security measure, the Compound protocol added a temporary proposal admin which has the power to cancel malicious proposals.


Kraken Exchange Balance Printing Bug

Centralized exchanges typically maintain user balances off-chain, updating them based on observed on-chain deposits and exchange activity. Users are assigned deposit addresses, and once a transfer to these addresses is detected, their exchange account is credited with the deposited tokens.

In June, a critical bug in the Kraken Centralized Exchange was reported, allowing malicious users to inflate their account balances without completing the actual on-chain deposit process. While user funds remained safe, this flaw endangered Kraken's treasury by enabling attackers to create and withdraw fake balances as real on-chain funds, totaling ~3M USD.

Details from DanielVF's analysis and his proof of concept suggest that if crafted correctly, a reverting token transfer would be interpreted by the Kraken system as a valid deposit. It is important to note we could not find an official post-mortem detailing the exact technical details behind it.

A transaction containing an inner call that transferred funds and subsequently reverted was mistakenly considered as valid deposit, without taking into consideration the rollback of the inner call. The credited exchange amount matched the token transfer in the reverted transaction, and hence attackers could amplify their gains using flash loans to inflate the apparent deposit value.

Ongoing investigations aim to clarify whether this was a proof-of-concept demonstration by researchers or a malicious attempt at fund theft.


Radiant Capital Hack

In October, Radiant Capital suffered a security breach resulting in the loss of approximately $50 million. At least three developer devices were compromised through a sophisticated malware injection further detailed in an incident update. The compromise allowed the attackers to intercept authentic transaction data from the Safe UI and tamper with it by the time it reached the signature process done by hardware wallets, ultimately resulting in gathering signatures of different, malicious on-chain actions.

The attack involved altering transaction details sent to the hardware wallet. While the Safe frontend displayed legitimate transaction data, the actual transactions being signed were malicious, enabling attackers to execute a transferOwnershipcall after a quorum of 3/11 authorized signers was reached. Rewind 5

By becoming the owner of Radiant's LendingPoolAddressesProvider contract, the attackers changed the implementation behind lending pools proxies and stole all funds and previously given token approvals. The discrepancy between the signature expected by Safe and the signature given by the hardware wallet caused errors within the Safe UI and during on-chain execution, but this did not cause immediate suspicions as such errors were common when submitting meta-transactions and hence, were not subject to detailed analysis.

For a deeper understanding of how Safe interacts with hardware wallets and the risks of blind signing, refer to this analysis.

The Usual

Missing Input Validations

  • In the Beanstalk stablecoin protocol, users could supply BEAN tokens as liquidity into Well contracts (similar to liquidity pools) in exchange for LP tokens. When withdrawing liquidity, users would specify the address of the Wellthey wanted to withdraw from and the amount of LP tokens to burn. The Well contract was responsible for burning the LP tokens and calculating how many BEAN tokens to return to the user from the Beanstalk contract. However, the Well contract was neither validated nor subject to any whitelist. An attacker could create a contract that when called, would falsely report that any amount of burned LP tokens entitled the user to the entire balance of BEAN tokens held in the Beanstalk contract.

  • The BankrollNetworkStack contract functions as a vault in which users deposit WBNB through the buyFor function, in exchange for Bankroll shares. Part of the deposited amount is distributed as WBNB dividends to each shareholder. After accumulating sufficient dividends, a user can sell their shares back into the vault and withdraw all the accumulated dividends. However, the buyFor function was missing a check for the _customerAddress parameter and would not validate that it is not set to the Vault's own address. This oversight allowed an attacker to call the function repeatedly with the _customerAddress as the Vault address and the buy_amount as the total amount of WBNB in the vault. This would transfer the WBNB from the vault to itself, while distributing part of the amount as dividends, a slice of which were accumulated for the attacker. After calling the function sufficient times, the attacker sold their shares and withdrew an inflated amount of dividends, to the loss of other users.

Missing Access Control

  • When the TSURU system received an ERC1155 token, it would call the onERC1155Received function of the TsuruWrapper contract, minting corresponding TSURU tokens to the depositor. However, the onERC1155Receivedfunction lacked proper access control, allowing an attacker to call it directly and mint unlimited tokens to themselves. The attacker then used these tokens to drain the TSURU-ETH Uniswap pool.

  • The Galaxy Fox token airdrop worked by storing a Merkle Tree root on-chain, allowing participants to claim tokens by submitting a valid path with their address as the leaf. However, the setMerkleRoot function lacked access control, allowing an attacker to set a maliciously crafted root and claim enough GFOX tokens to drain the WETH-GFOX Uniswap pool.

  • The Alchemix project enabled users to deposit yield-bearing assets such as yvDAI or wstETH, which served as collateral for borrowing synthetic assets like alDAI or alETH, thereby providing immediate access to liquidity. Periodically, yield was collected via the harvest function and sometimes required unwrapping through swaps on Balancer, creating opportunities for sandwich attacks. To mitigate these risks, the harvest function included a minimumAmountOut parameter and could only be called by the Gelato Automate contract — a system designed to schedule recurrent on-chain calls with correct parameters, similar to traditional cron jobs.

    However, Alchemix integrated with the general, non-custom version of Gelato, which does not react to only dedicated msg.senders and allows anyone to trigger calls from the Automate contract. This lack of customization would allow an attacker to create call chains with arbitrary values for minimumAmountOut, facilitating significant sandwiching during the unwrapping of harvested yield.

  • The CoreBranchRouter contract in Maia DAO's system lacked necessary access control due to a virtual function not being overridden with proper caller restrictions. This oversight would allow an attacker to act as a trusted CoreBranchRouter and drain funds through a sequence of calls. As highlighted in the post-mortem, developers and security researchers should carefully review all inherited contracts for unguarded or hidden code to prevent similar vulnerabilities.

Underflows and Overflows

  • On Mantle, MNT and ETH transfers between two L2 addresses can be initiated on L1 by specifying the amount to transfer. Without validating the balances, a TransactionDeposited event is emitted and is picked up by the Mantle Node which ensures the from address has enough funds to transfer. However, when ETH transfers were involved, the Mantle node did not ensure the from address had enough funds and directly updated the balances. Due to the use of Golang’s BigInt type and the BigToHash function, underflows were not detected, and a negative value would be converted to a positive. This would allow an attacker to transfer ETH they did not have to another address they controlled, increasing both balances at the same time and hence, infinitely minting.

  • An attacker stole multiple users' LW tokens by exploiting a missing underflow check in the token's transferFromfunction. The function failed to verify that the transfer amount was within the approved amount, assuming the subtraction would automatically revert if it went negative. However, the contract was compiled with Solidity 0.7.6which lacks automatic underflow checks. This allowed the attacker to transfer tokens from users who hadn't provided approval, bypassing the intended restrictions.

    For another example with the same vulnerability pattern, see this writeup on the Scroll token exploit.

Missing Validations in Flashloan Callback

  • Prisma Finance's MigrateTroveZap contract was designed to help users migrate their Troves (positions with collateral and debt) by using a flashloan to close and reopen them in the new contract. However, the onFlashLoanmethod did not properly validate the input. An attacker bypassed the migrateTrove process and initiated a flashloan directly, invoking the onFlashLoan method with unexpected data. This allowed the attacker to close other users' troves and reopen them with the bare minimum collateral required to keep the positions healthy. The attacker then opened their own trove and was able to inherit the remaining collateral from the compromised positions, ultimately withdrawing the excess funds.

  • Similarly, Dough Finance's ConnectorDeleverageParaswap contract facilitated users in obtaining flashloans from AAVE through the flashloanReq function. Users could provide a swapData parameter that was intended to define the details for swapping the flashloaned tokens via specified routes. However, there was no validation on the swapDataparameter, allowing an attacker to invoke a WETH transferFrom instead of a token swap and steal assets that were approved to the contract.

Incomplete Signature Schemes

  • The isValidSignature function in ERC1271 provides a standard way for contracts to verify whether a signature on behalf of a given contract is valid. A common use case is smart accounts, which rely on signatures from their owners to authorize actions. Unfortunately, the reference implementation does not inherently tie the msg.sender or the target contract address to the signature. This omission can lead to signature replay attacks, where a signature intended for smart account A could be reused for account B, provided both accounts have the same owner and message format.

  • To receive a Taiko grant, a TimelockTokenPool contract was deployed, allowing recipients to withdraw tokens after a time delay and with a valid signature. The signature was validated against the keccak256(abi.encodePacked("Withdraw unlocked Taiko token to: ", _to)) digest, missing a few key fields:

    • without a nonce, the signature could be replayed for the same TimelockTokenPool contract.

    • without the verifying contract's address, the signature could be replayed between different TimelockTokenPoolcontracts.

    • lastly, without the chainId, the signature could be replayed on other chains.

  • In the Phi off-chain system, a signer authority would issue signatures for users to claim rewards on-chain. While the signatures were constructed with all the necessary security fields, the on-chain validation mechanism did not check if the chainId matched the current chain. As a result, signatures generated for one chain could be reused on another chain to claim additional rewards. By using the EIP712 abstract contract, developers can enforce that all security fields are both included in the signature and validated against the context of execution (eg. version, address(this), block.chainid).

  • In the Phi system, the signer authority would issue off-chain signatures using parameters validated by the frontend, for users to submit on-chain and create NFT art. However, not all parameters were included in the signature, allowing an attacker to frontrun the signature submission and alter certain fields, and thus change the artist, maximum supply or royalty receiver of the NFT.

Unsafe Casts

  • In Uniswap v4, an unsafe cast from int128 to uint128 in the validateMinOut function can allow slippage checks to be bypassed. This happens because a hook might return a negative liquidityDelta, which, when cast to uint128, turns into a very large positive number that exceeds user-specified minimum outputs. The fix involves using SafeCast.toUint128, which will revert the transaction when liquidityDelta is negative.

  • A vulnerability was identified in the Lotus and Venus clients of the Filecoin network, which could allow an attacker to remotely crash nodes. The root cause was due to an unsafe cast from an unsigned to a signed integer, combined with subsequent out-of-bounds access. In the validateCompressedIndices function, message indices, which are unsigned integers, were unsafely cast to signed integers for validation. This validation checked if the index was less than the length of the Bls slice, but since the peer could control the index value, it was possible to set an index larger than the maximum value for a signed integer. When cast to a signed integer, such a large value would wrap around to become negative, thereby bypassing the validation check. This error allowed for out-of-bounds access after the validation, leading to a panic and causing the node to crash.

  • A vulnerability was identified in the Sei Node within the AsEthereumData function, which is used to decode Ethereum transaction data. The vulnerability arises because the function does not validate the v, r, and s signature values (type big.Int) before casting them to uint256 with uint256.MustFromBig. If these big.Int values are maliciously crafted to be overly large, the MustFromBig function can panic due to an overflow, leading to the Sei Node shutting down.

Reentrancies

  • When depositing ERC20 tokens with sender hooks on Scroll, a misplaced reentrancy guard could have allowed an attacker to receive inflated amounts and potentially drain the contract. The nonReentrant modifier was placed on the internal _deposit function which did not contain the token transfer, instead of the external depositERC20 which contained the safeTransferFrom call.

  • The SumerMoney project, a Compound fork supporting assets like ETH and USDC, differed in its loan repayment logic: it refunded any excess before updating totalBorrows, potentially handing execution to the caller while the exchange rate between the underlying asset and cToken was inflated. An attacker exploited this by borrowing ETH, repaying it with a 1 wei surplus, and redeeming collateral at the inflated rate. By doing so, the attacker recuperated their initial collateral by redeeming less cTokens and used the leftover amount to further borrow more underlying tokens without repaying, draining funds from the market.

  • The ETH/OETH Curve pool's withdraw_admin_fees function sent excess funds determined by the difference between internal (self.balance; OETH.balanceOf(address(this))) and accounting balances (self.balances[0]; self.balances[1]) to a fee-receiver. Because the behavior of the function was not dependent on the msg.sender, the function lacked access control.

    The remove_liquidity_imbalance function allowed users to withdraw liquidity in disproportionate amounts, transfering ETH and handing execution to the caller before transfering OETH. This would temporarily leave OETH.balanceOf(address(this)) in the initial, and now inconsistent, state. By calling withdraw_admin_fees at the right time, an attacker could reduce the pool’s internal OETH balance below the accounting balance, temporarily breaking pool accounting.

  • Onyx, a lending platform forked from Compound V2, implemented its own liquidation flow. A low-liquidity market within the platform allowed an attacker to exploit the system by continuously minting and redeeming a single LP token, gradually decreasing its exchange rate. This process reduced the collateral value until it was marginally underwater, allowing the attacker to initiate a profitable self-liquidation.

    Furthermore, a missing input validation in Onyx’s liquidation logic enabled the attacker to use a fake token as repayment, increasing the profitability of the attack.

  • The Penpie project, offering yield-boosting services for Pendle Finance users, allowed users to collect rewards from specific Pendle markets via the batchHarvestMarketRewards function. This function records token balances before, redeems rewards and records token balances after, with the differences representing the rewards to distribute.

    However, an attacker exploited this by attaching a valid Pendle market with a malicious custom token that handed over execution during token transfer inside IPendleMarket.redeemRewards(). The attacker used this execution window to call depositMarket, depositing tokens to mint LP tokens while simultaneously inflating the "after" token balances used to calculate rewards.

Price Manipulations

  • In the HYDT token system, the InitialMint contract let users deposit BNB in exchange for newly minted HYDTtokens. The minted amount was based on the WBNB/USDT PancakeSwap pool spot price. An attacker flashloaned USDT, traded it for nearly all the WBNB in the pool to manipulate the exchange rate and then minted themselves an inflated amount of HYDT tokens.

  • PolterFinance used the average of two spot prices - one from a Uniswap V2 pool and one from a Uniswap V3 pool — to value BOO tokens as collateral for borrowing. Although aggregating prices can reduce manipulation risk, relying entirely on spot prices leaves the system vulnerable to on-chain price manipulation. By leaving small amounts of BOO in these Uniswap pools, an attacker artificially inflated the spot prices, causing the aggregated price to rise sharply. This enabled the attacker to borrow most of PolterFinance’s assets using the overvalued BOO as collateral.

  • The AIZPT314 system facilitated users to trade BNB for AIZPT tokens, using the formula token0Out = token1In * token0InContract / (token1In + token1InContract). Unlike constant-product formulas, the formula above allows value extraction through back and forth trades of big amounts. An attacker exploited this by depositing a large amount of BNB to receive most of the contract’s AIZPT tokens, then recovering their BNB by trading back small amounts of AIZPT, effectively draining the contract.

  • The WOOFI decentralized exchange used a spot pricing mechanism that was not time-weighted and could be manipulated in cases of low liquidity and large-value trades. An attacker identified a low-liquidity market on Arbitrum and through a sequence of flashloans and trades, caused one WOO to be valued at 0.00000009 USDC. Normally, WOOFI would fallback to a Chainlink oracle in case of extreme price fluctuations, but the oracle was not properly set up. Because of this oversight, the system used the manipulated price and the attacker subsequently drained all the WOOtokens out of the market.

Rounding Errors

  • In The Graph system, staking tokens incurred a 1% curation fee. However, due to the fee calculation function rounding down, a curator could pay no fee by repeatedly staking only 99 tokens at once. The potency of the attack could be amplified using multicall on a low-cost network like Arbitrum.

  • The article Precision Loss Accumulation: The “Two Parser Bug” Lurking in the Shadows explores how small, repeated precision losses can accumulate, potentially leading to denial-of-service or broken invariants.

    The first example describes a reward system where each reward is rounded up while fees are rounded down, resulting in cumulative over-distribution that may prevent the last user from claiming their reward.

    The second example discusses a token sale with public and private participants, where accumulated precision loss in exchange rate calculations can allow participants to claim slightly more tokens than the sale cap allows.

Arbitrary Calls

  • In Mantle, bridging ETH or MNT locks the tokens on the source chain and mints them to the CrossDomainMessengercontract on the destination chain. Then, the CrossDomainMessenger calls relayMessage on the target, forwarding the funds and executing user-defined arbitrary logic. If the call fails, the message is stored for replay and the funds remain in the contract until then. Due to insufficient validation of the target address, an attacker could craft and send a malicious cross-chain message which would trigger an approve call. Since the transaction is executed with the CrossDomainMessenger as msg.sender, it would grant the attacker approval over the funds locked for failed messages.

  • Li.Fi Dex Aggregator introduced a new diamond facet that allowed users to deposit tokens and specify a route for swapping them. However, insufficient validation of the target and function selector in the route enabled arbitrary calls. A malicious attacker exploited this vulnerability by encoding the target as token addresses and the function selector as transferFrom, ultimately draining tokens from all users who had previously approved the vulnerable Li.Fi contract.

  • Spectra Finance allowed users to choose from various routes for executing token trades, one of which was through KyberSwap. However, the kyberRouter and targetData parameters were not properly validated. Similar to the Li.Fi exploit, this vulnerability allowed an attacker to encode the asdCRV token address and the transferFrom selector, draining tokens from addresses who had previously granted approvals to the Spectra Finance contract.

  • Similarly, Socket's Bungee Bridge was exploited using an arbitrary execution bug. Because of a missing validation on the swapExtraData parameter, an attacker would be able to encode a transferFrom and call it from the bridge on the WETH address, draining all lingering approvals.

Faulty Business Logic

  • The uniBTC system allowed minting uniBTC tokens against BTC or BTC-equivalent tokens, at a 1-to-1 exchange rate. To restrict minting against non-BTC-equivalent tokens, the system used a capping mechanism, where a deposit reverts if the vault's holdings (returned by the totalSupply function) exceed a set cap (set to 0 for non-BTC-equivalent tokens). On non-BTC-native chains, the NATIVE_BTC address represented the chain’s native token which might not be equivalent to BTC. For such tokens, the totalSupply function mistakenly returned 0, practically always passing the cap check: (totalSupply == 0) <= (cap == 0). This oversight enabled an attacker to mint uniBTCagainst the native token, which was not BTC-equivalent on several chains.

  • The Zyfy project's PermissionlessPaymaster contract allowed gas fee sponsorship by enabling a user to set a manager address that funds their transactions. Following a sponsored transaction, leftover gas was credited back to the previousManager state variable, which kept track of the sponsor of the last transaction. This refund could later be withdrawn by the respective manager. However, a vulnerability arose from the selfRevokeSigner function, which let users revoke their manager while also updating the previousManager variable.

    An attacker could exploit this by backrunning a sponsored transaction with a call to selfRevokeSigner, updating the previousManager to their own address. As the refund for the previous transaction was processed using this variable, the attacker could steal gas credits intended for the legitimate sponsor.

Locked Funds

  • In Renzo Finance, ETH withdrawals were sent to claimers using transfer, which forwards only 2300 gas. If the claimer was a smart contract with a non-trivial receive function, the gas limit would have been insufficient, causing the transaction to revert and effectively lock the funds in an unclaimable state.

  • The Bridged USDC Standard is Circle’s recommended approach for bridging USDC to new blockchains, with an option to later transition into native USDC, reducing liquidity fragmentation. The DeFi Wonderland opUSDC system bridges USDC to OP stack-based chains, allowing for contract ownership transfer to Circle for native USDC transition. After locking USDC on L1, the L2 counterpart contract had the permission and would mint USDC to the receiver. If minting failed temporarily on L2 (due to out-of-gas errors or bridged USDC being paused), the transaction could be replayed on L2. However, if the transition to native USDC occurred in the meantime, minting permissions would be transferred to Circle, causing any replayed transaction to revert indefinitely, thereby locking L1 funds without recovery options.

Signature Malleability

Lingering Approvals

Faulty Array Handling

  • In the Alchemix Finance system, simultaneously creating multiple checkpoints would result in data points with duplicate timestamps within the pointHistory array. The binary search used to locate an entry by timestamp failed to handle duplicates properly, stopping at the first matching timestamp and potentially leading to stale or incorrect results.

  • In the Royco system, offers were created to incentivize and reward arbitrary on-chain actions with incentive tokens. Similarly to the above, the system failed to account for and sanitize duplicate tokens in the reward array. An attacker could exploit this by creating an offer with duplicate reward tokens, completing the action themselves, and claiming a reward multiplied by the number of duplicate tokens included in the offer, draining the system.

Weak RNG

  • The RedKeysGame contract contract allowed users to place token bets, rewarding them if they correctly guessed the _betResult. However, the result was derived from predictable on-chain values such as a block's timestamp, prevrandao, gaslimit, coinbase and number fields. This allowed an attacker to craft a contract that calculates and submits the winning number, guaranteeing consistent winnings.

  • Similarly, the Boost AA Wallet determined the winner of its raffle using block.prevrandao and block.timestamp as sources of randomness. A validator with control over block production could influence the raffle outcome by selectively proposing or withholding blocks until the values of block.prevrandao and block.timestamp aligned in their favor.

Self Transfers

  • The SuperSushiSamurai token's custom ERC20 logic failed to account for transfers where the sender (from) and receiver (to) were the same. Additionally, instead of increasing the receiver's balance, it overwrote it. These flaws allowed an attacker to transfer tokens to themselves repeatedly, doubling their balance with each transaction. The key vulnerability is highlighted in the code snippet:

    uint256 toBalance = _postCheck(from, to, amountAfterTax)
    _balances[from] = fromBalanceBeforeTransfer - amount;
    _balances[to] = toBalance;

    For another example with the same vulnerability pattern, see this writeup on the GPU token exploit.

  • The Hackathon token had custom logic for token transfers involving certain AMM pairs but failed to account for scenarios where a pair was both the sender and recipient. This oversight was exploited through the standard AMM skim function, which forces token balances to match reserves by withdrawing surpluses. By sending excess tokens to the pair and calling skim with the pool as receiver, the faulty logic doubled the excess token balance. Subsequently, the attacker called skim with their own address as receiver, collecting the excess tokens.

Unsafe Storage Use

  • The Delta Prime project was hacked due to a storage collision between its proxy and implementation contracts, along with a forgotten init function. The project used a pattern called DiamondBeaconProxy, where each user's Prime Account smart contract was deployed as an on-chain clone, reducing gas costs by reusing the same contract logic across accounts. Accounts served as storage, while execution was delegatecalled to the DiamondBeacon contract, which then delegated to various facets based on the function selector. This setup allowed updates to the diamond facets to simultaneously modify the functionality of all Prime Accounts.

    Each Prime Account was initialized upon deployment through the initialize function that set the account owner. Similarly, the diamond was initialized via an init function, which set the diamond's owner. However, both contracts used the same storage slot for the owner address but different storage slots for their initialized flags. Moreover, the development team forgot to remove the diamond's init function after deployment. A malicious exploited this by invoking the init function on other users' Prime Accounts, leading to a delegatecall to the diamond’s initfunction rather than the fallback. The initialized flag check passed, as it relied on a different storage slot, allowing the attacker to change the Prime Account’s owner by exploiting the shared storage slot for ownership. This enabled the attacker to take control of multiple Prime Accounts and drain funds using administrative privileges.

  • In Restake Finance, withdrawal requests store the amount of shares a user wants to burn in exchange for underlying tokens. Once processed, the protocol sends the tokens, deletes the request, and burns the shares. However, the burn operation retrieved the shares amount from the already-deleted request, resulting in a value of 0. Consequently, shares were never burned and remained stuck in the contract, diluting the shares-to-underlying conversion.

Flawed Reward Systems

  • The Pythia Finance staking contract allowed users to stake Pythia tokens in exchange for rewards. However, the rewards calculation only multiplied the user's balance by an accumulating rewards-per-share variable, without considering the duration of staking. This flaw allowed an attacker to deposit large amounts of Pythia tokens and repeatedly claim excessive rewards.

  • The BGM token rewarded transfers by allocating BGM claims to the sender, recipient, and their referrers, with rewards sourced from a BGM/USDT liquidity pool. An attacker exploited this system by creating numerous accounts and conducting artificial transfers to inflate rewards. These rewards were then withdrawn, depleting the pool of BGM and significantly increasing the token's value. Finally, the attacker leveraged the inflated price to drain the pool’s USDT.

  • The Elfi protocol allowed users to stake tokens, minting stakeToken (st) as proof of deposit while also recording the staked amount internally. Rewards were calculated based on the st.balanceOf(account) instead of the internal accounting. This oversight, coupled with the transferability of st introduced a vulnerability: users could create multiple accounts, stake tokens, and transfer st back and forth between accounts. This would allow them to claim rewards from both accounts repeatedly, effectively doubling their earnings.

  • The Ramses Exchange rewarded users based on the combination of reward periods and veNFT tokenId, using the tokenTotalSupplyByPeriod value. However, it failed to reduce tokenTotalSupplyByPeriod when rewards were claimed, allowing users to claim the same rewards multiple times. Additionally, it did not check if the current block timestamp matched the designated reward period, enabling claims for past periods. Lastly, to bypass the limit of reward claims for a tokenId, the attacker leveraged a system feature which allows minting veNFT with new tokenIds.

Transaction Timing Attacks

  • EIP-2612 (Permit) allows users to sign a token approval off-chain and have it executed on-chain by another party, eliminating the need to hold native token for gas. The signature is single-use and immutable, ensuring consistent results regardless of who submits it. However, a denial-of-service and incomplete execution attack vector was identified. In this scenario, a user submits a transaction (T) to a smart contract that both approves funds via permit()and performs an additional action (A). An attacker can observe the submitted signature, frontrun transaction T with their own transaction that solely executes the permit() and succeeds. Due to signature replay protection, this causes transaction T to revert, resulting in a situation where the funds are correctly approved, but action A is never executed.

  • Uniswap V4 Periphery Contracts used Permit2 to approve spenders in a function designed to be part of a multicall. Similarly to the above, an attacker could exploit this by frontrunning the multicall and submitting the permits separately. This would cause the legitimate multicall transaction to fail, as it attempted to consume a permit with a signature that had already been used.

  • The SquidTokenSwap contract allowed any user to trigger PancakeSwap trades between squidV1 and squidV2tokens held in the contract. Because of this and a set 5% slippage tolerance, an attacker repeatedly triggered and sandwiched these trades, taking the slippage margin as profit.

  • When claiming rewards in Opal Omnipool, the contract used Balancer.batchSwap to perform token swaps without specifying any slippage protection. Similarly to the above, this oversight would allow an attacker to steal part of the rewards by sandwiching the swaps with two swaps if his own.

  • Updating the Pyth price oracles for a Radiant market required sending ETH to the contract’s receive function, followed by calling updateUnderlyingPrices for the desired market. Since these steps were independent, an attacker could exploit this by backrunning the receive function and using the supplied ETH to update a different market than the one intended by the original transaction sender.

Forgetting to Blind Polynomials In Zero-Knowledge Protocols

  • In some zero-knowledge (ZK) protocols, particularly those using zk-SNARKs, provers demonstrate knowledge of a secret witness by encoding it as part of a polynomial. To verify correctness, the prover reveals evaluations of this polynomial at specific points, often determined by the protocol's setup. However, without protection, revealing sufficient evaluations could allow an attacker to reconstruct the polynomial and deduce the secret witness, compromising the zero-knowledge property. For example, a polynomial of degree n (i.e., n+1 coefficients) can be fully recovered from n+1 evaluations.
  • To counteract this, polynomial blinding is employed. This involves obscuring the polynomial by incorporating randomness, such as adding random coefficients to prevent reconstruction. For instance, a polynomial of degree n may be blinded to a degree n+r polynomial when evaluated at r points. This ensures that information about the polynomial cannot be inferred from its evaluations, preserving the secrecy of the witness and maintaining the protocol's zero-knowledge guarantees.
  • The implementation of Linea's PLONK Go Prover did not apply proper blinding to polynomial shards, which could allow an attacker to statistically recover the witness.

Protocol Misconfigurations

  • The VOW token was exploited due to a non-atomic testing transaction being used in production. Changing an operational parameter, testing the behavior, and then reverting the parameter were done in separate transactions. This allowed an attacker to insert their own transaction between these steps, exploiting the contract due to the critical operational parameters being in an abnormal state.

  • The UWU lending protocol set the liquidation threshold at 90% and the liquidation bonus at 10%, which were excessively high and provided little margin of safety. This setup made the protocol susceptible to even minor price fluctuations, potentially leading to bad debts. During the hack, an attacker manipulated the oracle price by just 4%, creating a position with a Loan-to-Value (LTV) ratio of 93%. By doing this, the attacker could liquidate the position, earning a profit calculated as 93% * 110% (liquidator) - 100% (liquidated) = 2% from the protocol.

Two Parser Bugs

  • In Taiko, cross-chain messages are subject to an Ether rate limiting quota on the destination chain, which caps the value transferred in a given time frame. If a message exceeds the remaining quota for the time period, it is marked as RETRIABLE and can be re-executed later. Otherwise, the message undergoes validation checks that it does not target forbidden addresses and is then executed.

    A vulnerability was discovered in the message retry flow, where the implementation did not perform the forbidden address checks before executing the message. By intentionally exceeding the quota, a malicious attacker could execute a cross-chain message not bound to any targets with the Bridge as msg.sender, and potentially drain other users' assets.

    This vulnerability highlights a common issue where identical data should not but is processed differently depending on execution paths. Similar patterns have been exploited in other systems, as described in this critical vulnerability analysis.

Incorrect VM Gas Charge

  • The Fuel VM's CCP (Code Copy) instruction copies code from a contract into memory, charging gas based solely on the size of the contract's bytecode rather than the actual number of bytes being copied into memory. Furthermore, when the destination region for copying exceeds the bytecode size, the instruction zero-fills the exceeded area. By setting an offset and copy length larger than the bytecode length, users can cause the function to zero-fill large memory regions without incurring the appropriate gas costs. This manipulation allows for performing expensive memory-clearing operations at a reduced cost, which could potentially lead to network resource exhaustion and denial-of-service attacks.

Bridge Status Mismatch

  • The Fuel Layer 1 (L1) executor will include relayed messages even when the transaction is reverted on L2. This enables an attacker to perform fake token withdrawals repeatedly. For example, the attacker triggers two token withdrawals on L2; the first one reverts in the end but still sends a message to L1, while the second succeeds. As long as there are sufficient tokens on L1, the attacker receives double the amount of tokens he initially deposited, effectively allowing the attacker to steal funds from the bridge.

LayerZero Denial of Service

  • LayerZero V1 is a cross-chain protocol enabling message sending, verification, and execution across blockchains. When a message is sent on the source chain, off-chain Relayer and Oracle services are notified, triggering a consensus on message authenticity. The Oracle submits a Merkle root on the destination chain, and the Relayer submits a proof to execute the message.

    The first part of a research article series describes a DoS attack vector through message nonces overlap, which could prevent any cross-chain message from being delivered. Messages in LayerZero are ordered by a unique nonce based on the srcChain, srcAddress, and dstChain, without considering the dstAddress. However, as applications can choose their own Relayer and Oracle, they can forge messages from any srcChain and srcAddress but not to any dstAddress. Since the nonce did not account for the dstAddress, an attacker could send a spoofed message with the same nonce, blocking legitimate messages with the same nonce from reaching the destination chain.

  • LayerZero’s OFTs (Omnichain Fungible Tokens) and ONFTs (Omnichain Non-Fungible Tokens) allow bridging of ERC20 and ERC721 tokens, respectively, by locking them on one chain and minting them on another. The third part of the series shows an attack vector where when bridging ERC721 tokens, an attacker can cause a DoS by using the onERC721Received callback on the destination chain to consume all gas, blocking the message delivery and freezing the cross-chain transfer.

LayerZero Integration Denial Of Service

  • The second part of the series involves Stargate, a protocol that enables cross-chain asset transfers and swapping through LayerZero. When swapping tokens, a message is sent together with a payload, enabling the recipient of the funds to execute arbitrary logic upon receipt. If a message delivery fails, it is handled gracefully through a try-catch, an error and payload are stored and the system considers the message delivered, ensuring the process does not block subsequent messages due to the nonce ordering.

    By encoding a payload with a recipient address that is not a smart contract, a malicious attacker could force the try-catch clause to revert without going into the catch block. This would reserve the upcoming nonce while being unable to deliver the message, causing a freeze of the messaging channel.

    A second way to block the messaging channel is by making it revert through an out-of-gas error. Because the catchclause would store the payload in contract storage, a malicious attacker could send a payload so big that it would spend all remaining gas when being stored.

  • Similarly to the Stargate vulnerabilities of reverting at the bridge level, the following thread uncovers a denial-of-service possibility in the Drips Network: if a message indefinitely reverts at the bridge level on the destination chain, the communication channel will be blocked and should ideally have a means to recover from this state. Unfortunately, a recovery mechanism was not present, and to upgrade the implementation on the destination chain, a successful cross-chain message was required but was not possible because of the above.

Faulty Upgrades

  • To approve withdrawals, the Ronin bridge required signatures from a validator set with a total weight above a certain threshold. However, during the upgrade from version 2 to version 4, the development team neglected calling the initialization function for version 3. This oversight left the threshold uninitialized and set to its default value of 0, allowing anyone to initiate withdrawals without submitting valid signatures. As a result, the bridge suffered a $12 million exploit.

  • Pike Finance’s protocol upgrade introduced a storage misalignment, mistakenly moving the initialized variable to a different slot. This allowed an attacker to reinitialize the proxy, upgrade it to a malicious implementation and ultimately steal funds.

Unset Initialization Flags

  • The Wrapped XETA contract failed to set the initialized flag in its initialize function, allowing an attacker to reinitialize the contract. The attacker gave himself token minting permissions, which they used to drain the USDT/WXETA liquidity pools.

  • Similarly, the NFGSToken contract failed to set the uniswapV2Dele flag upon deployment, which was supposed to lock the privileged _uniswapV2Proxy address from further modification. This oversight allowed an attacker to assign _uniswapV2Proxy to their own address, giving themselves access to restricted operations such as the minting of unlimited NFGS tokens. Declaring critical state variables as immutable upon initialization, rather than relying on additional locking logic, would have provided stronger protection against unauthorized changes.

Insufficient Verification of Proposer Response

  • Titan Relay's Helix implementation lacks integrity validation on KZG commitments of the proposer's response. This oversight allowed a malicious proposer to sign a blinded block with valid headers but invalid KZG commitments, receive the unblinded block, and then propose a profitable block with reordered transactions. The fix involved ensuring that the KZG commitments provided by the proposer matched those from the builder.

Others

  • The Munchables smart contract securely tracked user deposits, limiting withdrawals to each user's recorded balance. However, the proxy admin previously set an unverified and malicious implementation behind the proxy, assigning themselves a 1e24 ETH balance. This inflated balance persisted in storage, allowing the admin to later drain funds through the verified, secure implementation.

  • The research paper, Demystifying and Detecting Cryptographic Defects in Ethereum Smart Contracts, provides an analysis of cryptographic issues in smart contracts. The authors outline nine distinct categories of defects, which can serve as a checklist for security researchers:

    • Single and Cross-Contract Signature Replay

    • Signature Front-Running and Malleability

    • Insufficient Signature Verification

    • Merkle Proof Replay and Front-Running

    • Hash Collisions with Dynamic-Length Arguments

    • Weak Randomness from Hashing Chain Attributes

Conclusion

Similar to previous years, 2024 has been rich in valuable research, innovative vulnerabilities and creative exploits. It's important to emphasise that the intent behind the article is not to criticise or blame the affected projects, but rather provide objective overviews that serve as educational material for the community to learn from and better protect projects in the future.