Worked example · 2026-09
Safe strategy executor — the permission check passed because the module asked itself for permission, and an MEV bot took the proceeds before the attacker could — Gnosis Safe / whitelisted strategy-executor module / Ethereum — 2026-09-15
Explicitly not OAK-T11.003 (In-Use Multisig Smart-Contract Manipulation) and explicitly not OAK-T10.001-style key compromise. No Safe owner key was compromised, no signer set was modified, and no owner signature was forged or required. The multisig threshold was never tested, because the path used never asks for one: a module executes on the account's behalf by design. Explicitly not OAK-T5.004 (Sandwich MEV) either — the MEV bot's interception is fund disposition, not the attack mechanism, and is recorded here as metadata.
Summary
The victim was a Gnosis Safe on Ethereum holding a large aEthrsETH position — Aave-wrapped rsETH, KelpDAO's liquid restaking token. To manage that position the Safe had enabled a strategy-executor module and whitelisted it, a standard arrangement for automated Uniswap V4 LP management.
That module, at 0x4f0055926c839D1d960a82CBF84E2eE933958ebC, had two defects that compose into a full account takeover. Its keeper multicall(address,bytes[]) was publicly callable. And its internal check, _isAuthorized, returned true whenever the target _contract was address(this) — so a caller could route a call through the module's own address and clear the permission gate without holding any permission.
Past that gate, the module forwarded caller-supplied calldata to the Safe's execTransactionFromModuleReturnData using operation = 1, i.e. DELEGATECALL. At that point the attacker was executing arbitrary code in the Safe's own context, with the Safe's assets.
The extraction was staged rather than direct. The attacker deployed a Uniswap V4 pool with a custom hook and a worthless token they had minted, then drove the Safe's whitelisted V4 LP path into that pool. The hook unwrapped ~2,900 aEthrsETH into rsETH and siphoned it out during the operation, leaving the Safe holding a valueless LP NFT — a position that looks like a position and is worth nothing.
The ending is unusual. The exploit transaction sat in the public mempool, and an MEV bot called yoink front-ran it, capturing essentially the entire ~2,882 rsETH for itself. The attacker built the exploit and someone else collected. KelpDAO paused the bot's receiving address for 24 hours as a precaution; rsETH backing and Kelp's own contracts were never in scope.
Timeline (UTC, 2026-09-15)
| When | Event | OAK ref |
|---|---|---|
| (standing) | Safe enables and whitelists a strategy-executor module for Uniswap V4 LP management | (normal operation) |
| (standing) | Module exposes a public keeper multicall(address,bytes[]); _isAuthorized returns true when _contract == address(this) |
(latent T9.004 defect) |
| (standing) | Same path forwards caller-supplied calldata to execTransactionFromModuleReturnData, operation = 1 (DELEGATECALL) |
(latent privilege amplifier) |
| before the drain | Attacker deploys a hooked Uniswap V4 pool containing a worthless self-minted token (reported as "PAT") | (execution vehicle — no Technique at v0.x) |
| 04:38 | Single transaction: public multicall entered with _contract = address(this); gate clears; DELEGATECALL executes attacker calldata in the Safe's context |
T9.004 exploitation |
| same tx | Hook unwraps ~2,900 aEthrsETH into rsETH and removes it; Safe left holding a worthless LP NFT | (extraction) |
| same block | MEV bot yoink front-runs the exploit transaction and captures 0xC70f00CD7E461686b04B0E912E309becA8b80ea0 |
(fund disposition) |
| shortly after | ~$160K additional moved in follow-on transactions | (extraction tail) |
| same day | Blockaid, PeckShield and BlockSec confirm the exploit and publish the mechanism | (external forensics) |
| same day | KelpDAO applies a precautionary 24-hour pause to the receiving address; states core contracts and backing unaffected | (third-party response) |
What defenders observed
- The whole security boundary was one helper function, and it was checking the wrong subject.
_isAuthorizedtook an address parameter and compared it against a target rather than againstmsg.sender. Passingaddress(this)turned the check into a tautology. This is not an obscure composition bug — it is the authorisation predicate reading a value the attacker supplies, and it is findable by reading the function signature and asking whose permission is being tested. - A public entrypoint plus
DELEGATECALLis not a vulnerability with a severity rating, it is account ownership. Once caller-supplied calldata reachesexecTransactionFromModuleReturnDatawithoperation = 1, everything downstream — which token, which pool, which hook — is decoration. The attacker's choice of a hooked V4 pool was a convenience for laundering the value out through something that looks like an LP operation, not a requirement. - Uniswap V4 hooks give the attacker a place to put their logic inside a legitimate-looking call. The Safe's whitelisted path was "manage a V4 LP position," and the attacker supplied the pool. The hook ran as part of normal pool mechanics. A whitelisted operation is not a whitelisted counterparty — the module was permitted to LP, and nothing constrained what it could LP into.
- The victim's residual asset was an NFT, which delays the alarm. The Safe did not go to zero; it went to a position whose token still existed and whose value did not. Balance-based monitoring that counts positions rather than pricing them reports nothing here.
- An MEV bot taking the proceeds changes recovery, not risk. The Safe lost the funds either way. What changes is that the value sat with an identifiable, address-stable searcher rather than with the exploiter — which is why KelpDAO's 24-hour pause on that address was worth applying, and why the disposition is recorded as metadata rather than as mitigation. Nothing about this outcome is repeatable or defensive; the next identical bug clears the mempool without a bot in the way.
Public references
[mpostsafersweth2026]— Metaverse Post, "Exploit Drains $7.8M In rsETH From User's Safe Wallet Through Compromised Strategy Executor" (2026-09-15 04:38 UTC; executor0x4f0055926c839D1d960a82CBF84E2eE933958ebC;operation=1DELEGATECALL viaexecTransactionFromModuleReturnData; hooked Uniswap V4 pool with "Permissionless Attacker Token";yoinkfront-run to0xC70f00CD7E461686b04B0E912E309becA8b80ea0; Blockaid / PeckShield / BlockSec; KelpDAO 24-hour pause): https://mpost.io/exploit-drains-7-8m-in-rseth-from-users-safe-wallet-through-compromised-strategy-executor/[bitcoinworldsafe2026]— BitcoinWorld, "Gnosis Safe Wallet Loses $7.7 Million, And Hackers Never Had To Crack It" ($7.73M; attack entered through a connected module rather than the wallet; faulty check of who was issuing the orders): https://bitcoinworld.co.in/gnosis-safe-wallet-loses-7-7-million-and-hackers-never-had-to-crack-it/[cryptoeconomysafe2026]— Crypto Economy, "Attacker Drains 2,900 rsETH From Gnosis Safe Wallet in Major Ethereum Heist" (~2,900 rsETH;multicall(address,bytes[])with_contract = address(this)causing_isAuthorizedto returntrueunconditionally): https://crypto-economy.com/attacker-drains-2900-rseth-from-gnosis-safe-wallet-in-major-ethereum-heist/
Discussion
This is the third Safe-module authority failure OAK has recorded in 2026, after SquidRouterModule (2026-05-25, a counterfeit module whose "authorisation" was a publicly-known constant string) and Gnosis Pay / Zodiac (2026-06-01, where a vulnerable fallback handler on a Safe assigned as a module or role member let an audited Delay/Roles modifier authorise and execute without its cooldown predicate being satisfied). All three classify as T9.004 and all three share a shape the parent Technique does not name: the failing component is a module holding standing, signature-free execution authority over an account, and the module's own caller-gating is the entire security boundary.
The three are not the same bug. SquidRouterModule's gate was fake by design — a malicious module presented as a legitimate one. Gnosis Pay's gate was real, audited and composed wrongly — the predicate was evaluated against the wrong subject under a configuration its designers had not modelled. This one's gate was real, honestly intended and self-referential — _isAuthorized(address(this)). What they share is the blast radius: in each case, clearing the module's check yields the account, because the Safe's own threshold is not in the path. A multisig's n-of-m is a statement about one execution path, and the module path is a different one.
That is enough regularity across enough distinct root causes to be worth naming, and it is proposed in TAXONOMY-GAPS.md as the forward candidate OAK-T9.004.002 — Smart-Account Module Authority Escalation, with these three as anchors. Its detection signal is distinct from generic access-control review and does not require reading the module's source: enumerate the modules enabled on an account, and treat each as an independent holder of full withdrawal authority. The Safe's own interface makes that enumeration trivial and almost nobody performs it on a schedule. The mitigation is equally concrete — module review at account-owner tier rather than integration tier, self forbidden as a forwarding target, and DELEGATECALL unreachable from any unauthenticated entrypoint.