Restructure code: split features into separate files
This commit is contained in:
parent
fcd5378f51
commit
de6dcf2b82
3
addons/addons.typ
Normal file
3
addons/addons.typ
Normal file
@ -0,0 +1,3 @@
|
||||
#import "cia.typ"
|
||||
#import "cvss.typ"
|
||||
#import "tlp.typ"
|
||||
32
addons/cia.typ
Normal file
32
addons/cia.typ
Normal file
@ -0,0 +1,32 @@
|
||||
#let isUsed = state("isUsed", false)
|
||||
|
||||
// Return the table cell formatted according to its content - for use with CIA values
|
||||
#let colorize(str) = {
|
||||
if str == "H" {
|
||||
table.cell(str, fill: red, align: center)
|
||||
} else if str == "L" {
|
||||
table.cell(str, fill: yellow, align: center)
|
||||
} else if str == "N" {
|
||||
table.cell(str, fill: lime, align: center)
|
||||
} else {
|
||||
panic("Unknown CIA state: " + str)
|
||||
}
|
||||
context(isUsed.update(true))
|
||||
}
|
||||
|
||||
#let appendix() = {
|
||||
[
|
||||
== CIA Triad
|
||||
|
||||
The CIA triad is a fundamental framework for information security that encompasses three important principles: confidentiality, integrity, and availability. In this report, each of the three principles is used to highlight specific aspects of the security implications for the area under observation. All three principles are explained in more detail below.
|
||||
|
||||
=== Confidentiality (C)
|
||||
The principle of confidentiality means that information is only accessible to authorized users or entities. It protects sensitive data from unauthorized access or disclosure through measures such as encryption, access controls, and data classification. By maintaining confidentiality, companies reduce the risks of data breaches and unauthorized disclosure, thereby preserving the privacy and trustworthiness of their information assets.
|
||||
|
||||
=== Integrity (I)
|
||||
Integrity preserves the accuracy, consistency, and reliability of data. It prevents unauthorized changes, deletions, or falsifications through techniques such as checksums, digital signatures, and access controls. By maintaining data integrity, companies ensure the reliability and credibility of their information assets, thereby minimizing the risk of fraud or manipulation.
|
||||
|
||||
=== Availability (A)
|
||||
Availability ensures the smooth and reliable provision of information. Potential interruptions to services, systems, or networks are mitigated through redundancy, failover mechanisms, and disaster recovery planning. By maintaining high availability, companies maintain operational continuity and user satisfaction by mitigating the impact of downtime or failures. At the same time, the unavailability of information can have serious consequences, such as lost revenue.
|
||||
]
|
||||
}
|
||||
176
addons/cvss.typ
Normal file
176
addons/cvss.typ
Normal file
@ -0,0 +1,176 @@
|
||||
#import "../aux/valval.typ": panicOnInvalid
|
||||
#import "cia.typ"
|
||||
|
||||
#let isUsed = state("isUsed", false)
|
||||
|
||||
// Statistics, used e.g. for the management summary
|
||||
#let riskCategoryStats = (
|
||||
Critical: state("riskCriticalStat", 0),
|
||||
High: state("riskHighStat", 0),
|
||||
Medium: state("riskMediumStat", 0),
|
||||
Low: state("riskLowStat", 0),
|
||||
None: state("riskInformativeStat", 0),
|
||||
Other: state("riskOtherStat", 0)
|
||||
)
|
||||
|
||||
// Function to update the statistics
|
||||
#let updateRiskCategoryStats(status) = {
|
||||
// Update status
|
||||
if status == "Critical" {
|
||||
context(riskCategoryStats.Critical.update(v => v + 1))
|
||||
} else if status == "High" {
|
||||
context(riskCategoryStats.High.update(v => v + 1))
|
||||
} else if status == "Medium" {
|
||||
context(riskCategoryStats.Medium.update(v => v + 1))
|
||||
} else if status == "Low" {
|
||||
context(riskCategoryStats.Low.update(v => v + 1))
|
||||
} else if status == "None" {
|
||||
context(riskCategoryStats.None.update(v => v + 1))
|
||||
} else if status == "Other" {
|
||||
context(riskCategoryStats.Other.update(v => v + 1))
|
||||
} else {
|
||||
panic("Unknown state: " + status)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the table cell formatted according to its content - for the CVSS result
|
||||
#let colorize(str) = {
|
||||
if str == "Critical" {
|
||||
table.cell(str, fill: red, align: center)
|
||||
} else if str == "High" {
|
||||
table.cell(str, fill: orange, align: center)
|
||||
} else if str == "Medium" {
|
||||
table.cell(str, fill: yellow, align: center)
|
||||
} else if str == "Low" {
|
||||
table.cell(str, fill: lime, align: center)
|
||||
} else if str == "None" {
|
||||
table.cell(str, fill: white, align: center)
|
||||
} else {
|
||||
panic("Unknown CVSS state: " + str)
|
||||
}
|
||||
}
|
||||
|
||||
// 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") = {
|
||||
// Check values
|
||||
panicOnInvalid(attackVector, ("N", "A", "L", "P"))
|
||||
panicOnInvalid(attackComplexity, ("L", "H"))
|
||||
panicOnInvalid(privilegesRequired, ("N", "L", "H"))
|
||||
panicOnInvalid(userInteraction, ("N", "R"))
|
||||
panicOnInvalid(scope, ("U", "C"))
|
||||
panicOnInvalid(confidentiality, ("H", "L", "N"))
|
||||
panicOnInvalid(integrity, ("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 = "?"
|
||||
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"
|
||||
}
|
||||
|
||||
block(
|
||||
[
|
||||
#block(
|
||||
spacing: 0.4em,
|
||||
table(
|
||||
columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr),
|
||||
align: center,
|
||||
stroke: 1pt,
|
||||
table.cell(colspan: 5)[*Exploitability Metrics*],
|
||||
table.cell(colspan: 3)[*Impact Metrics*],
|
||||
table.cell(rowspan: 2, align: bottom)[*#sym.sum*],
|
||||
[*AV*], [*AC*], [*PR*], [*UI*], [*S*], [*C*], [*I*], [*A*],
|
||||
attackVector, attackComplexity, privilegesRequired, userInteraction, scope, cia.colorize(confidentiality), cia.colorize(integrity), cia.colorize(availability), colorize(status),
|
||||
)
|
||||
)
|
||||
#align(right)[
|
||||
#text(
|
||||
size: 10pt,
|
||||
fill: gray,
|
||||
"CVSS:3.1/AV:" + attackVector +
|
||||
"/AC:" + attackComplexity +
|
||||
"/PR:" + privilegesRequired +
|
||||
"/UI:" + userInteraction +
|
||||
"/S:" + scope +
|
||||
"/C:" + confidentiality +
|
||||
"/I:" + integrity +
|
||||
"/A:" + availability
|
||||
)]
|
||||
]
|
||||
)
|
||||
|
||||
updateRiskCategoryStats(status)
|
||||
isUsed.update(true)
|
||||
}
|
||||
|
||||
#let appendix() = {
|
||||
[
|
||||
== Common Vulnerability Scoring System (CVSS)
|
||||
|
||||
The Common Vulnerability Scoring System (CVSS) provides a standardized, vendor- and platform-agnostic methodology for quantifying the technical severity of software, hardware, and firmware vulnerabilities. Its outputs deliver numerical scores that contextualize a vulnerability’s risk relative to others, facilitating consistent prioritization across diverse systems. CVSS is structured around three interdependent metric groups: *Base*, *Temporal*, and *Environmental*. The Base Score captures the inherent characteristics of a vulnerability (e.g., exploitability, impact), assigning it a severity rating under idealized conditions. Temporal Metrics dynamically adjust this base score based on time-sensitive factors like exploit availability or the existence of patches. Finally, Environmental Metrics tailor the severity assessment to an organization’s specific deployment, accounting for mitigations, criticality of affected assets, and other contextual factors unique to the environment.
|
||||
|
||||
The only metric group that can be calculated without deep knowledge of the environment and situation is the Base Score, as the Base Score reflects intrinsic vulnerability characteristics (e.g., exploit complexity, impact on confidentiality/integrity) and is designed to be vendor-neutral and environment-agnostic. It does not account for the specific client’s infrastructure, patch status, or operational context. Since a penetration test reports on a single target environment, the Base Score represents the objective severity of the flaw within the scope of the test (e.g., "this flaw could be exploited in this network").
|
||||
|
||||
The version of the Common Vulnerability Scoring System used in this report is 3.1#footnote("https://www.first.org/cvss/v3-1/user-guide").
|
||||
|
||||
=== Attack Vector (AV)
|
||||
|
||||
This metric quantifies how remotely an attacker can exploit a vulnerability, directly influencing the Base Score:
|
||||
|
||||
- *Network (N)*: Highest severity. Attack possible from anywhere on the internet (e.g., sending a malicious packet across routers).
|
||||
- *Adjacent (A)*: Moderate severity. Exploit limited to local networks (e.g., same subnet, Bluetooth/Wi-Fi, or secure VPN).
|
||||
- *Local (L)*: Lower severity. Requires local access (console/SSH) or user interaction (e.g., phishing a document).
|
||||
- *Physical (P)*: Lowest severity. Requires direct physical contact (e.g., evil-maid attacks, cold boot, DMA via USB).
|
||||
|
||||
=== Attack Complexity (AC)
|
||||
|
||||
This metric quantifies the technical difficulty of exploiting a vulnerability, independent of user interaction. It directly impacts the Base Score:
|
||||
|
||||
- *Low (L)*: Attack is repeatable and predictable with no special conditions. Example: Exploiting a buffer overflow in a service accessible via network.
|
||||
- *High (H)*: Exploit requires attacker preparation or external factors, reducing reliability. This may include:
|
||||
- Gathering target-specific knowledge (e.g., configuration settings, shared secrets).
|
||||
- Overcoming mitigations (e.g., race conditions, anti-exploit techniques).
|
||||
- Network manipulation (e.g., man-in-the-middle attacks).
|
||||
- Example: Exploiting a flaw requiring a victim’s browser to accept a malicious file.
|
||||
|
||||
=== Privileges Required (PR)
|
||||
|
||||
This metric measures the attacker’s initial access level needed to exploit a vulnerability, directly impacting the Base Score:
|
||||
|
||||
- *None (N)*: Highest severity. Exploitable by unauthorized attackers with no prior access (e.g., unauthenticated web attack).
|
||||
- *Low (L)*: Moderate severity. Requires basic user privileges (e.g., standard account access to non-sensitive resources).
|
||||
- *High (H)*: Lowest severity. Needs administrative privileges to access critical system settings/files (e.g., root/superuser access).
|
||||
|
||||
=== User Interaction (UI)
|
||||
|
||||
This metric assesses whether a vulnerability requires human involvement (beyond the attacker) to be exploited, directly influencing the Base Score:
|
||||
|
||||
- *None (N)*: Highest severity. Exploitable without user action (e.g., automated network attack).
|
||||
- *Required (R)*: Lower severity. Requires user interaction (e.g., clicking a malicious link or installing software).
|
||||
|
||||
=== Scope (S)
|
||||
|
||||
This metric determines if a vulnerability breaches security boundaries, allowing impact on components outside its original security scope (e.g., an app exploiting a database). Directly impacts Base Score severity:
|
||||
|
||||
- *Unchanged (U)*: Lowest severity. Vulnerability only affects resources within the same security authority (e.g., a web app affecting its own files).
|
||||
- *Changed (C)*: Highest severity. Vulnerability crosses security boundaries, impacting components under different authorities (e.g., a compromised web server accessing a database).
|
||||
]
|
||||
}
|
||||
101
addons/tlp.typ
Normal file
101
addons/tlp.typ
Normal file
@ -0,0 +1,101 @@
|
||||
#import "../aux/valval.typ": panicOnInvalid
|
||||
|
||||
#let isUsed = state("isUsed", false)
|
||||
|
||||
// maps TLP status, color, title, and subtitle
|
||||
#let tlpLightMap = (
|
||||
"RED": (color: rgb("#FF2B2B"), title: "TLP:RED", content: "recipient only"),
|
||||
"AMBER": (color: rgb("#FFC000"), title: "TLP:AMBER", content: "organisation\nand its clients"),
|
||||
"AMBER+STRICT": (color: rgb("#FFC000"), title: "TLP:AMBER+STRICT", content: "organisation only"),
|
||||
"GREEN": (color: rgb("#33FF00"), title: "TLP:GREEN", content: "within community"),
|
||||
"CLEAR": (color: rgb("#FFFFFF"), title: "TLP:CLEAR", content: "public")
|
||||
)
|
||||
|
||||
// label draws an inline TLP label with appropiate color and black background
|
||||
// light may be one of "RED", "AMBER", "AMBER+STRICT", "GREEN", or "CLEAR"
|
||||
#let label(light) = {
|
||||
light = upper(light)
|
||||
// check argument
|
||||
panicOnInvalid(light, tlpLightMap.keys())
|
||||
|
||||
highlight(
|
||||
fill: black,
|
||||
text(
|
||||
weight: "semibold",
|
||||
fill: tlpLightMap.at(light).color,
|
||||
tlpLightMap.at(light).title
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// mark draws a Traffic Light Protocol mark, used on the cover page
|
||||
// light may be one of "RED", "AMBER", "AMBER+STRICT", "GREEN", or "CLEAR"
|
||||
#let mark(light) = {
|
||||
light = upper(light)
|
||||
// check argument
|
||||
panicOnInvalid(light, tlpLightMap.keys())
|
||||
|
||||
rect(
|
||||
height: 100%,
|
||||
width: 100%,
|
||||
stroke: (paint: tlpLightMap.at(light).color.darken(10%), thickness: 2pt, dash: "solid"),
|
||||
align(center + horizon,
|
||||
grid(
|
||||
columns: (80%),
|
||||
rows: (18pt, auto),
|
||||
gutter: 8pt,
|
||||
[
|
||||
#set text(size: if light == "AMBER+STRICT" { 13pt } else { 18pt })
|
||||
#label(light)
|
||||
],
|
||||
text(
|
||||
size: 12pt,
|
||||
fill: if light == "CLEAR" { black } else { tlpLightMap.at(light).color.darken(10%) },
|
||||
tlpLightMap.at(light).content
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
isUsed.update(true)
|
||||
}
|
||||
|
||||
// appendix explains the different values (RED, AMBER, ...) of the Traffic Light protocol, to be included in the appendix of a report
|
||||
#let appendix() = {
|
||||
[
|
||||
== Traffic Light Protocol
|
||||
|
||||
The Traffic Light Protocol (TLP) is a standardised system designed to accelerate collaborative response to security incidents by clarifying sharing boundaries for sensitive information. Information flows from an information source (e.g., a pentesting team) to recipients (e.g., clients or partners), with TLP labels governing permissible dissemination. The TLP labels standardised by FIRST#footnote("https://www.first.org/tlp/") are *RED*, *AMBER* and *AMBER+STRICT*, *GREEN*, and *CLEAR*.
|
||||
|
||||
=== TLP Label #label("RED")
|
||||
|
||||
TLP:RED means _for the eyes of individual recipients only._
|
||||
The purpose is to protect highly sensitive information where unauthorized disclosure risks privacy, reputation, or operations.
|
||||
Sharing is strictly prohibited outside the recipient - even within the recipients organisation.
|
||||
|
||||
For example: an unauthenticated RCE vulnerability in a payment gateway API is classified TLP:RED. Sharing this internally (e.g., within a client’s incident response team or development team) is allowed; sharing anywhere else violates the classification.
|
||||
|
||||
=== TLP Label #label("AMBER") and #label("AMBER+STRICT")
|
||||
|
||||
TLP:AMBER limits sharing to the recipient’s organization and its clients on a need-to-know basis. TLP:AMBER+STRICT restricts this further to the organisation, excluding clients.
|
||||
The purpose is to allow collaboration with controlled exposure. Recipients may share with their own organization (and clients or partners if not using AMBER+STRICT), but must not share publicly or with non-authorized third parties.
|
||||
|
||||
For example: a session fixation flaw in a client’s SaaS platform is TLP:AMBER. Their security team may share with their provider for patching, but cannot share it with unaffiliated parties.
|
||||
|
||||
=== TLP Label #label("GREEN")
|
||||
|
||||
TLP:GREEN means _Restricted to the recipient’s community; not public._
|
||||
The purpose is to share awareness within a trusted community without public risk.
|
||||
This allows recipients to share it with peers and partners within their defined community.
|
||||
|
||||
For example: a misconfigured AWS S3 bucket exposing anonymized test data is TLP:GREEN. The testing team may share this within the cybersecurity community (e.g., via ISACs or industry forums) to improve collective awareness, but cannot publish it on the internet.
|
||||
|
||||
=== TLP Label #label("CLEAR")
|
||||
|
||||
TLP:CLEAR means _No restrictions on disclosure._
|
||||
The purpose is to publicly share low-risk findings with no foreseeable misuse or patched vulnerabilities for transparency.
|
||||
While sharing is unrestricted, the information may still be subject e.g. to copyright rules like attribution.
|
||||
|
||||
For example: a server running an outdated but non-vulnerable SSL/TLS library qualifies as TLP:CLEAR. The testing team may publish this in a blog post to demonstrate tooling for automatic detection and general security practice.
|
||||
]
|
||||
}
|
||||
16
aux/placeholder.typ
Normal file
16
aux/placeholder.typ
Normal file
@ -0,0 +1,16 @@
|
||||
#let panicOnPlaceholder = state("panicOnPlaceholder", true)
|
||||
|
||||
// Return the value with a colorful background so it is visibly a placeholder.
|
||||
// Has the ability to panic if panicOnPlaceholder is set to true.
|
||||
#let placeholder(value) = {
|
||||
highlight(
|
||||
fill: rgb(0xff, 0xa2, 0x9c, 0xff),
|
||||
value
|
||||
)
|
||||
|
||||
context(
|
||||
if panicOnPlaceholder.get() {
|
||||
panic("Found placeholder and panicOnPlaceholder is set.")
|
||||
}
|
||||
)
|
||||
}
|
||||
8
aux/valval.typ
Normal file
8
aux/valval.typ
Normal file
@ -0,0 +1,8 @@
|
||||
// valval - the value validator (tm)
|
||||
|
||||
// panics if value is not in the allowed array
|
||||
#let panicOnInvalid(value, allowed) = {
|
||||
if allowed.find(x => x == value) == none {
|
||||
panic("Value " + value + " is not in " + allowed.join(", "))
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
#import "helper.typ"
|
||||
#import "addons/cvss.typ" as cvss
|
||||
|
||||
= Findings
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
== Administration Interfaces reachable
|
||||
|
||||
#helper.cvsstable(confidentiality: "N", integrity: "N", availability: "N")
|
||||
#cvss.createTable(confidentiality: "N", integrity: "N", availability: "N")
|
||||
|
||||
=== Description
|
||||
|
||||
|
||||
246
helper.typ
246
helper.typ
@ -1,246 +0,0 @@
|
||||
#let panicOnPlaceholder = state("panicOnPlaceholder", true)
|
||||
#let hasCIATable = state("hasCIATable", false)
|
||||
#let hasCVSSTable = state("hasCVSSTable", false)
|
||||
#let usesTLP = state("usesTLP", false)
|
||||
|
||||
// Function panics if value is not in the allowed array
|
||||
#let panicOnInvalid(value, allowed) = {
|
||||
if allowed.find(x => x == value) == none {
|
||||
panic("Value " + value + " is not in " + allowed.join(", "))
|
||||
}
|
||||
}
|
||||
|
||||
// Statistics for the Finding risk categories
|
||||
#let riskCategoryStats = (
|
||||
Critical: state("riskCriticalStat", 0),
|
||||
High: state("riskHighStat", 0),
|
||||
Medium: state("riskMediumStat", 0),
|
||||
Low: state("riskLowStat", 0),
|
||||
None: state("riskInformativeStat", 0),
|
||||
Other: state("riskOtherStat", 0)
|
||||
)
|
||||
// Function to update the statistics
|
||||
#let updateRiskCategoryStats(status) = {
|
||||
// Update status
|
||||
if status == "Critical" {
|
||||
context(riskCategoryStats.Critical.update(v => v + 1))
|
||||
} else if status == "High" {
|
||||
context(riskCategoryStats.High.update(v => v + 1))
|
||||
} else if status == "Medium" {
|
||||
context(riskCategoryStats.Medium.update(v => v + 1))
|
||||
} else if status == "Low" {
|
||||
context(riskCategoryStats.Low.update(v => v + 1))
|
||||
} else if status == "None" {
|
||||
context(riskCategoryStats.None.update(v => v + 1))
|
||||
} else if status == "Other" {
|
||||
context(riskCategoryStats.Other.update(v => v + 1))
|
||||
} else {
|
||||
panic("Unknown state: " + status)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the table cell formatted according to its content - for use with CIA values
|
||||
#let ciacolor(str) = {
|
||||
if str == "H" {
|
||||
table.cell(str, fill: red, align: center)
|
||||
} else if str == "L" {
|
||||
table.cell(str, fill: yellow, align: center)
|
||||
} else if str == "N" {
|
||||
table.cell(str, fill: lime, align: center)
|
||||
} else {
|
||||
panic("Unknown CIA state: " + str)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the table cell formatted according to its content - for the CVSS result
|
||||
#let cvsscolor(str) = {
|
||||
if str == "Critical" {
|
||||
table.cell(str, fill: red, align: center)
|
||||
} else if str == "High" {
|
||||
table.cell(str, fill: orange, align: center)
|
||||
} else if str == "Medium" {
|
||||
table.cell(str, fill: yellow, align: center)
|
||||
} else if str == "Low" {
|
||||
table.cell(str, fill: lime, align: center)
|
||||
} else if str == "None" {
|
||||
table.cell(str, fill: white, align: center)
|
||||
} else {
|
||||
panic("Unknown CVSS state: " + str)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a small CIA table to be included for every finding
|
||||
#let cvsstable(attackVector: "N", attackComplexity: "L", privilegesRequired: "N", userInteraction: "N", scope: "U", confidentiality: "H", integrity: "H", availability: "H") = {
|
||||
// Check values
|
||||
panicOnInvalid(attackVector, ("N", "A", "L", "P"))
|
||||
panicOnInvalid(attackComplexity, ("L", "H"))
|
||||
panicOnInvalid(privilegesRequired, ("N", "L", "H"))
|
||||
panicOnInvalid(userInteraction, ("N", "R"))
|
||||
panicOnInvalid(scope, ("U", "C"))
|
||||
panicOnInvalid(confidentiality, ("H", "L", "N"))
|
||||
panicOnInvalid(integrity, ("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 = "?"
|
||||
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"
|
||||
}
|
||||
|
||||
block(
|
||||
[
|
||||
#block(
|
||||
spacing: 0.4em,
|
||||
table(
|
||||
columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr),
|
||||
align: center,
|
||||
stroke: 1pt,
|
||||
table.cell(colspan: 5)[*Exploitability Metrics*],
|
||||
table.cell(colspan: 3)[*Impact Metrics*],
|
||||
table.cell(rowspan: 2, align: bottom)[*#sym.sum*],
|
||||
[*AV*], [*AC*], [*PR*], [*UI*], [*S*], [*C*], [*I*], [*A*],
|
||||
attackVector, attackComplexity, privilegesRequired, userInteraction, scope, ciacolor(confidentiality), ciacolor(integrity), ciacolor(availability), cvsscolor(status),
|
||||
)
|
||||
)
|
||||
#align(right)[
|
||||
#text(
|
||||
size: 10pt,
|
||||
fill: gray,
|
||||
"CVSS:3.1/AV:" + attackVector +
|
||||
"/AC:" + attackComplexity +
|
||||
"/PR:" + privilegesRequired +
|
||||
"/UI:" + userInteraction +
|
||||
"/S:" + scope +
|
||||
"/C:" + confidentiality +
|
||||
"/I:" + integrity +
|
||||
"/A:" + availability
|
||||
)]
|
||||
]
|
||||
)
|
||||
|
||||
updateRiskCategoryStats(status)
|
||||
hasCVSSTable.update(true)
|
||||
}
|
||||
|
||||
// Return the value with a colorful background so it is visibly a placeholder.
|
||||
// Has the ability to panic if panicOnPlaceholder is set to true.
|
||||
#let placeholder(value) = {
|
||||
highlight(
|
||||
fill: rgb(0xff, 0xa2, 0x9c, 0xff),
|
||||
value
|
||||
)
|
||||
|
||||
context(
|
||||
if panicOnPlaceholder.get() {
|
||||
panic("Found placeholder and panicOnPlaceholder is set.")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// confidentialMark draws a "CONFIDENTIAL" stamp, used on the cover page
|
||||
#let confidentialMark() = {
|
||||
rect(
|
||||
height: 100%,
|
||||
width: 100%,
|
||||
stroke: (paint: red, thickness: 2pt, dash: "solid"),
|
||||
align(center + horizon,
|
||||
text(
|
||||
size: 18pt,
|
||||
weight: "semibold",
|
||||
fill: red,
|
||||
"CONFIDENTIAL"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#let draftMark() = {
|
||||
rect(
|
||||
height: 100%,
|
||||
width: 100%,
|
||||
stroke: (paint: blue, thickness: 2pt, dash: "solid"),
|
||||
align(center + horizon,
|
||||
text(
|
||||
size: 18pt,
|
||||
weight: "semibold",
|
||||
fill: blue,
|
||||
"DRAFT"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#let tlpLightMap = (
|
||||
"RED": (color: rgb("#FF2B2B"), title: "TLP:RED", content: "recipient only"),
|
||||
"AMBER": (color: rgb("#FFC000"), title: "TLP:AMBER", content: "organisation\nand its clients"),
|
||||
"AMBER+STRICT": (color: rgb("#FFC000"), title: "TLP:AMBER+STRICT", content: "organisation only"),
|
||||
"GREEN": (color: rgb("#33FF00"), title: "TLP:GREEN", content: "within community"),
|
||||
"CLEAR": (color: rgb("#FFFFFF"), title: "TLP:CLEAR", content: "public")
|
||||
)
|
||||
|
||||
// tlpLabel draws an inline TLP label with appropiate color and black backgropund
|
||||
// light may be one of "RED", "AMBER", "AMBER+STRICT", "GREEN", or "CLEAR"
|
||||
#let tlpLabel(light) = {
|
||||
light = upper(light)
|
||||
// check argument
|
||||
panicOnInvalid(light, tlpLightMap.keys())
|
||||
|
||||
highlight(
|
||||
fill: black,
|
||||
text(
|
||||
weight: "semibold",
|
||||
fill: tlpLightMap.at(light).color,
|
||||
tlpLightMap.at(light).title
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// tlpMark draws a Traffic Light Protocol mark, used on the cover page
|
||||
// light may be one of "RED", "AMBER", "AMBER+STRICT", "GREEN", or "CLEAR"
|
||||
#let tlpMark(light) = {
|
||||
light = upper(light)
|
||||
// check argument
|
||||
panicOnInvalid(light, tlpLightMap.keys())
|
||||
|
||||
rect(
|
||||
height: 100%,
|
||||
width: 100%,
|
||||
stroke: (paint: tlpLightMap.at(light).color.darken(10%), thickness: 2pt, dash: "solid"),
|
||||
align(center + horizon,
|
||||
grid(
|
||||
columns: (80%),
|
||||
rows: (18pt, auto),
|
||||
gutter: 8pt,
|
||||
[
|
||||
#set text(size: if light == "AMBER+STRICT" { 13pt } else { 18pt })
|
||||
#tlpLabel(light)
|
||||
],
|
||||
text(
|
||||
size: 12pt,
|
||||
fill: if light == "CLEAR" { black } else { tlpLightMap.at(light).color.darken(10%) },
|
||||
tlpLightMap.at(light).content
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
usesTLP.update(true)
|
||||
}
|
||||
96
main.typ
96
main.typ
@ -1,14 +1,17 @@
|
||||
#import "pages.typ"
|
||||
#import "helper.typ"
|
||||
#import "addons/addons.typ"
|
||||
#import "pages/pages.typ"
|
||||
|
||||
#import "aux/placeholder.typ": placeholder, panicOnPlaceholder
|
||||
|
||||
// Project-specific variables
|
||||
#helper.panicOnPlaceholder.update(false)
|
||||
#let place = helper.placeholder("Düsseldorf")
|
||||
#let author = helper.placeholder("Martin \"maride\" Dessauer")
|
||||
#let targetFull = helper.placeholder("FooBar Dummy Lab")
|
||||
#let targetInSentence = helper.placeholder("the Dummy Lab")
|
||||
// ---
|
||||
#panicOnPlaceholder.update(false)
|
||||
#let place = placeholder("Düsseldorf")
|
||||
#let author = placeholder("Martin \"maride\" Dessauer")
|
||||
#let targetFull = placeholder("FooBar Dummy Lab")
|
||||
#let targetInSentence = placeholder("the Dummy Lab")
|
||||
#let reportType = placeholder("Penetration Test Report")
|
||||
|
||||
// Styling setup
|
||||
#set text(font: "Helvetica Neue")
|
||||
#show heading: it => {
|
||||
v(1em)
|
||||
@ -16,50 +19,46 @@
|
||||
}
|
||||
#set heading(numbering: "1.1")
|
||||
#set par(justify: true)
|
||||
|
||||
// ----- Cover & Legal disclaimer(s) -----
|
||||
#pages.cover(targetFull, place, author,
|
||||
confidential: false, // set to true for a "CONFIDENTIAL" mark on the cover
|
||||
tlp: "red", // set to one of "RED", "AMBER+STRICT", "AMBER", "GREEN", "CLEAR", or none. See https://www.first.org/tlp/
|
||||
draft: true // set to true for a "DRAFT" mark on the cover
|
||||
)
|
||||
#pages.legal(author)
|
||||
|
||||
#set page(
|
||||
paper: "a4",
|
||||
background: none,
|
||||
margin: auto,
|
||||
numbering: "1",
|
||||
footer: text(size: 12pt, weight: "extralight")[
|
||||
#text(fill: silver, [Penetration Test Report #targetFull])
|
||||
#h(1fr)
|
||||
#context(
|
||||
text(fill: silver, counter(page).display("1 of 1", both: true))
|
||||
)
|
||||
]
|
||||
footer: context(if here().page() > 2 {
|
||||
text(size: 12pt, weight: "extralight")[
|
||||
#text(fill: silver, [#reportType #targetFull])
|
||||
#h(1fr)
|
||||
#context(
|
||||
text(fill: silver, counter(page).display("1 of 1", both: true))
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// ----- Cover & Legal disclaimer(s) -----
|
||||
#pages.cover.render(targetFull, place, author, reportType,
|
||||
confidential: false, // set to true for a "CONFIDENTIAL" mark on the cover
|
||||
tlp: "amber+strict", // set to one of "RED", "AMBER+STRICT", "AMBER", "GREEN", "CLEAR", or none. See https://www.first.org/tlp/
|
||||
draft: true // set to true for a "DRAFT" mark on the cover
|
||||
)
|
||||
#pages.legal.render(author)
|
||||
|
||||
// ----- Table of contents -----
|
||||
#pages.toc()
|
||||
#pages.toc.render()
|
||||
#pagebreak()
|
||||
|
||||
// ----- Management Summary -----
|
||||
#pages.managementSummary(
|
||||
#pages.mgmtsum.render(
|
||||
target: targetFull,
|
||||
targetInSentence: targetInSentence,
|
||||
testFocus: helper.placeholder("external attackers in real-world scenarios"),
|
||||
testObject: helper.placeholder(lorem(30)),
|
||||
testScenario: helper.placeholder("black box test"),
|
||||
recommendation: helper.placeholder([Based on the results of this penetration test, #targetInSentence may be exposed to a production environment.])
|
||||
)
|
||||
#pagebreak()
|
||||
|
||||
// ----- Test Scope & Setup -----
|
||||
#pages.scopeAndSetup(
|
||||
target: targetFull,
|
||||
start: helper.placeholder("01.01.1970"),
|
||||
end: helper.placeholder("31.12.1970"),
|
||||
setup: helper.placeholder([Connection to #targetFull was made through a dedicated VPN connection. The inner IP address of the test device was 10.0.0.42.]),
|
||||
testFocus: placeholder("external attackers in real-world scenarios"),
|
||||
testObject: placeholder(lorem(30)),
|
||||
testScenario: placeholder("black box test"),
|
||||
recommendation: placeholder([Based on the results of this penetration test, #targetInSentence may be exposed to a production environment.]),
|
||||
start: placeholder("01.01.1970"),
|
||||
end: placeholder("31.12.1970"),
|
||||
setup: placeholder([Connection to #targetFull was made through a dedicated VPN connection. The inner IP address of the test device was 10.0.0.42.]),
|
||||
nodes: "
|
||||
subgraph stage1 {
|
||||
label=\"Connection\";
|
||||
@ -87,12 +86,11 @@
|
||||
}
|
||||
",
|
||||
scope: (
|
||||
( type: "Address", content: helper.placeholder("10.23.42.1"), inScope: true ),
|
||||
( type: "Address", content: helper.placeholder("10.23.42.1"), inScope: true ),
|
||||
( type: "Address", content: helper.placeholder("2001:db8::2342"), inScope: true ),
|
||||
( type: "Domain", content: helper.placeholder("*.maride.inv"), inScope: true ),
|
||||
( type: "URL", content: helper.placeholder("secret.maride.inv/flag.txt"), inScope: false),
|
||||
( type: "URL", content: helper.placeholder("important.maride.inv/rickroll"), inScope: false)
|
||||
( type: "Address", content: placeholder("10.23.42.1"), inScope: true ),
|
||||
( type: "Address", content: placeholder("2001:db8::2342"), inScope: true ),
|
||||
( type: "Domain", content: placeholder("*.maride.inv"), inScope: true ),
|
||||
( type: "URL", content: placeholder("secret.maride.inv/flag.txt"), inScope: false),
|
||||
( type: "URL", content: placeholder("important.maride.inv/rickroll"), inScope: false)
|
||||
)
|
||||
)
|
||||
#pagebreak()
|
||||
@ -107,10 +105,10 @@
|
||||
#context(
|
||||
[
|
||||
#((
|
||||
pages.boxAppendix(),
|
||||
pages.ciaAppendix(),
|
||||
if helper.hasCVSSTable.get() { pages.cvssAppendix() },
|
||||
if helper.usesTLP.get() { pages.tlpAppendix() }
|
||||
pages.boxes.render(),
|
||||
if addons.cia.isUsed.get() { addons.cia.appendix() },
|
||||
if addons.cvss.isUsed.get() { addons.cvss.appendix() },
|
||||
if addons.tlp.isUsed.get() { addons.tlp.appendix() }
|
||||
).join(pagebreak()))
|
||||
]
|
||||
)
|
||||
|
||||
332
pages.typ
332
pages.typ
@ -1,332 +0,0 @@
|
||||
#import "@preview/diagraph:0.3.6": render
|
||||
|
||||
#import "helper.typ"
|
||||
|
||||
#let cover(title, place, author, confidential: false, tlp: none, draft: false) = {
|
||||
// Define page
|
||||
set page(
|
||||
paper: "a4",
|
||||
background: [
|
||||
#image("title.png")
|
||||
],
|
||||
margin: (
|
||||
top: 50%
|
||||
)
|
||||
)
|
||||
// Reset counter
|
||||
counter(page).update(n => n - 1)
|
||||
|
||||
text(size: 32pt, [
|
||||
#text("Penetration Test Report")\
|
||||
#text(title, weight: "black")
|
||||
])
|
||||
|
||||
v(0.25pt)
|
||||
|
||||
text(size: 16pt, [
|
||||
#place, #datetime.today().display("[day].[month].[year]") • #author
|
||||
])
|
||||
|
||||
v(1fr)
|
||||
|
||||
grid(
|
||||
columns: (1fr, 1fr, 1fr),
|
||||
gutter: 5pt,
|
||||
rows: (75pt),
|
||||
// "Confidential" marking
|
||||
if confidential {
|
||||
helper.confidentialMark()
|
||||
},
|
||||
// "Draft" marking
|
||||
if draft {
|
||||
helper.draftMark()
|
||||
},
|
||||
// TLP marking, see https://www.first.org/tlp/
|
||||
if tlp != none {
|
||||
helper.tlpMark(tlp)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#let legal(author) = {
|
||||
// Define page
|
||||
set page(
|
||||
paper: "a4",
|
||||
)
|
||||
// Reset counter
|
||||
counter(page).update(n => n - 1)
|
||||
|
||||
block(height: 1fr)
|
||||
|
||||
show heading: it => {
|
||||
v(1em)
|
||||
par(text(it.body, fill: gray))
|
||||
}
|
||||
text(fill: silver,
|
||||
[
|
||||
#heading(level: 2, outlined:false, "Report Version & Authorship")
|
||||
|
||||
#table(
|
||||
columns: (15%, 15%, 40%, 30%,),
|
||||
align: center,
|
||||
stroke: gray,
|
||||
[*Version*], [*Date*], [*Author*], [*Changes & Comment*],
|
||||
[#helper.placeholder("Draft")], [#helper.placeholder("01.01.1970")], [#helper.placeholder(author)], [#helper.placeholder("Some")]
|
||||
)
|
||||
|
||||
#heading(level: 2, outlined:false, "Classification")
|
||||
This report and all associated materials are strictly confidential and may be communicated and/or distributed by the Client only with written approval of the author. All report data, including findings and recommendations, may be stored encrypted in the author's secure archive post-engagement, in compliance with applicable data protection regulations.
|
||||
|
||||
#heading(level: 2, outlined:false, "Legal Disclaimer")
|
||||
This report constitutes the professional findings of a penetration test conducted under the scope and terms agreed upon by the client. All results reflect the state of security vulnerabilities as assessed during the engagement using standardized methodologies and tools. The findings presented are not guarantees of security or compliance; they represent identified weaknesses which may require remediation as by the professional impression of the author.
|
||||
|
||||
The client is responsible for interpreting these findings within their operational context and for implementing appropriate security measures. No liability is taken for actions which are based on this report. This assessment does not constitute security certification, and its results are valid only for the scope and timeframe of the engagement. Neither the contents of this document nor the expressions of the author in any form are legal advice.
|
||||
])
|
||||
}
|
||||
|
||||
#let toc() = {
|
||||
outline(
|
||||
title: "Table of contents",
|
||||
indent: 10pt,
|
||||
depth: 2
|
||||
)
|
||||
}
|
||||
|
||||
#let managementSummary(target: str, targetInSentence: str, testFocus: str, testObject: str, testScenario: str, recommendation: str) = {
|
||||
[
|
||||
= Management Summary
|
||||
|
||||
== Motivation
|
||||
|
||||
This penetration test was conducted to proactively identify, assess, and validate the severity of security vulnerabilities within the defined scope of #targetInSentence, specifically those exploitable by #testFocus. Adversarial techniques to bypass controls, gain unauthorized access, and compromise critical assets were performed for this test scenario to determine the actual impact on confidentiality, integrity, and availability.
|
||||
|
||||
== Test Object
|
||||
|
||||
#testObject
|
||||
|
||||
== Test Methodology
|
||||
|
||||
The aim of the test was to uncover vulnerabilities and weaknesses of all kinds. These were carried out in accordance with the OWASP Web Security Testing Guide, version 4#footnote("https://owasp.org/www-project-web-security-testing-guide/stable/4-Web_Application_Security_Testing"), and, where applicable, the MITRE ATT&CK Framework#footnote("https://attack.mitre.org"). Recommendations for system hardening were made based on the current version of the CIS Benchmarks#footnote("https://www.cisecurity.org/") and the appropriate variant in each case.
|
||||
|
||||
The penetration test was performed as a #testScenario.
|
||||
|
||||
== Findings
|
||||
|
||||
The penetration test revealed #context([
|
||||
#let num = helper.riskCategoryStats.values().map(v => v.final()).sum()
|
||||
#if num == 1 {
|
||||
[ #num finding ]
|
||||
} else {
|
||||
[ #num findings ]
|
||||
}
|
||||
]) which can be categorized by risk:
|
||||
|
||||
#table(
|
||||
columns: (16.66%, 16.66%, 16.66%, 16.66%, 16.66%, 16.66%),
|
||||
align: center,
|
||||
[Critical], [High], [Medium], [Low], [None], [Other],
|
||||
table.cell(context(helper.riskCategoryStats.Critical.final()), fill: red, align: center),
|
||||
table.cell(context(helper.riskCategoryStats.High.final()), fill: orange, align: center),
|
||||
table.cell(context(helper.riskCategoryStats.Medium.final()), fill: yellow, align: center),
|
||||
table.cell(context(helper.riskCategoryStats.Low.final()), fill: lime, align: center),
|
||||
table.cell(context(helper.riskCategoryStats.None.final()), fill: white, align: center),
|
||||
table.cell(context(helper.riskCategoryStats.Other.final()), fill: gray, align: center),
|
||||
)
|
||||
|
||||
== Recommendations & Next Steps
|
||||
|
||||
#recommendation
|
||||
]
|
||||
}
|
||||
|
||||
#let scopeAndSetup(target: str, start: str, end: str, setup: str, nodes: str, scope: array) = {
|
||||
[
|
||||
== Test Scope and Setup
|
||||
|
||||
#if start != "" {
|
||||
if end != "" {
|
||||
[ The test was conducted from #start to #end. ]
|
||||
} else {
|
||||
[ The test was conducted on #start. ]
|
||||
}
|
||||
}
|
||||
|
||||
#setup
|
||||
|
||||
The following scope was set for the penetration test:
|
||||
|
||||
#table(
|
||||
columns: (25%, 50%, 25%),
|
||||
align: center,
|
||||
inset: 10pt,
|
||||
table.cell(fill: color.linear-rgb(4.5%, 14.5%, 14.5%, 100))[*Type*],
|
||||
table.cell(fill: color.linear-rgb(4.5%, 14.5%, 14.5%, 100))[*Value*],
|
||||
table.cell(fill: color.linear-rgb(4.5%, 14.5%, 14.5%, 100))[*State*],
|
||||
..for (i, value) in scope.enumerate() {
|
||||
(
|
||||
table.cell(value.type),
|
||||
table.cell(value.content),
|
||||
if value.inScope {
|
||||
table.cell("In scope", fill: lime, align: center)
|
||||
} else {
|
||||
table.cell("Out of scope", fill: gray, align: center)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
#if nodes != "" {
|
||||
[ From the perspective of the machine used for the penetration test, the network layout was seen as shown in the graph below - simplified by leaving out hops that are not relevant for the penetration test, like third-party network operators and ISPs. ]
|
||||
|
||||
figure(
|
||||
render("
|
||||
digraph G {
|
||||
rankdir=LR;
|
||||
node [shape=rectangle];
|
||||
|
||||
" + nodes + "
|
||||
}
|
||||
"),
|
||||
caption: [
|
||||
Schematic graph showing the test objective network
|
||||
]
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
#let ciaAppendix() = {
|
||||
[
|
||||
== CIA Triad
|
||||
|
||||
The CIA triad is a fundamental framework for information security that encompasses three important principles: confidentiality, integrity, and availability. In this report, each of the three principles is used to highlight specific aspects of the security implications for the area under observation. All three principles are explained in more detail below.
|
||||
|
||||
=== Confidentiality (C)
|
||||
The principle of confidentiality means that information is only accessible to authorized users or entities. It protects sensitive data from unauthorized access or disclosure through measures such as encryption, access controls, and data classification. By maintaining confidentiality, companies reduce the risks of data breaches and unauthorized disclosure, thereby preserving the privacy and trustworthiness of their information assets.
|
||||
|
||||
=== Integrity (I)
|
||||
Integrity preserves the accuracy, consistency, and reliability of data. It prevents unauthorized changes, deletions, or falsifications through techniques such as checksums, digital signatures, and access controls. By maintaining data integrity, companies ensure the reliability and credibility of their information assets, thereby minimizing the risk of fraud or manipulation.
|
||||
|
||||
=== Availability (A)
|
||||
Availability ensures the smooth and reliable provision of information. Potential interruptions to services, systems, or networks are mitigated through redundancy, failover mechanisms, and disaster recovery planning. By maintaining high availability, companies maintain operational continuity and user satisfaction by mitigating the impact of downtime or failures. At the same time, the unavailability of information can have serious consequences, such as lost revenue.
|
||||
]
|
||||
}
|
||||
|
||||
#let cvssAppendix() = {
|
||||
[
|
||||
== Common Vulnerability Scoring System (CVSS)
|
||||
|
||||
The Common Vulnerability Scoring System (CVSS) provides a standardized, vendor- and platform-agnostic methodology for quantifying the technical severity of software, hardware, and firmware vulnerabilities. Its outputs deliver numerical scores that contextualize a vulnerability’s risk relative to others, facilitating consistent prioritization across diverse systems. CVSS is structured around three interdependent metric groups: *Base*, *Temporal*, and *Environmental*. The Base Score captures the inherent characteristics of a vulnerability (e.g., exploitability, impact), assigning it a severity rating under idealized conditions. Temporal Metrics dynamically adjust this base score based on time-sensitive factors like exploit availability or the existence of patches. Finally, Environmental Metrics tailor the severity assessment to an organization’s specific deployment, accounting for mitigations, criticality of affected assets, and other contextual factors unique to the environment.
|
||||
|
||||
The only metric group that can be calculated without deep knowledge of the environment and situation is the Base Score, as the Base Score reflects intrinsic vulnerability characteristics (e.g., exploit complexity, impact on confidentiality/integrity) and is designed to be vendor-neutral and environment-agnostic. It does not account for the specific client’s infrastructure, patch status, or operational context. Since a penetration test reports on a single target environment, the Base Score represents the objective severity of the flaw within the scope of the test (e.g., "this flaw could be exploited in this network").
|
||||
|
||||
The version of the Common Vulnerability Scoring System used in this report is 3.1#footnote("https://www.first.org/cvss/v3-1/user-guide").
|
||||
|
||||
=== Attack Vector (AV)
|
||||
|
||||
This metric quantifies how remotely an attacker can exploit a vulnerability, directly influencing the Base Score:
|
||||
|
||||
- *Network (N)*: Highest severity. Attack possible from anywhere on the internet (e.g., sending a malicious packet across routers).
|
||||
- *Adjacent (A)*: Moderate severity. Exploit limited to local networks (e.g., same subnet, Bluetooth/Wi-Fi, or secure VPN).
|
||||
- *Local (L)*: Lower severity. Requires local access (console/SSH) or user interaction (e.g., phishing a document).
|
||||
- *Physical (P)*: Lowest severity. Requires direct physical contact (e.g., evil-maid attacks, cold boot, DMA via USB).
|
||||
|
||||
=== Attack Complexity (AC)
|
||||
|
||||
This metric quantifies the technical difficulty of exploiting a vulnerability, independent of user interaction. It directly impacts the Base Score:
|
||||
|
||||
- *Low (L)*: Attack is repeatable and predictable with no special conditions. Example: Exploiting a buffer overflow in a service accessible via network.
|
||||
- *High (H)*: Exploit requires attacker preparation or external factors, reducing reliability. This may include:
|
||||
- Gathering target-specific knowledge (e.g., configuration settings, shared secrets).
|
||||
- Overcoming mitigations (e.g., race conditions, anti-exploit techniques).
|
||||
- Network manipulation (e.g., man-in-the-middle attacks).
|
||||
- Example: Exploiting a flaw requiring a victim’s browser to accept a malicious file.
|
||||
|
||||
=== Privileges Required (PR)
|
||||
|
||||
This metric measures the attacker’s initial access level needed to exploit a vulnerability, directly impacting the Base Score:
|
||||
|
||||
- *None (N)*: Highest severity. Exploitable by unauthorized attackers with no prior access (e.g., unauthenticated web attack).
|
||||
- *Low (L)*: Moderate severity. Requires basic user privileges (e.g., standard account access to non-sensitive resources).
|
||||
- *High (H)*: Lowest severity. Needs administrative privileges to access critical system settings/files (e.g., root/superuser access).
|
||||
|
||||
=== User Interaction (UI)
|
||||
|
||||
This metric assesses whether a vulnerability requires human involvement (beyond the attacker) to be exploited, directly influencing the Base Score:
|
||||
|
||||
- *None (N)*: Highest severity. Exploitable without user action (e.g., automated network attack).
|
||||
- *Required (R)*: Lower severity. Requires user interaction (e.g., clicking a malicious link or installing software).
|
||||
|
||||
=== Scope (S)
|
||||
|
||||
This metric determines if a vulnerability breaches security boundaries, allowing impact on components outside its original security scope (e.g., an app exploiting a database). Directly impacts Base Score severity:
|
||||
|
||||
- *Unchanged (U)*: Lowest severity. Vulnerability only affects resources within the same security authority (e.g., a web app affecting its own files).
|
||||
- *Changed (C)*: Highest severity. Vulnerability crosses security boundaries, impacting components under different authorities (e.g., a compromised web server accessing a database).
|
||||
]
|
||||
}
|
||||
|
||||
#let boxAppendix() = {
|
||||
[
|
||||
== Penetration Testing Box Scenarios
|
||||
|
||||
Penetration testing engagements are classified by the level of information provided to the testing team. This classification directly impacts the methodology, scope, and findings. The three standard scenarios are *Black Box*, *Grey Box*, and *White Box*. While Black Box and White Box refer to quite clear and precise situations, Grey box is a bit vague, in-between of both. Due to this, these terms should always be interpreted in the current situation and not be seen as precise and clear terminology. Nonetheless, the selection of the box scenario directly determines the scope of findings and realism of threat simulation. Black box tests external risks, grey box evaluates insider-adjacent threats, and white box provides the deepest technical analysis.
|
||||
|
||||
=== Black Box Testing
|
||||
|
||||
The testing team has no prior knowledge of the target system (e.g., internal architecture, source code, credentials, or network topology). Testing simulates an external attacker with only publicly available information. The purpose is to evaluate realistic attack surfaces, unpatched vulnerabilities exposed to the internet, and the effectiveness of perimeter defenses.
|
||||
|
||||
For example: a penetration test against a company’s public-facing e-commerce website (`www.maride.inv`). Testers are provided only the domain name and must identify vulnerabilities using only publicly available and gathered information. No access to internal systems or credentials is granted.
|
||||
|
||||
=== Grey Box Testing
|
||||
|
||||
The testing team is provided limited, realistic information (e.g., user credentials, network diagrams, or documentation). This simulates a threat actor with compromised credentials or insider access. The purpose is to bridge the gap between external and internal testing; uncover vulnerabilities exploitable by authenticated users or partial insider knowledge.
|
||||
|
||||
For example: testing a corporate web application where testers are given a low-privileged user account (e.g., "standard employee" credentials). Testers identify issues like privilege escalation, insecure data handling, or business logic flaws that an internal user could exploit.
|
||||
|
||||
=== White Box Testing
|
||||
|
||||
The testing team has complete, detailed access to the target system (e.g., source code, infrastructure diagrams, credentials, and system configurations). This simulates a highly motivated insider threat with deep system knowledge. The purpose is to perform exhaustive code-level analysis, identify root causes of vulnerabilities, and validate secure development practices.
|
||||
|
||||
For example: reviewing the source code of a custom mobile banking application. Testers are provided full access to the code repository, database schemas, and API documentation to identify flaws like hardcoded secrets, cryptographic misconfigurations, or insecure API endpoints.
|
||||
]
|
||||
}
|
||||
|
||||
#let tlpAppendix() = {
|
||||
[
|
||||
== Traffic Light Protocol
|
||||
|
||||
The Traffic Light Protocol (TLP) is a standardised system designed to accelerate collaborative response to security incidents by clarifying sharing boundaries for sensitive information. Information flows from an information source (e.g., a pentesting team) to recipients (e.g., clients or partners), with TLP labels governing permissible dissemination. The TLP labels standardised by FIRST#footnote("https://www.first.org/tlp/") are *RED*, *AMBER* and *AMBER+STRICT*, *GREEN*, and *CLEAR*.
|
||||
|
||||
=== TLP Label #helper.tlpLabel("RED")
|
||||
|
||||
TLP:RED means _for the eyes of individual recipients only._
|
||||
The purpose is to protect highly sensitive information where unauthorized disclosure risks privacy, reputation, or operations.
|
||||
Sharing is strictly prohibited outside the recipient - even within the recipients organisation.
|
||||
|
||||
For example: an unauthenticated RCE vulnerability in a payment gateway API is classified TLP:RED. Sharing this internally (e.g., within a client’s incident response team or development team) is allowed; sharing anywhere else violates the classification.
|
||||
|
||||
=== TLP Label #helper.tlpLabel("AMBER") and #helper.tlpLabel("AMBER+STRICT")
|
||||
|
||||
TLP:AMBER limits sharing to the recipient’s organization and its clients on a need-to-know basis. TLP:AMBER+STRICT restricts this further to the organisation, excluding clients.
|
||||
The purpose is to allow collaboration with controlled exposure. Recipients may share with their own organization (and clients or partners if not using AMBER+STRICT), but must not share publicly or with non-authorized third parties.
|
||||
|
||||
For example: a session fixation flaw in a client’s SaaS platform is TLP:AMBER. Their security team may share with their provider for patching, but cannot share it with unaffiliated parties.
|
||||
|
||||
=== TLP Label #helper.tlpLabel("GREEN")
|
||||
|
||||
TLP:GREEN means _Restricted to the recipient’s community; not public._
|
||||
The purpose is to share awareness within a trusted community without public risk.
|
||||
This allows recipients to share it with peers and partners within their defined community.
|
||||
|
||||
For example: a misconfigured AWS S3 bucket exposing anonymized test data is TLP:GREEN. The testing team may share this within the cybersecurity community (e.g., via ISACs or industry forums) to improve collective awareness, but cannot publish it on the internet.
|
||||
|
||||
=== TLP Label #helper.tlpLabel("CLEAR")
|
||||
|
||||
TLP:CLEAR means _No restrictions on disclosure._
|
||||
The purpose is to publicly share low-risk findings with no foreseeable misuse or patched vulnerabilities for transparency.
|
||||
While sharing is unrestricted, the information may still be subject e.g. to copyright rules like attribution.
|
||||
|
||||
For example: a server running an outdated but non-vulnerable SSL/TLS library qualifies as TLP:CLEAR. The testing team may publish this in a blog post to demonstrate tooling for automatic detection and general security practice.
|
||||
]
|
||||
}
|
||||
26
pages/boxes.typ
Normal file
26
pages/boxes.typ
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
#let render() = {
|
||||
[
|
||||
== Penetration Testing Box Scenarios
|
||||
|
||||
Penetration testing engagements are classified by the level of information provided to the testing team. This classification directly impacts the methodology, scope, and findings. The three standard scenarios are *Black Box*, *Grey Box*, and *White Box*. While Black Box and White Box refer to quite clear and precise situations, Grey box is a bit vague, in-between of both. Due to this, these terms should always be interpreted in the current situation and not be seen as precise and clear terminology. Nonetheless, the selection of the box scenario directly determines the scope of findings and realism of threat simulation. Black box tests external risks, grey box evaluates insider-adjacent threats, and white box provides the deepest technical analysis.
|
||||
|
||||
=== Black Box Testing
|
||||
|
||||
The testing team has no prior knowledge of the target system (e.g., internal architecture, source code, credentials, or network topology). Testing simulates an external attacker with only publicly available information. The purpose is to evaluate realistic attack surfaces, unpatched vulnerabilities exposed to the internet, and the effectiveness of perimeter defenses.
|
||||
|
||||
For example: a penetration test against a company’s public-facing e-commerce website (`www.maride.inv`). Testers are provided only the domain name and must identify vulnerabilities using only publicly available and gathered information. No access to internal systems or credentials is granted.
|
||||
|
||||
=== Grey Box Testing
|
||||
|
||||
The testing team is provided limited, realistic information (e.g., user credentials, network diagrams, or documentation). This simulates a threat actor with compromised credentials or insider access. The purpose is to bridge the gap between external and internal testing; uncover vulnerabilities exploitable by authenticated users or partial insider knowledge.
|
||||
|
||||
For example: testing a corporate web application where testers are given a low-privileged user account (e.g., "standard employee" credentials). Testers identify issues like privilege escalation, insecure data handling, or business logic flaws that an internal user could exploit.
|
||||
|
||||
=== White Box Testing
|
||||
|
||||
The testing team has complete, detailed access to the target system (e.g., source code, infrastructure diagrams, credentials, and system configurations). This simulates a highly motivated insider threat with deep system knowledge. The purpose is to perform exhaustive code-level analysis, identify root causes of vulnerabilities, and validate secure development practices.
|
||||
|
||||
For example: reviewing the source code of a custom mobile banking application. Testers are provided full access to the code repository, database schemas, and API documentation to identify flaws like hardcoded secrets, cryptographic misconfigurations, or insecure API endpoints.
|
||||
]
|
||||
}
|
||||
48
pages/cover.typ
Normal file
48
pages/cover.typ
Normal file
@ -0,0 +1,48 @@
|
||||
#import "marks.typ"
|
||||
|
||||
#let render(title, place, author, reportType, confidential: false, tlp: none, draft: false) = {
|
||||
// Define page
|
||||
set page(
|
||||
background: [
|
||||
#image("title.png")
|
||||
],
|
||||
margin: (
|
||||
top: 50%
|
||||
)
|
||||
)
|
||||
// Reset counter
|
||||
counter(page).update(n => n - 1)
|
||||
|
||||
text(size: 32pt, [
|
||||
#text(reportType)\
|
||||
#text(title, weight: "black")
|
||||
])
|
||||
|
||||
v(0.25pt)
|
||||
|
||||
text(size: 16pt, [
|
||||
#place, #datetime.today().display("[day].[month].[year]")
|
||||
|
||||
#author
|
||||
])
|
||||
|
||||
v(1fr)
|
||||
|
||||
grid(
|
||||
columns: (1fr, 1fr, 1fr),
|
||||
gutter: 5pt,
|
||||
rows: (75pt),
|
||||
// "Confidential" marking
|
||||
if confidential {
|
||||
marks.confidential()
|
||||
},
|
||||
// "Draft" marking
|
||||
if draft {
|
||||
marks.draft()
|
||||
},
|
||||
// TLP marking, see https://www.first.org/tlp/
|
||||
if tlp != none {
|
||||
marks.tlp(tlp)
|
||||
}
|
||||
)
|
||||
}
|
||||
35
pages/legal.typ
Normal file
35
pages/legal.typ
Normal file
@ -0,0 +1,35 @@
|
||||
#import "../aux/placeholder.typ": placeholder
|
||||
|
||||
#let render(author) = {
|
||||
// Reset counter
|
||||
counter(page).update(n => n - 1)
|
||||
|
||||
block(height: 1fr)
|
||||
|
||||
show heading: it => {
|
||||
v(1em)
|
||||
par(text(it.body, fill: gray))
|
||||
}
|
||||
text(fill: silver,
|
||||
[
|
||||
#heading(level: 2, outlined:false, "Report Version & Authorship")
|
||||
|
||||
#table(
|
||||
columns: (15%, 15%, 35%, 35%),
|
||||
align: left,
|
||||
stroke: gray,
|
||||
[*Version*], [*Date*], [*Author*], [*Changes & Comment*],
|
||||
[#placeholder("Draft")], [#placeholder("01.01.1970")], [#placeholder(author)], [#placeholder("Some")]
|
||||
)
|
||||
|
||||
#heading(level: 2, outlined:false, "Classification")
|
||||
This report and all associated materials are strictly confidential and may be communicated and/or distributed by the Client only with written approval of the author. All report data, including findings and recommendations, may be stored encrypted in the author's secure archive post-engagement, in compliance with applicable data protection regulations.
|
||||
|
||||
#heading(level: 2, outlined:false, "Legal Disclaimer")
|
||||
This report constitutes the professional findings of a penetration test conducted under the scope and terms agreed upon by the client. All results reflect the state of security vulnerabilities as assessed during the engagement using standardized methodologies and tools. The findings presented are not guarantees of security or compliance; they represent identified weaknesses which may require remediation as by the professional impression of the author.
|
||||
|
||||
The client is responsible for interpreting these findings within their operational context and for implementing appropriate security measures. No liability is taken for actions which are based on this report. This assessment does not constitute security certification, and its results are valid only for the scope and timeframe of the engagement. Neither the contents of this document nor the expressions of the author in any form are legal advice.
|
||||
])
|
||||
|
||||
pagebreak()
|
||||
}
|
||||
36
pages/marks.typ
Normal file
36
pages/marks.typ
Normal file
@ -0,0 +1,36 @@
|
||||
// confidential draws a "CONFIDENTIAL" stamp, used on the cover page
|
||||
#let confidential() = {
|
||||
rect(
|
||||
height: 100%,
|
||||
width: 100%,
|
||||
stroke: (paint: red, thickness: 2pt, dash: "solid"),
|
||||
align(center + horizon,
|
||||
text(
|
||||
size: 18pt,
|
||||
weight: "semibold",
|
||||
fill: red,
|
||||
"CONFIDENTIAL"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// draft draws a "DRAFT" stamp, used on the cover page
|
||||
#let draft() = {
|
||||
rect(
|
||||
height: 100%,
|
||||
width: 100%,
|
||||
stroke: (paint: blue, thickness: 2pt, dash: "solid"),
|
||||
align(center + horizon,
|
||||
text(
|
||||
size: 18pt,
|
||||
weight: "semibold",
|
||||
fill: blue,
|
||||
"DRAFT"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// for the TLP mark, see addons/tlp.typ:mark()
|
||||
#import "../addons/tlp.typ": mark as tlp
|
||||
104
pages/mgmtsum.typ
Normal file
104
pages/mgmtsum.typ
Normal file
@ -0,0 +1,104 @@
|
||||
#import "@preview/diagraph:0.3.6"
|
||||
|
||||
#import "../addons/cvss.typ"
|
||||
|
||||
#let render(target: str, targetInSentence: str, testFocus: str, testObject: str, testScenario: str, recommendation: str, start: str, end: str, setup: str, nodes: str, scope: array) = {
|
||||
[
|
||||
= Management Summary
|
||||
|
||||
== Motivation
|
||||
|
||||
#target ordered Acme Security, LLC to perform a #testScenario penetration test onto a selected number of hosts in a live and productive environment.
|
||||
|
||||
This penetration test was conducted to proactively identify, assess, and validate the severity of security vulnerabilities within the defined scope of #targetInSentence, specifically those exploitable by #testFocus. Adversarial techniques to bypass controls, gain unauthorized access, and compromise critical assets were performed for this test scenario to determine the actual impact on confidentiality, integrity, and availability.
|
||||
|
||||
== Test Object
|
||||
|
||||
#testObject
|
||||
|
||||
== Test Methodology
|
||||
|
||||
The aim of the test was to uncover vulnerabilities and weaknesses of all kinds and chaining findings and vulnerabilities accordingly to gain a deep understanding of the audited hosts, following a security-in-depth approach. The tests were carried out in accordance with the MITRE ATT&CK Framework#footnote("https://attack.mitre.org").
|
||||
|
||||
The penetration test was performed as a #testScenario.
|
||||
|
||||
== Findings
|
||||
|
||||
The penetration test revealed #context([
|
||||
#let num = cvss.riskCategoryStats.values().map(v => v.final()).sum()
|
||||
#if num == 1 {
|
||||
[ #num finding ]
|
||||
} else {
|
||||
[ #num findings ]
|
||||
}
|
||||
]) which can be categorized by risk:
|
||||
|
||||
#table(
|
||||
columns: (16.66%, 16.66%, 16.66%, 16.66%, 16.66%, 16.66%),
|
||||
align: center,
|
||||
[Critical], [High], [Medium], [Low], [None], [Other],
|
||||
table.cell(context(cvss.riskCategoryStats.Critical.final()), fill: red, align: center),
|
||||
table.cell(context(cvss.riskCategoryStats.High.final()), fill: orange, align: center),
|
||||
table.cell(context(cvss.riskCategoryStats.Medium.final()), fill: yellow, align: center),
|
||||
table.cell(context(cvss.riskCategoryStats.Low.final()), fill: lime, align: center),
|
||||
table.cell(context(cvss.riskCategoryStats.None.final()), fill: white, align: center),
|
||||
table.cell(context(cvss.riskCategoryStats.Other.final()), fill: gray, align: center),
|
||||
)
|
||||
|
||||
== Recommendations & Next Steps
|
||||
|
||||
#recommendation
|
||||
|
||||
== Test Scope and Setup
|
||||
|
||||
#if start != "" {
|
||||
if end != "" {
|
||||
[ The test was conducted from #start to #end. ]
|
||||
} else {
|
||||
[ The test was conducted on #start. ]
|
||||
}
|
||||
}
|
||||
|
||||
#setup
|
||||
|
||||
The following scope was set for the penetration test:
|
||||
|
||||
#table(
|
||||
columns: (25%, 50%, 25%),
|
||||
align: center,
|
||||
inset: 10pt,
|
||||
table.cell(fill: color.linear-rgb(4.5%, 14.5%, 14.5%, 100))[*Type*],
|
||||
table.cell(fill: color.linear-rgb(4.5%, 14.5%, 14.5%, 100))[*Value*],
|
||||
table.cell(fill: color.linear-rgb(4.5%, 14.5%, 14.5%, 100))[*State*],
|
||||
..for (i, value) in scope.enumerate() {
|
||||
(
|
||||
table.cell(value.type),
|
||||
table.cell(value.content),
|
||||
if value.inScope {
|
||||
table.cell("In scope", fill: lime, align: center)
|
||||
} else {
|
||||
table.cell("Out of scope", fill: gray, align: center)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
#if nodes != "" {
|
||||
[ From the perspective of the machine used for the penetration test, the network layout was seen as shown in the graph below. ]
|
||||
|
||||
figure(
|
||||
diagraph.render("
|
||||
digraph G {
|
||||
rankdir=LR;
|
||||
node [shape=rectangle];
|
||||
|
||||
" + nodes + "
|
||||
}
|
||||
"),
|
||||
caption: [
|
||||
Schematic graph showing the test objective network
|
||||
]
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
5
pages/pages.typ
Normal file
5
pages/pages.typ
Normal file
@ -0,0 +1,5 @@
|
||||
#import "boxes.typ"
|
||||
#import "cover.typ"
|
||||
#import "legal.typ"
|
||||
#import "mgmtsum.typ"
|
||||
#import "toc.typ"
|
||||
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
7
pages/toc.typ
Normal file
7
pages/toc.typ
Normal file
@ -0,0 +1,7 @@
|
||||
#let render() = {
|
||||
outline(
|
||||
title: "Table of contents",
|
||||
indent: 10pt,
|
||||
depth: 2
|
||||
)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user