Reporting & Professional Practice · intermediate · ~11 min

Writing Findings: Severity, CVSS, and Impact

**What you will learn** - Write a self-contained penetration-test finding using a complete finding template (title, severity, affected component, preconditions, safe reproduction, evidence, impact, likelihood, remediation, retest). - Rate severity as **impact x likelihood**, adjusted for business context, instead of guessing or copying a scanner label. - Use CVSS correctly: as a standardized *input* you calculate from metrics, not as the final verdict — and record your rationale when you override it. - Recognize and avoid the two ratings that destroy report trust: severity **inflation** (everything is Critical) and **deflation** (real risk buried as Low). - Merge duplicates, deconflict overlapping issues, and write titles a busy engineer can triage in five seconds. - Separate what a scanner *reported* from what you *verified*, so every severity you assign is defensible.

Overview

Security objective: the asset you are protecting here is the client's remediation budget and attention. The threat is a misprioritized report — where a trivial issue is rated Critical and a business-ending one is rated Medium, so the client fixes the wrong thing first. A good severity model detects and prevents that misallocation. Your deliverable is not an exploit; it is a decision aid.

A penetration test produces findings. Each finding is a self-contained unit: the client can read it, understand it, triage it, and fix it without reading the rest of the report. The severity you attach to that finding is the single number that decides what gets fixed on Monday morning and what waits a quarter. That is a large responsibility, and it rests on clear writing plus a defensible rating method.

This lesson builds directly on The penetration test report: structure (your prereq). There you learned where findings sit inside the overall report — executive summary, methodology, scope, then the findings section. Here we zoom into a single finding: what fields it must contain, how to phrase them, and how to score severity so the ranking is honest. The next lesson, Evidence, reproduction, and remediation, goes deeper on proving the finding and writing the fix; this lesson focuses on the anatomy and the rating.

Severity is not a feeling. It is impact times likelihood, adjusted for the business context that a generic score cannot see. CVSS (the Common Vulnerability Scoring System) gives you a consistent starting number; your judgment turns that number into a priority. By the end you will be able to take a raw observation — 'the /account endpoint returns any user's record if you change the id' — and turn it into a finding that a developer can act on and a manager can prioritize.

Why it matters

In authorized professional work, the report is the product. The client did not pay for you to find bugs; they paid for a ranked, actionable list of what to fix and in what order. Severity is the ranking key.

Get it wrong in the inflation direction and the client learns to distrust you. If your report says nine Criticals and eight are cosmetic, the real one gets the same shrug as the rest, and next year they hire someone else. Get it wrong in the deflation direction and a genuinely dangerous flaw sits in a backlog labeled 'Low' until an attacker finds it first — now your report is evidence that the risk was known and mis-rated.

Credible severity also protects you. When a finding is disputed in a remediation call — and Criticals almost always are — a rating you can trace to concrete impact and likelihood, plus a CVSS vector string anyone can recompute, ends the argument. 'I felt it was Critical' does not. A written rationale converts a subjective call into a reviewable one.

Severity ratings feed real machinery downstream: SLA clocks ('Criticals patched within 7 days'), risk registers, compliance evidence, and sometimes contractual penalties. Teams schedule sprints around your labels. Treating the severity column as decoration wastes their time and yours; treating it as a careful, documented judgment is what separates a professional report from a scanner dump.

Core concepts

1. The finding as a self-contained unit

Definition. A finding is one security issue, written so it can be read, understood, and fixed in isolation.

Plain explanation. Imagine the developer assigned to fix it never reads the rest of the report. Everything they need — what is wrong, where, how to see it, how bad it is, how to fix it, how you will confirm the fix — lives inside that one finding.

How it works. You fill a fixed template (see below) for every issue. Consistency lets the client sort, filter, and assign findings mechanically.

When / when not. Every distinct root cause gets its own finding. Do not split one root cause across ten findings (e.g., the same missing auth check on ten endpoints is usually one finding with ten affected components), and do not merge two unrelated bugs because they look similar.

Pitfall. Writing the finding as a story of your testing session ('First I tried..., then I noticed...') instead of a stable description of the issue. The reader wants the conclusion, not the diary.

2. The finding template (the REPORTING backbone)

A professional finding has these fields:

