Skip to content
Severity Labs
← All posts

CVSS in five minutes (without the hand-waving)

Severity Labs · Notes

CVSS in five minutes (without the hand-waving)

Most CVSS scores in bug bounty reports are wrong, and not in interesting ways. Here's the version of the spec you actually need.

April 15, 20264 min read

CVSS, the Common Vulnerability Scoring System, is the closest thing the industry has to a shared language for severity. It's also the source of endless arguments, mostly because people copy-paste vector strings from similar bugs without thinking about what each metric actually means.

Here's the version you need to score a bug correctly in under five minutes.

What CVSS is, and what it isn't

CVSS produces a number from 0.0 to 10.0 that's meant to reflect the intrinsic severity of a vulnerability, how bad it is, in the abstract, in a worst-case configuration. It deliberately excludes things like "how exposed is the affected asset" or "is this in production".

That's the part most people get wrong. CVSS is not a risk score. A 9.8 in your sandbox is not a 9.8 to your business. A real risk picture combines the CVSS base score with environmental context, what we'd call a business-context severity in our reports.

Score the bug correctly first. Apply context after.

The base metrics, in plain English

The CVSS 3.1 base score has eight metrics. Get these right and you'll agree with the official calculator nine times out of ten.

Attack Vector (AV)

Where does the attacker need to be?

  • Network (N), anywhere on the internet
  • Adjacent (A), same broadcast or VPN segment
  • Local (L), local shell or physical/console access
  • Physical (P), physically touching the device

Most web bugs are N. Only drop to L if exploitation literally requires local access, not just "they have an account on the app".

Attack Complexity (AC)

Does exploitation require conditions outside the attacker's control?

  • Low (L), just runs
  • High (H), requires winning a race, knowing internal state, or some other non-deterministic precondition

If you can write a script that exploits it reliably, it's L. "It's hard to exploit" is not H. "It only works on Tuesdays when the cron job hasn't run" is H.

Privileges Required (PR)

What level of access does the attacker need before the bug?

  • None (N), unauthenticated
  • Low (L), any logged-in user
  • High (H), admin or equivalent

Self-registration counts as N for almost every consumer app. "Anyone with a GitHub account" is N. "An employee" is L. "An admin" is H.

User Interaction (UI)

Does the victim have to do something?

  • None (N), bug fires without help
  • Required (R), victim must click, paste, or visit something

Stored XSS that fires on page load is N. Reflected XSS in a link is R.

Scope (S)

Does the bug let the attacker affect resources beyond what the vulnerable component manages?

  • Unchanged (U), impact stays in the vulnerable component's domain
  • Changed (C), impact crosses a security boundary

This is the metric people most often get wrong. A subdomain takeover that lets an attacker steal cookies for *.example.com is C, they've crossed a domain boundary. A SQLi in one tenant of a multi-tenant app that exposes other tenants' data is C. SSRF that pivots to internal metadata services is C.

If in doubt, ask: did the attack break out of where the bug lives?

Confidentiality / Integrity / Availability (C / I / A)

The impact triad. Each one is None (N), Low (L), or High (H).

  • None, no impact on this property
  • Low, limited or partial impact (some data exposed, some integrity loss, some availability loss)
  • High, total or near-total impact

Reading the entire user database is C:H. Reading one user's email address is C:L. Defacing the homepage is I:H. Crashing the auth service for an hour is A:H. Most bugs only score on one or two of these , you don't have to fill in all three with H.

The vector string

Once you've picked each metric, the vector string is just the codes concatenated:

CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N

Drop that into the FIRST CVSS calculator and you'll get the base score. Or use the CVSS 3.1 specification if you want to argue with someone.

A worked example

Stored XSS in a reviewer comment field. The attacker is a logged-in low-privilege user. The victim has to open the affected review for the payload to fire. The payload steals the victim's session cookie, which lets the attacker log in as them on *.example.com.

  • AV: Network, anywhere on the internet works → N
  • AC: Low, runs every time → L
  • PR: Low, attacker needs an account → L
  • UI: Required, victim must open the review → R
  • S: Changed, cookie theft pivots to other subdomains → C
  • C: High, full session compromise → H
  • I: Low, limited integrity impact via the hijacked session → L
  • A: None, no availability hit → N

Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N → 7.4 (High).

What CVSS leaves out

CVSS won't tell you that the affected reviewer role only exists for ten internal users, that the bug is behind a feature flag turned off in production, or that the cookie is Secure and SameSite=Strict so real-world session theft is much harder. Those are environmental and temporal metrics, separate sub-scores in the spec, and most reports don't bother with them.

That's fine, as long as you remember the base score is the worst case. The actual risk to your business needs the second pass.

If you'd like that second pass written for you on every report, that's what we do.

Get started

Stop letting reports pile up.

Hunters lose interest. Engineers lose mornings. The next report is already in the inbox.