Access-control bugs (Solidity)
TL;DR: Privileged functions left unprotected,
tx.originused for auth, ordelegatecallinto attacker-influenced code — any of these grants ownership or drains funds.
What it is
Access-control flaws are missing or wrong authorisation checks on functions that change ownership, mint tokens, withdraw balances, or upgrade logic. In Solidity the bug usually looks like a function that should be gated by onlyOwner / role check but isn’t, or one that gates on tx.origin (phishable), or a proxy that delegatecalls into an address the attacker can set.
Preconditions / where it applies
- Public/external function that mutates privileged state (owner, admin role, treasury, upgrade slot)
- No modifier, or a modifier that checks the wrong principal (
tx.originvsmsg.sender) - Proxy contracts following EIP-1967 / UUPS where
_authorizeUpgradeis missing or weak - Initialiser functions on upgradeable contracts not protected against re-init
Technique
- Find the sinks. Grep the source (or decompiled bytecode via Dedaub / Panoramix) for
selfdestruct,delegatecall,transferOwnership,mint,setImplementation,initialize,upgradeTo. - Check the guard. Does it have an
onlyOwner/AccessControlmodifier? Is the modifier itself sane? A common bug:modifier onlyOwner() { require(tx.origin == owner); _; }tx.originlets any contract the owner interacts with relay a privileged call — classic phishing primitive. - Exploit unprotected init. On a freshly deployed UUPS proxy where
initialize()was never called, anyone can call it and become owner, then callupgradeTo(attackerLogic)anddelegatecallinto a payload thatselfdestructs the implementation (the Parity multisig 2017 pattern) or steals funds. - Delegatecall hijack. If a function does
target.delegatecall(data)andtargetis settable by a non-owner, point it at a contract whose function signature collides withtransferOwnershipand rewrite slot 0.
Forge PoC skeleton:
vm.prank(attacker);
victim.initialize(attacker); // unprotected initialiser
victim.upgradeTo(address(evilImpl)); // attacker now controls logic
Detection and defence
- Static analysis: Slither detectors
unprotected-upgrade,arbitrary-send,tx-origin,uninitialized-state. - Use OpenZeppelin
Ownable2Step/AccessControland theinitializer/reinitializermodifiers; disable initialisers in the constructor of upgradeable logic contracts (_disableInitializers()). - Never authenticate with
tx.origin. Alwaysmsg.sender. - On monitoring: alert on
OwnershipTransferred,Upgraded, andRoleGrantedevents from unexpected addresses.
Related: reentrancy, integer-overflow-solidity, smart-contracts-overview.
References
- SWC-115 Authorization through tx.origin — registry entry on tx.origin auth bug
- SWC-118 Incorrect Constructor Name — historical init/ownership pitfall
- OpenZeppelin AccessControl docs — canonical role pattern
- Trail of Bits — Not so smart contracts — annotated bug examples