Reproduced Exploit
Crestal Network: `Payment::payWithERC20` is public — anyone can drain an approved victim
Chain
Other
Category
untagged
Date
Jan 1970
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: Payment.sol#L25-L32. Standalone Foundry PoC and full write-up: 55092-crestal-network-anyone-approving-blueprintv5-can-drain-erc20-via-payment-paywitherc20_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/theft · vuln/access-control
Reproduction: a faithful minimal reproduction of the vulnerable finding — the vulnerable function is reproduced verbatim (marked
@>) with faithful minimal doubles; local deploy, no fork.
Root cause#
In crestal-omni-contracts/src/Payment.sol#L25-L32, the token-pull helper is declared public (it should be internal), and it takes an arbitrary fromAddress. The function is reproduced verbatim:
@> function payWithERC20(address erc20TokenAddress, uint256 amount, address fromAddress, address toAddress) public {
// check from and to address
require(fromAddress != toAddress, "Cannot transfer to self address");
require(toAddress != address(0), "Invalid to address");
require(amount > 0, "Amount must be greater than 0");
IERC20 token = IERC20(erc20TokenAddress);
token.safeTransferFrom(fromAddress, toAddress, amount);
}
Because it is public, anyone can call it directly and supply any fromAddress that still has an allowance to the BlueprintV5 (Payment) contract, moving those tokens to any toAddress of the attacker's choosing.
Why it's exploitable here#
- A victim approves the
BlueprintV5/Payment contract to spend their USDC (the normal flow to pay for a service) — here, the full 10,000 USDC. - An unrelated attacker calls
payWithERC20(USDC, 10_000e6, victim, attacker)directly. - Every
requirepasses (from ≠ to, to ≠ 0, amount > 0), andsafeTransferFrompulls the victim's entire approved balance straight to the attacker. - The victim took no action beyond the ordinary approval and loses all 10,000 USDC.
Attack path#
Marked-line walkthrough (Playground)#
The EVM Playground pins each step to the exact executed source line in 0x671d353a…:
- L46 — SafeERC20 confirms the transfer: The SafeERC20 wrapper checks
transferFromsucceeded, so the victim's approved tokens move exactly like real USDC. - L50 — USDC payment token in play: Setup: the payment token is a faithful USDC double with 6 decimals and real balance accounting.
- L51 — Real allowance accounting enforced: Setup: allowances are tracked exactly like real USDC, so
transferFromonly moves tokens the victim actually approved. - L100 — Public payWithERC20 takes any caller: Root cause:
payWithERC20ispublicand takes an arbitraryfromAddress, so anyone can pull an approved victim's tokens. - L106 — Honest victim approved the contract: Setup: the victim is an ordinary user who approved the BlueprintV5 (Payment) contract to spend USDC.
- L111 — Victim grants max allowance: Setup: the victim approves BlueprintV5 for the max amount, arming the exact allowance the attacker abuses.
PoC#
Registry (Foundry, local deploy — verbatim vulnerable source + harm-asserting test):
cd 55092-crestal-network-anyone-approving-blueprintv5-can-drain-erc20-via-payment-paywitherc20_exp
forge test -vvv
The browser Playground replays the same synthetic opcode-for-opcode and measures the harm: an unrelated attacker drains the victim's full 10,000 USDC approved balance with zero victim action. Both gates are green (registry forge test PASS + Playground _verify-poc VERDICT: PASS).
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 55092-crestal-network-anyone-approving-blueprintv5-can-drain-erc20-via-payment-paywitherc20_exp (evm-hack-registry mirror).
- AuditVault finding: Payment.sol#L25-L32.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Crestal Network:
Payment::payWithERC20is public". - 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.