Access Control Attacks
How the attack works and how to prevent it
Summarize with AI
Access Control Attacks
Access Control Attacks: Common Failures and Defenses
Access control vulnerabilities let unauthorized accounts execute privileged operations. OWASP ranks broken access control first in its 2025 Smart Contract Top 10, and recent incidents show how a missing permission check or compromised admin path can expose an entire protocol.
A contract can implement its business logic correctly and still fail if it does not verify who may invoke that logic.
This guide explains common access control failures, the Parity Wallet incidents, vulnerable Solidity patterns, and defenses such as role-based access control, multisigs, and timelocks.
What is an access control attack?
An access control attack happens when a smart contract fails to restrict who can call a sensitive function. An unauthorized user may then drain a treasury, mint tokens, take ownership, or disable the contract.
The affected function may behave exactly as implemented. The failure is that the contract lets the wrong account invoke it.
An unlocked control room
Consider a control room whose equipment works correctly but whose door is left unlocked.
An unauthorized visitor can operate controls that were intended only for trained staff.
The machinery is not the problem. The system failed to verify the operator. Smart contract access control has the same responsibility: authenticate the caller and authorize the requested action.
How broken access control becomes protocol takeover
Access control failures have contributed to several large crypto losses:
| Attack | Year | Impact |
|---|---|---|
| Bybit Exchange | 2025 | $1.5B stolen via multisig compromise |
| Ronin Bridge | 2022 | $625M drained, validator keys compromised |
| Poly Network | 2021 | $611M stolen via cross-contract privilege escalation |
| Parity Wallet | 2017 | $30M stolen + $280M permanently frozen |
OWASP ranks access control #1 on its 2025 Smart Contract Top 10. Documented smart contract losses attributed to access control reached $953 million in 2024, roughly 67% of the year's total.
The Smart Contract Hacking course includes lab exercises on unprotected initializers, ownership takeover, and privilege escalation.
The Parity Wallet access control failures
Parity suffered two related wallet incidents in 2017. Together, they led to roughly $30 million being stolen and about $280 million becoming inaccessible.
The setup: what was the Parity multisig wallet?
In 2017, Parity Technologies, founded by Ethereum co-founder Gavin Wood, offered a multisig wallet implementation.
The architecture used:
-
Thin "proxy" wallet contracts that actually held user funds.
-
A single, shared "library" contract containing all the wallet logic.
-
Whenever a wallet needed to do something, it forwarded calls to the library using
delegatecall.
Many projects used Parity wallets to manage ICO funds. By November 2017, 587 wallets held more than $280 million in ETH.
July 19, 2017: approximately $30 million stolen
The library contract contained a seemingly harmless initialization function called initWallet(). It was designed to set up the wallet owners during deployment.
The function had no access control modifier. An arbitrary caller could use it to become the owner of a wallet.
The attacker used two transactions against each target wallet:
-
Called
initWallet()to declare themselves the sole owner. -
Called the transfer function to drain all the funds into their own pocket.
The attacker stole about $30 million from three wallets associated with Swarm City, Edgeless, and Aeternity.
A white-hat group used the same exploit to move approximately 377,000 ETH out of other vulnerable wallets before they could be attacked.
November 6, 2017: approximately $280 million frozen
After the first incident, the wallet code was patched, but the shared library contract itself remained uninitialized.
Months later, a user named "devops199" called initWallet() directly on the library and became its owner. The user then called kill(), which triggered the selfdestruct opcode.
Because the wallets delegated execution to that library, destroying it left 587 wallets, holding roughly $280 million in ETH, unable to execute withdrawals.
Those funds remain frozen in the blockchain to this day. No fork, no recovery, no way to ever access them again.
What the incidents demonstrate
The incidents show why initialization must be restricted and single-use. initWallet() should have been callable only during deployment, protected by a constructor or initializer guard, and unavailable after initialization.
How access control attacks work: step by step
Nearly every case follows the same order: find a state-changing function with no modifier, use it to take ownership, then call the privileged functions that ownership opens up.
The attack flow
| Step | What Happens | Result |
|---|---|---|
| 1 | Attacker scans contract for public functions | Finds unprotected initialize() |
| 2 | Attacker calls initialize(attackerAddress) |
Becomes the new owner |
| 3 | Attacker calls mint(attacker, 1000000) |
Mints unlimited tokens |
| 4 | Attacker calls withdraw() |
Drains all ETH |
| 5 | Attacker calls selfdestruct(attacker) |
Destroys contract, sends remaining funds |
The attack phases
The attacker reviews the contract source code (often verified on Etherscan) looking for state-changing functions that lack access modifiers like onlyOwner, onlyRole, or initializer.
The attacker calls an unprotected initialize() or transferOwnership() function to claim admin privileges. In proxy contracts, an uninitialized implementation can expose privileged setup logic.
With admin access, the attacker exploits every privileged function: minting tokens, changing fee recipients, pausing deposits while draining funds, or upgrading the contract to a malicious implementation.
The attacker drains all funds and optionally calls selfdestruct to destroy evidence and send any remaining ETH to their address. In the Parity hack, this step froze $280 million permanently.
{
"title": "🎬 Access-control takeover: one unguarded function, total control",
"stage": { "width": 920, "height": 440 },
"nodes": [
{ "id": "attacker", "label": "Attacker", "role": "no privileges", "emoji": "🧑💻", "x": 60, "y": 200, "color": "red" },
{ "id": "vault", "label": "Token Vault", "role": "owner + funds", "emoji": "🏦", "x": 440, "y": 60, "color": "cyan" },
{ "id": "funds", "label": "Pooled ETH", "role": "users' deposits", "emoji": "💰", "x": 440, "y": 330, "color": "gold" }
],
"links": [
{ "from": "attacker", "to": "vault" },
{ "from": "vault", "to": "funds" },
{ "from": "attacker", "to": "funds" }
],
"nets": [
{ "id": "atk", "label": "Attacker" },
{ "id": "vault", "label": "Contract" }
],
"legend": [
{ "cls": "call", "label": "contract call" },
{ "cls": "token", "label": "ETH transfer" },
{ "cls": "sig", "label": "ownership write" },
{ "cls": "fail", "label": "reverted / destroyed" }
],
"scenarios": {
"Vulnerable (no access control)": [
{ "note": "The vault's initialize() sets the owner but has <b>no guard</b> - the attacker spots it on the verified source.", "hi": ["vault"], "bal": { "vault": "owner: deployer", "funds": "100 ETH", "attacker": "no access" }, "net": { "atk": "no access", "vault": "100 ETH" } },
{ "note": "Attacker calls <b>initialize(attacker)</b> and instantly becomes the owner.", "tone": "bad", "hi": ["attacker","vault"], "chip": { "from": "attacker", "to": "vault", "label": "initialize(attacker)", "cls": "sig" }, "bal": { "vault": "owner: ATTACKER" } },
{ "note": "As 'owner', the attacker calls <b>mint()</b> and prints unlimited tokens.", "tone": "bad", "hi": ["attacker","vault"], "chip": { "from": "attacker", "to": "vault", "label": "mint()", "cls": "call" } },
{ "note": "Then <b>withdraw()</b> drains every deposit to the attacker.", "tone": "bad", "hi": ["funds","attacker"], "chip": { "from": "funds", "to": "attacker", "label": "withdraw 100 ETH", "cls": "token" }, "bal": { "funds": "0 ETH", "attacker": "+100 ETH" }, "net": { "atk": "+100 ETH", "vault": "0 ETH" } },
{ "note": "A final <b>emergencyShutdown()</b> selfdestructs the contract - evidence gone.", "tone": "bad", "hi": ["attacker","vault"], "chip": { "from": "attacker", "to": "vault", "label": "selfdestruct()", "cls": "fail" }, "bal": { "vault": "destroyed" } }
],
"Fixed (initializer + onlyOwner)": [
{ "note": "Now initialize() is wrapped in OpenZeppelin's <b>initializer</b> modifier - it can run exactly once, at deployment.", "hi": ["vault"], "bal": { "vault": "owner: sealed", "funds": "100 ETH", "attacker": "no access" }, "net": { "atk": "no access", "vault": "100 ETH" } },
{ "note": "The attacker calls initialize(attacker) - the guard reverts: <b>already initialized</b>.", "tone": "ok", "hi": ["attacker","vault"], "chip": { "from": "attacker", "to": "vault", "label": "initialize() reverts", "cls": "fail" } },
{ "note": "mint(), the withdraw-admin paths and shutdown all sit behind <b>onlyOwner</b>. The attacker is not the owner, so each call reverts.", "tone": "ok", "hi": ["attacker","vault"], "chip": { "from": "attacker", "to": "vault", "label": "mint() reverts", "cls": "fail" } },
{ "note": "With no way to seize ownership, the deposits stay put.", "tone": "ok", "hi": ["funds"], "bal": { "funds": "100 ETH safe" }, "net": { "atk": "no access", "vault": "100 ETH" } }
]
}
}
Access control vulnerable code example
Let's examine a contract with multiple access control vulnerabilities - each one a real pattern seen in production exploits.
This contract is intentionally vulnerable. Never use this pattern in production.
The vulnerable contract
// VULNERABLE CONTRACT - DO NOT USE IN PRODUCTION
pragma solidity ^0.8.20;
contract VulnerableTokenVault {
address public owner;
mapping(address => uint256) public balances;
bool public paused;
// VULNERABILITY 1: No initializer protection
// Anyone can call this again to hijack ownership
function initialize(address _owner) external {
owner = _owner;
}
// VULNERABILITY 2: Uses tx.origin for authentication
// Attacker can phish the owner through a malicious contract
modifier onlyOwner() {
require(tx.origin == owner, "Not owner");
_;
}
// VULNERABILITY 3: No access control - anyone can mint
function mint(address to, uint256 amount) external {
balances[to] += amount;
}
// VULNERABILITY 4: No access control on pause
function setPaused(bool _paused) external {
paused = _paused;
}
function deposit() external payable {
require(!paused, "Paused");
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
require(!paused, "Paused");
require(balances[msg.sender] >= amount, "Insufficient");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
// VULNERABILITY 5: Unprotected selfdestruct
function emergencyShutdown(address payable recipient) external {
selfdestruct(recipient);
}
// VULNERABILITY 6: One-step ownership transfer, irreversible
function transferOwnership(address newOwner) external onlyOwner {
owner = newOwner;
}
}
Why is this vulnerable?
This contract has six access control flaws:
-
Unprotected
initialize()- anyone can re-initialize and become owner -
tx.originauthentication - vulnerable to phishing attacks -
Public
mint()- anyone can create unlimited tokens -
Public
setPaused()- anyone can freeze or unfreeze the contract -
Public
selfdestruct- anyone can destroy the contract -
One-step ownership transfer - typos cause permanent loss
Access control attacker contract examples
Here's how attackers exploit the vulnerabilities above.
// ATTACKER CONTRACTS - Educational purposes only
pragma solidity ^0.8.20;
interface IVulnerableVault {
function initialize(address _owner) external;
function mint(address to, uint256 amount) external;
function withdraw(uint256 amount) external;
function emergencyShutdown(address payable recipient) external;
function transferOwnership(address newOwner) external;
}
// EXPLOIT 1: Hijack ownership via unprotected initialize
contract InitializeExploit {
function attack(IVulnerableVault vault) external {
// Re-initialize to become owner - no protection!
vault.initialize(address(this));
}
}
// EXPLOIT 2: Mint fake balance, drain real ETH
contract MintAndDrainExploit {
function attack(IVulnerableVault vault) external {
uint256 vaultBalance = address(vault).balance;
// Mint a fake balance equal to the vault's ETH
vault.mint(address(this), vaultBalance);
// Withdraw real ETH using the fake balance
vault.withdraw(vaultBalance);
// Send stolen funds to attacker
payable(msg.sender).transfer(address(this).balance);
}
receive() external payable {}
}
// EXPLOIT 3: Phish the owner via tx.origin
contract TxOriginPhishing {
IVulnerableVault public vault;
constructor(address _vault) {
vault = IVulnerableVault(_vault);
}
// Disguised as an airdrop claim function
function claimReward() external {
// tx.origin is the real owner, so onlyOwner passes!
vault.transferOwnership(msg.sender);
}
}
Attack execution summary
-
Exploit 1: Call
initialize()to become owner - no modifiers to stop you -
Exploit 2: Call
mint()to create fake balance, thenwithdraw()real ETH -
Exploit 3: Trick the owner into calling your contract -
tx.originbypasses the check
In real attacks, these steps are usually combined into a single transaction. The SafeMoon hack ($9M, 2023) needed only one of them: a burn() function that a contract upgrade accidentally left public.
How to prevent access control attacks: best practices
No single check covers every privileged path, so these four defenses are meant to stack: explicit visibility, roles instead of a lone owner, guarded initialization, and two-step ownership transfer.
1. Use explicit visibility modifiers
Always declare function visibility explicitly. Mark everything internal or private by default and only expose what's necessary.
// BAD: Function visibility not considered
function _internalHelper() { /* logic */ }
// GOOD: Explicit internal visibility
function _internalHelper() internal { /* logic */ }
2. Implement role-based access control
Use OpenZeppelin's AccessControl for granular permissions instead of a single-owner pattern:
import "@openzeppelin/contracts/access/AccessControl.sol";
contract SecureProtocol is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
}
3. Use two-step ownership transfer
Never use single-step ownership transfer. One typo can permanently lock you out.
import "@openzeppelin/contracts/access/Ownable2Step.sol";
// Step 1: Owner proposes new owner
// Step 2: New owner must call acceptOwnership()
4. Protect initialization functions
For proxy/upgradeable contracts, always use the initializer modifier and disable initializers in the constructor:
constructor() {
_disableInitializers(); // Prevents init on implementation
}
function initialize(address admin) external initializer {
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
This single rule eliminates an entire class of phishing attacks. The Solidity documentation itself warns against using tx.origin for authorization.
Prevention effectiveness comparison
What it does: Separates permissions into distinct roles (MINTER, PAUSER, ADMIN), each assignable to different addresses. Compromise of one role does not compromise others.
When to use: Every production DeFi protocol with multiple admin functions.
Limitation: More complex to configure than Ownable. Requires careful role hierarchy design.
What it does: Requires M-of-N signatures for critical operations. Eliminates single-key compromise risk.
When to use: All protocol treasuries, upgrade authorities, and admin functions.
Limitation: Not immune to social engineering (Bybit $1.5B) or malware (Radiant $53M) that compromises the signing process itself.
What it does: Forces a delay (24-48 hours) before critical changes take effect. Gives the community and monitoring systems time to react to malicious proposals.
When to use: All governance operations, parameter changes, and contract upgrades.
Limitation: Adds latency to legitimate operations. Doesn't help if the admin keys are already compromised and users don't monitor.
What it does: Restricts functions to a single owner address using OpenZeppelin's Ownable.
When to use: Simple contracts and prototypes only. Never for production DeFi protocols.
Limitation: Single point of failure. If the owner key is compromised, the attacker gains unrestricted access to every protected function. The Ronin Bridge ($625M) proved this conclusively.

Access control secure code example
The following example combines several access control mechanisms. Adapt roles, thresholds, and delays to the protocol, then test and audit the result before deployment.
// DEFENSIVE EXAMPLE - adapt and audit before production use
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract SecureTokenVault is
Initializable,
AccessControlUpgradeable,
Ownable2StepUpgradeable,
PausableUpgradeable
{
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
mapping(address => uint256) public balances;
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event Minted(address indexed to, uint256 amount, address indexed minter);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers(); // STEP 1: Block init on implementation
}
// STEP 2: initializer modifier ensures single initialization
function initialize(address admin) external initializer {
require(admin != address(0), "Zero address");
__AccessControl_init();
__Ownable2Step_init();
__Pausable_init();
_transferOwnership(admin);
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, admin);
_grantRole(PAUSER_ROLE, admin);
}
function deposit() external payable whenNotPaused {
require(msg.value > 0, "Must deposit more than 0");
balances[msg.sender] += msg.value;
emit Deposited(msg.sender, msg.value);
}
function withdraw(uint256 amount) external whenNotPaused {
require(balances[msg.sender] >= amount, "Insufficient balance");
// CEI pattern: Effects before Interactions
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawn(msg.sender, amount);
}
// STEP 3: Role-restricted minting
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
require(to != address(0), "Cannot mint to zero address");
balances[to] += amount;
emit Minted(to, amount, msg.sender);
}
// STEP 4: Role-restricted pause/unpause
function pause() external onlyRole(PAUSER_ROLE) { _pause(); }
function unpause() external onlyRole(PAUSER_ROLE) { _unpause(); }
// STEP 5: Admin-only emergency (no selfdestruct!)
function emergencyWithdraw() external onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 bal = address(this).balance;
(bool success, ) = msg.sender.call{value: bal}("");
require(success, "Transfer failed");
}
// Ownership transfer is two-step via Ownable2Step:
// Current owner calls transferOwnership(newOwner)
// newOwner must call acceptOwnership()
}
Security features at a glance
| Feature | Protection | Vulnerable Version's Flaw |
|---|---|---|
initializer modifier |
Initialize only callable once | Anyone could re-initialize |
onlyRole(MINTER_ROLE) |
Only authorized minters | Anyone could mint |
onlyRole(PAUSER_ROLE) |
Only authorized pausers | Anyone could pause |
Ownable2StepUpgradeable |
Two-step ownership transfer | One-step, irreversible |
msg.sender (in modifiers) |
Immune to tx.origin phishing | Used tx.origin |
| Events on all changes | Full transparency | No event emission |
No selfdestruct |
Cannot be destroyed | Unprotected selfdestruct |
_disableInitializers() |
Implementation can't be initialized | Implementation left open |
The initializer guard and the role checks in this version block the specific mistakes behind the Parity Wallet and SafeMoon incidents.
Types of access control vulnerabilities
As DeFi protocols become more complex, so do access control attack vectors.
Additional attack vectors
tx.origin Phishing: Using tx.origin instead of msg.sender for authentication allows attackers to relay authorized calls through malicious intermediary contracts.
Centralization Risks: Concentrating all power in a single EOA creates a critical single point of failure. The Ronin Bridge ($625M) used a 5-of-9 multisig, but 4 validators were controlled by one entity - and a 5th had stale temporary access that was never revoked.
Signature Replay Attacks: When contracts use off-chain signatures without validating nonces or chainId, attackers can replay valid signatures to execute unauthorized transactions multiple times.
Default Visibility (Pre-Solidity 0.5): Before Solidity 0.5.0, functions without explicit visibility defaulted to public. Legacy contracts and their forks still run on mainnet with these vulnerabilities.
Comparing defense architectures
Single EOA Owner
One private key controls everything. If compromised, the attacker gains god-mode access to every function. The Ronin Bridge proved this model fails at scale.
Basic Multisig
Requires M-of-N signatures, but weak thresholds (2-of-5, 3-of-11) and poor key management undermine security. Bybit and Radiant were both multisig-protected.
RBAC + Multisig + Timelock
Role-based access control with multisig ownership and timelocked critical operations. Defense-in-depth that limits blast radius even if one layer fails.
Common misconceptions
"If my contract compiles without warnings, my access control is fine."
Tap to revealThe Solidity compiler checks syntax, not authorization logic. A public function that should be internal compiles perfectly. A missing onlyOwner modifier produces zero warnings. Manual review is essential.
"Using onlyOwner on all sensitive functions means my contract is secure."
Tap to revealA single-owner pattern creates a critical single point of failure. The Ronin Bridge ($625M) and Bybit ($1.5B) both involved compromised signing keys. Use role-based access control with multisig and timelocks.
"tx.origin is safe because only the wallet owner can initiate a transaction."
Tap to revealWhen a legitimate owner interacts with a malicious contract (via a phishing link or fake dApp), that contract can call back into the vulnerable contract. The tx.origin check passes because the owner IS the transaction originator. Always use msg.sender.
"Access control issues only affect admin functions like withdraw or pause."
Tap to revealAny state-changing function needs evaluation. Unprotected mint() lets attackers create unlimited tokens. Unprotected setPrice() enables oracle manipulation. Unprotected upgrade() lets attackers replace entire contract logic.
Related vulnerabilities
Access control failures frequently overlap with other vulnerability classes, creating compound exploits that amplify damage.
Call attacks and delegatecall vulnerabilities are deeply intertwined with access control. The Parity Wallet hack used delegatecall as the execution mechanism, but the root cause was the unprotected initWallet() function. The Bybit hack ($1.5B, 2025) combined both: attackers exploited access to multisig signers to execute a delegatecall that replaced wallet logic with a malicious implementation.
Flash loan attacks can amplify governance access control weaknesses by giving attackers temporary voting power within a single transaction.
When access control fails on price-sensitive functions, oracle manipulation attacks become trivially easy. The KiloEx hack ($7.4M, 2025) demonstrated this perfectly - a missing access control check on the MinimalForwarder allowed anyone to submit arbitrary price updates.
Test your access control knowledge
5 questions on initializers, roles, and privileged functions
Frequently asked questions about access control attacks
An access control vulnerability is a security flaw that allows unauthorized users to execute restricted functions in a smart contract - such as minting tokens, withdrawing funds, or changing ownership. It occurs when developers fail to implement proper permission checks like onlyOwner or role-based modifiers, allowing any address to call privileged functions.
Access control failures caused an estimated $953 million in smart contract losses in 2024 alone, representing 67% of all losses that year. Including bridge and CeFi access control exploits (like Bybit's $1.5B hack in 2025 and Ronin's $625M in 2022), the total exceeds $6 billion across all known incidents since 2017.
msg.sender returns the immediate caller of a function (safe for access control). tx.origin returns the original EOA that initiated the entire transaction chain (unsafe). If you use tx.origin for authentication, an attacker can trick an authorized user into calling a malicious contract that relays the call - tx.origin still shows the victim's address, bypassing the check.
Ownable provides a single-owner model - simple but creates a single point of failure. AccessControl supports unlimited custom roles (MINTER, PAUSER, ADMIN), each assignable to multiple addresses with hierarchical management. Use Ownable for simple contracts; use AccessControl for production DeFi protocols needing granular permissions.
In proxy/upgradeable contracts, constructors don't execute in the proxy's storage context, so initialize() functions replace them. If this function lacks an initializer modifier, an attacker can call it after deployment to claim ownership. This was the exact root cause of the Parity Wallet hack (2017, $310M).
Yes. The OWASP Smart Contract Top 10 (2025 edition) ranks access control as the #1 vulnerability. Hacken's 2024 report found access control flaws accounted for 75-81% of all crypto hack losses. It causes more financial damage than reentrancy, oracle manipulation, and integer overflow combined.
Quick reference: access control prevention checklist
Before deploying any contract with privileged functions:
-
Use explicit visibility modifiers on every function (
external,public,internal,private) -
Implement role-based access control (OpenZeppelin
AccessControl) for production contracts -
Protect all initializer functions with the
initializermodifier -
Call
_disableInitializers()in implementation contract constructors -
Use
msg.senderfor authentication - nevertx.origin -
Use
Ownable2Stepfor two-step ownership transfer (not single-step) -
Add timelocks to critical admin operations (parameter changes, upgrades)
-
Use multisig wallets with strong thresholds (e.g., 4-of-7 minimum)
-
Validate zero-address checks on all ownership and role transfers
-
Get professional security audits focused on access control patterns
-
Monitor deployed contracts for unauthorized role changes
-
Remove
selfdestructunless absolutely required (and if needed, gate it behind multisig + timelock)
Where to start on your own contracts
Access control causes more financial damage than any other smart contract bug class: OWASP ranks it #1 for 2025, it accounted for $953 million of documented losses in 2024, and it is behind the largest crypto theft on record (Bybit, $1.5B).
Start with the initializers, since that is where Parity died. Then move to role-based access control, and put upgrades and parameter changes behind a timelock and a multisig. If you are budgeting for external review, the smart contract audit cost estimator can help scope the work based on the contract's access control complexity.
Practice access control review
The Smart Contract Hacking course includes hands-on exercises for identifying and fixing access control failures. You can also try a free lesson before enrolling.
Sources and editorial notes
Reviewed by JohnnyTime. Last updated .
Real Access Control Attacks hacks to study
A stable selection of high-signal incidents linked to this attack class, ordered by reported loss and recency.
Master Access Control Attacks in a safe lab
Practice the exploit path, debug the vulnerable code, and learn the prevention workflow auditors use in real reviews.
Related Attack Classes
Continue exploring smart contract vulnerabilities
Access Control Attacks Attacks
Historical breach data timelineCoinsbuy
Aug 10, 2026MOKE Token
Aug 02, 2026WEMIX
Jul 26, 2026Triple-A
Jul 25, 2026B² Network
Jul 23, 2026AFX
Jul 23, 2026BonkDAO
Jul 06, 2026Humanity Protocol
Jun 09, 2026Token of Power
Jun 09, 2026DxSale
Jun 02, 2026Tessera DAO
Jun 01, 2026Gravity Bridge
May 30, 2026Stake DAO
May 27, 2026Third-party SquidRouterModule
May 25, 2026StablR
May 24, 2026Echo Protocol
May 19, 2026Aurellion
May 12, 2026Ink Finance
May 11, 2026Renegade
May 10, 2026Ekubo Protocol
May 05, 2026Wasabi
Apr 30, 2026Syndicate
Apr 29, 2026ZetaChain
Apr 27, 2026Purrlend
Apr 25, 2026Giddy
Apr 23, 2026Kelp DAO
Apr 18, 2026SubQuery Network
Apr 12, 2026Aethir
Apr 09, 2026SquidMulticall Approval Exploitation
Apr 07, 2026Drift Protocol
Apr 01, 2026Resolv
Mar 21, 2026DBXen
Mar 11, 2026Gondi
Mar 09, 2026V4 Swap Router by z0r0z
Mar 03, 2026Holdstation
Feb 25, 2026IoTeX
Feb 21, 2026Step Finance
Jan 31, 2026Holdstation
Jan 28, 2026SynapLogic
Jan 20, 2026IPOR Fusion
Jan 06, 2026Unleash Protocol
Dec 30, 2025Trust Wallet
Dec 25, 2025Rari Capital
Dec 18, 2025Ribbon
Dec 12, 2025Eden Network
Dec 08, 2025USPD
Dec 05, 2025Upbit
Nov 26, 2025GANA Payment
Nov 20, 2025DIMO
Nov 07, 2025Balancer V2
Nov 03, 2025Beets
Nov 03, 2025Garden
Oct 30, 2025402bridge
Oct 27, 2025Hyperliquid User Private Key Compromise
Oct 10, 2025SBI Crypto
Sep 24, 2025GriffinAI
Sep 24, 2025Seedify
Sep 23, 2025UXLINK
Sep 22, 2025Shibarium
Sep 15, 2025Evoq Finance
Sep 10, 2025OlaXBT
Sep 01, 2025Cozy Finance
Aug 30, 2025BtcTurk
Aug 14, 2025CrediX
Aug 04, 2025SuperRare
Jul 28, 2025CoinDCX
Jul 19, 2025BigONE
Jul 16, 2025Pundi AI
Jul 12, 2025MEV Bots
Jun 25, 2025Hacken Token
Jun 20, 2025Nobitex
Jun 18, 2025ForceBridge
Jun 06, 2025Nervos Network
Jun 02, 2025Cork V1
May 28, 2025Zunami
May 15, 2025BitoPro BitoGroup
May 08, 2025The R0AR
Apr 16, 2025ZKsync
Apr 15, 2025KiloEx
Apr 14, 2025UPCX
Apr 01, 2025Zoth ZeUSD
Mar 21, 2025Zoth
Mar 21, 2025Arcade
Mar 17, 2025Berally
Mar 14, 2025MAID
Mar 13, 2025Infini
Feb 24, 2025Bybit
Feb 21, 2025Cardex
Feb 18, 2025DogWifTools
Jan 28, 2025Phemex
Jan 23, 2025Orange Finance
Jan 08, 2025Moby
Jan 08, 2025Moby Trade
Jan 08, 2025Fegex
Dec 29, 2024BTC24H
Dec 16, 2024XT Exchange
Nov 28, 2024$2M Coin Poker
Nov 18, 2024DEXX
Nov 16, 2024Metawin
Nov 03, 2024M2 Exchange
Oct 31, 20241inch
Oct 31, 2024M2
Oct 31, 2024Sunray
Oct 31, 2024SUNRAY·FINANCE
Oct 30, 2024Tapioca DAO
Oct 18, 2024Radiant V2
Oct 16, 2024Radiant Capital
Oct 16, 2024Truflation
Sep 25, 2024BingX
Sep 20, 2024DeltaPrime
Sep 16, 2024WXETA
Sep 16, 2024Indodax
Sep 10, 2024Truflation
Sep 05, 2024Nexera
Aug 07, 2024Ronin
Aug 06, 2024Monoswap
Jul 31, 2024Karastar
Jul 26, 2024MonoSwap
Jul 24, 2024WazirX: India
Jul 18, 2024Dough Finance
Jul 12, 2024CoinStats
Jul 12, 2024Bittensor
Jul 02, 2024BtcTurk
Jun 22, 2024Sportsbet.io
Jun 21, 2024OKX NFT Aggregator
Jun 20, 2024Farcana
Jun 19, 2024YOLO Games
Jun 11, 2024Loopring
Jun 09, 2024DMM Bitcoin
May 31, 2024Gala Games
May 20, 2024pump.fun
May 16, 2024ALEX
May 16, 2024GeniusAI
May 05, 2024XBridge
Apr 24, 2024FENGSHOU Token
Apr 24, 2024Wilder
Apr 16, 2024Grand Base
Apr 15, 2024Munchables
Mar 26, 2024Lucky Star
Mar 22, 2024Dolomite
Mar 20, 2024AirDAO
Mar 20, 2024Remilia
Mar 17, 2024Mozaic
Mar 15, 2024Shido
Feb 29, 2024Seneca
Feb 28, 2024Jeffrey Zirlin
Feb 22, 2024FixedFloat
Feb 18, 2024Fixed Float
Feb 16, 2024DuelBits
Feb 13, 2024PlayDapp
Feb 09, 2024Chris Larsen
Jan 30, 2024Saga DAO
Jan 24, 2024Concentric
Jan 22, 2024GAMEE
Jan 22, 2024Orbit Chain
Dec 31, 2023Locus Finance
Dec 30, 2023OKX DEX
Dec 13, 2023OKX
Dec 12, 2023Heco Bridge
Nov 22, 2023Huobi
Nov 22, 2023HECO
Nov 22, 2023HTX
Nov 22, 2023Binance User
Nov 11, 2023Poloniex
Nov 10, 2023Samudai
Nov 10, 2023CoinSpot
Nov 09, 2023Fantom Foundation
Oct 17, 2023HTX
Sep 25, 2023Remitano
Sep 15, 2023CoinEx
Sep 12, 2023Stake.com
Sep 04, 2023SharedStake
Sep 01, 2023RocketSwap Base
Aug 15, 2023Steadefi
Aug 08, 2023Speadefi
Aug 07, 2023AlphaPo
Jul 26, 2023AlphaPo
Jul 22, 2023Multichain
Jul 07, 2023Multichain
Jul 06, 2023Poly Network
Jul 02, 2023Poly Network
Jul 01, 2023Ara Finance
Jun 19, 2023Keep3r Network
Jun 12, 2023Atlantis Loans
Jun 10, 2023Atomic Wallet
Jun 03, 2023unshETH
May 31, 2023Local Traders
May 24, 2023MERLIN DEX
Apr 25, 2023Tales of Elleria
Apr 19, 2023Bitrue
Apr 14, 2023MetaPoint
Apr 11, 2023GDAC
Apr 08, 2023Safemoon
Mar 28, 2023LaunchZone
Feb 27, 2023Shata Capital
Feb 24, 2023Dexible
Feb 17, 2023Mars Meta Space
Feb 12, 2023LianGo Protocol
Feb 07, 2023Degen Millionares Club
Feb 05, 2023Thoreum Finance
Jan 19, 2023Luke Dashjr
Dec 31, 2022Raydium AMM
Dec 16, 2022Ratio Finance
Dec 03, 2022Ankr
Dec 02, 2022FTX
Nov 12, 2022Bo Shen Wallet Hack
Nov 10, 2022Pando
Nov 05, 2022Rubic
Nov 02, 2022Deribit
Nov 01, 2022Deribit Exchange
Nov 01, 2022DappNode
Oct 29, 2022friesDAO
Oct 27, 2022Universe Token
Oct 27, 2022Melody
Oct 24, 2022Layer2DAO
Oct 23, 2022LiveArtX
Oct 17, 2022QAN Platform
Oct 11, 2022Wintermute
Sep 20, 2022Profanity Wallet Hack
Sep 18, 2022Ragnarok Online Invasion
Sep 08, 2022Bill Muray Wallet Hack
Aug 31, 2022Slope Wallet
Aug 02, 2022PREMINT | NFT
Jul 17, 2022Citizen Finance
Jul 11, 2022FlippazOne
Jul 05, 2022Crema Finance
Jul 02, 2022Fantom
Jul 01, 2022Harmony Bridge
Jun 23, 2022Horizon by Harmony
Jun 23, 2022Ronin
Mar 29, 2022Rare Bears
Mar 17, 2022Pirate x Pirate
Mar 09, 2022Ariva Digital
Feb 25, 2022OpenSea
Feb 20, 2022TopGoal
Feb 16, 2022BuildFinance
Feb 14, 2022Dego Finance
Feb 10, 2022Dego Finance
Feb 09, 2022Ratz Club
Feb 07, 2022DePo
Feb 04, 2022BNS
Feb 01, 2022Crypto.com
Jan 18, 2022Lympo
Jan 10, 2022CityDAO
Jan 10, 2022LCX
Jan 08, 2022Stobox Exchange
Jan 07, 2022Eco DeFi
Dec 28, 2021Gamma
Dec 21, 2021Vulcan Forged
Dec 13, 2021AscenDEX
Dec 11, 20218ightDAO
Dec 08, 20218ight Finance
Dec 06, 2021Bitmart
Dec 04, 2021BitMart Exchange
Dec 04, 2021Unlock Protocol
Nov 21, 2021bZx
Nov 05, 2021Boy X Highspeed
Oct 30, 2021DAO Maker Vesting
Sep 04, 2021Bilaxy
Aug 29, 2021Sentinel DVPN
Aug 21, 2021Liquid
Aug 19, 2021Fetch.ai
Aug 14, 2021DAO Maker Vesting
Aug 12, 2021Poly Network
Aug 10, 2021stazie
Jul 31, 2021Levyathan
Jul 30, 2021Bondly
Jul 15, 2021Multichain
Jul 10, 2021Anyswap
Jul 10, 2021Circle
Jul 09, 2021SharedStake
Jun 24, 2021Fireblocks
Jun 22, 2021Visor Finance
Jun 19, 2021Wild Credit
May 27, 2021FinNexus
May 17, 2021ValueDefi
May 05, 2021EasyFi
Apr 19, 2021Seascape Network
Mar 15, 2021Roll
Mar 14, 2021DODO AMM
Mar 09, 2021Cryptopia Exchange
Feb 20, 2021LuBian
Dec 28, 2020EXMO
Dec 21, 2020Ledger
Oct 24, 2020KuCoin
Sep 29, 2020KuCoin
Sep 25, 2020ETERBASE
Sep 08, 2020Electrum
Aug 30, 2020Cashaa
Jul 11, 2020Ledger
Mar 25, 2020Electrum
Jan 19, 2020Upbit
Nov 27, 2019CoinTiger
Aug 17, 2019Bitpoint
Jul 12, 2019Bitrue
Jun 26, 2019GateHub
Jun 06, 2019Kraken
Jun 02, 2019Coinbase
May 22, 2019Binance
May 07, 2019Bithumb
Mar 29, 2019CoinBene
Mar 26, 2019Biki
Mar 25, 2019Coinbin
Feb 20, 2019QuadrigaCX
Feb 04, 2019Electrum
Dec 27, 2018Trade.io
Oct 20, 2018Zaif
Sep 14, 2018Bithumb
Jun 19, 2018Coinrail
Jun 10, 2018Taylor
May 21, 2018MyEtherWallet
Apr 24, 2018Gate
Apr 21, 2018Coinsecure
Apr 12, 2018Blockchain.com
Feb 19, 2018Coincheck
Jan 26, 2018BlackWallet
Jan 13, 2018EtherDelta
Dec 20, 2017Parity Multisig
Nov 09, 2017Coinis
Sep 21, 2017Bithumb
Jun 03, 2017Bitfinex
Aug 02, 2016Gatecoin
May 10, 2016Shapeshift
Apr 01, 2016Rubixi
Mar 14, 2016Purse
Oct 11, 2015BitFinex
May 22, 2015Kipcoin
Feb 18, 2015BTER
Feb 15, 2015Gate.io
Feb 14, 2015LocalBitcoins
Jan 27, 2015EgoPay
Jan 23, 2015Bitstamp
Jan 04, 2015MintPal
Oct 19, 2014BitPay
Sep 12, 2014Cryptsy
Jul 29, 2014MintPal
Jul 13, 2014PicoStocks
Nov 29, 2013Inputs.io
Oct 23, 2013Just-Dice
Jul 15, 2013PicoStocks
Jun 01, 2013Vircurex
May 10, 2013Bitmit
Apr 26, 2013ZigGap
Apr 01, 2013Bit LC Inc.
Feb 13, 2013Cdecker
Sep 28, 2012Bitcoinica
Jul 13, 2012Bitcoin Syndicate
Jul 04, 2012Betcoin
Apr 11, 2012Bitomat
Jul 26, 2011Master Access Control Attacks with Guided Labs
Turn this concept into repeatable audit workflow skills.
Claim Your Free Trial
Access Request Received
Thanks for signing up. We'll email your trial onboarding details within 2-3 business days.
By submitting, you agree to our Privacy Policy.