Accept CVSS strings in finding creation

This commit is contained in:
2026-08-24 15:51:44 +02:00
parent a74aa0fb8a
commit febe95f7a5
5 changed files with 123 additions and 51 deletions
+1
View File
@@ -1,4 +1,5 @@
#import "cia.typ"
#import "cvss.typ"
#import "finding.typ"
#import "mitre.typ"
#import "tlp.typ"
+40 -7
View File
@@ -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)
@@ -57,6 +55,17 @@
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) } }
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 {
@@ -65,12 +74,11 @@
status = "MEDIUM"
} else if baseScore >= 0.1 {
status = "LOW"
} else {
status = "NONE"
}
} else {
} 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)
+50
View File
@@ -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
}
)
}
+14 -27
View File
@@ -1,30 +1,17 @@
#import "addons/cve.typ"
#import "addons/cvss.typ"
#import "addons/finding.typ"
#import "addons/mitre.typ"
= Findings
////////////////////////////////////////////////////////////////////////////////////////////////
== Administration Interfaces reachable
#cvss.createTable(
attackVector: "N",
attackComplexity: "L",
privilegesRequired: "N",
userInteraction: "N",
scope: "U",
confidentiality: "N",
integrity: "N",
availability: "N",
)
=== Description
Administrative web applications and interfaces enable the management of organizational resources, processes, and data. These applications are typically used by administrative staff and other authorized persons to perform a variety of tasks. For example, administrators can use technical administration interfaces to read runtime data from servers and ensure smooth operation.
=== Finding
#finding.create(
name: "Administration Interfaces reachable",
cvss: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
description: [
Administrative web applications and interfaces enable the management of organizational resources, processes, and data. These applications are typically used by administrative staff and other authorized persons to perform a variety of tasks. For example, administrators can use technical administration interfaces to read runtime data from servers.
],
finding: [
When searching for administration interfaces, the applications `Uptime Kuma` at `https://status.ellingson-mineral.co` and `Nginx Proxy Manager` at `https://nginx.ellingson-mineral.co` were found. The URLs of the administration interfaces were found via TLS Transparency Logs#footnote[https://letsencrypt.org/docs/ct-logs/].
Both applications have a login screen and cannot be used by unauthorized visitors. Since administration accounts are set up during the initial configuration of the applications, it was not possible to log in using default credentials. A brute force attack was not performed to check for common passwords.
@@ -32,13 +19,13 @@ Both applications have a login screen and cannot be used by unauthorized visitor
The version of `Uptime Kuma` is not specified, while the version of `Nginx Proxy Manager` is 2.11.2. This instance of `Nginx Proxy Manager` is not vulnerable to the Command Injection vulnerability referenced to as #cve.reference("2024-39935").
This is referenced in MITRE's ATT&CK framework as #mitre.reference(name: "External Remote Services").
=== Evaluation
],
evaluation: [
The administration interfaces are not vulnerable and cannot be used without valid credentials. Because of this, the findings are considered purely informative.
=== Recommendation
],
recommendation: [
It should be checked whether these administration interfaces must be accessible via the Internet. Protecting the interfaces behind an additional authentication layer, such as HTTP Basic Auth, or only offering them within a protected network such as a VPN would minimize the attack surface and prevent the possible exploitation of security vulnerabilities in the administration interfaces that may be found in the future.
]
)
////////////////////////////////////////////////////////////////////////////////////////////////
+3 -2
View File
@@ -3,6 +3,8 @@
#import "aux/placeholder.typ": placeholder, rawPlaceholder, panicOnPlaceholder
#include "findings.typ"
// Project-specific variables
#panicOnPlaceholder.update(false)
#let place = placeholder("New York")
@@ -114,8 +116,7 @@
#pagebreak()
// ----- Findings -----
#include "findings.typ"
#pagebreak()
#addons.finding.print(sort: "cvss")
// ----- Appendix -----
= Appendix