Recompute a CVSS base score, then list what it cannot see

short · 35 min · Objective 2.1

Task

Write a small calculator that turns a CVSS v3.1 vector into its base score, check it against values the specification publishes, and then write down the facts about your own estate that no vector can carry. Doing the arithmetic once takes the mystery out of the number; listing what it leaves out is the point of the lesson.

Steps

  1. Write /tmp/cvss.py. It takes one vector argument such as CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H and prints the base score to one decimal place. Use the weights from FIRST's CVSS v3.1 specification: attack vector N 0.85, A 0.62, L 0.55, P 0.2; complexity L 0.77, H 0.44; privileges N 0.85, L 0.62 (0.68 when scope is changed), H 0.27 (0.50 when changed); user interaction N 0.85, R 0.62; each impact H 0.56, L 0.22, N 0.
  2. Implement the formulas. The impact sub-score is ISS = 1 - (1 - C) x (1 - I) x (1 - A). Impact is 6.42 x ISS for unchanged scope, or 7.52 x (ISS - 0.029) - 3.25 x (ISS - 0.02) to the power 15 for changed scope. Exploitability is 8.22 x AV x AC x PR x UI. If impact is zero or less the score is 0.0; otherwise it is the round-up of the smaller of (impact + exploitability) and 10, multiplying the sum by 1.08 first when scope is changed. Round up to one decimal place, working in whole numbers of 1/100000 as the specification's Roundup function does, so floating-point noise cannot push 4.0 to 4.1.
  3. Add the qualitative band as a second word on the output line: none for 0.0, low 0.1-3.9, medium 4.0-6.9, high 7.0-8.9, critical 9.0-10.0. The output for the first vector should read 9.8 critical.
  4. Score the five vectors listed in the Verify section. If one disagrees with the value given there, the bug is yours: fix it before continuing, because a plausible wrong score looks exactly like a right one.
  5. Write /tmp/cvss-gaps.md: at least four facts about your own lab estate that would change how urgently you fix a flaw but cannot be expressed in the base vector -- whether it is known to be exploited, whether the host is reachable from outside, what the host holds, and which compensating controls already stand in front of it. Finish with one line naming the two CVSS metric groups that exist to carry some of that context.

Verify

python3 /tmp/cvss.py CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
python3 - <<'PY'
import subprocess
known = {
    'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H': '9.8',
    'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H': '10.0',
    'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N': '6.1',
    'CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H': '7.8',
    'CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N': '0.0',
}
for vector, want in known.items():
    got = subprocess.run(['python3', '/tmp/cvss.py', vector],
                         capture_output=True, text=True).stdout.split()
    print(vector, '->', ' '.join(got))
    assert got and got[0] == want, 'expected ' + want
print('all five vectors score as the specification says')
PY
grep -ciE "exploit|expos|reachab|internet|holds|compensat" /tmp/cvss-gaps.md

The first line must print 9.8 critical. The assertion then runs all five vectors, which between them exercise unchanged and changed scope, a local vector and a zero-impact vector; every one must match. The grep must report at least four lines: the context you listed is exactly what the base score cannot see.

Notes

FIRST says plainly that CVSS measures severity, not risk. The two numbers you can now produce on demand -- 9.8 for the network worst case, 7.8 for the same impact reachable only locally -- describe the flaw in the abstract. Whether it is your most urgent problem depends on the facts in your gaps file, which is the subject of the applied lab.

This is an independent study companion for CompTIA Security+ SY0-801 and is not produced by or endorsed by CompTIA.