Free snapshot slots open this week info@inversez.com
DetectionVulnerabilitiesToolsAuditsPricingBlogFree monitoring assessmentContact
Home/Vulnerabilities/Broken access control
SWC-105 · Typically critical

Broken access control

Access control bugs are the most common critical finding in real audits, and the least interesting technically. A function that should be restricted simply isn't: no modifier, or the wrong one. There is nothing clever about the exploit: the attacker just calls it.

Vulnerable code

Do not ship this
address public owner;

function setOwner(address newOwner) external {
    owner = newOwner;              // anyone can call this
}

function emergencyWithdraw() public {
    payable(msg.sender).transfer(address(this).balance);
}

Why it breaks

Neither function has a modifier. Any address can call setOwner to take ownership, or skip that entirely and call emergencyWithdraw to empty the contract. This looks obvious in isolation, but in a 2,000-line codebase with forty functions it is genuinely easy to miss one, particularly during a refactor where a modifier gets dropped from a function signature.

Watch the attack run

The same bug as a stepped sequence. Play it, or walk through with the arrow keys.

The fix

Corrected
address public owner;

modifier onlyOwner() {
    require(msg.sender == owner, "not owner");
    _;
}

function setOwner(address newOwner) external onlyOwner {
    require(newOwner != address(0), "zero address");
    owner = newOwner;
}

function emergencyWithdraw() external onlyOwner {
    payable(owner).transfer(address(this).balance);
}

The invariant

The fix above is what an audit gives you. This is what monitoring gives you: the thing that should always be true once the contract is live, stated precisely enough to check every block.

InvariantEVM · access control

Owner, admin and role assignments never change outside a transaction you expected.

invariant  owner(t) == owner(t-1)
  unless tx in expected_governance_set

watch  OwnershipTransferred, RoleGranted, RoleRevoked
Backtest: not yet run. This entry carries the reasoning, not the evidence. When the invariant has been replayed against the real transactions around the exploit, the firing block, how far ahead it fired, and its false-positive rate over a clean control period will be published here, along with the recording of the run.

How to test for it

A finding you cannot reproduce is an opinion. Write the test before you write the fix, watch it fail, then make it pass.

function testOnlyOwnerCanSetOwner() public {
    vm.prank(attacker);
    vm.expectRevert("not owner");
    target.setOwner(attacker);
}

// Write one of these for EVERY privileged function.
// Missing tests are how missing modifiers survive to mainnet.

Where it has caused real losses

Access control failures account for a large share of total value lost across Web3. The Poly Network incident in 2021, roughly $611M, came down to a cross-chain contract accepting a crafted message that let the attacker change the keeper: an authorisation check that did not do what its authors believed it did.

What we check during review

Two patterns worth flagging in review. First, ownership transfer without a two-step handshake: a single-transaction transferOwnership to a mistyped address bricks the contract permanently. Use a propose/accept pair. Second, initialiser functions on upgradeable contracts that can be called by anyone if left unprotected, see our page on uninitialised proxies.

Want us to check your contract for this?

A free monitoring assessment covers one contract up to 200 lines, manually reviewed, findings back within 72 hours. This class is on the checklist for every review we run.

Free monitoring assessment

Found this useful?

We publish these because the alternative, asking you to trust us, is worth less. Send a contract and we’ll apply the same thinking to your code.

  • Reply written by the auditor who read your code
  • No sales sequence, no drip campaign, no retargeting
  • We’ll tell you if you don’t need a paid audit yet
  • Report published only with your written permission

Request your snapshot

We read every submission. If it doesn’t fit the free tier we’ll say so and quote you instead, no obligation.