Reproduced Exploit
Oku — Failure to reset unspent approval to the target address wipes the contract balance
1. execute approves an arbitrary target for the full order.amountIn. 2. The target call need only move 1 wei of tokenIn / tokenOut to pass the minAmountOut / over-spend checks. 3. Residual allowance is never set to zero; the order creator is refunded
Chain
Other
Category
approval
Date
Nov 2024
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
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.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 44376-h-6-failure-to-reset-unspent-approval-to-the-target-address. Standalone Foundry PoC and full write-up: 44376-h-6-failure-to-reset-unspent-approval-to-the-target-address_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/approval/residual · vuln/logic/direct-drain · vuln/access/untrusted-target
Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only
forge-std— no fork, no RPC, noanvil_state. Full trace: output.txt. PoC: test/44376-h-6-failure-to-reset-unspent-approval-to-the-target-address_exp.sol.
AuditVault taxonomy: lang/solidity · sector/dex · sector/oracle · sector/token · platform/sherlock · has/github · has/poc · severity/high · impact/loss-of-funds/direct-drain · genome: wrong-condition · direct-drain · fot-slippage · oracle-freshness
Key info#
| Impact | HIGH — residual ERC20 allowance on an untrusted fill target lets an attacker drain other users' pending-order inventory |
| Protocol | Oku Custom Order Types — OracleLess.execute |
| Vulnerable code | OracleLess.execute — order.tokenIn.safeApprove(target, order.amountIn) with no post-call zeroing |
| Bug class | Residual approval / untrusted-target drain |
| Finding | Sherlock — 2024-11-oku · #44376 (H-6) · reporter 0x0x0xw3 |
| Report | 2024-11-oku-judging |
| Source | AuditVault |
| Status | Audit finding. Reproduced here as a standalone local PoC. |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
executeapproves an arbitrarytargetfor the fullorder.amountIn.- The target call need only move 1 wei of tokenIn / tokenOut to pass the minAmountOut / over-spend checks.
- Residual allowance is never set to zero; the order creator is refunded unspent tokenIn, so inventory of other orders remains in the vault.
- The malicious target later
transferFroms the residual against the vault, wiping pending-order balances.
The vulnerable code#
//approve
order.tokenIn.approve(target, order.amountIn); // @> VULN: never reset after call
//perform the call
(bool success, bytes memory reason) = target.call(txData);
// MISSING: order.tokenIn.approve(target, 0);
Fix (per report):
+ order.tokenIn.safeApprove(target, 0);
Root cause#
Approvals to untrusted fill targets are not scoped to the amount actually spent. Combined with refunding unspent tokenIn to the order creator, residual allowance becomes a free option on the vault's remaining inventory.
Preconditions#
- Pending orders leave tokenIn inventory in the vault.
- Attacker can create an order and choose the fill
target.
Attack walkthrough#
- Victim creates a 100e18 tokenIn order (inventory sits in OracleLess).
- Attacker creates a 100e18 order, fills via malicious target that pulls 1 wei and returns 1 wei tokenOut.
- Residual allowance ≈ 100e18 − 1 remains for the target against the vault.
spendAllowancedrains leftover into the attacker — wiping victim inventory.
Diagrams#
Impact#
Entire vault tokenIn balance (all pending orders of the approved token) can be stolen by repeating the residual-allowance drain.
Sources#
- AuditVault finding #44376
- Sherlock 2024-11-oku judging
- Reduced source: sherlock-audit/2024-11-oku
oku-custom-order-types/contracts/automatedTrigger/OracleLess.sol(execute approval path)
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 44376-h-6-failure-to-reset-unspent-approval-to-the-target-address_exp (evm-hack-registry mirror).
- AuditVault finding: 44376-h-6-failure-to-reset-unspent-approval-to-the-target-address.
Alerts & third-party analyses
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.