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.
Bank
? ETH
Nudge
Show me the approach
Show me the code
Explain the exploit
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?
Check
`sweep` checked the owner correctly. Why did the bank still get drained?
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.