Field What it answers
Title What is wrong, and where, in one line
Severity The final priority (with rationale)
Affected component Exact endpoints, hosts, parameters, versions
Preconditions What an attacker needs first (auth? network position? a valid account?)
Reproduction Safe, minimal steps to observe it
Evidence Redacted proof it is real (request/response, screenshot)
Impact What an attacker gains — in business terms
Likelihood How easy/probable exploitation is
Remediation The concrete fix
Retest How you will confirm the fix works

Preconditions and retest are the fields beginners forget, and they are exactly the fields that make a finding professional. Preconditions stop severity inflation (a bug needing admin access is not the same as one needing nothing). Retest closes the loop — a finding is not truly done until you verify the remediation.

3. Severity = Impact x Likelihood

Definition. Severity is a two-axis judgment: how bad the outcome (impact) multiplied by how reachable the exploit (likelihood).

Plain explanation.

  • Impact — if this is exploited, what does the attacker get? Full customer database = high. A stack-trace revealing a library version = low.
  • Likelihood — how hard is it? Unauthenticated, one HTTP request, no special conditions = high. Requires an insider, a race window, and a rare config = low.
Low impact High impact
High likelihood Low / Medium Critical
Low likelihood Info / Low Medium / High

How it works. You place the finding on both axes, read the cell, then adjust for context. A trivially exploitable dump of PII is Critical. A hard-to-reach info leak is Low.

When / when not. Always rate both axes. Never rate on impact alone ('data breach = always Critical') — a data breach that requires domain-admin to trigger is not the top priority.

Pitfall. Ignoring preconditions. 'Reads any user's record' sounds Critical until you note it needs an authenticated session and the record IDs are unguessable UUIDs — likelihood drops, and so does severity.

4. CVSS — a standardized input, not the answer

Definition. CVSS is an open framework that turns metrics (attack vector, attack complexity, privileges required, user interaction, and impact to confidentiality/integrity/availability) into a 0.0-10.0 base score and a vector string.

Plain explanation. CVSS forces you to answer the same questions for every finding, so two testers score similar bugs similarly. The vector string (e.g., CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) is a receipt: anyone can recompute your score.

How it works. You select each metric, an official calculator produces the number, and you publish both the score and the vector.

When / when not. Use CVSS as your consistent starting point. Do not treat the base score as final — the base score deliberately knows nothing about your client. A CVSS 5.3 'Medium' on the system that processes every payment may be your #1 priority. When your final severity differs from the CVSS band, write the reason. This is the misconception to correct: a CVSS number is an input you adjust for business context, never the last word.

Pitfall. Copy-pasting a scanner's or an advisory's CVSS score without checking whether your environment matches the metrics. If the exploit needs local access but the advisory scored it as network-reachable, your score is wrong.

5. Verified vs. reported

Definition. A reported issue is something a tool flagged; a verified issue is something you reproduced and confirmed.

Plain explanation. Scanners produce false positives. A finding you personally reproduced deserves a confident severity; a raw scanner hit deserves a caveat or a lower confidence until confirmed.

Pitfall / misconception. Passing a scanner does not prove security, and a scanner alert does not prove a vulnerability. Never paste an unverified scanner severity into your report as fact. Mark unconfirmed items clearly, or verify them before rating.

