Reproduced Exploit

Securitize OnRamp replay — signed transactions remain valid after use

executePreApprovedTransaction includes a nonce in the signed payload, but only increments the stored nonce. It never requires the supplied nonce to equal the current expected nonce. A once-valid nonce-zero subscription can therefore be submitted twice, executing the destination call twice and issui…

Jul 2025Otherbridge3 min read

Chain

Other

Category

bridge

Date

Jul 2025

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 64270-missing-nonce-validation-in-signature-verification-allows-tr. Standalone Foundry PoC and full write-up: 64270-missing-nonce-validation-in-signature-verification-allows-tr_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/bridge/replay · vuln/auth/signature-validation · vuln/logic/missing-validation

Reproduction: Local synthetic, runnable offline with forge test -vvv in the PoC folder. The browser replay calls Exploit.run() and verifies duplicate DS issuance.

Key info#

FieldValue
Loss200 synthetic DS tokens issued after replaying one 100-USDC authorization
Vulnerable contractSecuritizeOnRamp
AttackerAny party able to resubmit a previously valid pre-approved transaction
ChainLocal EVM synthetic (audit finding; no historical fork)
CompilerSolidity 0.8.24
Bug classMissing nonce validation / signature replay

TL;DR#

executePreApprovedTransaction includes a nonce in the signed payload, but only increments the stored nonce. It never requires the supplied nonce to equal the current expected nonce. A once-valid nonce-zero subscription can therefore be submitted twice, executing the destination call twice and issuing duplicate investor tokens.

The vulnerable code#

The reduction preserves the material line from SecuritizeOnRamp.sol:

SOLIDITY
// FIX: require(txData.nonce == noncePerInvestor[txData.senderInvestor], "invalid nonce");
noncePerInvestor[txData.senderInvestor] = noncePerInvestor[txData.senderInvestor] + 1; // @> VULN: stored nonce is incremented but txData.nonce is never validated, so an old valid signature replays.
(bool ok,) = txData.destination.call(txData.data);

Root cause#

Authenticity and freshness are separate properties. Signature recovery proves an authorization was produced by a permitted signer; it does not prove that the authorization has not already been consumed. Incrementing noncePerInvestor after recovery does not invalidate an old payload unless txData.nonce is compared to that stored value first.

Preconditions#

  • A valid signed subscription payload exists.
  • The investor still has the assets/allowance required by the destination on replay.
  • The attacker can submit the valid payload again.

Attack walkthrough#

  1. Exploit.run() funds its synthetic investor with 200 USDC and prepares one nonce-zero subscription.
  2. The first call transfers 100 USDC and issues 100 DS tokens.
  3. The on-ramp increments its stored nonce but accepts no equality check.
  4. The exact same signature and txData execute again.
  5. The invariant assertions show stored nonce 2, 200 DS issued, and all 200 USDC consumed.

Diagrams#

sequenceDiagram participant A as Attacker participant R as SecuritizeOnRamp participant D as Subscription destination A->>R: valid signature and nonce 0 R->>D: execute subscription D-->>A: issue 100 DS A->>R: replay same signature and nonce 0 R->>D: executes again because nonce is unchecked D-->>A: issue another 100 DS

Impact#

Every previously valid authorization can be replayed until the investor's available balance or allowance is exhausted. That can duplicate subscriptions, swaps, token issuance, and accounting effects.

Remediation#

Before signature recovery and the destination call, require txData.nonce == noncePerInvestor[txData.senderInvestor]; then increment it exactly once. Reject mismatched or stale nonces.

How to reproduce#

BASH
cd /workspaces/RustroverProjects/audits/evm-hack-registry/64270-missing-nonce-validation-in-signature-verification-allows-tr_exp
forge test -vvv

The browser bundle is generated from scripts/poc-configs/64270-missing-nonce-validation-in-signature-verification-allows-tr.mjs.

Sources#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.