CVSS Scoring & Reporting

Cyber Security · Lesson 14 of 31 7 min read Updated

A finding is only useful if the right people understand how serious it is and how to fix it. Two testers who describe the same SQL injection as "bad" and "very bad" do not help anyone. Security teams therefore use a shared scoring system, CVSS, and a predictable report structure.

In this lesson you will learn how CVSS scores and vector strings work in versions 4.0 and 3.1, why a CVSS score is not the same as risk, which other sources (CVE, CWE, EPSS and KEV) add context, and how to write a finding that a developer can fix and a manager can approve.

What CVSS is

The Common Vulnerability Scoring System (CVSS) is maintained by FIRST, the global forum of incident response teams. It gives a vulnerability a score from 0.0 to 10.0 and a vector string that records every choice behind the score. CVSS v4.0 was published on 1 November 2023. CVSS v3.1 is still very common, and public databases such as NIST's National Vulnerability Database show scores in both versions.

Both versions use the same severity ratings:

ScoreRating
0.0None
0.1 to 3.9Low
4.0 to 6.9Medium
7.0 to 8.9High
9.0 to 10.0Critical
PythonRun it yourself
def rating(score):
    if score == 0:
        return "None"
    if score < 4.0:
        return "Low"
    if score < 7.0:
        return "Medium"
    if score < 9.0:
        return "High"
    return "Critical"

for s in [0.0, 3.9, 4.0, 6.9, 7.5, 9.3, 10.0]:
    print(f"{s:>4} {rating(s)}")
Output
 0.0 None
 3.9 Low
 4.0 Medium
 6.9 Medium
 7.5 High
 9.3 Critical
10.0 Critical

CVSS v4.0 base metrics

CVSS v4.0 has four metric groups: Base (the vulnerability itself, which never changes), Threat (whether exploitation is happening), Environmental (your own setup and needs) and Supplemental (extra information that does not change the score). A score is labelled by the groups used: CVSS-B for base only, CVSS-BT for base plus threat, and so on.

The base metrics describe how the vulnerability is exploited and what it affects:

