Support partially-filled CVSS tables, categorize finding as 'Other' then

This commit is contained in:
maride 2026-01-29 14:49:27 +01:00
parent a354919fca
commit 201caff2fc
3 changed files with 64 additions and 49 deletions

View File

@ -8,6 +8,8 @@
table.cell(str, fill: yellow, align: center) table.cell(str, fill: yellow, align: center)
} else if str == "N" { } else if str == "N" {
table.cell(str, fill: lime, align: center) table.cell(str, fill: lime, align: center)
} else if str == "-" {
table.cell(str, fill: white, align: center)
} else { } else {
panic("Unknown CIA state: " + str) panic("Unknown CIA state: " + str)
} }

View File

@ -33,58 +33,62 @@
} }
// Create a small CIA table to be included for every finding // Create a small CIA table to be included for every finding
#let createTable(attackVector: "N", attackComplexity: "L", privilegesRequired: "N", userInteraction: "N", scope: "U", confidentiality: "H", integrity: "H", availability: "H") = { #let createTable(attackVector: "-", attackComplexity: "-", privilegesRequired: "-", userInteraction: "-", scope: "-", confidentiality: "-", integrity: "-", availability: "-") = {
// Check values // Check values
panicOnInvalid(attackVector, ("N", "A", "L", "P")) panicOnInvalid(attackVector, ("N", "A", "L", "P", "-"))
panicOnInvalid(attackComplexity, ("L", "H")) panicOnInvalid(attackComplexity, ("L", "H", "-"))
panicOnInvalid(privilegesRequired, ("N", "L", "H")) panicOnInvalid(privilegesRequired, ("N", "L", "H", "-"))
panicOnInvalid(userInteraction, ("N", "R")) panicOnInvalid(userInteraction, ("N", "R", "-"))
panicOnInvalid(scope, ("U", "C")) panicOnInvalid(scope, ("U", "C", "-"))
panicOnInvalid(confidentiality, ("H", "L", "N")) panicOnInvalid(confidentiality, ("H", "L", "N", "-"))
panicOnInvalid(integrity, ("H", "L", "N")) panicOnInvalid(integrity, ("H", "L", "N", "-"))
panicOnInvalid(availability, ("H", "L", "N")) panicOnInvalid(availability, ("H", "L", "N", "-"))
// Calculate base result, see https://www.first.org/cvss/v3-1/specification-document#7-1-Base-Metrics-Equations
let issLookup = ("H": 0.56, "L": 0.22, "N": 0)
let attackVectorLookup = ("N": 0.85, "A": 0.62, "L": 0.55, "P": 0.2)
let attackComplexityLookup = ("L": 0.77, "H": 0.44)
let privilegesLookup = ("N": 0.85, "L": if scope == "U" { 0.62 } else { 0.68 }, "H": if scope == "U" { 0.27 } else { 0.5 })
let userInteractionLookup = ("N": 0.85, "R": 0.62)
let iss = 1 - ((1 - issLookup.at(confidentiality)) * (1 - issLookup.at(integrity)) * (1 - issLookup.at(availability)))
let impact = if scope == "U" { 6.42 * iss } else { 7.52 * (iss - 0.029) - 3.25 * (iss - 0.02)}
let exploitability = 8.22 * attackVectorLookup.at(attackVector) * attackComplexityLookup.at(attackComplexity) * privilegesLookup.at(privilegesRequired) * userInteractionLookup.at(userInteraction)
let baseScore = if impact <= 0 { 0 } else { if scope == "U" { calc.round(calc.min(impact + exploitability, 10), digits: 1) } else { calc.round(calc.min(1.08 * (impact + exploitability), 10), digits: 1) } }
let status = "?" let status = "?"
if baseScore >= 9.0 { if((attackVector, attackComplexity, privilegesRequired, userInteraction, scope, confidentiality, integrity, availability).find(x => x == "-") == none) {
status = "CRITICAL" // Calculate base result, see https://www.first.org/cvss/v3-1/specification-document#7-1-Base-Metrics-Equations
} else if baseScore >= 7.0 { let issLookup = ("H": 0.56, "L": 0.22, "N": 0)
status = "HIGH" let attackVectorLookup = ("N": 0.85, "A": 0.62, "L": 0.55, "P": 0.2)
} else if baseScore >= 4.0 { let attackComplexityLookup = ("L": 0.77, "H": 0.44)
status = "MEDIUM" let privilegesLookup = ("N": 0.85, "L": if scope == "U" { 0.62 } else { 0.68 }, "H": if scope == "U" { 0.27 } else { 0.5 })
} else if baseScore >= 0.1 { let userInteractionLookup = ("N": 0.85, "R": 0.62)
status = "LOW" let iss = 1 - ((1 - issLookup.at(confidentiality)) * (1 - issLookup.at(integrity)) * (1 - issLookup.at(availability)))
let impact = if scope == "U" { 6.42 * iss } else { 7.52 * (iss - 0.029) - 3.25 * (iss - 0.02)}
let exploitability = 8.22 * attackVectorLookup.at(attackVector) * attackComplexityLookup.at(attackComplexity) * privilegesLookup.at(privilegesRequired) * userInteractionLookup.at(userInteraction)
let baseScore = if impact <= 0 { 0 } else { if scope == "U" { calc.round(calc.min(impact + exploitability, 10), digits: 1) } else { calc.round(calc.min(1.08 * (impact + exploitability), 10), digits: 1) } }
if baseScore >= 9.0 {
status = "CRITICAL"
} else if baseScore >= 7.0 {
status = "HIGH"
} else if baseScore >= 4.0 {
status = "MEDIUM"
} else if baseScore >= 0.1 {
status = "LOW"
} else {
status = "NONE"
}
} else { } else {
status = "NONE" // At least one value is unspecified, so this finding will be categorized as "other" and CVSS Score calculation is skipped
status = "OTHER"
} }
block( stack(
[ dir: ttb,
#block( table(
spacing: 0.4em, columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr),
table( align: center,
columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr), stroke: 1pt,
align: center, table.cell(colspan: 5)[*Exploitability Metrics*],
stroke: 1pt, table.cell(colspan: 3)[*Impact Metrics*],
table.cell(colspan: 5)[*Exploitability Metrics*], table.cell(rowspan: 2, align: bottom)[*#sym.sum*],
table.cell(colspan: 3)[*Impact Metrics*], [*AV*], [*AC*], [*PR*], [*UI*], [*S*], [*C*], [*I*], [*A*],
table.cell(rowspan: 2, align: bottom)[*#sym.sum*], attackVector, attackComplexity, privilegesRequired, userInteraction, scope, cia.colorize(confidentiality), cia.colorize(integrity), cia.colorize(availability), coloredCell(status),
[*AV*], [*AC*], [*PR*], [*UI*], [*S*], [*C*], [*I*], [*A*], ),
attackVector, attackComplexity, privilegesRequired, userInteraction, scope, cia.colorize(confidentiality), cia.colorize(integrity), cia.colorize(availability), coloredCell(status), v(.25em),
) align(
) right,
#align(right)[ text(
#text(
size: 10pt, size: 10pt,
fill: gray, fill: gray,
"CVSS:3.1/AV:" + attackVector + "CVSS:3.1/AV:" + attackVector +
@ -95,8 +99,8 @@
"/C:" + confidentiality + "/C:" + confidentiality +
"/I:" + integrity + "/I:" + integrity +
"/A:" + availability "/A:" + availability
)] )
] )
) )
updateRiskCategoryStats(status) updateRiskCategoryStats(status)

View File

@ -6,7 +6,16 @@
== Administration Interfaces reachable == Administration Interfaces reachable
#cvss.createTable(confidentiality: "N", integrity: "N", availability: "N") #cvss.createTable(
attackVector: "N",
attackComplexity: "L",
privilegesRequired: "N",
userInteraction: "N",
scope: "U",
confidentiality: "N",
integrity: "N",
availability: "N",
)
=== Description === Description