THREAT MODEL — the report itself as a decision system

  ASSET: client's finite remediation capacity (engineer-hours, sprint slots)
  ASSET: the client's real systems the findings describe

  ENTRY POINTS (into the report's quality):
    - raw scanner output (may contain false positives / borrowed CVSS)
    - tester's manual observations (may be incomplete)
    - copy-pasted severities from advisories (may not match this environment)

  +----------------------------------------------------------+
  |  TRUST BOUNDARY: nothing enters the report as "fact"      |
  |  until the tester VERIFIES it and RATES it with a         |
  |  documented impact x likelihood + CVSS vector.           |
  +----------------------------------------------------------+
            |                          |
            v                          v
     [ verified finding ]       [ unverified / rejected ]
     severity + rationale        marked as such or dropped
            |
            v
   CLIENT PRIORITIZATION  ->  fixes highest real risk first

  FAILURE MODES:
    - inflation  -> capacity wasted on cosmetics, trust lost
    - deflation  -> real risk mislabeled Low, sits unfixed

Knowledge check

  1. What asset is protected by an honest severity rating? (Answer: the client's limited remediation time/attention — and, downstream, the systems that get fixed in the right order.)
  2. Where is the trust boundary in the report pipeline? (Answer: at the point where an observation becomes a rated finding — nothing crosses without verification plus a documented rationale.)
  3. What insecure assumption causes severity inflation? (Answer: assuming impact alone sets severity, ignoring likelihood and preconditions, so every data-touching bug becomes Critical.)

Syntax notes

There is no programming syntax here; the 'syntax' of reporting is the CVSS vector string and the finding skeleton. Learn both shapes.

CVSS v3.1 base vector — annotated:

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
|        |    |    |    |    |   |   |   +-- Availability impact: None
|        |    |    |    |    |   |   +------ Integrity impact:   None
|        |    |    |    |    |   +---------- Confidentiality:    High
|        |    |    |    |    +-------------- Scope:              Unchanged
|        |    |    |    +------------------- User Interaction:   None
|        |    |    +------------------------ Privileges Req'd:   None
|        |    +----------------------------- Attack Complexity:  Low
|        +---------------------------------- Attack Vector:      Network
+------------------------------------------- CVSS version 3.1

This example vector computes to a base score of 7.5 (High): network-reachable, no privileges, no user interaction, and it leaks confidential data. Always publish the vector and the number so the score is reproducible.

Finding skeleton (Markdown) — the shape to fill for every issue:

### [Title: impact + location, one line]

**Severity:** High (CVSS 7.5 / AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)
**Affected component:** GET /api/v1/account?id=<n> (app.lab.local, build 2.3.1)
**Preconditions:** Authenticated session as any low-privilege user.

**Description / impact:** <what is wrong and what an attacker gains, in business terms>

**Reproduction (lab only):**
1. ...
2. ...

**Evidence:** <redacted request/response or screenshot reference>

**Remediation:** <the concrete fix>

**Retest:** <how you will confirm the fix rejects the attack and still serves legit use>

Keep it lab-safe: hostnames like app.lab.local, placeholder IDs, and redacted evidence.

Lesson

Each finding is a self-contained unit that the client can triage and fix on its own. The quality of your findings defines the quality of the report.

Anatomy of a finding

  • Title - specific and impact-oriented. "Unauthenticated access to customer records via IDOR" is far better than "IDOR".
  • Severity - see below.
  • Description - what the issue is and why it matters here. Focus on the business or technical impact, not a textbook definition.
  • Affected assets - the exact endpoints, hosts, or parameters involved.
  • Reproduction steps plus evidence (covered in the next lesson).
  • Remediation plus references (covered in the next lesson).

IDOR (Insecure Direct Object Reference) means a user can access data they should not, simply by changing an identifier such as an ID in a URL.

Severity = impact x likelihood

Rate each finding on two axes:

  • Impact - how bad it is if exploited.
  • Likelihood - how easy or probable the exploit is.

A data exposure that is trivial to exploit is Critical. A low-impact issue that is hard to reach is Low.

Context matters. The same bug is more severe on a payment system than on a marketing blog.

CVSS

The Common Vulnerability Scoring System (CVSS) produces a 0-10 score from standardized metrics:

  • Attack vector
  • Attack complexity
  • Privileges required
  • User interaction
  • Impact to confidentiality, integrity, and availability

CVSS aids consistency and comparability. But CVSS is an input, not the final word.

Always adjust for business context that the base score cannot know. A "medium" on a crown-jewel system may be your top priority. State the rationale behind your final severity.

Common mistakes

  • Vague titles.
  • Severity inflation - if everything is Critical, trust erodes.
  • Severity deflation.
  • Missing business context.
  • Duplicate findings that should be merged.

Code examples

Below is a complete worked example: turning one observation into a rated finding, in the INSECURE -> SECURE -> VERIFY shape. The 'code' here is the finding document plus the fix and the retest, because that is the deliverable this lesson produces.

(1) The observation — WARNING: intentionally vulnerable — use only in a local, isolated, authorized lab. Do not deploy.

# Logged in as user 1001. Request my own account — fine:
GET /api/v1/account?id=1001 HTTP/1.1
Host: app.lab.local
Cookie: session=<lab-session-for-user-1001>

HTTP/1.1 200 OK
{"id":1001,"email":"me@lab.local","plan":"free"}

# Change one digit — I now read a DIFFERENT user's record:
GET /api/v1/account?id=1002 HTTP/1.1
Host: app.lab.local
Cookie: session=<lab-session-for-user-1001>

HTTP/1.1 200 OK
{"id":1002,"email":"someone-else@lab.local","plan":"paid"}

This is an IDOR (Insecure Direct Object Reference): the server trusts the id in the URL and never checks it belongs to the caller.

The finding, rated

### Authenticated user can read any account record by changing the id parameter (IDOR)

**Severity:** High (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N = 6.5)
  Rationale: base score is High for confidentiality loss of all customer
  records. Requires only a low-privilege authenticated account (PR:L),
  which any signed-up user has, and a single request (AC:L). Records include
  billing plan + email (PII). Business context: this app stores payment
  tier data, so we hold at High rather than reducing to Medium.

**Affected component:** GET /api/v1/account?id=<n> on app.lab.local (build 2.3.1)
**Preconditions:** Any authenticated low-privilege session. IDs are sequential integers, so enumeration is trivial.

**Impact:** An attacker with one free account can enumerate id=1..N and read every user's email and plan — a full customer-data disclosure.
**Likelihood:** High. Sequential IDs, single unauthenticated-of-object request, no rate limiting observed.

**Reproduction (lab only):** log in as user 1001, request id=1002, observe another user's record returned (evidence redacted).

(2) SECURE fix — enforce object-level authorization

# Pseudocode for the endpoint handler. The fix: never trust the client's id
# as an authorization decision. Serve only the caller's own record, or
# explicitly check ownership.

def get_account(request):
    session_user = authenticate(request)          # who is actually calling
    requested_id = int(request.query["id"])

    # Object-level authorization check (the missing control):
    if requested_id != session_user.id and not session_user.is_admin:
        log_authz_denied(session_user.id, requested_id, request)  # detection
        return http_403_forbidden()

    record = db.get_account(requested_id)
    return http_200(record)

(3) VERIFY — prove the fix REJECTS bad input and ACCEPTS good input

Retest checklist (run in the lab after the fix is deployed):

[PASS required] Negative test — attack is now rejected:
    logged in as 1001, GET /api/v1/account?id=1002  -> expect 403 Forbidden
    AND an authz-denied entry in the security log.

[PASS required] Positive test — legitimate use still works:
    logged in as 1001, GET /api/v1/account?id=1001  -> expect 200 with 1001's record.

[PASS required] Enumeration test — sweep a small id range as user 1001:
    every id != 1001 returns 403; none leaks another record.

If all three pass, mark the finding "Remediated - retest passed (date)".
If the negative test still returns 200, the fix is incomplete; reopen.

Expected outcome. Before the fix, id=1002 returned another user's record (the vulnerability). After the fix, the same request returns 403 Forbidden and writes a security log line, while id=1001 still returns the caller's own record. That contrast — bad input rejected, good input accepted — is what closes the finding.

Line by line

Walking the worked example from observation to closed finding:

  1. Baseline request (id=1001). The tester requests their own record while logged in as 1001. A 200 with their own data is correct — this establishes normal behavior. Nothing wrong yet.
  2. The probe (id=1002). The tester changes one digit. If authorization were enforced, the server would compare 1002 against the session owner (1001), see a mismatch, and refuse. Instead it returns 1002's record. The value that changed is only the URL parameter — the session cookie is unchanged — which proves the server authorized on the wrong thing.
  3. Naming it. This pattern (trusting a client-supplied object id) is IDOR. The title is written impact-first: 'read any account record by changing the id parameter' — a manager understands the risk without knowing the acronym.
  4. Rating impact. What does the attacker get? Every user's email and billing plan — PII across the whole customer base. That is High confidentiality impact (C:H), no integrity or availability change (I:N/A:N).
  5. Rating likelihood / preconditions. The attacker needs an account (PR:L, not PR:N) but nothing more — one HTTP request (AC:L), reachable over the network (AV:N), no victim interaction (UI:N). IDs are sequential, so enumeration is trivial. High likelihood.
  6. Computing CVSS. Feeding AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N into the calculator yields 6.5 (Medium band edges to High). Note how PR:L (needs a login) pulled the score down from the 7.5 it would be if unauthenticated (PR:N). Preconditions are doing real work in the math.
  7. Adjusting for business context. The base score doesn't know this app holds payment-tier data. The tester documents that and holds the final severity at High, writing the rationale — the override is transparent and reviewable.
  8. The fix. The remediation adds the missing object-level authorization check: compare requested_id to the authenticated session_user.id; deny and log otherwise. The root cause (server trusts client-supplied id) is addressed once, not patched per-symptom.
  9. Retest — the closing step. Three tests: the attack now returns 403 (negative test passes), legitimate self-access still returns 200 (positive test passes), and an id sweep leaks nothing (enumeration test passes). Only then does the finding move to 'Remediated - retest passed'.
Step id sent session server response meaning
Baseline 1001 user 1001 200 own record normal
Probe (pre-fix) 1002 user 1001 200 other record VULNERABLE
Probe (post-fix) 1002 user 1001 403 + log line fixed, attack rejected
Self (post-fix) 1001 user 1001 200 own record legit use preserved

Common mistakes

Mistake 1 — Everything is Critical (inflation).

  • WRONG: labeling every data-touching bug Critical because 'data is involved'.
  • WHY wrong: it ignores likelihood and preconditions, floods the client with false urgency, and buries the one finding that genuinely is Critical.
  • CORRECTED: rate impact and likelihood; let preconditions (needs admin? needs a race?) pull likelihood down. Reserve Critical for high-impact and easily reachable.
  • RECOGNIZE/PREVENT: if more than a small fraction of your findings are Critical, re-score them. A report that is 'all Critical' is a red flag to any reviewer.

Mistake 2 — Vague title.

  • WRONG: a finding titled 'IDOR' or 'XSS'.
  • WHY wrong: the reader cannot triage or locate it; two different XSS bugs collide under one name.
  • CORRECTED: 'Stored XSS in the profile bio field lets any user run script in an admin's browser'. Impact + location.
  • RECOGNIZE/PREVENT: if the title could describe a textbook chapter rather than this system, rewrite it.

Mistake 3 — Copying a scanner's or advisory's CVSS blindly.

  • WRONG: pasting 'CVSS 9.8 Critical' from a CVE advisory into your finding.
  • WHY wrong: the advisory scored a generic case; your environment may need local access, or already mitigate part of it. The number no longer matches reality.
  • CORRECTED: recompute the vector for the metrics you actually observed, and note where it differs from the advisory.
  • RECOGNIZE/PREVENT: never publish a score without a vector string you can defend.

Mistake 4 — Treating a scanner hit as a verified finding.

  • WRONG: reporting a raw scanner alert as a confirmed vulnerability with a firm severity.
  • WHY wrong: scanners produce false positives; passing or failing a scan is not proof either way. You may rate something that does not exist.
  • CORRECTED: reproduce it manually first; if you cannot, mark it 'unconfirmed / needs verification' and rate confidence accordingly.
  • RECOGNIZE/PREVENT: separate 'reported' from 'verified' in your notes before anything reaches the report.

Mistake 5 — Ten findings that are one root cause (or one finding hiding ten root causes).

  • WRONG: filing the same missing auth check on ten endpoints as ten Criticals; or lumping three unrelated bugs into one entry.
  • WHY wrong: inflates counts and mis-assigns fixes; the client can't tell how many actual fixes are needed.
  • CORRECTED: one root cause = one finding with a list of affected components; distinct root causes = distinct findings.
  • RECOGNIZE/PREVENT: ask 'is there one fix or many?' The number of fixes usually equals the number of findings.

Mistake 6 — Omitting the retest field.

  • WRONG: a finding with a remediation but no way stated to confirm it.
  • WHY wrong: a fix is not done until verified; without a retest step the client may 'fix' it wrongly and never know.
  • CORRECTED: always include how you will confirm — a negative test (attack rejected) and a positive test (legit use preserved).
  • RECOGNIZE/PREVENT: every finding should answer 'how will we know it's fixed?'

Debugging tips

'Debugging' a finding means catching problems in the rating and writing before the client does.

Symptom: the client disputes your severity on the call.

  • Check: did you publish the CVSS vector string, not just the number? Re-walk each metric with them.
  • Check: is your business-context adjustment written down, or was it in your head? Add the rationale line.
  • Ask: are we disagreeing on impact, on likelihood, or on the business value of the asset? Name which axis.

Symptom: your Criticals list looks too long.

  • Re-score each on likelihood specifically — many 'Criticals' are high-impact but low-likelihood (need admin, need a rare config) and belong lower.
  • Check preconditions: a bug needing an authenticated admin session is rarely Critical.

Symptom: a developer says 'I can't reproduce it.'

  • Check your preconditions field — did you state the required session/role/state? Missing preconditions are the usual cause.
  • Check that reproduction steps are minimal and in order, with the exact endpoint and parameter.

Symptom: your CVSS score 'feels wrong'.

  • Recompute with an official calculator; verify each metric matches what you observed, not what the textbook says.
  • Common errors: setting PR:N (no privileges) when a login is actually required, or AV:N (network) when the attack needs local access.

Symptom: two findings overlap.

  • Ask 'one fix or two?' If one fix closes both, merge them. If not, keep separate and cross-reference.

Questions to ask when a finding feels off: What exactly does the attacker gain? What do they need first? Did I verify it or did a tool? Can someone recompute my score from the vector? Have I said how the fix will be confirmed?

Memory safety

Security & safety — detection, logging, and evidence hygiene for reporting.

A severity rating is only credible if the underlying activity is observable. Part of writing a good finding is telling the client what their logs should have captured — both to prove the issue and to detect real attackers later.

What a remediated control should log (and what your retest checks for):

  • Timestamp (with timezone).
  • Source identity: the authenticated user id / API key id making the request.
  • Resource requested: the endpoint and the object id (e.g., account?id=1002).
  • Result: allowed or denied (the 200 vs 403).
  • The security decision itself: 'object-level authorization denied — caller 1001 requested object 1002'.
  • A correlation id / request id so one event can be traced across services.

In the IDOR example, the fix's log_authz_denied(...) line is what turns a silent bypass into a detectable event. In your finding, note: 'before the fix, cross-user reads produced no distinguishable log entry — the attack was invisible.' That absence is itself part of the impact.

What to NEVER log (and never paste into a finding as evidence):

  • Passwords, session tokens, session cookies, API secrets, private keys.
  • Full payment card numbers (PANs) — redact to last four at most.
  • Unnecessary PII — mask emails and names in evidence; you rarely need the real value to prove the bug.

When you include a request/response as evidence, redact the session cookie and any real user data. Your proof should show the shape of the vulnerability, not leak the very data you are protecting. Store the report itself as sensitive: it is a map of the client's weaknesses.

Which events signal abuse (worth recommending as detections): a single account requesting many sequential object ids in a short window; a spike in 403s from one identity (probing); access to objects the account never legitimately touches.

How false positives arise: legitimate admin tools, batch jobs, or support staff may sweep many records lawfully; a shared service account may look like enumeration. Recommend the client tune detections by role and baseline normal behavior, so alerting on 'many object reads' does not drown real signals. Note this caveat in findings that recommend monitoring.

Real-world uses

Authorized real-world use case. A SaaS company engages your firm for an authorized web-application penetration test, scope limited to their staging environment and a list of in-scope hosts, with a signed statement of work. You find the IDOR above (plus a dozen smaller issues). Your job is to hand back a findings section where severity ranking tells their engineering lead exactly what to fix before the next release. The IDOR, rated High with a documented rationale, jumps the queue; the informational TLS-config note waits. Weeks later you perform the retest and mark items 'Remediated - retest passed', which becomes their compliance evidence.

Authorization checklist (any lab or engagement):

  • Written authorization / signed scope naming the systems and time window.
  • Testing only systems you own or are explicitly permitted to test — localhost, a container, an intentionally-vulnerable VM, or a CTF for practice.
  • Out-of-scope systems left untouched, even if reachable.
  • Evidence redacted; report stored as sensitive.
  • Lab reset/cleanup after practice (see below).

Lab cleanup / reset (practice): revert the vulnerable VM/container to a clean snapshot, delete any test accounts you created, clear captured requests containing lab data, and stop the lab environment. Never carry lab exploit artifacts onto production networks.

Best-practice habits by level:

Habit Beginner Advanced
Severity Rate impact x likelihood; publish a CVSS vector Add temporal/environmental context, defend overrides in remediation calls
Validation Verify every finding manually before rating Deconflict overlapping findings, merge by root cause across services
Least privilege Test with the lowest role that triggers the bug Map exactly which roles reach the issue; reflect in PR metric
Secure defaults Recommend deny-by-default authorization Recommend systemic control (central authz middleware) over per-endpoint patches
Logging Note what the fixed control should log Design detections and tune out false positives with the client
Error handling Include a retest step per finding Track remediation SLAs and produce retest evidence for audit

Across all levels: never claim a system is 'completely secure' — a report says 'no further issues found within this scope and time window', nothing stronger.

Practice tasks

All tasks are lab-only, on systems you own or are authorized to test (localhost, a container, an intentionally-vulnerable VM, or a CTF). Each ends by remediating and verifying, not by exploiting.

Beginner 1 — Write two honest titles.

  • Objective: practice impact-first titles.
  • Requirements: take two weak titles ('SQLi' and 'Missing header') and rewrite each as one line stating impact + location for a fictional lab app (e.g. shop.lab.local).
  • Output: two rewritten titles.
  • Constraints: no acronym-only titles; include where the issue lives.
  • Hints: format is ' via in '.
  • Concepts: finding anatomy, title quality.

Beginner 2 — Fill the finding skeleton.

  • Objective: produce one complete finding for a known lab bug (reuse the IDOR or any bug in your vulnerable VM).
  • Requirements: fill every template field — title, severity, affected component, preconditions, reproduction, evidence (redacted), impact, likelihood, remediation, retest.
  • Constraints: redact session tokens and PII in evidence; lab hostnames only.
  • Hints: don't skip preconditions and retest — those are the fields that make it professional.
  • Concepts: the finding template, evidence hygiene.

Intermediate 1 — Score three findings with CVSS.

  • Objective: rate consistently and document overrides.
  • Requirements: for three lab findings, choose CVSS v3.1 base metrics, compute the score with an official calculator, then write a final severity that adjusts for a stated business context (e.g. one system processes payments).
  • Input/output: for each — the vector string, the base score, the final severity, and one line of rationale.
  • Constraints: at least one finding's final severity must differ from its CVSS band, with the reason written.
  • Hints: watch PR (does it need a login?) and AV (is it really network-reachable?).
  • Concepts: CVSS as input, business-context adjustment.

Intermediate 2 — Deconflict and de-inflate.

  • Objective: fix a bad findings list.
  • Requirements: given a list of eight findings where six are labeled Critical and three describe the same missing auth check on different endpoints, re-rate them and merge duplicates by root cause.
  • Output: a corrected table (title, severity, merged-from) with a note on why each Critical stayed or moved.
  • Constraints: justify every remaining Critical by both impact and likelihood.
  • Hints: ask 'one fix or many?' and 'high impact but low likelihood?'.
  • Concepts: inflation, duplicates, root-cause grouping.

Challenge — Full finding with remediation verification.

  • Objective: take one vulnerability in an intentionally-vulnerable lab VM from observation to closed.
  • Requirements: (1) reproduce it safely; (2) write the complete finding with a defensible severity and CVSS vector; (3) apply or describe the concrete fix; (4) write a retest with a negative test (attack rejected) AND a positive test (legit use preserved) AND a detection/logging recommendation; (5) run the retest and record the result; (6) perform lab cleanup (revert snapshot, delete test accounts).
  • Constraints: lab-only, authorization checklist completed first; no real targets; redacted evidence.
  • Defensive conclusion: the deliverable is a remediated, retest-passed finding plus a logging recommendation — not an exploit.
  • Hints: your retest is not done until the negative test returns a denial and a security log line.
  • Concepts: end-to-end reporting, mitigation verification, detection.

Summary

Main concepts. A finding is a self-contained unit filled from a fixed template: title, severity, affected component, preconditions, reproduction, evidence, impact, likelihood, remediation, retest. The two fields beginners skip — preconditions and retest — are what make a finding professional. Severity = impact x likelihood, adjusted for business context. CVSS is a standardized input (metrics -> score + vector), never the final word; when you override the band, write the reason. Separate what a scanner reported from what you verified.

Key 'syntax'. The CVSS vector string (e.g. AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N) — always publish it alongside the number so anyone can recompute. The finding skeleton in Markdown, filled the same way every time.

Common mistakes. Severity inflation (everything Critical) and deflation; vague acronym-only titles; copying scanner/advisory CVSS blindly; treating a scanner hit as verified; ten findings for one root cause; omitting the retest.

What to remember. Severity decides what the client fixes first, so it must be defensible, not felt. Rate both axes, honor preconditions, publish the vector, document overrides, verify before you rate, redact evidence, and close every finding with a retest that shows bad input rejected and good input still accepted. Never call anything 'completely secure' — only 'no further issues found within this scope and time window'.

Next: Evidence, reproduction, and remediation goes deeper on proving the finding and writing the fix.

Practice with these exercises