Reproduced Exploit
Oku OracleLess: a cancelled order can be modified to withdraw its escrow twice
Chain
Other
Category
logic
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: 44374-h-4-users-can-modify-a-cancelled-order-withdrawing-the-same. Standalone Foundry PoC and full write-up: 44374-h-4-users-can-modify-a-cancelled-order-withdrawing-the-same_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/missing-check · vuln/defi/direct-drain · vuln/accounting/double-spend
Reproduction: the test deploys the real, unmodified
OracleLessandAutomationMasterfrom the audited Oku repo (only the opaque order tokens are minimal real ERC20s) and runs the real cancel → modify path to withdraw the same escrow twice, draining a second depositor's funds.
Root cause#
OracleLess.cancelOrder → _cancelOrder removes an order's id from pendingOrderIds and refunds order.amountIn to the recipient, but it never deletes orders[orderId]. The order struct is left fully populated:
function _cancelOrder(Order memory order) internal returns (bool) {
for (uint96 i = 0; i < pendingOrderIds.length; i++) {
if (pendingOrderIds[i] == order.orderId) {
pendingOrderIds = ArrayMutation.removeFromArray(i, pendingOrderIds);
order.tokenIn.safeTransfer(order.recipient, order.amountIn); // refund #1
emit OrderCancelled(order.orderId);
return true;
}
}
return false;
}
modifyOrder → _modifyOrder then re-reads orders[orderId] and only checks msg.sender == order.recipient. It never checks that the order is still pending/active, so the owner of an already-cancelled order can reduce it and be refunded again:
uint256 newAmountIn = order.amountIn; // still 1e18 for a cancelled order
...
} else {
require(amountInDelta < order.amountIn, "invalid delta");
newAmountIn -= amountInDelta;
order.tokenIn.safeTransfer(order.recipient, amountInDelta); // refund #2
}
The second refund is paid from the contract's remaining balance, i.e. other depositors' escrows. Repeating cancel-then-modify drains the whole contract. The same flaw exists in Bracket and StopLimit; OracleLess is the minimal representative cited by the finding.
The real contract is vendored at src/oku/contracts/automatedTrigger/OracleLess.sol.
Exploit walkthrough (numbers from the test)#
- An honest depositor escrows a real
1e18order →OracleLessholds1e18. - The attacker escrows their own
1e18order →OracleLessholds2e18, attacker holds0. cancelOrder(id)refunds the attacker1e18(withdrawal #1).orders[id]still hasamountIn = 1e18.modifyOrder(id, …, amountInDelta = 1e18 - 1, increasePosition = false)refunds the attacker1e18 - 1(withdrawal #2) — paid out of the honest depositor's escrow.- The attacker deposited
1e18and walks away with2e18 - 1; the pool is left with1wei. Net theft ≈ 1e18 (999999999999999999 wei), exactly the honest depositor's escrow.
Reproduce#
# from the evm-hack-registry root
_shared/run-poc/run_poc.sh 44374-h-4-users-can-modify-a-cancelled-order-withdrawing-the-same_exp -vvvvv
Expected: 1 passed. The test in test/44374-…_exp.sol asserts the attacker's balance rises from 1e18 to 2e18 - 1 while the contract is drained to 1 wei.
Sources#
- AuditVault finding #44374
- Sherlock Oku contest (issue #542)
- Audited source:
sherlock-audit/2024-11-oku@ee3f781 - Fix: gfx-labs/oku-custom-order-types PR #1
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 44374-h-4-users-can-modify-a-cancelled-order-withdrawing-the-same_exp (evm-hack-registry mirror).
- AuditVault finding: 44374-h-4-users-can-modify-a-cancelled-order-withdrawing-the-same.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Oku OracleLess: a cancelled order can be modified to withdraw its escrow twice".
- 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.