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.
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.
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.
The fix ensures that the settle
call must settle the previously synced token, otherwise the transaction will revert.
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:
Ethereum Contracts: Manage staking, checkpoint submissions and validator rewards.
Consensus Layer (Heimdall): Determines block producers based on Ethereum's staking state and pushes checkpoint updates.
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 newSigner
that parses into a massive newAmount
, artificially inflating their voting power.
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.
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.
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.
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.
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 transferOwnership
call after a quorum of 3/11 authorized signers was reached.
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.
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 Well
they 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.
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 onERC1155Received
function 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.
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 transferFrom
function. 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.6
which 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.
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 onFlashLoan
method 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 swapData
parameter, allowing an attacker to invoke a WETH transferFrom
instead of a token swap and steal assets that were approved to the contract.
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 TimelockTokenPool
contracts.
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.
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.
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.
In the HYDT token system, the InitialMint
contract let users deposit BNB
in exchange for newly minted HYDT
tokens. 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 WOO
tokens out of the market.
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.
In Mantle, bridging ETH
or MNT
locks the tokens on the source chain and mints them to the CrossDomainMessenger
contract 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.
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 uniBTC
against 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.
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.
The TCH
system allowed token burning from a Uniswap pair using a signature from an authorized signer. However, it had two significant vulnerabilities: it used ecrecover
for signer recovery, which is susceptible to malleability, and it enforced the uniqueness of signatures rather than message digests. This combination allowed an attacker to modify and re-submit past signatures, enabling them to burn double the intended amount and inflate the value of their TCH
tokens.
Similarly, Ironblocks Onchain Firewall implemented a custom signer recovery process, where the signature was split into v
, r
and s
and then fed into ecrecover
. However, it failed to validate that the s
value is within the secure bounds, creating the potential for signature malleability. To fully mitigate this attack vector, it is recommended to use the OpenZeppelin ECDSA
library.
Hedgey Finance allowed users to create vesting campaigns via the createLockedCampaign
function, which transfered funds from the campaign creator (msg.sender
) and approved a ClaimLockup
contract to manage these funds based on the campaign terms. When a campaign was canceled, the remaining funds were returned to the creator, but the approval for the ClaimLockup
contract was not revoked. An attacker exploited this flaw by creating a malicious ClaimLockup
contract. The contract set up a campaign, canceled it to retrieve all deposited funds, and then called transferFrom
on the lingering approval to receive the funds a second time.
The OwnershipNFTs.sol
contract of the Superposition AMM implemented custom ERC721 logic which did not revoke token approvals upon transfer. This oversight would allow an NFT owner to approve the token to themselves, sell it to a victim and then use the lingering approval to reclaim the NFT without consent.
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.
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.
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.
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 init
function 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.
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
.
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 squidV2
tokens 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.
n
(i.e., n+1
coefficients) can be fully recovered from n+1
evaluations.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.
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.
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.
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.
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 catch
clause 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.
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.
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.
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
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.