◇ OpenCyber Academy
Engineering · Chapter 27 · English

Chapter 27 — Authorized offensive security and red team: control design

Open, free learning for anyone who wants to protect people and technology. No account is required to read this text or its worked answers.

Chapter 27 — Authorized offensive security and red team: control design

Lesson map

Lesson 27 published. Map, example, EX079.

flowchart TD
  N0["Learning outcomes"]
  N1["27.1 Three kinds"]
  N2["27.2 Operator skill"]
  N3["27.3 Compensation"]
  N4["27.2b Control ownership"]
  N5["27.3b Testing controls"]
  N6["27.1b Detecting control bypass"]
  N7["Practice note"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7
  N7 --> EX["Worked example"]
  EX --> Q["Exercise EX079"]

Logical drawing

flowchart LR
  N0["Learning outcomes"]
  N1["27.1 Three kinds"]
  N2["27.2 Operator skill"]
  N3["27.3 Compensation"]
  N4["27.2b Control ownership"]
  N5["27.3b Testing controls"]
  N6["27.1b Detecting control bypass"]
  N7["Practice note"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7 --> Q["EX079"]
[Learning outcomes]
    |
    v
[27.1 Three kinds]
    |
    v
[27.2 Operator skill]
    |
    v
[27.3 Compensation]
    |
    v
[27.2b Control ownership]
    |
    v
[27.3b Testing controls]
    |
    v
[27.1b Detecting control bypass]
    |
    v
[Practice note]
    |
    v
[Worked example] --> [Exercise EX079]

Learning outcomes

You will (1) design preventive, detective and corrective controls for the platform; (2) bind controls to requirements; (3) avoid controls that require exploit skills to operate.

27.1 Three kinds

Preventive: scope predicate before action. Detective: alert on deny-list hit. Corrective: auto-disable assessment account at window end. Each maps to a requirement id.

Mapping each kind to a requirement id makes gaps visible. Preventive without detective leaves silent failure; detective without corrective leaves accounts open after the window. Sam's control table has three columns and refuses empty cells on Must rows. Window-end account disable is tested by leaving a canary account enabled in staging and watching corrective control fire. Staging canaries are named clearly so nobody mistakes them for production assessment accounts.

27.2 Operator skill

Controls must be operable by engineers who follow runbooks — not only by exploit specialists. A control that needs a custom shellcode to verify is the wrong control for this programme.

Runbook-operable controls survive night shifts and new hires. Exploit-only verification excludes the engineers this programme trains. Sam rewrites any control whose verify step needs custom offensive tooling. Verify steps are rewritten until a new hire can perform them from the runbook in under fifteen minutes. Timing the new-hire run is part of accepting the control, recorded next to the owner field.

27.3 Compensation

If preventive control is weak (legacy system cannot enforce port scope), add detective monitoring and a tighter human approval. Document the compensation in the ROE.

"""Evaluate proposed actions against synthetic rules of engagement."""
ROE = {
    "hours": ("09:00", "17:00"),
    "deny_actions": {"denial-of-service", "phishing-staff", "exfiltrate-real-data"},
    "require_approval": {"password-spray-lab", "privilege-escalation-lab"},
    "max_concurrency": 5,
}
proposals = [
    ("service-enum", "10:30", 3, False),
    ("denial-of-service", "10:30", 1, False),
    ("password-spray-lab", "10:30", 2, False),
    ("password-spray-lab", "10:30", 2, True),
    ("service-enum", "18:00", 2, False),
    ("service-enum", "11:00", 20, False),
]

def ok_time(t):
    return ROE["hours"][0] <= t <= ROE["hours"][1]

for action, when, conc, approved in proposals:
    reasons=[]
    if action in ROE["deny_actions"]: reasons.append("deny-action")
    if action in ROE["require_approval"] and not approved: reasons.append("needs-approval")
    if not ok_time(when): reasons.append("outside-hours")
    if conc > ROE["max_concurrency"]: reasons.append("concurrency")
    print(f"action={action:20} when={when} conc={conc:2} approved={approved} -> {'ALLOW' if not reasons else 'DENY'} {','.join(reasons) or '-'}")

Expected output:

action=service-enum         when=10:30 conc= 3 approved=False -> ALLOW -
action=denial-of-service    when=10:30 conc= 1 approved=False -> DENY deny-action
action=password-spray-lab   when=10:30 conc= 2 approved=False -> DENY needs-approval
action=password-spray-lab   when=10:30 conc= 2 approved=True -> ALLOW -
action=service-enum         when=18:00 conc= 2 approved=False -> DENY outside-hours
action=service-enum         when=11:00 conc=20 approved=False -> DENY concurrency

Synthetic case — Riverstone Transit. Password-spray-lab is denied unless approved — preventive gate with a detective event when someone tries without approval.

Legacy gaps are honest when named in the ROE with monitoring and approval attached. Hidden gaps become incidents. Sam lists compensations in the kickoff deck so the client accepts them explicitly. Kickoff slides that omit a listed compensation are corrected before signatures, not after the first deny storm.

27.4 Errors and limits

Stacks of compensating controls without an owner become ignored alerts.

Unowned alert piles train people to ignore pages. Every compensating control needs a name and a review date. Sam deletes or re-homes orphans monthly, and an alert that fired without an owner for a week is raised in the leadership pack as a control defect. Leadership packs include a one-line orphan-alert count so silence cannot be mistaken for health. A rising orphan count is discussed before feature velocity in the same leadership meeting.

27.2b Control ownership

Every control has an owner who receives its alerts and a review date. Orphan detective controls become noise; orphan preventive controls become folklore nobody dares to change. Put owner and review date in the same repo as the control code.

Owner plus review date in-repo beats a wiki nobody updates. Sam's CODEOWNERS maps control paths to people who get the pages. Remapping owners is a reviewed config change, with a week of shadowed pages before the old owner drops off.

27.3b Testing controls

Each control needs a negative test: remove it or break it in a lab and show the bad outcome is caught by a higher control or by fail-closed defaults. Untested controls are wishes.

Negative tests prove the higher layer catches a broken preventive control. Untested controls are wishes with dashboards. Sam schedules the break-it lab quarterly and files gaps as P1s. Open P1s from the lab appear on the engagement readiness dashboard beside ROE and version pins. Readiness dashboards go red on open P1s and stay red until the fix merges and the lab is re-run.

27.1b Detecting control bypass

Log when an operator uses break-glass to skip dual approval. Break-glass without detection is just a hidden single control. Weekly review of break-glass events is part of the control design.

Weekly review keeps emergency paths exceptional. Sam graphs break-glass counts next to engagement volume. Design reviews of dual-control paths invite a client liaison when counts rise for two weeks running. Two weeks is long enough to see a pattern and short enough to act before the next window. Each break-glass entry records who used it, which approval was skipped and why.

Practice note

Controls degrade. Schedule a quarterly control-health day: break each preventive control in a synthetic lab and confirm detective controls fire. Record the date next to the control owner. A control that has never been broken on purpose is a control you are only hoping works, and hope is not an engineering control in an authorized assessment platform.

Exercises

Synthesis

Controls are preventive/detective/corrective, owned, and operable without exploits.

Continue studying — chapter 27 of 50

← Chapter 26 (draft) · Course chapter index · Chapter 28 (draft) →