Accept CVSS strings in finding creation
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#import "cia.typ"
|
||||
#import "cvss.typ"
|
||||
#import "finding.typ"
|
||||
#import "mitre.typ"
|
||||
#import "tlp.typ"
|
||||
+48
-15
@@ -32,8 +32,7 @@
|
||||
table.cell(riskCategories.at(status).title, fill: riskCategories.at(status).color, align: center)
|
||||
}
|
||||
|
||||
// Create a small CIA table to be included for every finding
|
||||
#let createTable(attackVector: "-", attackComplexity: "-", privilegesRequired: "-", userInteraction: "-", scope: "-", confidentiality: "-", integrity: "-", availability: "-") = {
|
||||
#let score(attackVector: "-", attackComplexity: "-", privilegesRequired: "-", userInteraction: "-", scope: "-", confidentiality: "-", integrity: "-", availability: "-") = {
|
||||
// Check values
|
||||
panicOnInvalid(attackVector, ("N", "A", "L", "P", "-"))
|
||||
panicOnInvalid(attackComplexity, ("L", "H", "-"))
|
||||
@@ -44,7 +43,6 @@
|
||||
panicOnInvalid(integrity, ("H", "L", "N", "-"))
|
||||
panicOnInvalid(availability, ("H", "L", "N", "-"))
|
||||
|
||||
let status = "?"
|
||||
if((attackVector, attackComplexity, privilegesRequired, userInteraction, scope, confidentiality, integrity, availability).find(x => x == "-") == none) {
|
||||
// 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)
|
||||
@@ -56,21 +54,31 @@
|
||||
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"
|
||||
}
|
||||
|
||||
return baseScore
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Create a small CIA table to be included for every finding.
|
||||
#let createTable(attackVector: "-", attackComplexity: "-", privilegesRequired: "-", userInteraction: "-", scope: "-", confidentiality: "-", integrity: "-", availability: "-") = {
|
||||
let baseScore = score(attackVector: attackVector, attackComplexity: attackComplexity, privilegesRequired: privilegesRequired, userInteraction: userInteraction, scope: scope, confidentiality: confidentiality, integrity: integrity, availability: availability)
|
||||
|
||||
let status = "?"
|
||||
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 if baseScore == -1 {
|
||||
// At least one value is unspecified, so this finding will be categorized as "other" and CVSS Score calculation is skipped
|
||||
status = "OTHER"
|
||||
} else {
|
||||
status = "NONE"
|
||||
}
|
||||
|
||||
stack(
|
||||
@@ -107,6 +115,31 @@
|
||||
isUsed.update(true)
|
||||
}
|
||||
|
||||
// parse eats a CVSS:3.1 string and creates the appropiate table for it
|
||||
#let parse(input: str) = {
|
||||
let parts = input.split("/")
|
||||
if parts.len() != 9 {
|
||||
panic("Invalid CVSS string: " + input + ", expected 9 parts")
|
||||
}
|
||||
if parts.at(0) != "CVSS:3.1" {
|
||||
panic("Unsupported CVSS string version: " + parts.at(0) + ", expected 'CVSS:3.1'")
|
||||
}
|
||||
if not parts.at(1).starts-with("AV:") or not parts.at(2).starts-with("AC:") or not parts.at(3).starts-with("PR:") or not parts.at(4).starts-with("UI:") or not parts.at(5).starts-with("S:") or not parts.at(6).starts-with("C:") or not parts.at(7).starts-with("I:") or not parts.at(8).starts-with("A:") {
|
||||
panic("Invalid CVSS string: " + input + ", expected format CVSS:3.1/AV:*/AC:*/PR:*/UI:*/S:*/C:*/I:*/A:*")
|
||||
}
|
||||
|
||||
return (
|
||||
"attackVector": parts.at(1).last(),
|
||||
"attackComplexity": parts.at(2).last(),
|
||||
"privilegesRequired": parts.at(3).last(),
|
||||
"userInteraction": parts.at(4).last(),
|
||||
"scope": parts.at(5).last(),
|
||||
"confidentiality": parts.at(6).last(),
|
||||
"integrity": parts.at(7).last(),
|
||||
"availability": parts.at(8).last()
|
||||
)
|
||||
}
|
||||
|
||||
#let appendix() = {
|
||||
[
|
||||
== Common Vulnerability Scoring System (CVSS)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#import "cvss.typ" as cvssAddon
|
||||
|
||||
#let findingsList = state("findingsList", ())
|
||||
|
||||
#let create(
|
||||
name: str,
|
||||
cvss: str,
|
||||
description: content,
|
||||
finding: content,
|
||||
evaluation: content,
|
||||
recommendation: content
|
||||
) = {
|
||||
findingsList.update(f => {
|
||||
f.push(
|
||||
[
|
||||
== #name
|
||||
|
||||
#cvssAddon.createTableFromString(input: cvss)
|
||||
|
||||
=== Description
|
||||
|
||||
#description
|
||||
|
||||
=== Finding
|
||||
|
||||
#finding
|
||||
|
||||
=== Evaluation
|
||||
|
||||
#evaluation
|
||||
|
||||
=== Recommendation
|
||||
|
||||
#recommendation
|
||||
|
||||
#pagebreak(weak: true)
|
||||
]
|
||||
)
|
||||
f
|
||||
})
|
||||
}
|
||||
|
||||
#let print(sort: str) = {
|
||||
context(
|
||||
for f in findingsList.final() {
|
||||
f
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user