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…

Jun 2024Otheraccess-control3 min read

Chain

Other

Category

access-control

Date

Jun 2024

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

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.

Loading fork state…

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-registry mirror.


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, no anvil_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#

ImpactHIGH — revoked admins retain hasRole and can still drain privileged paths
ProtocolAstrolab (AsSequentialSet / role members)
Vulnerable codeAsSequentialSet.remove — does not clear index[o]
Bug classBroken set membership after revoke (stale index)
FindingPashov Audit Group · Astrolab · #58098
Reportpashov/audits Astrolab
SourceAuditVault
StatusAudit finding. Reproduced as a standalone local PoC.
Compiler^0.8.24 (PoC)

TL;DR#

  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 stays in range → hasRole still true.
  5. A "revoked" admin can still call adminWithdraw and drain the treasury. Fix: q.index[o] = 0 before removeAt.

The vulnerable code#

SOLIDITY
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#

  1. OldAdmin is sole DEFAULT_ADMIN (index 1); vault holds 1000 tokens.
  2. Grant NEW_ADMIN (index 2).
  3. OldAdmin.revokeSelf() — intended full handoff.
  4. hasRole(OldAdmin) remains true (stale index).
  5. OldAdmin.drain succeeds → 1000 tokens stolen.

Diagrams#

sequenceDiagram participant Old as OldAdmin participant Vault as RoleVault participant Set as AsSequentialSet Old->>Vault: grantRole NEW_ADMIN Old->>Vault: revokeRole self Vault->>Set: remove oldAdmin Note over Set: index not cleared Old->>Vault: adminWithdraw treasury Vault-->>Old: 1000 tokens sent

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#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • 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.