Skip to content

06 · Labs and reporting

Depth: developed lesson · Prerequisites: web authorization, detection evaluation · Time: two 90-minute sessions.

A report should let another person reconstruct the tested boundary and challenge your conclusion. A long command transcript is insufficient if the reader cannot tell which actor was active, which policy applied or why the result matters. The goal is a small, reproducible chain from a claim to evidence and a retest.

Outcomes

  • Separate an observed behavior, its explanation and its bounded impact.
  • Reproduce a finding with positive and negative controls.
  • Preserve evidence with clear identifiers and explain what a checksum cannot prove.
  • Write a remediation retest that checks legitimate behavior as well as denial.

Session 1: build a finding from one boundary

Use the bundled web fixture from chapter 08. The target is the loopback service you start yourself. The intended policy is that Alice and Bob can each read only their own record. The identity header is deliberately a simulation; this lab does not implement real authentication.

Record the repository commit and Python version, then run the vulnerable mode in one terminal:

git rev-parse HEAD
python3 --version
python3 -m labs.web_boundary --mode vulnerable

In a second terminal, perform the positive control and the cross-owner comparison:

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

Expect both requests to return 200, with the second body identifying Bob as owner. Keep the actor unchanged. Record the request ID from each response, the mode and the body fields relevant to ownership. A response code alone is not the ownership evidence.

Stop that server with Ctrl-C, start the fixed mode, and repeat the two requests:

python3 -m labs.web_boundary --mode fixed

Now Alice's own-record request should remain 200 and the cross-owner request should return 403 without Bob's record. If the first request also fails, the change may have broken legitimate access; blocking everything is not a successful functional retest.

If a port is already occupied, identify your own existing lab process before continuing. Do not kill an unknown process. Chapter 08 explains how to select another port consistently. Preserve unexpected results as observations instead of editing a transcript to match the answer.

Turn the transcript into a defensible claim

A suitable finding title is “Cross-owner record read in the synthetic vulnerable mode.” A bounded impact statement is: “Simulated Alice obtained the contents of Bob's fixture record despite the stated ownership rule.” This shows a confidentiality boundary failure for the tested read operation.

It does not establish arbitrary writes, access to every record in a real system, administrator authority or exposure of real personal data. The fixture has two synthetic records. Do not attach a severity score that assumes a production deployment you have not examined.

Part of the finding Concrete content
Preconditions Own loopback fixture, vulnerable mode, simulated Alice identity, existing record 202
Expected rule Alice may read record 101; Bob owns record 202
Observation Request for 202 returns Bob's record under Alice identity
Explanation The vulnerable branch omits the ownership check exercised by the fixed branch
Remediation principle Check the caller's authorization for the requested object before returning it
Retest Own-record read succeeds; cross-owner read is denied in fixed mode
Limits Mock identity, synthetic data, read endpoint only, two modes of a teaching app

The explanation can be cross-checked against the fixture implementation. Source inspection explains the branch; it is separate evidence from the actual HTTP observation.

Session 2: produce the report and challenge it

Write the following in your private learning directory. Use evidence IDs such as WEB-01 rather than a public paste of raw machine output.

# Cross-owner record read in the synthetic vulnerable mode

Scope: my loopback teaching fixture; no external targets.
Environment: actual commit, Python version, port, mode and time.
Policy: each simulated actor may read only their own record.
Preconditions: identity, target object and server state.
Steps: exact minimal requests, each linked to an evidence ID.
Observed result: status plus relevant returned ownership fields.
Expected result: intended decision for the same actor and object.
Impact: the narrow operation and data scope demonstrated.
Cause: source-supported explanation, separate from observed behavior.
Remediation: boundary to enforce and legitimate behavior to preserve.
Retest: own-record and cross-owner outcomes in fixed mode.
Detection: available telemetry, tested rule and known miss.
Cleanup: server stopped; health request no longer reaches this process.
Uncertainty: claims not tested and the next distinguishing experiment.

For the detection section, run python3 -m labs.detection and reference the six-case synthetic evaluation. State that both status-only rules miss the successful cross-owner request. Do not claim these are production alerts or that the evaluator replayed your live HTTP requests: it reads a fixed teaching dataset.

Ask a reviewer to reconstruct the experiment from the report. Have them identify one sentence that exceeds the evidence and one missing precondition. If working alone, return after a break and attempt the reproduction using only your report. Label self-review honestly.

Preserve evidence without overstating integrity

A checksum can identify the bytes you retained and reveal a later change relative to that checksum. It does not prove that a transcript was truthful when created, that a timestamp is authoritative, or that someone independently witnessed the test.

Save a redacted transcript as evidence-redacted.txt in your private exercise directory. From that directory, compute a hash using Python's standard library:

python3 - <<'PY'
from hashlib import sha256
from pathlib import Path

path = Path('evidence-redacted.txt')
with path.open('rb') as stream:
    digest = sha256()
    for block in iter(lambda: stream.read(65536), b''):
        digest.update(block)
print(digest.hexdigest(), path.name)
PY

Hash the retained redacted artifact, and record that it is redacted. If a private original also exists, keep its reference separate. Replacing the contents of an evidence ID without updating its hash and revision history breaks the report's ability to identify which bytes were reviewed.

Evidence and acceptance

Your report passes this lesson's local check when another reader can identify the actor, action, object, policy, result, cause and limitation; follow the exact minimal steps; and distinguish successful remediation from a server that simply stopped working. Preserve both expected and unexpected outcomes.

This is an authored exercise with software-tested fixtures. Completing a build or copying the worked finding does not demonstrate your independent assessment skill. The assessment rubric requires your own execution and explanation evidence.

Safety and scope

Operate only the bundled fixture on loopback. Stop the server after the retest and confirm that its health endpoint is no longer reachable. Keep transcripts private if they contain machine identifiers. No source-book command is automatically executed by this lesson.

Teach-back

  1. Which line of evidence distinguishes cross-owner access from a generic 200 response?
  2. Why must a remediation retest include an authorized request?
  3. What can a transcript hash establish, and what remains outside that claim?
  4. What additional evidence would be needed before assessing production impact?

Continue: 15 · SCADA architecture for a different control model, or use the coverage map to choose an available lesson. The capstone and remaining tracks are still being expanded.