Track 7 · The Breach Lab · lesson 3

Broken access control

15 min


The most common serious vulnerability in audited contracts is not clever. It is a missing word.

Below is a bank. sweep is properly guarded — it checks the owner. But look at how the owner gets set.

Attacker.solAttacker
Loading editor…
Take ownership of the bank, then take the money.

Bank

? ETH

Nudge
`sweep` is properly guarded — it checks the owner. But how is the owner set?
Show me the approach
`setOwner` lets anyone become the owner. Call it, then sweep.
Show me the code
In attack(): `bank.setOwner(address(this)); bank.sweep();`.
Explain the exploit
Broken access control is the most common serious vulnerability in audited contracts, year after year. It is almost never clever — it is a missing `onlyOwner`, exactly like this. Needs a `receive()` so the swept ETH can arrive.

The missing modifier

function setOwner(address newOwner) public {
    owner = newOwner;        // anyone. anyone at all.
}

It should have been onlyOwner. It was not. So you make yourself the owner, and then the properly-guarded sweep waves you straight through.

This is worth sitting with, because it is genuinely the number-one finding in professional audits, year after year. Not exotic maths — a state-changing function that forgot to ask who was calling.

The sweep function did everything right and it did not matter, because the door beside it was unlocked. A contract is only as protected as its least guarded state-changing function.

How to find it

The audit habit from track 4's modifiers lesson, applied for real: list every function that changes state, and next to each, write who is allowed to call it. Any row where the answer is "anyone" and should not be is a finding.

Predict

A contract has an `initialize()` function instead of a constructor (as upgradeable proxies do). What is the classic access-control bug there?

Choose one answer

Check

`sweep` checked the owner correctly. Why did the bank still get drained?

Choose one answer

Worth remembering

  • Broken access control is the most common serious audit finding, and usually just a missing modifier.
  • A contract is only as protected as its least-guarded state-changing function.
  • Seizing ownership defeats every owner-only check at once.
  • Audit method: list every state-changing function and who may call it.
  • `initialize()` on proxies is a frequent instance — unguarded, it is front-run.