01 · Foundations: systems, authority, and evidence
Depth: developed lesson with a local exercise · Prerequisite: course orientation · Time: two 90-minute sessions.
A useful security assessment explains how an actor obtains a capability that the system should withhold. Tools produce observations along that path; they do not replace the model of the system. This chapter builds that model using the same original web fixture you will investigate more deeply in chapter 08.
Outcomes
- Identify assets, actors, actions, rules and enforcement points.
- Separate an authentication decision from an authorization decision.
- Demonstrate the difference between an observation, a hypothesis and a supported finding.
- Use positive and negative controls to assess a boundary.
Model a decision before testing it
Start with a small specification:
Assets: two synthetic records, 101 and 202
Actors: simulated Alice and Bob
Action: read a record
Policy: an actor may read only the record they own
Enforcement point: server-side decision before the record is returned
The asset is what the rule protects. The actor is the entity requesting an operation. The action names the capability at issue. A trust boundary separates components or contexts where the required assumptions differ—for example, a client-controlled record ID entering a server that must enforce ownership.
Authentication establishes an identity context; authorization evaluates whether an action is permitted in that context. OWASP recommends least privilege, default denial, and permission checks on each request. Client-side hiding of an object identifier does not replace an appropriate server-side check. See the OWASP Authorization Cheat Sheet.
Our fixture deliberately accepts X-Lab-User as a mock identity. That is a teaching input, not an authentication implementation. Keep it unchanged when testing ownership so you can isolate the authorization decision.
Distinguish weakness, exploitation, and impact
In this scenario, omitting the owner check is the weakness. Sending a request that crosses the owner boundary exercises it. Receiving the other actor's record demonstrates a particular unauthorized read. Broader consequences require broader evidence.
| Statement | Evidence required |
|---|---|
| A process listens on the lab port | Socket or successful connection observation |
| The process speaks the expected HTTP application | Health response and known fixture identity |
| Alice reads Bob's record | Unchanged Alice identity, record 202 response, owner Bob, ownership policy |
| Alice can modify records | A separately authorized write experiment and resulting state |
| Another application has the same weakness | Evidence from that application in its own authorized scope |
Confidentiality concerns who learns protected information. Integrity concerns unauthorized changes. Availability concerns the ability to use the service as required. Our cross-owner read addresses one confidentiality boundary. It does not demonstrate integrity or availability impact.
Session one: establish the baseline
Requirements: repository checkout, Python 3.12+, and curl. Start the fixed fixture in one terminal:
python3 -m labs.web_boundary --mode fixed
The program prints its loopback address and mode. If port 8080 is occupied, identify the existing process or choose another port with --port; update every URL consistently. Do not kill an unfamiliar process to make the exercise fit.
In a second terminal, record the health and a request with no simulated identity:
curl --noproxy '*' --include --max-time 5 http://127.0.0.1:8080/api/health
curl --noproxy '*' --include --max-time 5 http://127.0.0.1:8080/api/records/101
The health response should identify the fixed synthetic lab. The protected request should return 401. This shows that this request lacked the fixture's required mock identity. It does not prove that a real login mechanism is secure, because the fixture has none.
Now change one input:
curl --noproxy '*' --include --max-time 5 -H 'X-Lab-User: alice' http://127.0.0.1:8080/api/records/101
curl --noproxy '*' --include --max-time 5 -H 'X-Lab-User: alice' http://127.0.0.1:8080/api/records/202
Predict both results first. The own-record read is your positive control: 200 with owner Alice. The cross-owner read is the negative control: 403 without Bob's record. If every request failed, you could not conclude that the ownership rule alone explained the difference.
Session two: compare the missing control
Stop the server with Ctrl-C and restart it in vulnerable mode:
python3 -m labs.web_boundary --mode vulnerable
Repeat the health check and the two identity-bearing requests. Both now return records. The important difference is the cross-owner case: the actor and requested object remain the same while the server's ownership enforcement changes.
Draw the decision path:
client chooses identity label and object ID
|
v
server recognizes mock identity
|
v
server finds the requested object
|
checks ownership? <--- the controlled difference
|
v
returns data
This is a controlled comparison, not proof that every implementation with a similar interface has the same weakness. The fixture source makes the single missing check inspectable.
Lab: challenge your own finding
Write a finding of no more than six sentences. Include the declared policy, exact actor/object pair, result in each mode, the affected operation, and two limits.
Then try to refute it using three questions:
- Did you accidentally change the mock identity along with the object?
- Did you verify the server mode instead of assuming the restart succeeded?
- Does the returned body identify the other owner, or did you rely only on status 200?
Add the relevant evidence for each question. If it is missing, repeat only the necessary bounded request. Do not invent the absent observation from memory.
Controls belong to different parts of the path
A correct owner check prevents this demonstrated read. Network restrictions limit who can reach the application. Logging helps an investigator reconstruct events, provided the necessary fields exist. Recovery supports restoration after a change or failure. These controls address different questions; a log entry does not substitute for denying the unauthorized read.
For each proposed control, name the test that would show its intended effect and a normal action it must preserve. This keeps recommendations from becoming a list of product names.
Evidence
Save the commit ID, mode, actor label, object ID, status, returned owner where present, and request ID. Match a response ID with the server log. Include the positive control, negative control, vulnerable comparison, and final reset. No actual account secrets or private records are involved.
Acceptance requires reproducing the difference, explaining its cause, and resisting the unsupported claim that a read implies a write. Continue to chapter 08 for the fuller request matrix and reporting exercise.
Safety and scope
Use the bundled loopback server only. The exercise reads immutable fictional records and makes no outbound calls. Stop it with Ctrl-C and confirm that a subsequent health request cannot connect to that server. Do not reuse the mock identity pattern as a real application login.
Teach-back
- Which input does the client control, and which decision must the server own?
- Why is the successful own-record request necessary evidence?
- What would you need to support a claim of unauthorized modification?
- How would you explain this finding without using a tool name?