Reproduced Exploit
Astrolab — Accounts not properly removed from roles upon revoking
1. Roles are stored in AsSequentialSet.Set (data[] + 1-based index map). 2. remove(o) reads index[o] and calls removeAt, but never sets index[o] = 0. 3. has(o) returns true when index[o] > 0 && index[o] <= data.length. 4. After removing a non-last member (swap-and-pop), the revoked account's index…
Chain
Other
Category
access-control
Date
Jun 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: 58098-h-01-accounts-not-properly-removed-from-roles-upon-revoking. Standalone Foundry PoC and full write-up: 58098-h-01-accounts-not-properly-removed-from-roles-upon-revoking_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/access-control/broken-logic · privilege-escalation/role-bypass · known-pattern
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/58098-h-01-accounts-not-properly-removed-from-roles-upon-revoking_exp.sol.
AuditVault taxonomy: severity/high · sector/bridge · access-roles · access-control/broken-logic · privilege-escalation/role-bypass · known-pattern · single-tx · redesign-logic · blast-radius/single-user
Key info#
| Impact | HIGH — revoked admins retain hasRole and can still drain privileged paths |
| Protocol | Astrolab (AsSequentialSet / role members) |
| Vulnerable code | AsSequentialSet.remove — does not clear index[o] |
| Bug class | Broken set membership after revoke (stale index) |
| Finding | Pashov Audit Group · Astrolab · #58098 |
| Report | pashov/audits Astrolab |
| Source | AuditVault |
| Status | Audit finding. Reproduced as a standalone local PoC. |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
- Roles are stored in
AsSequentialSet.Set(data[]+ 1-basedindexmap). remove(o)readsindex[o]and callsremoveAt, but never setsindex[o] = 0.has(o)returns true whenindex[o] > 0 && index[o] <= data.length.- After removing a non-last member (swap-and-pop), the revoked account's index stays in range →
hasRolestill true. - A "revoked" admin can still call
adminWithdrawand drain the treasury. Fix:q.index[o] = 0beforeremoveAt.
The vulnerable code#
function remove(Set storage q, bytes32 o) internal {
uint32 i = q.index[o];
// FIX: q.index[o] = 0;
require(i > 0, "Element not found");
removeAt(q, i - 1); // @> VULN
}
function has(Set storage q, bytes32 o) internal view returns (bool) {
return q.index[o] > 0 && q.index[o] <= q.data.length;
}
Root cause#
Membership is decided by the index map, not by scanning data. Clearing an array slot / popping without zeroing index[o] leaves a stale positive index. After a non-last removal, data.length still satisfies index[o] <= length, so has lies.
Preconditions#
- At least two members in the role set.
- Revoke targets a non-last member (typical ownership handoff: first admin renounces after granting the second).
Attack walkthrough#
OldAdminis soleDEFAULT_ADMIN(index 1); vault holds 1000 tokens.- Grant
NEW_ADMIN(index 2). OldAdmin.revokeSelf()— intended full handoff.hasRole(OldAdmin)remains true (stale index).OldAdmin.drainsucceeds → 1000 tokens stolen.
Diagrams#
Impact#
Critical roles (including default admin) can be "revoked" while retaining full privileges. Ownership transfers and emergency revocations fail silently from an access-control perspective; the old principal can continue privileged operations including fund extraction.
Sources#
- AuditVault finding #58098
- Pashov Astrolab security review
- PoC gist referenced in finding: thangtranth/685dd8fa…
- Reduced source: Astrolab
AsSequentialSet.solremove/hasas quoted in the report
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 58098-h-01-accounts-not-properly-removed-from-roles-upon-revoking_exp (evm-hack-registry mirror).
- AuditVault finding: 58098-h-01-accounts-not-properly-removed-from-roles-upon-revoking.
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.