MetricAbbreviationValues
Attack VectorAVNetwork, Adjacent, Local, Physical
Attack ComplexityACLow, High
Attack RequirementsATNone, Present (conditions outside the attacker's control)
Privileges RequiredPRNone, Low, High
User InteractionUINone, Passive, Active
Vulnerable system impactVC, VI, VAHigh, Low, None for confidentiality, integrity, availability
Subsequent system impactSC, SI, SAHigh, Low, None for other systems affected afterwards

A SQL injection in a public login page that lets anyone read and change the whole database, with no effect on other systems, has this vector:

Text
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

FIRST's official calculator gives this vector a score of 9.3 (Critical). CVSS v4.0 scores are computed from lookup tables, not a short formula, so always use the official calculator on the FIRST website rather than doing it by hand. A program can still read a vector and explain it:

PythonRun it yourself
NAMES = {
    "AV": ("Attack Vector", {"N": "Network", "A": "Adjacent", "L": "Local", "P": "Physical"}),
    "AC": ("Attack Complexity", {"L": "Low", "H": "High"}),
    "AT": ("Attack Requirements", {"N": "None", "P": "Present"}),
    "PR": ("Privileges Required", {"N": "None", "L": "Low", "H": "High"}),
    "UI": ("User Interaction", {"N": "None", "P": "Passive", "A": "Active"}),
}
IMPACT = {"H": "High", "L": "Low", "N": "None"}

def explain(vector):
    version, *parts = vector.split("/")
    print("Version:", version.split(":")[1])
    for part in parts:
        key, value = part.split(":")
        if key in NAMES:
            label, values = NAMES[key]
            print(f"  {label:<20} {values[value]}")
        else:
            print(f"  {key + ' impact':<20} {IMPACT[value]}")

explain("CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N")
Output
Version: 4.0
  Attack Vector        Network
  Attack Complexity    Low
  Attack Requirements  None
  Privileges Required  Low
  User Interaction     None
  VC impact            High
  VI impact            Low
  VA impact            None
  SC impact            None
  SI impact            None
  SA impact            None

This vector describes a logged-in user (PR:L) who can read other users' data (VC:H) and change a little of it (VI:L), a typical broken access control finding.

Severity is not risk

CVSS measures how severe a vulnerability is in general. Risk depends on your situation. A 9.8 in an internal test server with no data may matter less than a 6.5 in your payment page. Good reports add context from:

  • CVE (Common Vulnerabilities and Exposures): a unique ID for a publicly known vulnerability, such as CVE-2021-44228 (Log4Shell). CERT-In is a CVE Numbering Authority for products made in India.
  • CWE (Common Weakness Enumeration): the type of weakness, such as CWE-89 (SQL injection) or CWE-79 (cross-site scripting). Findings in custom code usually have a CWE but no CVE.
  • EPSS (Exploit Prediction Scoring System, also from FIRST): the estimated probability that a CVE will be exploited in the next 30 days.
  • KEV: the US CISA catalogue of Known Exploited Vulnerabilities, a list of CVEs that attackers are using right now.

A sensible rule: fix anything that is in KEV or actively exploited first, then high CVSS findings on important assets.

Writing a finding

Every finding in a report should answer the same questions in the same order:

Text
Title:        SQL injection in product search (DVWA, port 4280)
Severity:     Critical, CVSS-B 9.3
Vector:       CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Weakness:     CWE-89
Asset:        http://192.168.56.101:4280/vulnerabilities/sqli/ (lab)
Description:  The id parameter is joined directly into an SQL query.
Steps:        1. Log in as the test user. 2. Open the page. 3. Enter a single quote.
              4. The page shows a database error, confirming the input reaches SQL.
Evidence:     Screenshot 4, request and response in appendix B (credentials redacted).
Impact:       An attacker could read or change any data in the database.
Fix:          Use parameterised queries for every database call; give the app's
              database user only the permissions it needs.
References:   OWASP SQL Injection Prevention Cheat Sheet

Write for two readers. Managers read the executive summary: a short, non-technical page with the overall picture, the most serious issues in business terms and the top priorities. Developers read the detailed findings, so steps must be exact and reproducible and the fix must be specific. A typical report has: title page and version, executive summary, scope and methodology (dates, targets, tools, limitations), a summary table of findings, detailed findings, and appendices.

The summary table can be generated from your findings list:

PythonRun it yourself
findings = [
    ("Missing security headers", 2.3),
    ("SQL injection in product search", 9.3),
    ("Other users' orders visible by changing the ID", 7.1),
    ("Verbose error messages", 5.3),
    ("Session cookie without Secure flag", 2.1),
]

def rating(s):
    return "Critical" if s >= 9 else "High" if s >= 7 else "Medium" if s >= 4 else "Low"

counts = {}
for n, (title, score) in enumerate(sorted(findings, key=lambda f: f[1], reverse=True), start=1):
    r = rating(score)
    counts[r] = counts.get(r, 0) + 1
    print(f"F{n:02} {score:>4} {r:<8} {title}")
print("Totals:", ", ".join(f"{k} {v}" for k, v in counts.items()))
Output
F01  9.3 Critical SQL injection in product search
F02  7.1 High     Other users' orders visible by changing the ID
F03  5.3 Medium   Verbose error messages
F04  2.3 Low      Missing security headers
F05  2.1 Low      Session cookie without Secure flag
Totals: Critical 1, High 1, Medium 1, Low 2

Common mistakes

  • Scoring by feel. Pick each metric deliberately and include the vector so others can check it.
  • Mixing v3.1 and v4.0 scores in one table without saying which is which.
  • Steps nobody can repeat. Include the exact page, parameter, account role and expected result.
  • Leaving secrets in evidence. Redact passwords, tokens and personal data in screenshots and logs.
  • Blaming people. Describe the system's weakness, not the developer who wrote it.

Key points

  • CVSS gives a 0.0 to 10.0 severity score and a vector string; v4.0 (November 2023) and v3.1 are both in use.
  • Ratings: Low 0.1 to 3.9, Medium 4.0 to 6.9, High 7.0 to 8.9, Critical 9.0 to 10.0.
  • v4.0 base metrics are AV, AC, AT, PR, UI and impacts on the vulnerable and subsequent systems.
  • Severity is not risk: add business context, CVE, CWE, EPSS and KEV information.
  • A good finding has a title, severity and vector, asset, steps, evidence, impact and a specific fix.

Exercise

Use the explain function to describe this vector, then decide which kind of vulnerability it could be: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N. Add a sixth finding, "Stored XSS in product reviews" with score 5.1, to the summary table script and run it.

Show solution
Python
findings = [
    ("Missing security headers", 2.3),
    ("SQL injection in product search", 9.3),
    ("Other users' orders visible by changing the ID", 7.1),
    ("Verbose error messages", 5.3),
    ("Session cookie without Secure flag", 2.1),
    ("Stored XSS in product reviews", 5.1),
]
for title, score in sorted(findings, key=lambda f: f[1], reverse=True):
    print(score, title)

The vector has no impact on the vulnerable system itself but low confidentiality and integrity impact on a subsequent system after passive user interaction (a victim viewing a page). That pattern fits cross-site scripting, where the vulnerable server is unharmed but the victim's browser session is affected. The new finding is also Medium, so it sorts just after "Verbose error messages" (5.3) and before the Low findings.

Quick Quiz

1. What rating does a CVSS score of 7.5 have?

2. In CVSS v4.0, what does PR:N mean?

3. Which source tells you that attackers are actively exploiting a CVE right now?

4. Who is the executive summary of a report mainly written for?

Learn Cyber Security with a mentor

Free lessons get you started. Our Ethical Hacking & Cyber Security training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call