CVSS in five minutes (without the hand-waving)
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.
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.