Reproduced Exploit
Etherspot CredibleAccountModule — anyone can uninstall a wallet's validator
onUninstall loads the wallet address from the second calldata word and never checks that it equals msg.sender. An attacker passes the victim wallet. The module clears validatorInitialized for that wallet. The wallet's execute requires the validator to still be installed, so the owner's withdrawal o…
Loss
1,000.000000 USDC stuck in the victim wallet. Owner execute succeeds once, then reverts after the attacker cl…
Chain
Ethereum
Category
access-control
Date
Jun 2025
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: 61408-c-06-anyone-can-uninstall-the-validator-module-from-any-wall. Standalone Foundry PoC and full write-up: 61408-c-06-anyone-can-uninstall-the-validator-module-from-any-wall_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/access-control/missing-auth · vuln/dos/frozen-funds
Key info#
| Field | Value |
|---|---|
| Loss | 1,000.000000 USDC stuck in the victim wallet. Owner execute succeeds once, then reverts after the attacker clears validatorInitialized |
| Vulnerable contract | CredibleAccountModule.onUninstall |
| Attacker EOA | 0x1111111111111111111111111111111111111111 |
| Attack contract | Exploit |
| Attack tx | Exploit.run() |
| Chain / block / date | Ethereum model · block 0 · 2025-06 |
| Compiler | solc 0.8.27, cancun, via IR |
| Bug class | Calldata sender is trusted in place of msg.sender |
TL;DR#
onUninstall loads the wallet address from the second calldata word and never checks that it equals msg.sender. An attacker passes the victim wallet. The module clears validatorInitialized for that wallet. The wallet's execute requires the validator to still be installed, so the owner's withdrawal of the 1,000 USDC reverts and the balance stays in the wallet. A fixed module that reverts unless sender == msg.sender leaves the validator installed, and the same withdrawal pays the owner 1,000 USDC.
Background#
Shieldify reviewed Etherspot's Credible Account Module (C-06, critical) against commit d4774db9f544cc6f69000c55e97627f93fe7242b (2025-06-09). The module is an ERC-7579 validator and hook. Installing it as a validator requires the account's active hook to be the HookMultiPlexer and the module to be registered on that multiplexer. Uninstall is supposed to be the account itself calling back into the module. The implementation instead treats a caller-supplied address as the account.
The vulnerable code#
function onUninstall(bytes calldata data) external {
uint256 moduleType;
address sender;
assembly {
moduleType := calldataload(data.offset)
}
assembly {
// @> VULN: sender is taken from calldata and never compared to msg.sender
sender := calldataload(add(data.offset, 32))
}
moduleInitialized[sender].validatorInitialized = false;
}
The recommended fix is to reject the call unless the decoded account is the caller:
sender := calldataload(add(data.offset, 32))
}
+ if (sender != msg.sender) revert SenderMismatch(msg.sender, sender);
if (moduleType == MODULE_TYPE_VALIDATOR) {
Root cause#
ERC-7579 onUninstall is invoked by the account, so msg.sender is the account being modified. This function ignores msg.sender and writes moduleInitialized[sender], where sender is calldataload(data.offset + 32). The hook checks that follow (getActiveHook, hasHook) are evaluated against that spoofed account, not against the caller. With the multiplexer installed on the victim, those checks pass for any external caller.
Preconditions#
- The victim wallet has installed
CredibleAccountModuleas a validator, sogetActiveHook()is the multiplexer andhasHookis true. - The attacker has no live session keys of their own.
getSessionKeysByWallet()readsmsg.sender, so the locked-token loop is empty and does not revert. - The wallet's
executerefuses to run onceisInitializedis false. That is the account-side consequence of removing the only validator.
Attack walkthrough#
- The exploit funds the victim wallet with 1,000 USDC.
- The owner installs the validator.
validatorInitializedbecomes true. - The owner calls
executewith amount 0.executionsbecomes 1. The validator is live. - The attacker calls
onUninstall(abi.encode(MODULE_TYPE_VALIDATOR, wallet)). - The assembly word at
data.offset + 32is the wallet, not the attacker. validatorInitializedfor the wallet is set to false anduninstalledFromrecords the wallet.- The owner calls
executefor the full 1,000 USDC. The wallet reverts withvalidator uninstalled. - The wallet still holds 1,000 USDC. The owner holds 0.
Diagrams#
Impact#
The owner could move funds before the uninstall and cannot move them after. 1,000.000000 USDC stays in the wallet, executions stays 1, and isInitialized(wallet) is false. The control module reverts SenderMismatch for the same payload, isInitialized stays true, and execute transfers the 1,000 USDC to the owner.
PoC#
cd 61408-c-06-anyone-can-uninstall-the-validator-module-from-any-wall_exp
forge test -vv
test_exploit_uninstall_bricks_owner_execute asserts the stuck balance is 1,000e6, the post-uninstall execute reverted, and only the pre-uninstall execute ran. test_control_sender_must_equal_caller asserts the fixed module rejects the attacker and then pays the owner 1,000e6.
Sources#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/61408-c-06-anyone-can-uninstall-the-validator-module-from-any-wall.md
- Shieldify report: https://github.com/shieldify-security/audits-portfolio-md/blob/main/Etherspot-CredibleAccountModule-Security-Review.md
- Reviewed source: https://github.com/etherspot/etherspot-modular-accounts/blob/d4774db9f544cc6f69000c55e97627f93fe7242b/src/modules/validators/CredibleAccountModule.sol
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 61408-c-06-anyone-can-uninstall-the-validator-module-from-any-wall_exp (evm-hack-registry mirror).
- AuditVault finding: 61408-c-06-anyone-can-uninstall-the-validator-module-from-any-wall.
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.