Reproduced Exploit
Hydro — anyone can clear a DenyList entry and move the frozen balance
remove_addr_from_blacklist takes the current DenyList and an address and removes that address. Other mutating functions in the same module check that the caller is the owner. This one does not.
Chain
Other
Category
access-control
Date
Apr 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: 35502-anyone-could-remove-anyone-from-the-blacklist-zokyo-none-hyd. Standalone Foundry PoC and full write-up: 35502-anyone-could-remove-anyone-from-the-blacklist-zokyo-none-hyd_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/access-control/missing-owner-check · vuln/access-control/missing-modifier
Reproduction: a self-contained Foundry PoC that compiles and runs offline with only
forge-std— no fork and no RPC. Trace: output.txt. PoC: test/35502-anyone-could-remove-anyone-from-the-blacklist-zokyo-none-hyd_exp.sol.
Key info#
| Impact | HIGH — a blacklisted balance of 1,000 HYDRO is unlisted by its holder and transferred out |
| Protocol | Hydro remove_addr_from_blacklist |
| Vulnerable code | The entry takes the current DenyList and an address and deletes it, with no owner check |
| Bug class | Missing owner check on a blacklist removal |
| Finding | Zokyo — Hydro, April 2024 · id 35502 |
| Report | Zokyo Hydro, 23 April 2024 |
| Source | AuditVault finding |
| Status | The report marks the item Invalid; the missing check is reproduced here as described. The fixed function requires the owner. |
| Compiler | solc 0.8.27, cancun, via IR |
TL;DR#
remove_addr_from_blacklist takes the current DenyList and an address and removes that address. Other mutating functions in the same module check that the caller is the owner. This one does not.
A holder with 1,000 HYDRO is blacklisted, so transfer reverts. They pass the one-address list and their own address. The flag clears. The 1,000 HYDRO moves to the receiver.
The owner-checked function reverts for any other caller and the flag stays set.
run() does not warp. The Move entry is modeled as the Solidity function the report describes. The list argument is the DenyList the entry requires the caller to pass.
The vulnerable code#
function removeAddrFromBlacklist(address[] calldata listed, address who) external {
require(found, "listed");
// @> VULN: the DenyList is updated with no owner == msg.sender check
denylist[who] = false;
}
transfer then requires !denylist[msg.sender]. Once the flag is false, the frozen balance moves.
Root cause#
The deletion is authorized by possession of the list argument, not by the owner capability the other mutating functions take. Anyone who can pass the current list can drop any address that is on it, including themselves.
Preconditions#
- The holder is on the DenyList and has a balance of 1,000 HYDRO.
transferreverts while the flag is set.- No timestamp change happens inside
run().
Attack walkthrough#
- Read
denylist[holder]. It is true. The balance is 1,000 HYDRO. - Call
removeAddrFromBlacklist([holder], holder). The caller is not the owner. denylist[holder]is false.transfersends 1,000 HYDRO to the receiver. The holder's balance is 0.
Control: the same call from a non-owner on the fixed token reverts with owner. The address stays on the list.
Diagrams#
Impact#
1,000 HYDRO that the blacklist was holding in place moves to an arbitrary receiver. The owner is not involved.
Remediation#
Take the owner capability as an argument and require owner == msg.sender, matching the other mutating functions in the module.
Sources#
Taxonomy#
severity/highvuln/access-control/missing-owner-checkvuln/access-control/missing-modifier
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 35502-anyone-could-remove-anyone-from-the-blacklist-zokyo-none-hyd_exp (evm-hack-registry mirror).
- AuditVault finding: 35502-anyone-could-remove-anyone-from-the-blacklist-zokyo-none-hyd.
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.