From 774066c766beb545209a176c6625dc764e6b6297 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 20 Sep 2023 10:51:32 -0400 Subject: [PATCH 01/53] add decision point, options, group implementations --- requirements.txt | 1 + src/ssvc/__init__.py | 14 ++++++++++ src/ssvc/decisionpoints.py | 57 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/ssvc/__init__.py create mode 100644 src/ssvc/decisionpoints.py diff --git a/requirements.txt b/requirements.txt index c8939a0b..9e3b5be4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ mkdocs-include-markdown-plugin mkdocs-table-reader-plugin mkdocs-material mkdocs-material-extensions +dataclasses-json diff --git a/src/ssvc/__init__.py b/src/ssvc/__init__.py new file mode 100644 index 00000000..03989558 --- /dev/null +++ b/src/ssvc/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +''' +file: __init__.py +author: adh +created_at: 9/20/23 10:36 AM +''' + + +def main(): + pass + + +if __name__ == '__main__': + main() diff --git a/src/ssvc/decisionpoints.py b/src/ssvc/decisionpoints.py new file mode 100644 index 00000000..fc0716df --- /dev/null +++ b/src/ssvc/decisionpoints.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +''' +file: decisionpoints +author: adh +created_at: 9/20/23 10:07 AM +''' + +from dataclasses import dataclass +from typing import List + +from dataclasses_json import dataclass_json + +## notes +# based on https://gist.githubusercontent.com/sei-vsarvepalli/de2cd7dae33e1e1dc906d50846becb45/raw/2a85d08a4028f6dd3cc162d4ace3509e0458426f/Exploitation.json +# TODO: "Options" should be "Values" +# TODO: "label" should be "name" +# TODO: "key" should be namespaced (add a namespace field?) + +@dataclass_json +@dataclass +class SsvcOption: + label: str + key: str + description: str = None + +@dataclass_json +@dataclass +class SsvcDecisionPoint: + options: List[SsvcOption] + label: str + description: str + key: str + version: str + +@dataclass_json +@dataclass +class SsvcDecisionPointGroup: + label: str + description: str + key: str + version: str + decision_points: List[SsvcDecisionPoint] + + +def main(): + opt_none = SsvcOption(label="None", key="N", description="No exploit available") + opt_poc = SsvcOption(label="PoC", key="P", description="Proof of concept exploit available") + opt_active= SsvcOption(label="Active", key="A", description="Active exploitation observed") + opts = [opt_none, opt_poc, opt_active] + + dp = SsvcDecisionPoint(options=opts, label="Exploitation", description="Is there an exploit available?", key="E", version="1.0.0") + + dpg = SsvcDecisionPointGroup(label="DPgroup", description="A group of decision points", key="DPG1", version="1.0.0", decision_points=[dp]) + print(dpg.to_json(indent=2)) + +if __name__ == '__main__': + main() From fb41121b8a60580a08c5c4ccdc7e1bffce3c5ef5 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 20 Sep 2023 10:57:55 -0400 Subject: [PATCH 02/53] add base classes s/option/value/ s/label/name/ add namespace --- src/ssvc/decisionpoints.py | 48 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/ssvc/decisionpoints.py b/src/ssvc/decisionpoints.py index fc0716df..a5f42134 100644 --- a/src/ssvc/decisionpoints.py +++ b/src/ssvc/decisionpoints.py @@ -12,45 +12,45 @@ ## notes # based on https://gist.githubusercontent.com/sei-vsarvepalli/de2cd7dae33e1e1dc906d50846becb45/raw/2a85d08a4028f6dd3cc162d4ace3509e0458426f/Exploitation.json -# TODO: "Options" should be "Values" -# TODO: "label" should be "name" -# TODO: "key" should be namespaced (add a namespace field?) @dataclass_json -@dataclass -class SsvcOption: - label: str +@dataclass(kw_only=True) +class SsvcBase: + name: str + description: str key: str - description: str = None + namespace: str="ssvc" @dataclass_json -@dataclass -class SsvcDecisionPoint: - options: List[SsvcOption] - label: str - description: str - key: str +@dataclass(kw_only=True) +class SsvcVersionedBase(SsvcBase): version: str @dataclass_json -@dataclass -class SsvcDecisionPointGroup: - label: str - description: str - key: str - version: str +@dataclass(kw_only=True) +class SsvcOption(SsvcBase): + pass + +@dataclass_json +@dataclass(kw_only=True) +class SsvcDecisionPoint(SsvcVersionedBase): + values: List[SsvcOption] + +@dataclass_json +@dataclass(kw_only=True) +class SsvcDecisionPointGroup(SsvcVersionedBase): decision_points: List[SsvcDecisionPoint] def main(): - opt_none = SsvcOption(label="None", key="N", description="No exploit available") - opt_poc = SsvcOption(label="PoC", key="P", description="Proof of concept exploit available") - opt_active= SsvcOption(label="Active", key="A", description="Active exploitation observed") + opt_none = SsvcOption(name="None", key="N", description="No exploit available") + opt_poc = SsvcOption(name="PoC", key="P", description="Proof of concept exploit available") + opt_active= SsvcOption(name="Active", key="A", description="Active exploitation observed") opts = [opt_none, opt_poc, opt_active] - dp = SsvcDecisionPoint(options=opts, label="Exploitation", description="Is there an exploit available?", key="E", version="1.0.0") + dp = SsvcDecisionPoint(values=opts, name="Exploitation", description="Is there an exploit available?", key="E", version="1.0.0") - dpg = SsvcDecisionPointGroup(label="DPgroup", description="A group of decision points", key="DPG1", version="1.0.0", decision_points=[dp]) + dpg = SsvcDecisionPointGroup(name="DPgroup", description="A group of decision points", key="DPG1", version="1.0.0", decision_points=[dp]) print(dpg.to_json(indent=2)) if __name__ == '__main__': From b44a1a8e01d92e44af009e7baaee352f6eb20ca6 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 20 Sep 2023 11:03:14 -0400 Subject: [PATCH 03/53] fix import (+27 squashed commits) Squashed commits: [83a77f3] objects instead of classes [5a59166] objects instead of classes [7b3713a] objects instead of classes [073ca1f] objects instead of classes [566d353] objects instead of classes [f4d8c4d] objects instead of classes [4c88f78] objects instead of classes [ab2ae3e] objects instead of classes [f79af19] objects instead of classes [70409e4] objects instead of classes [9e8dbb2] objects instead of classes [4681eb0] objects instead of classes [9d9f7e7] objects instead of classes [8a01597] objects instead of classes [73d60ef] objects instead of classes [f893a71] objects instead of classes [40003b0] objects instead of classes [7bf8692] objects instead of classes [bbec973] objects instead of classes [838fbf4] refactor imports [c81545c] copy objects instead of new classes [0d4d8ee] add default version 0.0.0 [bd0b9d9] add cvss v1, v2, v3 [700d049] rename classes, clean up mixins [10884aa] add namespaced mixin class [5a83e0c] add s/option/value/ [42f2110] add docstrings --- src/ssvc/decision_points/__init__.py | 14 +++ src/ssvc/decision_points/base.py | 108 +++++++++++++++++ src/ssvc/decision_points/cvss/__init__.py | 14 +++ .../decision_points/cvss/access_complexity.py | 54 +++++++++ .../decision_points/cvss/access_vector.py | 58 +++++++++ .../decision_points/cvss/attack_complexity.py | 36 ++++++ .../decision_points/cvss/attack_vector.py | 46 +++++++ .../decision_points/cvss/authentication.py | 59 +++++++++ .../cvss/availability_impact.py | 64 ++++++++++ .../cvss/availability_requirement.py | 47 ++++++++ src/ssvc/decision_points/cvss/base.py | 22 ++++ .../cvss/collateral_damage_potential.py | 83 +++++++++++++ .../cvss/confidentiality_impact.py | 63 ++++++++++ .../cvss/confidentiality_requirement.py | 46 +++++++ .../decision_points/cvss/exploitability.py | 91 ++++++++++++++ src/ssvc/decision_points/cvss/impact_bias.py | 46 +++++++ .../decision_points/cvss/integrity_impact.py | 62 ++++++++++ .../cvss/integrity_requirement.py | 46 +++++++ .../cvss/privileges_required.py | 41 +++++++ .../decision_points/cvss/remediation_level.py | 79 ++++++++++++ .../decision_points/cvss/report_confidence.py | 80 +++++++++++++ src/ssvc/decision_points/cvss/scope.py | 36 ++++++ .../cvss/target_distribution.py | 81 +++++++++++++ .../decision_points/cvss/user_interaction.py | 37 ++++++ src/ssvc/decision_points/cvss/v1.py | 69 +++++++++++ src/ssvc/decision_points/cvss/v2.py | 77 ++++++++++++ src/ssvc/decision_points/cvss/v3.py | 112 ++++++++++++++++++ src/ssvc/decisionpoints.py | 57 --------- 28 files changed, 1571 insertions(+), 57 deletions(-) create mode 100644 src/ssvc/decision_points/__init__.py create mode 100644 src/ssvc/decision_points/base.py create mode 100644 src/ssvc/decision_points/cvss/__init__.py create mode 100644 src/ssvc/decision_points/cvss/access_complexity.py create mode 100644 src/ssvc/decision_points/cvss/access_vector.py create mode 100644 src/ssvc/decision_points/cvss/attack_complexity.py create mode 100644 src/ssvc/decision_points/cvss/attack_vector.py create mode 100644 src/ssvc/decision_points/cvss/authentication.py create mode 100644 src/ssvc/decision_points/cvss/availability_impact.py create mode 100644 src/ssvc/decision_points/cvss/availability_requirement.py create mode 100644 src/ssvc/decision_points/cvss/base.py create mode 100644 src/ssvc/decision_points/cvss/collateral_damage_potential.py create mode 100644 src/ssvc/decision_points/cvss/confidentiality_impact.py create mode 100644 src/ssvc/decision_points/cvss/confidentiality_requirement.py create mode 100644 src/ssvc/decision_points/cvss/exploitability.py create mode 100644 src/ssvc/decision_points/cvss/impact_bias.py create mode 100644 src/ssvc/decision_points/cvss/integrity_impact.py create mode 100644 src/ssvc/decision_points/cvss/integrity_requirement.py create mode 100644 src/ssvc/decision_points/cvss/privileges_required.py create mode 100644 src/ssvc/decision_points/cvss/remediation_level.py create mode 100644 src/ssvc/decision_points/cvss/report_confidence.py create mode 100644 src/ssvc/decision_points/cvss/scope.py create mode 100644 src/ssvc/decision_points/cvss/target_distribution.py create mode 100644 src/ssvc/decision_points/cvss/user_interaction.py create mode 100644 src/ssvc/decision_points/cvss/v1.py create mode 100644 src/ssvc/decision_points/cvss/v2.py create mode 100644 src/ssvc/decision_points/cvss/v3.py delete mode 100644 src/ssvc/decisionpoints.py diff --git a/src/ssvc/decision_points/__init__.py b/src/ssvc/decision_points/__init__.py new file mode 100644 index 00000000..d4ce8764 --- /dev/null +++ b/src/ssvc/decision_points/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +file: __init__.py +author: adh +created_at: 9/20/23 12:38 PM +""" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py new file mode 100644 index 00000000..130ec008 --- /dev/null +++ b/src/ssvc/decision_points/base.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +""" +file: decisionpoints +author: adh +created_at: 9/20/23 10:07 AM +""" + +from dataclasses import dataclass +from typing import List + +from dataclasses_json import dataclass_json + +## notes +# based on https://gist.githubusercontent.com/sei-vsarvepalli/de2cd7dae33e1e1dc906d50846becb45/raw/2a85d08a4028f6dd3cc162d4ace3509e0458426f/Exploitation.json + + +@dataclass_json +@dataclass(kw_only=True) +class _Base: + """ + Base class for SSVC objects. + """ + + name: str + description: str + key: str + + +@dataclass_json +@dataclass(kw_only=True) +class _Versioned: + """ + Mixin class for versioned SSVC objects. + """ + + version: str = "0.0.0" + + +@dataclass_json +@dataclass(kw_only=True) +class _Namespaced: + """ + Mixin class for namespaced SSVC objects. + """ + + namespace: str = "ssvc" + + +@dataclass_json +@dataclass(kw_only=True) +class SsvcValue(_Base): + """ + Models a single value option for a decision point. + """ + + pass + + +@dataclass_json +@dataclass(kw_only=True) +class SsvcDecisionPoint(_Base, _Versioned, _Namespaced): + """ + Models a single decision point as a list of values. + """ + + values: List[SsvcValue] + + +@dataclass_json +@dataclass(kw_only=True) +class SsvcDecisionPointGroup(_Base, _Versioned): + """ + Models a group of decision points. + """ + + decision_points: List[SsvcDecisionPoint] + + +def main(): + opt_none = SsvcValue(name="None", key="N", description="No exploit available") + opt_poc = SsvcValue( + name="PoC", key="P", description="Proof of concept exploit available" + ) + opt_active = SsvcValue( + name="Active", key="A", description="Active exploitation observed" + ) + opts = [opt_none, opt_poc, opt_active] + + dp = SsvcDecisionPoint( + values=opts, + name="Exploitation", + description="Is there an exploit available?", + key="E", + version="1.0.0", + ) + + dpg = SsvcDecisionPointGroup( + name="DPgroup", + description="A group of decision points", + key="DPG1", + version="1.0.0", + decision_points=[dp], + ) + print(dpg.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/__init__.py b/src/ssvc/decision_points/cvss/__init__.py new file mode 100644 index 00000000..afae4312 --- /dev/null +++ b/src/ssvc/decision_points/cvss/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +file: __init__.py +author: adh +created_at: 9/20/23 12:39 PM +""" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/access_complexity.py b/src/ssvc/decision_points/cvss/access_complexity.py new file mode 100644 index 00000000..8e42f078 --- /dev/null +++ b/src/ssvc/decision_points/cvss/access_complexity.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +""" +file: access_complexity +author: adh +created_at: 9/20/23 1:35 PM +""" +from copy import deepcopy + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +ACCESS_COMPLEXITY_1 = CvssDecisionPoint( + name="Access Complexity", + description="This metric measures the complexity of the attack required to exploit the vulnerability once an attacker has gained access to the target system.", + key="AC", + version="1.0.0", + values=[ + SsvcValue( + name="Low", + key="L", + description="Specialized access conditions or extenuating circumstances do not exist; the system is always exploitable.", + ), + SsvcValue( + name="High", + key="H", + description="Specialized access conditions exist; for example: the system is exploitable during specific windows of time (a race condition), the system is exploitable under specific circumstances (nondefault configurations), or the system is exploitable with victim interaction (vulnerability exploitable only if user opens e-mail)", + ), + ], +) + +ACCESS_COMPLEXITY_2 = deepcopy(ACCESS_COMPLEXITY_1) +ACCESS_COMPLEXITY_2.version = "2.0.0" +ACCESS_COMPLEXITY_2.values = [ + SsvcValue( + name="Low", + key="L", + description="Specialized access conditions or extenuating circumstances do not exist.", + ), + SsvcValue( + name="Medium", + key="M", + description="The access conditions are somewhat specialized.", + ), + SsvcValue(name="High", key="H", description="Specialized access conditions exist."), +] + + +def main(): + print(ACCESS_COMPLEXITY_1.to_json(indent=2)) + print(ACCESS_COMPLEXITY_2.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/access_vector.py b/src/ssvc/decision_points/cvss/access_vector.py new file mode 100644 index 00000000..c5ee3e16 --- /dev/null +++ b/src/ssvc/decision_points/cvss/access_vector.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +""" +file: access_vector +author: adh +created_at: 9/20/23 1:30 PM +""" +from copy import deepcopy + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +ACCESS_VECTOR_1 = CvssDecisionPoint( + name="Access Vector", + description="This metric measures whether or not the vulnerability is exploitable locally or remotely.", + key="AV", + version="1.0.0", + values=[ + SsvcValue( + name="Local", + key="L", + description="The vulnerability is only exploitable locally (i.e., it requires physical access or authenticated login to the target system)", + ), + SsvcValue( + name="Remote", + key="R", + description="The vulnerability is exploitable remotely.", + ), + ], +) + +ACCESS_VECTOR_2 = deepcopy(ACCESS_VECTOR_1) +ACCESS_VECTOR_2.version = "2.0.0" +ACCESS_VECTOR_2.values = [ + SsvcValue( + name="Local", + key="L", + description="A vulnerability exploitable with only local access requires the attacker to have either physical access to the vulnerable system or a local (shell) account. Examples of locally exploitable vulnerabilities are peripheral attacks such as Firewire/USB DMA attacks, and local privilege escalations (e.g., sudo).", + ), + SsvcValue( + name="Adjacent Network", + key="A", + description="A vulnerability exploitable with adjacent network access requires the attacker to have access to either the broadcast or collision domain of the vulnerable software. Examples of local networks include local IP subnet, Bluetooth, IEEE 802.11, and local Ethernet segment.", + ), + SsvcValue( + name="Network", + key="N", + description="A vulnerability exploitable with network access means the vulnerable software is bound to the network stack and the attacker does not require local network access or local access. Such a vulnerability is often termed 'remotely exploitable'. An example of a network attack is an RPC buffer overflow.", + ), +] + + +def main(): + print(ACCESS_VECTOR_1.to_json(indent=2)) + print(ACCESS_VECTOR_2.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/attack_complexity.py b/src/ssvc/decision_points/cvss/attack_complexity.py new file mode 100644 index 00000000..ce952f8b --- /dev/null +++ b/src/ssvc/decision_points/cvss/attack_complexity.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +""" +file: attack_complexity +author: adh +created_at: 9/20/23 2:32 PM +""" + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +ATTACK_COMPLEXITY_1 = CvssDecisionPoint( + name="Attack Complexity", + description="This metric describes the conditions beyond the attacker's control that must exist in order to exploit the vulnerability.", + key="AC", + version="1.0.0", + values=[ + SsvcValue( + name="Low", + key="L", + description="Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success against the vulnerable component.", + ), + SsvcValue( + name="High", + key="H", + description="A successful attack depends on conditions beyond the attacker's control.", + ), + ], +) + + +def main(): + print(ATTACK_COMPLEXITY_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/attack_vector.py b/src/ssvc/decision_points/cvss/attack_vector.py new file mode 100644 index 00000000..dcad01cf --- /dev/null +++ b/src/ssvc/decision_points/cvss/attack_vector.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +""" +file: attack_vector +author: adh +created_at: 9/20/23 2:26 PM +""" + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +ATTACK_VECTOR_1 = CvssDecisionPoint( + name="Attack Vector", + description="This metric reflects the context by which vulnerability exploitation is possible. ", + key="AV", + version="1.0.0", + values=[ + SsvcValue( + name="Physical", + key="P", + description="A vulnerability exploitable with Physical access requires the attacker to physically touch or manipulate the vulnerable component. Physical interaction may be brief (e.g. evil maid attack [1]) or persistent. An example of such an attack is a cold boot attack which allows an attacker to access to disk encryption keys after gaining physical access to the system, or peripheral attacks such as Firewire/USB Direct Memory Access attacks.", + ), + SsvcValue( + name="Local", + key="L", + description="A vulnerability exploitable with Local access means that the vulnerable component is not bound to the network stack, and the attacker's path is via read/write/execute capabilities. In some cases, the attacker may be logged in locally in order to exploit the vulnerability, otherwise, she may rely on User Interaction to execute a malicious file.", + ), + SsvcValue( + name="Adjacent", + key="A", + description="A vulnerability exploitable with adjacent network access means the vulnerable component is bound to the network stack, however the attack is limited to the same shared physical (e.g. Bluetooth, IEEE 802.11), or logical (e.g. local IP subnet) network, and cannot be performed across an OSI layer 3 boundary (e.g. a router).", + ), + SsvcValue( + name="Network", + key="N", + description="A vulnerability exploitable with network access means the vulnerable component is bound to the network stack and the attacker's path is through OSI layer 3 (the network layer). Such a vulnerability is often termed 'remotely exploitable' and can be thought of as an attack being exploitable one or more network hops away (e.g. across layer 3 boundaries from routers).", + ), + ], +) + + +def main(): + print(ATTACK_VECTOR_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/authentication.py b/src/ssvc/decision_points/cvss/authentication.py new file mode 100644 index 00000000..aa1626e7 --- /dev/null +++ b/src/ssvc/decision_points/cvss/authentication.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +""" +file: authentication +author: adh +created_at: 9/20/23 1:39 PM +""" +from copy import deepcopy + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +AUTHENTICATION_1 = CvssDecisionPoint( + name="Authentication", + description="This metric measures whether or not an attacker needs to be authenticated to the target system in order to exploit the vulnerability.", + key="AU", + version="1.0.0", + values=[ + SsvcValue( + name="Not Required", + key="N", + description="Authentication is not required to access or exploit the vulnerability.", + ), + SsvcValue( + name="Required", + key="R", + description="Authentication is required to access and exploit the vulnerability.", + ), + ], +) + +AUTHENTICATION_2 = deepcopy(AUTHENTICATION_1) +AUTHENTICATION_2.version = "2.0.0" +AUTHENTICATION_2.description = "This metric measures the number of times an attacker must authenticate to a target in order to exploit a vulnerability. This metric does not gauge the strength or complexity of the authentication process, only that an attacker is required to provide credentials before an exploit may occur. The possible values for this metric are listed in Table 3. The fewer authentication instances that are required, the higher the vulnerability score." +AUTHENTICATION_2.values = [ + SsvcValue( + name="Multiple", + key="M", + description="Exploiting the vulnerability requires that the attacker authenticate two or more times, even if the same credentials are used each time.", + ), + SsvcValue( + name="Single", + key="S", + description="The vulnerability requires an attacker to be logged into the system (such as at a command line or via a desktop session or web interface).", + ), + SsvcValue( + name="None", + key="N", + description="Authentication is not required to exploit the vulnerability.", + ), +] + + +def main(): + print(AUTHENTICATION_1.to_json(indent=2)) + print(AUTHENTICATION_2.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/availability_impact.py b/src/ssvc/decision_points/cvss/availability_impact.py new file mode 100644 index 00000000..c719149c --- /dev/null +++ b/src/ssvc/decision_points/cvss/availability_impact.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +""" +file: availability_impact +author: adh +created_at: 9/20/23 1:46 PM +""" +from copy import deepcopy +from dataclasses import dataclass, field + +from dataclasses_json import dataclass_json + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + + +AVAILABILITY_IMPACT_1 = CvssDecisionPoint( + name="Availability Impact", + description="This metric measures the impact on availability a successful exploit of the vulnerability will have on the target system.", + key="A", + version="1.0.0", + values=[ + SsvcValue(name="None", key="N", description="No impact on availability."), + SsvcValue( + name="Partial", + key="P", + description="Considerable lag in or interruptions in resource availability. For example, a network-based flood attack that reduces available bandwidth to a web server farm to such an extent that only a small number of connections successfully complete.", + ), + SsvcValue( + name="Complete", + key="C", + description="Total shutdown of the affected resource. The attacker can render the resource completely unavailable.", + ), + ], +) + +AVAILABILITY_IMPACT_2 = deepcopy(AVAILABILITY_IMPACT_1) +AVAILABILITY_IMPACT_2.version = "2.0.0" +AVAILABILITY_IMPACT_2.description = "This metric measures the impact to availability of a successfully exploited vulnerability." +AVAILABILITY_IMPACT_2.values = [ + SsvcValue( + name="None", + key="N", + description="There is no impact to the availability of the system.", + ), + SsvcValue( + name="Low", + key="L", + description="There is reduced performance or interruptions in resource availability.", + ), + SsvcValue( + name="High", + key="H", + description="There is total loss of availability, resulting in the attacker being able to fully deny access to resources in the impacted component; this loss is either sustained (while the attacker continues to deliver the attack) or persistent (the condition persists even after the attack has completed).", + ), +] + + +def main(): + print(AVAILABILITY_IMPACT_1.to_json(indent=2)) + print(AVAILABILITY_IMPACT_2.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/availability_requirement.py b/src/ssvc/decision_points/cvss/availability_requirement.py new file mode 100644 index 00000000..64dcfc04 --- /dev/null +++ b/src/ssvc/decision_points/cvss/availability_requirement.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +""" +file: availability_requirement +author: adh +created_at: 9/20/23 2:11 PM +""" + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + + +AVAILABILITY_REQUIREMENT_1 = CvssDecisionPoint( + name="Availability Requirement", + description="This metric measures the impact to the availability of a successfully exploited vulnerability.", + key="CR", + version="1.0.0", + values=[ + SsvcValue( + name="Low", + key="L", + description="Loss of availability is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", + ), + SsvcValue( + name="Medium", + key="M", + description="Loss of availability is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", + ), + SsvcValue( + name="High", + key="H", + description="Loss of availability is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", + ), + SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", + ), + ], +) + + +def main(): + print(AVAILABILITY_REQUIREMENT_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/base.py b/src/ssvc/decision_points/cvss/base.py new file mode 100644 index 00000000..9a8263a5 --- /dev/null +++ b/src/ssvc/decision_points/cvss/base.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +""" +file: cvss_dp +author: adh +created_at: 9/20/23 12:08 PM +""" + + +from dataclasses import dataclass +from dataclasses_json import dataclass_json + +from ssvc.decision_points.base import SsvcDecisionPoint + + +@dataclass_json +@dataclass(kw_only=True) +class CvssDecisionPoint(SsvcDecisionPoint): + """ + Models a single CVSS decision point as a list of values. + """ + + namespace: str = "cvss" diff --git a/src/ssvc/decision_points/cvss/collateral_damage_potential.py b/src/ssvc/decision_points/cvss/collateral_damage_potential.py new file mode 100644 index 00000000..bf8955f5 --- /dev/null +++ b/src/ssvc/decision_points/cvss/collateral_damage_potential.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +""" +file: collateral_damage_potential +author: adh +created_at: 9/20/23 1:48 PM +""" +from copy import deepcopy + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +COLLATERAL_DAMAGE_POTENTIAL_1 = CvssDecisionPoint( + name="Collateral Damage Potential", + description="This metric measures the potential for a loss in physical equipment, property damage or loss of life or limb.", + key="CDP", + version="1.0.0", + values=[ + SsvcValue( + name="None", + key="N", + description="There is no potential for physical or property damage.", + ), + SsvcValue( + name="Low", + key="L", + description="A successful exploit of this vulnerability may result in light physical or property damage or loss. The system itself may be damaged or destroyed.", + ), + SsvcValue( + name="Medium", + key="M", + description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", + ), + SsvcValue( + name="High", + key="H", + description="A successful exploit of this vulnerability may result in catastrophic physical or property damage and loss. The range of effect may be over a wide area.", + ), + ], +) + +COLLATERAL_DAMAGE_POTENTIAL_2 = deepcopy(COLLATERAL_DAMAGE_POTENTIAL_1) +COLLATERAL_DAMAGE_POTENTIAL_2.version = "2.0.0" +COLLATERAL_DAMAGE_POTENTIAL_2.values = [ + SsvcValue( + name="None", + key="N", + description="There is no potential for loss of life, physical assets, productivity or revenue.", + ), + SsvcValue( + name="Low", + key="L", + description="A successful exploit of this vulnerability may result in light physical or property damage or loss. The system itself may be damaged or destroyed.", + ), + SsvcValue( + name="Low-Medium", + key="LM", + description="A successful exploit of this vulnerability may result in moderate physical or property damage or loss.", + ), + SsvcValue( + name="Medium-High", + key="MH", + description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", + ), + SsvcValue( + name="High", + key="H", + description="A successful exploit of this vulnerability may result in catastrophic physical or property damage and loss. The range of effect may be over a wide area.", + ), + SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", + ), +] + + +def main(): + print(COLLATERAL_DAMAGE_POTENTIAL_1.to_json(indent=2)) + print(COLLATERAL_DAMAGE_POTENTIAL_2.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/confidentiality_impact.py b/src/ssvc/decision_points/cvss/confidentiality_impact.py new file mode 100644 index 00000000..6bb6eef4 --- /dev/null +++ b/src/ssvc/decision_points/cvss/confidentiality_impact.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +""" +file: confidentiality_impact +author: adh +created_at: 9/20/23 1:46 PM +""" +from copy import deepcopy + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +CONFIDENTIALITY_IMPACT_1 = CvssDecisionPoint( + name="Confidentiality Impact", + description="This metric measures the impact on confidentiality of a successful exploit of the vulnerability on the target system.", + key="C", + version="1.0.0", + values=[ + SsvcValue( + name="None", + key="N", + description="No impact on confidentiality.", + ), + SsvcValue( + name="Partial", + key="P", + description="There is considerable informational disclosure. Access to critical system files is possible. There is a loss of important information, but the attacker doesn't have control over what is obtainable or the scope of the loss is constrained.", + ), + SsvcValue( + name="Complete", + key="C", + description="A total compromise of critical system information. A complete loss of system protection resulting in all critical system files being revealed. The attacker has sovereign control to read all of the system's data (memory, files, etc).", + ), + ], +) + +CONFIDENTIALITY_IMPACT_2 = deepcopy(CONFIDENTIALITY_IMPACT_1) +CONFIDENTIALITY_IMPACT_2.version = "2.0.0" +CONFIDENTIALITY_IMPACT_2.values = [ + SsvcValue( + name="None", + key="N", + description="There is no loss of confidentiality within the impacted component.", + ), + SsvcValue( + name="Low", + key="L", + description="There is some loss of confidentiality. Access to some restricted information is obtained, but the attacker does not have control over what information is obtained, or the amount or kind of loss is constrained. The information disclosure does not cause a direct, serious loss to the impacted component.", + ), + SsvcValue( + name="High", + key="H", + description="There is total loss of confidentiality, resulting in all resources within the impacted component being divulged to the attacker. Alternatively, access to only some restricted information is obtained, but the disclosed information presents a direct, serious impact. For example, an attacker steals the administrator's password, or private encryption keys of a web server.", + ), +] + + +def main(): + print(CONFIDENTIALITY_IMPACT_1.to_json(indent=2)) + print(CONFIDENTIALITY_IMPACT_2.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/confidentiality_requirement.py b/src/ssvc/decision_points/cvss/confidentiality_requirement.py new file mode 100644 index 00000000..8af4b8ae --- /dev/null +++ b/src/ssvc/decision_points/cvss/confidentiality_requirement.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +""" +file: confidentiality_requirement +author: adh +created_at: 9/20/23 2:11 PM +""" + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +CONFIDENTIALITY_REQUIREMENT_1 = CvssDecisionPoint( + name="Confidentiality Requirement", + description="This metric measures the impact to the confidentiality of a successfully exploited vulnerability.", + key="CR", + version="1.0.0", + values=[ + SsvcValue( + name="Low", + key="L", + description="Loss of confidentiality is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", + ), + SsvcValue( + name="Medium", + key="M", + description="Loss of confidentiality is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", + ), + SsvcValue( + name="High", + key="H", + description="Loss of confidentiality is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", + ), + SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", + ), + ], +) + + +def main(): + print(CONFIDENTIALITY_REQUIREMENT_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/exploitability.py b/src/ssvc/decision_points/cvss/exploitability.py new file mode 100644 index 00000000..336d671d --- /dev/null +++ b/src/ssvc/decision_points/cvss/exploitability.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +""" +file: exploitability +author: adh +created_at: 9/20/23 1:47 PM +""" + +from copy import deepcopy + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +EXPLOITABILITY_1 = CvssDecisionPoint( + name="Exploitability", + description="This metric measures the current state of exploit technique or code availability and suggests a likelihood of exploitation.", + key="E", + version="1.0.0", + values=[ + SsvcValue( + name="Unproven", + key="U", + description="No exploit code is yet available or an exploit method is entirely theoretical.", + ), + SsvcValue( + name="Proof of Concept", + key="P", + description="Proof of concept exploit code or an attack demonstration that is not practically applicable to deployed systems is available. The code or technique is not functional in all situations and may require substantial hand tuning by a skilled attacker for use against deployed systems.", + ), + SsvcValue( + name="Functional", + key="F", + description="Functional exploit code is available. The code works in most situations where the vulnerability is exploitable.", + ), + SsvcValue( + name="High", + key="H", + description="Either the vulnerability is exploitable by functional mobile autonomous code or no exploit is required (manual trigger) and the details for the manual technique are widely available. The code works in every situation where the vulnerability is exploitable and/or is actively being delivered via a mobile autonomous agent (a worm or virus).", + ), + ], +) + + +# CVSS v2 introduced the concept of "Not Defined" values without changing the other values. +EXPLOITABILITY_1_1 = deepcopy(EXPLOITABILITY_1) +EXPLOITABILITY_1_1.version = "1.1.0" +nd = SsvcValue(name="Not Defined", key="ND", description="Not Defined") +EXPLOITABILITY_1_1.values.append(nd) + + +# CVSS v3 modified the descriptions of the values. +EXPLOIT_CODE_MATURITY_1_1_1 = deepcopy(EXPLOITABILITY_1_1) +EXPLOIT_CODE_MATURITY_1_1_1.name = "Exploit Code Maturity" +EXPLOIT_CODE_MATURITY_1_1_1.description = "measures the likelihood of the vulnerability being attacked, and is typically based on the current state of exploit techniques, exploit code availability, or active, 'in-the-wild' exploitation" +EXPLOIT_CODE_MATURITY_1_1_1.version = "1.1.1" +EXPLOIT_CODE_MATURITY_1_1_1.values = [ + SsvcValue( + name="Unproven", + key="U", + description="No exploit code is available, or an exploit is theoretical.", + ), + SsvcValue( + name="Proof-of-Concept", + key="POC", + description="Proof-of-concept exploit code is available, or an attack demonstration is not practical for most systems. The code or technique is not functional in all situations and may require substantial modification by a skilled attacker.", + ), + SsvcValue( + name="Functional", + key="F", + description="Functional exploit code is available. The code works in most situations where the vulnerability exists.", + ), + SsvcValue( + name="High", + key="H", + description="Functional autonomous code exists, or no exploit is required (manual trigger) and details are widely available. Exploit code works in every situation, or is actively being delivered via an autonomous agent (such as a worm or virus). Network-connected systems are likely to encounter scanning or exploitation attempts. Exploit development has reached the level of reliable, widely-available, easy-to-use automated tools.", + ), + SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to a scoring equation to skip this metric.", + ), +] + + +def main(): + print(EXPLOITABILITY_1.to_json(indent=2)) + print(EXPLOITABILITY_1_1.to_json(indent=2)) + print(EXPLOIT_CODE_MATURITY_1_1_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/impact_bias.py b/src/ssvc/decision_points/cvss/impact_bias.py new file mode 100644 index 00000000..d59a4f9b --- /dev/null +++ b/src/ssvc/decision_points/cvss/impact_bias.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +""" +file: impact_bias +author: adh +created_at: 9/20/23 1:47 PM +""" + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +IMPACT_BIAS_1 = CvssDecisionPoint( + name="Impact Bias", + description="This metric measures the impact bias of the vulnerability.", + key="IB", + version="1.0.0", + values=[ + SsvcValue( + name="Normal", + key="N", + description="Confidentiality Impact, Integrity Impact, and Availability Impact are all assigned the same weight.", + ), + SsvcValue( + name="Confidentiality", + key="C", + description="Confidentiality impact is assigned greater weight than Integrity Impact or Availability Impact.", + ), + SsvcValue( + name="Integrity", + key="I", + description="Integrity Impact is assigned greater weight than Confidentiality Impact or Availability Impact.", + ), + SsvcValue( + name="Availability", + key="A", + description="Availability Impact is assigned greater weight than Confidentiality Impact or Integrity Impact.", + ), + ], +) + + +def main(): + print(IMPACT_BIAS_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/integrity_impact.py b/src/ssvc/decision_points/cvss/integrity_impact.py new file mode 100644 index 00000000..5e77352d --- /dev/null +++ b/src/ssvc/decision_points/cvss/integrity_impact.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +""" +file: integrity_impact +author: adh +created_at: 9/20/23 1:46 PM +""" +from copy import deepcopy +from dataclasses import dataclass, field + +from dataclasses_json import dataclass_json + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +INTEGRITY_IMPACT_1 = CvssDecisionPoint( + name="Integrity Impact", + description="This metric measures the impact on integrity a successful exploit of the vulnerability will have on the target system.", + key="I", + version="1.0.0", + values=[ + SsvcValue(name="None", key="N", description="No impact on integrity."), + SsvcValue( + name="Partial", + key="P", + description="Considerable breach in integrity. Modification of critical system files or information is possible, but the attacker does not have control over what can be modified, or the scope of what the attacker can affect is constrained. For example, key system or program files may be overwritten or modified, but at random or in a limited context or scope.", + ), + SsvcValue( + name="Complete", + key="C", + description="A total compromise of system integrity. There is a complete loss of system protection resulting in the entire system being compromised. The attacker has sovereign control to modify any system files.", + ), + ], +) + +INTEGRITY_IMPACT_2 = deepcopy(INTEGRITY_IMPACT_1) +INTEGRITY_IMPACT_2.version = "2.0.0" +INTEGRITY_IMPACT_2.values = [ + SsvcValue( + name="None", + key="N", + description="There is no impact to the integrity of the system.", + ), + SsvcValue( + name="Low", + key="L", + description="Modification of data is possible, but the attacker does not have control over the consequence of a modification, or the amount of modification is constrained. The data modification does not have a direct, serious impact on the impacted component.", + ), + SsvcValue( + name="High", + key="H", + description="There is a total loss of integrity, or a complete loss of protection. For example, the attacker is able to modify any/all files protected by the impacted component. Alternatively, only some files can be modified, but malicious modification would present a direct, serious consequence to the impacted component.", + ), +] + + +def main(): + print(INTEGRITY_IMPACT_1.to_json(indent=2)) + print(INTEGRITY_IMPACT_2.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/integrity_requirement.py b/src/ssvc/decision_points/cvss/integrity_requirement.py new file mode 100644 index 00000000..9cb1606d --- /dev/null +++ b/src/ssvc/decision_points/cvss/integrity_requirement.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +""" +file: integrity_requirement +author: adh +created_at: 9/20/23 2:11 PM +""" + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +INTEGRITY_REQUIREMENT_1 = CvssDecisionPoint( + name="Integrity Requirement", + description="This metric measures the impact to the integrity of a successfully exploited vulnerability.", + key="CR", + version="1.0.0", + values=[ + SsvcValue( + name="Low", + key="L", + description="Loss of integrity is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", + ), + SsvcValue( + name="Medium", + key="M", + description="Loss of integrity is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", + ), + SsvcValue( + name="High", + key="H", + description="Loss of integrity is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", + ), + SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", + ), + ], +) + + +def main(): + print(INTEGRITY_REQUIREMENT_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/privileges_required.py b/src/ssvc/decision_points/cvss/privileges_required.py new file mode 100644 index 00000000..e2af78e5 --- /dev/null +++ b/src/ssvc/decision_points/cvss/privileges_required.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +""" +file: privileges_required +author: adh +created_at: 9/20/23 2:38 PM +""" + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +PRIVILEGES_REQUIRED_1 = CvssDecisionPoint( + name="Privileges Required", + description="This metric describes the level of privileges an attacker must possess before successfully exploiting the vulnerability.", + key="PR", + version="1.0.0", + values=[ + SsvcValue( + name="None", + key="N", + description="The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files to carry out an attack.", + ), + SsvcValue( + name="Low", + key="L", + description="The attacker is authorized with (i.e. requires) privileges that provide basic user capabilities that could normally affect only settings and files owned by a user. Alternatively, an attacker with Low privileges may have the ability to cause an impact only to non-sensitive resources.", + ), + SsvcValue( + name="High", + key="H", + description="The attacker is authorized with (i.e. requires) privileges that provide significant (e.g. administrative) control over the vulnerable component that could affect component-wide settings and files.", + ), + ], +) + + +def main(): + print(PRIVILEGES_REQUIRED_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/remediation_level.py b/src/ssvc/decision_points/cvss/remediation_level.py new file mode 100644 index 00000000..846cc3ea --- /dev/null +++ b/src/ssvc/decision_points/cvss/remediation_level.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +""" +file: remediation_level +author: adh +created_at: 9/20/23 1:47 PM +""" +from copy import deepcopy + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +REMEDIATION_LEVEL_1 = CvssDecisionPoint( + name="Remediation Level", + description="This metric measures the remediation status of a vulnerability.", + key="RL", + version="1.0.0", + values=[ + SsvcValue( + name="Official Fix", + key="OF", + description="A complete vendor solution is available. Either the vendor has issued the final, official patch which eliminates the vulnerability or an upgrade that is not vulnerable is available.", + ), + SsvcValue( + name="Temporary Fix", + key="TF", + description="There is an official but temporary fix available. This includes instances where the vendor issues a temporary hotfix, tool or official workaround.", + ), + SsvcValue( + name="Workaround", + key="W", + description="There is an unofficial, non-vendor solution available. In some cases, users of the affected technology will create a patch of their own or provide steps to work around or otherwise mitigate against the vulnerability. When it is generally accepted that these unofficial fixes are adequate in plugging the hole for the mean time and no official remediation is available, this value can be set.", + ), + SsvcValue( + name="Unavailable", + key="U", + description="There is either no solution available or it is impossible to apply.", + ), + ], +) + + +REMEDIATION_LEVEL_1_1 = deepcopy(REMEDIATION_LEVEL_1) +REMEDIATION_LEVEL_1_1.version = "1.1.0" +REMEDIATION_LEVEL_1_1.values = [ + SsvcValue( + name="Official Fix", + key="OF", + description="A complete vendor solution is available. Either the vendor has issued the final, official patch which eliminates the vulnerability or an upgrade that is not vulnerable is available.", + ), + SsvcValue( + name="Temporary Fix", + key="TF", + description="There is an official but temporary fix available. This includes instances where the vendor issues a temporary hotfix, tool or official workaround.", + ), + SsvcValue( + name="Workaround", + key="W", + description="There is an unofficial, non-vendor solution available. In some cases, users of the affected technology will create a patch of their own or provide steps to work around or otherwise mitigate against the vulnerability. When it is generally accepted that these unofficial fixes are adequate in plugging the hole for the mean time and no official remediation is available, this value can be set.", + ), + SsvcValue( + name="Unavailable", + key="U", + description="There is either no solution available or it is impossible to apply.", + ), + SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", + ), +] + + +def main(): + print(REMEDIATION_LEVEL_1.to_json(indent=2)) + print(REMEDIATION_LEVEL_1_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/report_confidence.py b/src/ssvc/decision_points/cvss/report_confidence.py new file mode 100644 index 00000000..aed21b22 --- /dev/null +++ b/src/ssvc/decision_points/cvss/report_confidence.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +""" +file: report_confidence +author: adh +created_at: 9/20/23 1:48 PM +""" +from copy import deepcopy + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +REPORT_CONFIDENCE_1 = CvssDecisionPoint( + name="Report Confidence", + description="This metric measures the degree of confidence in the existence of the vulnerability and the credibility of the known technical details.", + key="RC", + version="1.0.0", + values=[ + SsvcValue( + name="Unconfirmed", + key="UC", + description="A single unconfirmed source or possibly several conflicting reports. There is little confidence in the validity of the report. For example, a rumor that surfaces from the hacker underground.", + ), + SsvcValue( + name="Uncorroborated", + key="UR", + description="Multiple non-official sources; possibily including independent security companies or research organizations. At this point there may be conflicting technical details or some other lingering ambiguity.", + ), + SsvcValue( + name="Confirmed", + key="C", + description="Vendor or author of the affected technology has acknowledged that the vulnerability exists. This value may also be set when existence of a vulnerability is confirmed with absolute confidence through some other event, such as publication of functional proof of concept exploit code or widespread exploitation.", + ), + ], +) + +# CVSS v2 added Not Defined. +REPORT_CONFIDENCE_1_1 = deepcopy(REPORT_CONFIDENCE_1) +REPORT_CONFIDENCE_1_1.version = "1.1.0" +nd = SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", +) +REPORT_CONFIDENCE_1_1.values.append(nd) + +# CVSS v3 changed names and descriptions for this metric. +REPORT_CONFIDENCE_2 = deepcopy(REPORT_CONFIDENCE_1_1) +REPORT_CONFIDENCE_2.version = "2.0.0" +REPORT_CONFIDENCE_2.values = [ + SsvcValue( + name="Unknown", + key="U", + description="There are reports of impacts that indicate a vulnerability is present. The reports indicate that the cause of the vulnerability is unknown, or reports may differ on the cause or impacts of the vulnerability. Reporters are uncertain of the true nature of the vulnerability, and there is little confidence in the validity of the reports or whether a static Base score can be applied given the differences described. An example is a bug report which notes that an intermittent but non-reproducible crash occurs, with evidence of memory corruption suggesting that denial of service, or possible more serious impacts, may result.", + ), + SsvcValue( + name="Reasonable", + key="R", + description="Significant details are published, but researchers either do not have full confidence in the root cause, or do not have access to source code to fully confirm all of the interactions that may lead to the result. Reasonable confidence exists, however, that the bug is reproducible and at least one impact is able to be verified (proof-of-concept exploits may provide this). An example is a detailed write-up of research into a vulnerability with an explanation (possibly obfuscated or 'left as an exercise to the reader') that gives assurances on how to reproduce the results.", + ), + SsvcValue( + name="Confirmed", + key="C", + description="Detailed reports exist, or functional reproduction is possible (functional exploits may provide this). Source code is available to independently verify the assertions of the research, or the author or vendor of the affected code has confirmed the presence of the vulnerability.", + ), + SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", + ), +] + + +def main(): + print(REPORT_CONFIDENCE_1.to_json(indent=2)) + print(REPORT_CONFIDENCE_1_1.to_json(indent=2)) + print(REPORT_CONFIDENCE_2.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/scope.py b/src/ssvc/decision_points/cvss/scope.py new file mode 100644 index 00000000..f497d3f2 --- /dev/null +++ b/src/ssvc/decision_points/cvss/scope.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +""" +file: scope +author: adh +created_at: 9/20/23 2:47 PM +""" + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +SCOPE_1 = CvssDecisionPoint( + name="Scope", + description="the ability for a vulnerability in one software component to impact resources beyond its means, or privileges", + key="S", + version="1.0.0", + values=[ + SsvcValue( + name="Unchanged", + key="U", + description="An exploited vulnerability can only affect resources managed by the same authority. In this case the vulnerable component and the impacted component are the same.", + ), + SsvcValue( + name="Changed", + key="C", + description="An exploited vulnerability can affect resources beyond the authorization privileges intended by the vulnerable component. In this case the vulnerable component and the impacted component are different.", + ), + ], +) + + +def main(): + print(SCOPE_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/target_distribution.py b/src/ssvc/decision_points/cvss/target_distribution.py new file mode 100644 index 00000000..2ab3e22d --- /dev/null +++ b/src/ssvc/decision_points/cvss/target_distribution.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +""" +file: target_distribution +author: adh +created_at: 9/20/23 1:48 PM +""" +from copy import deepcopy +from dataclasses import dataclass, field + +from dataclasses_json import dataclass_json + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + +TARGET_DISTRIBUTION_1 = CvssDecisionPoint( + name="Target Distribution", + description="This metric measures the relative size of the field of target systems susceptible to the vulnerability. It is meant as an environment-specific indicator in order to approximate the percentage of systems within the environment that could be affected by the vulnerability.", + key="TD", + version="1.0.0", + values=[ + SsvcValue( + name="None", + key="N", + description="No target systems exist, or targets are so highly specialized that they only exist in a laboratory setting. Effectively 0% of the environment is at risk.", + ), + SsvcValue( + name="Low", + key="L", + description="Targets exist inside the environment, but on a small scale. Between 1% - 15% of the total environment is at risk.", + ), + SsvcValue( + name="Medium", + key="M", + description="Targets exist inside the environment, but on a medium scale. Between 16% - 49% of the total environment is at risk.", + ), + SsvcValue( + name="High", + key="H", + description="Targets exist inside the environment on a considerable scale. Between 50% - 100% of the total environment is considered at risk.", + ), + ], +) + +TARGET_DISTRIBUTION_1_1 = deepcopy(TARGET_DISTRIBUTION_1) +TARGET_DISTRIBUTION_1_1.version = "1.1.0" +TARGET_DISTRIBUTION_1_1.values = [ + SsvcValue( + name="None", + key="N", + description="No target systems exist, or targets are so highly specialized that they only exist in a laboratory setting. Effectively 0% of the environment is at risk.", + ), + SsvcValue( + name="Low", + key="L", + description="Targets exist inside the environment, but on a small scale. Between 1% - 15% of the total environment is at risk.", + ), + SsvcValue( + name="Medium", + key="M", + description="Targets exist inside the environment, but on a medium scale. Between 16% - 49% of the total environment is at risk.", + ), + SsvcValue( + name="High", + key="H", + description="Targets exist inside the environment on a considerable scale. Between 50% - 100% of the total environment is considered at risk.", + ), + SsvcValue( + name="Not Defined", + key="ND", + description="This value is not defined in any specification, but is used in the CVSS v3.0 vector string when a value is required by the specification, but cannot be provided. This is a special case value and should be used sparingly.", + ), +] + + +def main(): + print(TARGET_DISTRIBUTION_1.to_json(indent=2)) + print(TARGET_DISTRIBUTION_1_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/user_interaction.py b/src/ssvc/decision_points/cvss/user_interaction.py new file mode 100644 index 00000000..b3c8a7ce --- /dev/null +++ b/src/ssvc/decision_points/cvss/user_interaction.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +""" +file: user_interaction +author: adh +created_at: 9/20/23 2:38 PM +""" + +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint + + +USER_INTERACTION_1 = CvssDecisionPoint( + name="User Interaction", + description="This metric captures the requirement for a user, other than the attacker, to participate in the successful compromise of the vulnerable component.", + key="UI", + version="1.0.0", + values=[ + SsvcValue( + name="None", + key="N", + description="The vulnerable system can be exploited without interaction from any user.", + ), + SsvcValue( + name="Required", + key="R", + description="Successful exploitation of this vulnerability requires a user to take some action before the vulnerability can be exploited.", + ), + ], +) + + +def main(): + print(USER_INTERACTION_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/v1.py b/src/ssvc/decision_points/cvss/v1.py new file mode 100644 index 00000000..8cf099df --- /dev/null +++ b/src/ssvc/decision_points/cvss/v1.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +""" +file: v1 +author: adh +created_at: 9/20/23 12:39 PM +""" + +from ssvc.decision_points.base import SsvcDecisionPointGroup +from ssvc.decision_points.cvss.access_complexity import ( + ACCESS_COMPLEXITY_1 as ACCESS_COMPLEXITY, +) +from ssvc.decision_points.cvss.access_vector import ACCESS_VECTOR_1 as ACCESS_VECTOR +from ssvc.decision_points.cvss.authentication import AUTHENTICATION_1 as AUTHENTICATION +from ssvc.decision_points.cvss.availability_impact import ( + AVAILABILITY_IMPACT_1 as AVAILABILITY_IMPACT, +) +from ssvc.decision_points.cvss.collateral_damage_potential import ( + COLLATERAL_DAMAGE_POTENTIAL_1 as COLLATERAL_DAMAGE_POTENTIAL, +) +from ssvc.decision_points.cvss.confidentiality_impact import ( + CONFIDENTIALITY_IMPACT_1 as CONFIDENTIALITY_IMPACT, +) +from ssvc.decision_points.cvss.exploitability import ( + EXPLOITABILITY_1 as EXPLOITABILITY, +) +from ssvc.decision_points.cvss.impact_bias import IMPACT_BIAS +from ssvc.decision_points.cvss.integrity_impact import ( + INTEGRITY_IMPACT_1 as INTEGRITY_IMPACT, +) +from ssvc.decision_points.cvss.remediation_level import ( + REMEDIATION_LEVEL_1 as REMEDIATION_LEVEL, +) +from ssvc.decision_points.cvss.report_confidence import ( + REPORT_CONFIDENCE_1 as REPORT_CONFIDENCE, +) +from ssvc.decision_points.cvss.target_distribution import ( + TARGET_DISTRIBUTION_1 as TARGET_DISTRIBUTION, +) + + +# Instantiate the CVSS v1 decision point group +CVSSv1 = SsvcDecisionPointGroup( + name="CVSS", + version="1.0", + key="CVSSv1", + description="CVSS v1 decision points", + decision_points=[ + ACCESS_VECTOR, + ACCESS_COMPLEXITY, + AUTHENTICATION, + CONFIDENTIALITY_IMPACT, + INTEGRITY_IMPACT, + AVAILABILITY_IMPACT, + IMPACT_BIAS, + EXPLOITABILITY, + REMEDIATION_LEVEL, + REPORT_CONFIDENCE, + COLLATERAL_DAMAGE_POTENTIAL, + TARGET_DISTRIBUTION, + ], +) + + +def main(): + print(CVSSv1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/v2.py b/src/ssvc/decision_points/cvss/v2.py new file mode 100644 index 00000000..cb14cf1b --- /dev/null +++ b/src/ssvc/decision_points/cvss/v2.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +""" +file: v2 +author: adh +created_at: 9/20/23 12:54 PM +""" + +from ssvc.decision_points.base import SsvcDecisionPointGroup +from ssvc.decision_points.cvss.access_vector import ACCESS_VECTOR_2 as ACCESS_VECTOR +from ssvc.decision_points.cvss.access_complexity import ( + ACCESS_COMPLEXITY_2 as ACCESS_COMPLEXITY, +) +from ssvc.decision_points.cvss.authentication import AUTHENTICATION_2 as AUTHENTICATION +from ssvc.decision_points.cvss.availability_requirement import ( + AVAILABILITY_REQUIREMENT_1 as AVAILABILITY_REQUIREMENT, +) +from ssvc.decision_points.cvss.collateral_damage_potential import ( + COLLATERAL_DAMAGE_POTENTIAL_2 as COLLATERAL_DAMAGE_POTENTIAL, +) +from ssvc.decision_points.cvss.confidentiality_impact import ( + CONFIDENTIALITY_IMPACT_1 as CONFIDENTIALITY_IMPACT, +) +from ssvc.decision_points.cvss.confidentiality_requirement import ( + CONFIDENTIALITY_REQUIREMENT_1 as CONFIDENTIALITY_REQUIREMENT, +) +from ssvc.decision_points.cvss.exploitability import ( + EXPLOITABILITY_1_1 as EXPLOITABILITY, +) +from ssvc.decision_points.cvss.integrity_impact import ( + INTEGRITY_IMPACT_1 as INTEGRITY_IMPACT, +) +from ssvc.decision_points.cvss.availability_impact import ( + AVAILABILITY_IMPACT_1 as AVAILABILITY_IMPACT, +) +from ssvc.decision_points.cvss.integrity_requirement import ( + INTEGRITY_REQUIREMENT_1 as INTEGRITY_REQUIREMENT, +) +from ssvc.decision_points.cvss.remediation_level import ( + REMEDIATION_LEVEL_1_1 as REMEDIATION_LEVEL, +) +from ssvc.decision_points.cvss.report_confidence import ( + REPORT_CONFIDENCE_1_1 as REPORT_CONFIDENCE, +) +from ssvc.decision_points.cvss.target_distribution import ( + TARGET_DISTRIBUTION_1_1 as TARGET_DISTRIBUTION, +) + +CVSSv2 = SsvcDecisionPointGroup( + name="CVSS Version 2", + description="The Common Vulnerability Scoring System (CVSS) is a free and open industry standard for assessing the severity of computer system security vulnerabilities. CVSS attempts to assign severity scores to vulnerabilities, allowing responders to prioritize responses and resources according to threat. Scores are calculated based on a formula that depends on several metrics that approximate ease of exploit and the impact of exploit. Scores range from 0 to 10, with 10 being the most severe.", + key="CVSSv2", + version="2.0", + decision_points=[ + ACCESS_VECTOR, + ACCESS_COMPLEXITY, + AUTHENTICATION, + CONFIDENTIALITY_IMPACT, + INTEGRITY_IMPACT, + AVAILABILITY_IMPACT, + EXPLOITABILITY, + REMEDIATION_LEVEL, + REPORT_CONFIDENCE, + COLLATERAL_DAMAGE_POTENTIAL, + TARGET_DISTRIBUTION, + CONFIDENTIALITY_REQUIREMENT, + INTEGRITY_REQUIREMENT, + AVAILABILITY_REQUIREMENT, + ], +) + + +def main(): + print(CVSSv2.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/v3.py b/src/ssvc/decision_points/cvss/v3.py new file mode 100644 index 00000000..e64ed821 --- /dev/null +++ b/src/ssvc/decision_points/cvss/v3.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +""" +file: v3 +author: adh +created_at: 9/20/23 2:30 PM +""" +from copy import deepcopy + +from ssvc.decision_points.base import SsvcDecisionPointGroup, SsvcValue +from ssvc.decision_points.cvss.attack_complexity import ( + ATTACK_COMPLEXITY_1 as ATTACK_COMPLEXITY, +) +from ssvc.decision_points.cvss.attack_vector import ATTACK_VECTOR_1 as ATTACK_VECTOR +from ssvc.decision_points.cvss.availability_impact import ( + AVAILABILITY_IMPACT_2 as AVAILABILITY_IMPACT, +) +from ssvc.decision_points.cvss.availability_requirement import ( + AVAILABILITY_REQUIREMENT_1 as AVAILABILITY_REQUIREMENT, +) +from ssvc.decision_points.cvss.confidentiality_impact import ( + CONFIDENTIALITY_IMPACT_2 as CONFIDENTIALITY_IMPACT, +) +from ssvc.decision_points.cvss.confidentiality_requirement import ( + CONFIDENTIALITY_REQUIREMENT_1 as CONFIDENTIALITY_REQUIREMENT, +) +from ssvc.decision_points.cvss.exploitability import ( + EXPLOIT_CODE_MATURITY_1_1_1 as EXPLOIT_CODE_MATURITY, +) +from ssvc.decision_points.cvss.integrity_impact import ( + INTEGRITY_IMPACT_2 as INTEGRITY_IMPACT, +) +from ssvc.decision_points.cvss.integrity_requirement import ( + INTEGRITY_REQUIREMENT_1 as INTEGRITY_REQUIREMENT, +) +from ssvc.decision_points.cvss.privileges_required import ( + PRIVILEGES_REQUIRED_1 as PRIVILEGES_REQUIRED, +) +from ssvc.decision_points.cvss.remediation_level import ( + REMEDIATION_LEVEL_1_1 as REMEDIATION_LEVEL, +) +from ssvc.decision_points.cvss.report_confidence import ( + REPORT_CONFIDENCE_2 as REPORT_CONFIDENCE, +) +from ssvc.decision_points.cvss.scope import SCOPE_1 as SCOPE +from ssvc.decision_points.cvss.user_interaction import ( + USER_INTERACTION_1 as USER_INTERACTION, +) + + +def _modify(obj): + """ + Prepends "Modified " to the name and "M" to the key of the given object. Also adds a value of "Not Defined" to the + values list. + :param obj: the object to modify + :return: the object + """ + o = deepcopy(obj) + o.name = "Modified " + o.name + o.key = "M" + o.key + nd = SsvcValue(name="Not Defined", key="ND", description="Ignore this value") + o.values.append(nd) + return o + + +MODIFIED_ATTACK_VECTOR = _modify(ATTACK_VECTOR) +MODIFIED_ATTACK_COMPLEXITY = _modify(ATTACK_COMPLEXITY) +MODIFIED_PRIVILEGES_REQUIRED = _modify(PRIVILEGES_REQUIRED) +MODIFIED_USER_INTERACTION = _modify(USER_INTERACTION) +MODIFIED_SCOPE = _modify(SCOPE) +MODIFIED_CONFIDENTIALITY_IMPACT = _modify(CONFIDENTIALITY_IMPACT) +MODIFIED_INTEGRITY_IMPACT = _modify(INTEGRITY_IMPACT) +MODIFIED_AVAILABILITY_IMPACT = _modify(AVAILABILITY_IMPACT) + + +CVSSv3 = SsvcDecisionPointGroup( + name="CVSS Version 3", + description="The Common Vulnerability Scoring System (CVSS) is a free and open industry standard for assessing the severity of computer system security vulnerabilities. CVSS attempts to assign severity scores to vulnerabilities, allowing responders to prioritize responses and resources according to threat. Scores are calculated based on a formula that depends on several metrics that approximate ease of exploit and the impact of exploit. Scores range from 0 to 10, with 10 being the most severe.", + key="CVSSv3", + version="3.0", + decision_points=[ + ATTACK_VECTOR, + ATTACK_COMPLEXITY, + PRIVILEGES_REQUIRED, + USER_INTERACTION, + SCOPE, + CONFIDENTIALITY_IMPACT, + INTEGRITY_IMPACT, + AVAILABILITY_IMPACT, + EXPLOIT_CODE_MATURITY, + REMEDIATION_LEVEL, + REPORT_CONFIDENCE, + CONFIDENTIALITY_REQUIREMENT, + INTEGRITY_REQUIREMENT, + AVAILABILITY_REQUIREMENT, + MODIFIED_ATTACK_VECTOR, + MODIFIED_ATTACK_COMPLEXITY, + MODIFIED_PRIVILEGES_REQUIRED, + MODIFIED_USER_INTERACTION, + MODIFIED_SCOPE, + MODIFIED_CONFIDENTIALITY_IMPACT, + MODIFIED_INTEGRITY_IMPACT, + MODIFIED_AVAILABILITY_IMPACT, + ], +) + + +def main(): + print(CVSSv3.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decisionpoints.py b/src/ssvc/decisionpoints.py deleted file mode 100644 index a5f42134..00000000 --- a/src/ssvc/decisionpoints.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python -''' -file: decisionpoints -author: adh -created_at: 9/20/23 10:07 AM -''' - -from dataclasses import dataclass -from typing import List - -from dataclasses_json import dataclass_json - -## notes -# based on https://gist.githubusercontent.com/sei-vsarvepalli/de2cd7dae33e1e1dc906d50846becb45/raw/2a85d08a4028f6dd3cc162d4ace3509e0458426f/Exploitation.json - -@dataclass_json -@dataclass(kw_only=True) -class SsvcBase: - name: str - description: str - key: str - namespace: str="ssvc" - -@dataclass_json -@dataclass(kw_only=True) -class SsvcVersionedBase(SsvcBase): - version: str - -@dataclass_json -@dataclass(kw_only=True) -class SsvcOption(SsvcBase): - pass - -@dataclass_json -@dataclass(kw_only=True) -class SsvcDecisionPoint(SsvcVersionedBase): - values: List[SsvcOption] - -@dataclass_json -@dataclass(kw_only=True) -class SsvcDecisionPointGroup(SsvcVersionedBase): - decision_points: List[SsvcDecisionPoint] - - -def main(): - opt_none = SsvcOption(name="None", key="N", description="No exploit available") - opt_poc = SsvcOption(name="PoC", key="P", description="Proof of concept exploit available") - opt_active= SsvcOption(name="Active", key="A", description="Active exploitation observed") - opts = [opt_none, opt_poc, opt_active] - - dp = SsvcDecisionPoint(values=opts, name="Exploitation", description="Is there an exploit available?", key="E", version="1.0.0") - - dpg = SsvcDecisionPointGroup(name="DPgroup", description="A group of decision points", key="DPG1", version="1.0.0", decision_points=[dp]) - print(dpg.to_json(indent=2)) - -if __name__ == '__main__': - main() From 4fea25de4ffa14381fef768223d1150fa3391cb1 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 20 Sep 2023 16:54:44 -0400 Subject: [PATCH 04/53] add decision point groups package, rearrange modules --- src/ssvc/_mixins.py | 49 +++++++++++++++++ src/ssvc/decision_points/base.py | 54 ++----------------- .../decision_points/cvss/report_confidence.py | 6 +-- src/ssvc/dp_groups/__init__.py | 14 +++++ src/ssvc/dp_groups/base.py | 31 +++++++++++ src/ssvc/dp_groups/cvss/__init__.py | 14 +++++ .../{decision_points => dp_groups}/cvss/v1.py | 2 +- .../{decision_points => dp_groups}/cvss/v2.py | 2 +- .../{decision_points => dp_groups}/cvss/v3.py | 3 +- 9 files changed, 117 insertions(+), 58 deletions(-) create mode 100644 src/ssvc/_mixins.py create mode 100644 src/ssvc/dp_groups/__init__.py create mode 100644 src/ssvc/dp_groups/base.py create mode 100644 src/ssvc/dp_groups/cvss/__init__.py rename src/ssvc/{decision_points => dp_groups}/cvss/v1.py (96%) rename src/ssvc/{decision_points => dp_groups}/cvss/v2.py (97%) rename src/ssvc/{decision_points => dp_groups}/cvss/v3.py (97%) diff --git a/src/ssvc/_mixins.py b/src/ssvc/_mixins.py new file mode 100644 index 00000000..744a855f --- /dev/null +++ b/src/ssvc/_mixins.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +""" +file: _basics +author: adh +created_at: 9/20/23 4:51 PM +""" +from dataclasses import dataclass + +from dataclasses_json import dataclass_json + + +def main(): + pass + + +if __name__ == "__main__": + main() + + +@dataclass_json +@dataclass(kw_only=True) +class _Versioned: + """ + Mixin class for versioned SSVC objects. + """ + + version: str = "0.0.0" + + +@dataclass_json +@dataclass(kw_only=True) +class _Namespaced: + """ + Mixin class for namespaced SSVC objects. + """ + + namespace: str = "ssvc" + + +@dataclass_json +@dataclass(kw_only=True) +class _Base: + """ + Base class for SSVC objects. + """ + + name: str + description: str + key: str diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index 130ec008..2747288c 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -10,40 +10,11 @@ from dataclasses_json import dataclass_json -## notes -# based on https://gist.githubusercontent.com/sei-vsarvepalli/de2cd7dae33e1e1dc906d50846becb45/raw/2a85d08a4028f6dd3cc162d4ace3509e0458426f/Exploitation.json - - -@dataclass_json -@dataclass(kw_only=True) -class _Base: - """ - Base class for SSVC objects. - """ - - name: str - description: str - key: str - - -@dataclass_json -@dataclass(kw_only=True) -class _Versioned: - """ - Mixin class for versioned SSVC objects. - """ - - version: str = "0.0.0" +from ssvc._mixins import _Base, _Namespaced, _Versioned -@dataclass_json -@dataclass(kw_only=True) -class _Namespaced: - """ - Mixin class for namespaced SSVC objects. - """ - - namespace: str = "ssvc" +## notes +# based on https://gist.githubusercontent.com/sei-vsarvepalli/de2cd7dae33e1e1dc906d50846becb45/raw/2a85d08a4028f6dd3cc162d4ace3509e0458426f/Exploitation.json @dataclass_json @@ -66,16 +37,6 @@ class SsvcDecisionPoint(_Base, _Versioned, _Namespaced): values: List[SsvcValue] -@dataclass_json -@dataclass(kw_only=True) -class SsvcDecisionPointGroup(_Base, _Versioned): - """ - Models a group of decision points. - """ - - decision_points: List[SsvcDecisionPoint] - - def main(): opt_none = SsvcValue(name="None", key="N", description="No exploit available") opt_poc = SsvcValue( @@ -94,14 +55,7 @@ def main(): version="1.0.0", ) - dpg = SsvcDecisionPointGroup( - name="DPgroup", - description="A group of decision points", - key="DPG1", - version="1.0.0", - decision_points=[dp], - ) - print(dpg.to_json(indent=2)) + print(dp.to_json(indent=2)) if __name__ == "__main__": diff --git a/src/ssvc/decision_points/cvss/report_confidence.py b/src/ssvc/decision_points/cvss/report_confidence.py index aed21b22..37e8fcea 100644 --- a/src/ssvc/decision_points/cvss/report_confidence.py +++ b/src/ssvc/decision_points/cvss/report_confidence.py @@ -62,11 +62,7 @@ key="C", description="Detailed reports exist, or functional reproduction is possible (functional exploits may provide this). Source code is available to independently verify the assertions of the research, or the author or vendor of the affected code has confirmed the presence of the vulnerability.", ), - SsvcValue( - name="Not Defined", - key="ND", - description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", - ), + deepcopy(nd), ] diff --git a/src/ssvc/dp_groups/__init__.py b/src/ssvc/dp_groups/__init__.py new file mode 100644 index 00000000..a3a2a181 --- /dev/null +++ b/src/ssvc/dp_groups/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +file: __init__.py +author: adh +created_at: 9/20/23 4:47 PM +""" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/base.py b/src/ssvc/dp_groups/base.py new file mode 100644 index 00000000..26440a37 --- /dev/null +++ b/src/ssvc/dp_groups/base.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +""" +file: base +author: adh +created_at: 9/20/23 4:47 PM +""" +from dataclasses import dataclass +from typing import List + +from dataclasses_json import dataclass_json + +from ssvc._mixins import _Base, _Versioned +from ssvc.decision_points.base import SsvcDecisionPoint + + +@dataclass_json +@dataclass(kw_only=True) +class SsvcDecisionPointGroup(_Base, _Versioned): + """ + Models a group of decision points. + """ + + decision_points: List[SsvcDecisionPoint] + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/cvss/__init__.py b/src/ssvc/dp_groups/cvss/__init__.py new file mode 100644 index 00000000..41ce5469 --- /dev/null +++ b/src/ssvc/dp_groups/cvss/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +file: __init__.py +author: adh +created_at: 9/20/23 4:54 PM +""" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/cvss/v1.py b/src/ssvc/dp_groups/cvss/v1.py similarity index 96% rename from src/ssvc/decision_points/cvss/v1.py rename to src/ssvc/dp_groups/cvss/v1.py index 8cf099df..56ea9915 100644 --- a/src/ssvc/decision_points/cvss/v1.py +++ b/src/ssvc/dp_groups/cvss/v1.py @@ -5,7 +5,7 @@ created_at: 9/20/23 12:39 PM """ -from ssvc.decision_points.base import SsvcDecisionPointGroup +from ssvc.dp_groups.base import SsvcDecisionPointGroup from ssvc.decision_points.cvss.access_complexity import ( ACCESS_COMPLEXITY_1 as ACCESS_COMPLEXITY, ) diff --git a/src/ssvc/decision_points/cvss/v2.py b/src/ssvc/dp_groups/cvss/v2.py similarity index 97% rename from src/ssvc/decision_points/cvss/v2.py rename to src/ssvc/dp_groups/cvss/v2.py index cb14cf1b..1e2d409a 100644 --- a/src/ssvc/decision_points/cvss/v2.py +++ b/src/ssvc/dp_groups/cvss/v2.py @@ -5,7 +5,7 @@ created_at: 9/20/23 12:54 PM """ -from ssvc.decision_points.base import SsvcDecisionPointGroup +from ssvc.dp_groups.base import SsvcDecisionPointGroup from ssvc.decision_points.cvss.access_vector import ACCESS_VECTOR_2 as ACCESS_VECTOR from ssvc.decision_points.cvss.access_complexity import ( ACCESS_COMPLEXITY_2 as ACCESS_COMPLEXITY, diff --git a/src/ssvc/decision_points/cvss/v3.py b/src/ssvc/dp_groups/cvss/v3.py similarity index 97% rename from src/ssvc/decision_points/cvss/v3.py rename to src/ssvc/dp_groups/cvss/v3.py index e64ed821..a4bd651a 100644 --- a/src/ssvc/decision_points/cvss/v3.py +++ b/src/ssvc/dp_groups/cvss/v3.py @@ -6,7 +6,8 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcDecisionPointGroup, SsvcValue +from ssvc.decision_points.base import SsvcValue +from ssvc.dp_groups.base import SsvcDecisionPointGroup from ssvc.decision_points.cvss.attack_complexity import ( ATTACK_COMPLEXITY_1 as ATTACK_COMPLEXITY, ) From ad2d9fffe4cd3ba3f2c413ddf6526435b8ee1ce4 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 09:49:11 -0400 Subject: [PATCH 05/53] add exploitation --- src/ssvc/decision_points/exploitation.py | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/ssvc/decision_points/exploitation.py diff --git a/src/ssvc/decision_points/exploitation.py b/src/ssvc/decision_points/exploitation.py new file mode 100644 index 00000000..8233af17 --- /dev/null +++ b/src/ssvc/decision_points/exploitation.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +""" +file: exploitation +author: adh +created_at: 9/20/23 11:41 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + + +def _strip_spaces(s): + return " ".join([x.strip() for x in s.splitlines()]) + + +EXPLOITATION_1 = SsvcDecisionPoint( + name="Exploitation", + description="The present state of exploitation of the vulnerability.", + key="E", + version="1.0.0", + values=[ + SsvcValue( + name="None", + key="N", + description="There is no evidence of active exploitation and no public proof of concept (PoC) of how to exploit the vulnerability.", + ), + SsvcValue( + name="PoC", + key="P", + description="One of the following cases is true: (1) private evidence of exploitation is attested but not shared; " + "(2) widespread hearsay attests to exploitation; (3) typical public PoC in places such as Metasploit" + " or ExploitDB; or (4) the vulnerability has a well-known method of exploitation.", + ), + SsvcValue( + name="Active", + key="A", + description="Shared, observable, reliable evidence that the exploit is being" + " used in the wild by real attackers; there is credible public reporting.", + ), + ], +) + + +def main(): + print(EXPLOITATION_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() From 3375011cf038f8e9f4c99c6c75f01e7d98b26b49 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 09:49:30 -0400 Subject: [PATCH 06/53] add docstring --- src/ssvc/decision_points/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/ssvc/decision_points/__init__.py b/src/ssvc/decision_points/__init__.py index d4ce8764..7018304c 100644 --- a/src/ssvc/decision_points/__init__.py +++ b/src/ssvc/decision_points/__init__.py @@ -1,5 +1,23 @@ #!/usr/bin/env python """ +The ssvc.decision_points package provides a set of decision points for use in SSVC decision functions. + +Decision points are the basic building blocks of SSVC decision functions. Individual decision points describe a +single aspect of the input to a decision function. Decision points should have the following characteristics: + +- A name or label +- A description +- A version (a semantic version string) +- A namespace (a short, unique string): For example, "ssvc" or "cvss" to indicate the source of the decision point +- A key (a short, unique string) that can be used to identify the decision point in a shorthand way +- A short enumeration of possible values + +In turn, each value should have the following characteristics: +- A name or label +- A description +- A key (a short, unique string) that can be used to identify the value in a shorthand way + + file: __init__.py author: adh created_at: 9/20/23 12:38 PM From c1cb8439470a251be00c6f3a1c82c982a3f559e5 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 10:01:24 -0400 Subject: [PATCH 07/53] add technical impact, utility, virulence --- src/ssvc/decision_points/technical_impact.py | 40 +++++++++++++++++++ src/ssvc/decision_points/utility.py | 37 +++++++++++++++++ src/ssvc/decision_points/virulence.py | 42 ++++++++++++++++++++ src/ssvc/dp_groups/v1.py | 32 +++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 src/ssvc/decision_points/technical_impact.py create mode 100644 src/ssvc/decision_points/utility.py create mode 100644 src/ssvc/decision_points/virulence.py create mode 100644 src/ssvc/dp_groups/v1.py diff --git a/src/ssvc/decision_points/technical_impact.py b/src/ssvc/decision_points/technical_impact.py new file mode 100644 index 00000000..1fcb563c --- /dev/null +++ b/src/ssvc/decision_points/technical_impact.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +""" +file: technical_impact +author: adh +created_at: 9/21/23 9:49 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +# Partial The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control. In this context, “low” means that the attacker cannot reasonably make enough attempts to overcome the low chance of each attempt +# not working. Denial of service is a form of limited control over the behavior of the vulnerable +# component. +# Total The exploit gives the adversary total control over the behavior of the software, or it gives total +# disclosure of all information on the system that contains the vulnerability + +TECHNICAL_IMPACT_1 = SsvcDecisionPoint( + name="Technical Impact", + description="The technical impact of the vulnerability.", + key="TI", + version="1.0.0", + values=[ + SsvcValue( + name="Partial", + key="P", + description="The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control. In this context, “low” means that the attacker cannot reasonably make enough attempts to overcome the low chance of each attempt not working. Denial of service is a form of limited control over the behavior of the vulnerable component.", + ), + SsvcValue( + name="Total", + key="T", + description="The exploit gives the adversary total control over the behavior of the software, or it gives total disclosure of all information on the system that contains the vulnerability.", + ), + ], +) + + +def main(): + print(TECHNICAL_IMPACT_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/utility.py b/src/ssvc/decision_points/utility.py new file mode 100644 index 00000000..1b7f49c1 --- /dev/null +++ b/src/ssvc/decision_points/utility.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +""" +file: utility +author: adh +created_at: 9/21/23 9:55 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +UTILITY_1 = SsvcDecisionPoint( + name="Utility", + description="The Usefulness of the Exploit to the Adversary", + key="U", + version="1.0.0", + values=[ + SsvcValue( + name="Laborious", key="L", description="Slow virulence and diffuse value" + ), + SsvcValue( + name="Efficient", + key="E", + description="Rapid virulence and diffuse value OR Slow virulence and concentrated value", + ), + SsvcValue( + name="Super Effective", + key="S", + description="Rapid virulence and concentrated value", + ), + ], +) + + +def main(): + print(UTILITY_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/virulence.py b/src/ssvc/decision_points/virulence.py new file mode 100644 index 00000000..42755d5f --- /dev/null +++ b/src/ssvc/decision_points/virulence.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +""" +file: virulence +author: adh +created_at: 9/21/23 9:58 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +# Virulence is described as slow or rapid: +# • Slow. Steps 1-4 of the kill chain12F +# 12 cannot be reliably automated for this vulnerability for some +# reason. These steps are reconnaissance, weaponization, delivery, and exploitation. Example reasons for why a step may not be reliably automatable include (1) the vulnerable component is not +# searchable or enumerable on the network, (2) weaponization may require human direction for +# each target, (3) delivery may require channels that widely deployed network security configurations block, and (3) exploitation may be frustrated by adequate exploit-prevention techniques enabled by default; ASLR is an example of an exploit-prevention tool. +# • Rapid. Steps 1-4 of the of the kill chain can be reliably automated. If the vulnerability allows remote code execution or command injection, the default response should be rapid. + +VIRULENCE_1 = SsvcDecisionPoint( + name="Virulence", + description="The speed at which the vulnerability can be exploited.", + key="V", + version="1.0.0", + values=[ + SsvcValue( + name="Slow", + key="S", + description="Steps 1-4 of the kill chain cannot be reliably automated for this vulnerability for some reason. These steps are reconnaissance, weaponization, delivery, and exploitation. Example reasons for why a step may not be reliably automatable include (1) the vulnerable component is not searchable or enumerable on the network, (2) weaponization may require human direction for each target, (3) delivery may require channels that widely deployed network security configurations block, and (3) exploitation may be frustrated by adequate exploit-prevention techniques enabled by default; ASLR is an example of an exploit-prevention tool.", + ), + SsvcValue( + name="Rapid", + key="R", + description="Steps 1-4 of the of the kill chain can be reliably automated. If the vulnerability allows remote code execution or command injection, the default response should be rapid.", + ), + ], +) + + +def main(): + print(VIRULENCE_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py new file mode 100644 index 00000000..e0de50bd --- /dev/null +++ b/src/ssvc/dp_groups/v1.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +""" +file: v1 +author: adh +created_at: 9/21/23 9:52 AM +""" +from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT +from ssvc.decision_points.virulence import VIRULENCE_1 as VIRULENCE +from ssvc.dp_groups.base import SsvcDecisionPointGroup +from ssvc.decision_points.utility import UTILITY_1 as UTILITY + +SSVCv1 = SsvcDecisionPointGroup( + name="SSVCv1", + description="The first version of the SSVC.", + key="SSVCv1", + version="1.0.0", + decision_points=[ + EXPLOITATION, + TECHNICAL_IMPACT, + UTILITY, + VIRULENCE, + ], +) + + +def main(): + print(SSVCv1.to_json(indent=2)) + + +if __name__ == "__main__": + main() From 0d4f8a7b354e6b269f4914389e71a332dbada577 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 10:04:42 -0400 Subject: [PATCH 08/53] add value density --- src/ssvc/decision_points/value_density.py | 46 +++++++++++++++++++++++ src/ssvc/dp_groups/v1.py | 2 + 2 files changed, 48 insertions(+) create mode 100644 src/ssvc/decision_points/value_density.py diff --git a/src/ssvc/decision_points/value_density.py b/src/ssvc/decision_points/value_density.py new file mode 100644 index 00000000..82c2978e --- /dev/null +++ b/src/ssvc/decision_points/value_density.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +""" +file: value_density +author: adh +created_at: 9/21/23 10:01 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +# Diffuse. The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively +# small. Examples of systems with diffuse value are email accounts, most consumer online banking +# accounts, common cell phones, and most personal computing resources owned and maintained by +# users. (A “user” is anyone whose professional task is something other than the maintenance of the +# system or component. As with safety impact, a “system operator” is anyone who is professionally +# responsible for the proper operation or maintenance of a system.) +# • Concentrated. The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of “system operators” rather than users. Examples of concentrated value are database systems, Kerberos servers, web servers hosting login +# pages, and cloud service providers. However, usefulness and uniqueness of the resources on the +# vulnerable system also inform value density. For example, encrypted mobile messaging platforms +# may have concentrated value, not because each phone’s messaging history has a particularly large +# amount of data, but because it is uniquely valuable to law enforcement. + +VALUE_DENSITY_1 = SsvcDecisionPoint( + name="Value Density", + description="The concentration of value in the target", + key="VD", + version="1.0.0", + values=[ + SsvcValue( + name="Diffuse", + key="D", + description="The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively small.", + ), + SsvcValue( + name="Concentrated", + key="C", + description="The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of “system operators” rather than users.", + ), + ], +) + + +def main(): + print(VALUE_DENSITY_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py index e0de50bd..a8aca075 100644 --- a/src/ssvc/dp_groups/v1.py +++ b/src/ssvc/dp_groups/v1.py @@ -6,6 +6,7 @@ """ from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT +from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY from ssvc.decision_points.virulence import VIRULENCE_1 as VIRULENCE from ssvc.dp_groups.base import SsvcDecisionPointGroup from ssvc.decision_points.utility import UTILITY_1 as UTILITY @@ -20,6 +21,7 @@ TECHNICAL_IMPACT, UTILITY, VIRULENCE, + VALUE_DENSITY, ], ) From 9f8f0d529d0b7e87d5ceab1c2800c2584316ee25 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 10:16:33 -0400 Subject: [PATCH 09/53] add safety impact --- src/ssvc/decision_points/safety_impact.py | 148 ++++++++++++++++++++++ src/ssvc/dp_groups/v1.py | 2 + 2 files changed, 150 insertions(+) create mode 100644 src/ssvc/decision_points/safety_impact.py diff --git a/src/ssvc/decision_points/safety_impact.py b/src/ssvc/decision_points/safety_impact.py new file mode 100644 index 00000000..cbd2927e --- /dev/null +++ b/src/ssvc/decision_points/safety_impact.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +""" +file: safety_impact +author: adh +created_at: 9/21/23 10:05 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +# Safety Impact16F +# 16 Type of Harm Description +# None All Does not mean no impact literally; it just means that the effect is below the threshold for all aspects +# described in Minor +# Minor +# (Any one or +# more of these +# conditions +# hold.) +# Physical harm Physical discomfort for users (not operators) of the system +# Operator +# resiliency +# Requires action by system operator to maintain safe system state as a result of exploitation of the +# vulnerability where operator actions would be well within expected operator abilities; OR causes a +# minor occupational safety hazard +# System +# resiliency +# Small reduction in built-in system safety margins; OR small reduction in system functional capabilities that support safe operation +# Environment Minor externalities (property damage, environmental damage, etc.) imposed on other parties +# Financial Financial losses, which are not readily absorbable, to multiple persons +# Psychological Emotional or psychological harm, sufficient to be cause for counselling or therapy, to multiple persons +# Safety Impact16F +# 16 Type of Harm Description +# Major +# (Any one or +# more of these +# conditions +# hold.) +# Physical harm Physical distress and injuries for users (not operators) of the system +# Operator +# resiliency +# Requires action by system operator to maintain safe system state as a result of exploitation of the +# vulnerability where operator actions would be within their capabilities but the actions require their full +# attention and effort; OR significant distraction or discomfort to operators; OR causes significant occupational safety hazard +# System +# resiliency +# System safety margin effectively eliminated but no actual harm; OR failure of system functional capabilities that support safe operation +# Environment Major externalities (property damage, environmental damage, etc.) imposed on other parties +# Financial Financial losses that likely lead to bankruptcy of multiple persons +# Psychological Widespread emotional or psychological harm, sufficient to be cause for counselling or therapy, to +# populations of people +# Hazardous +# (Any one or +# more of these +# conditions +# hold.) +# Physical harm Serious or fatal injuries, where fatalities are plausibly preventable via emergency services or other +# measures +# Operator +# resiliency +# Actions that would keep the system in a safe state are beyond system operator capabilities, resulting +# in adverse conditions; OR great physical distress to system operators such that they cannot be expected to operate the system properly +# System +# resiliency +# Parts of the cyber-physical system break; system’s ability to recover lost functionality remains intact +# Environment Serious externalities (threat to life as well as property, widespread environmental damage, measurable public health risks, etc.) imposed on other parties +# Financial Socio-technical system (elections, financial grid, etc.) of which the affected component is a part is +# actively destabilized and enters unsafe state +# Psychological N/A +# Catastrophic +# (Any one or +# more of these +# conditions +# hold.) +# Physical harm Multiple immediate fatalities (Emergency response probably cannot save the victims.) +# Operator +# resiliency +# Operator incapacitated (includes fatality or otherwise incapacitated) +# System resiliency Total loss of whole cyber-physical system, of which the software is a part +# Environment Extreme externalities (immediate public health threat, environmental damage leading to small ecosystem collapse, etc.) imposed on other parties +# Financial Social systems (elections, financial grid, etc.) supported by the software collapse +# Psychological N/A + +SAFETY_IMPACT_1 = SsvcDecisionPoint( + name="Safety Impact", + description="The safety impact of the vulnerability.", + key="SI", + version="1.0.0", + values=[ + SsvcValue( + name="None", + key="N", + description="The effect is below the threshold for all aspects described in Minor.", + ), + SsvcValue( + name="Minor", + key="M", + description="Any one or more of these conditions hold. " + "Physical harm: Physical discomfort for users (not operators) of the system. " + "Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the " + "vulnerability where operator actions would be well within expected operator abilities; OR causes a minor occupational safety hazard. " + "System resiliency: Small reduction in built-in system safety margins; OR small reduction in system functional capabilities that support safe operation. " + "Environment Minor externalities (property damage, environmental damage, etc.) imposed on other parties. " + "Financial Financial losses, which are not readily absorbable, to multiple persons. " + "Psychological: Emotional or psychological harm, sufficient to be cause for counselling or therapy, to multiple persons.", + ), + SsvcValue( + name="Major", + key="J", + description="Any one or more of these conditions hold. " + "Physical harm: Physical distress and injuries for users (not operators) of the system. " + "Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the " + "vulnerability where operator actions would be within their capabilities but the actions require their full attention and effort; OR significant distraction or discomfort to operators; OR causes significant occupational safety hazard. " + "System resiliency: System safety margin effectively eliminated but no actual harm; OR failure of system functional capabilities that support safe operation. " + "Environment: Major externalities (property damage, environmental damage, etc.) imposed on other parties. " + "Financial: Financial losses that likely lead to bankruptcy of multiple persons. " + "Psychological: Widespread emotional or psychological harm, sufficient to be cause for counselling or therapy, to populations of people.", + ), + SsvcValue( + name="Hazardous", + key="H", + description="Any one or more of these conditions hold. " + "Physical harm: Serious or fatal injuries, where fatalities are plausibly preventable via emergency services or other measures. " + "Operator resiliency: Actions that would keep the system in a safe state are beyond system operator capabilities, resulting in adverse conditions; OR great physical distress to system operators such that they cannot be expected to operate the system properly. " + "System resiliency: Parts of the cyber-physical system break; system’s ability to recover lost functionality remains intact. " + "Environment: Serious externalities (threat to life as well as property, widespread environmental damage, measurable public health risks, etc.) imposed on other parties. " + "Financial: Socio-technical system (elections, financial grid, etc.) of which the affected component is a part is actively destabilized and enters unsafe state. " + "Psychological: N/A.", + ), + SsvcValue( + name="Catastrophic", + key="C", + description="Any one or more of these conditions hold. " + "Physical harm: Multiple immediate fatalities (Emergency response probably cannot save the victims.) " + "Operator resiliency: Operator incapacitated (includes fatality or otherwise incapacitated). " + "System resiliency: Total loss of whole cyber-physical system, of which the software is a part. " + "Environment: Extreme externalities (immediate public health threat, environmental damage leading to small ecosystem collapse, etc.) imposed on other parties. " + "Financial: Social systems (elections, financial grid, etc.) supported by the software collapse. " + "Psychological: N/A.", + ), + ], +) + + +def main(): + print(SAFETY_IMPACT_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py index a8aca075..1a9182e1 100644 --- a/src/ssvc/dp_groups/v1.py +++ b/src/ssvc/dp_groups/v1.py @@ -5,6 +5,7 @@ created_at: 9/21/23 9:52 AM """ from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY from ssvc.decision_points.virulence import VIRULENCE_1 as VIRULENCE @@ -22,6 +23,7 @@ UTILITY, VIRULENCE, VALUE_DENSITY, + SAFETY_IMPACT, ], ) From 71788855df7f23a934cd1281e488afb89a5099d9 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 10:20:09 -0400 Subject: [PATCH 10/53] add exposure --- src/ssvc/decision_points/exposure.py | 50 ++++++++++++++++++++++++++++ src/ssvc/dp_groups/v1.py | 2 ++ 2 files changed, 52 insertions(+) create mode 100644 src/ssvc/decision_points/exposure.py diff --git a/src/ssvc/decision_points/exposure.py b/src/ssvc/decision_points/exposure.py new file mode 100644 index 00000000..66ccee8d --- /dev/null +++ b/src/ssvc/decision_points/exposure.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +""" +file: exposure +author: adh +created_at: 9/21/23 10:16 AM +""" +# Exposure +# The Accessible Attack Surface of the Affected System or Service + +# Small Local service or program; highly controlled network +# Controlled Networked service with some access restrictions or mitigations already in place (whether locally or on +# the network). A successful mitigation must reliably interrupt the adversary’s attack, which requires the +# attack is detectable both reliably and quickly enough to respond. Controlled covers the situation in +# which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is +# that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then exposure should be small. +# Unavoidable Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers) + +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +EXPOSURE_1 = SsvcDecisionPoint( + name="Exposure", + description="The Accessible Attack Surface of the Affected System or Service", + key="EXP", + version="1.0.0", + values=[ + SsvcValue( + name="Small", + key="S", + description="Local service or program; highly controlled network", + ), + SsvcValue( + name="Controlled", + key="C", + description="Networked service with some access restrictions or mitigations already in place (whether locally or on the network). A successful mitigation must reliably interrupt the adversary’s attack, which requires the attack is detectable both reliably and quickly enough to respond. Controlled covers the situation in which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then exposure should be small.", + ), + SsvcValue( + name="Unavoidable", + key="U", + description="Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers)", + ), + ], +) + + +def main(): + print(EXPOSURE_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py index 1a9182e1..daf9b994 100644 --- a/src/ssvc/dp_groups/v1.py +++ b/src/ssvc/dp_groups/v1.py @@ -5,6 +5,7 @@ created_at: 9/21/23 9:52 AM """ from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.exposure import EXPOSURE_1 as EXPOSURE from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY @@ -24,6 +25,7 @@ VIRULENCE, VALUE_DENSITY, SAFETY_IMPACT, + EXPOSURE, ], ) From 0bf5beadfb4ac0d14a3d5fb8d11b3f53693d3e32 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 10:24:00 -0400 Subject: [PATCH 11/53] add mission impact --- src/ssvc/decision_points/mission_impact.py | 59 ++++++++++++++++++++++ src/ssvc/dp_groups/v1.py | 2 + 2 files changed, 61 insertions(+) create mode 100644 src/ssvc/decision_points/mission_impact.py diff --git a/src/ssvc/decision_points/mission_impact.py b/src/ssvc/decision_points/mission_impact.py new file mode 100644 index 00000000..de271977 --- /dev/null +++ b/src/ssvc/decision_points/mission_impact.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +""" +file: mission_impact +author: adh +created_at: 9/21/23 10:20 AM +""" +# Impact on Mission Essential Functions of the Organization + +# None Little to no impact +# Non-Essential +# Degraded +# Degradation of non-essential functions; chronic degradation would eventually harm essential +# functions +# MEF Support +# Crippled +# Activities that directly support essential functions are crippled; essential functions continue for a +# time +# MEF Failure Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time +# Mission Failure Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails + +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +MISSION_IMPACT_1 = SsvcDecisionPoint( + name="Mission Impact", + description="Impact on Mission Essential Functions of the Organization", + key="MI", + version="1.0.0", + values=[ + SsvcValue(name="None", key="N", description="Little to no impact"), + SsvcValue( + name="Non-Essential Degraded", + key="NED", + description="Degradation of non-essential functions; chronic degradation would eventually harm essential functions", + ), + SsvcValue( + name="MEF Support Crippled", + key="MSC", + description="Activities that directly support essential functions are crippled; essential functions continue for a time", + ), + SsvcValue( + name="MEF Failure", + key="MEF", + description="Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time", + ), + SsvcValue( + name="Mission Failure", + key="MF", + description="Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails", + ), + ], +) + + +def main(): + print(MISSION_IMPACT_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py index daf9b994..126b63c5 100644 --- a/src/ssvc/dp_groups/v1.py +++ b/src/ssvc/dp_groups/v1.py @@ -6,6 +6,7 @@ """ from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION from ssvc.decision_points.exposure import EXPOSURE_1 as EXPOSURE +from ssvc.decision_points.mission_impact import MISSION_IMPACT_1 as MISSION_IMPACT from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY @@ -26,6 +27,7 @@ VALUE_DENSITY, SAFETY_IMPACT, EXPOSURE, + MISSION_IMPACT, ], ) From 8ece887a86dc2b1dadd88fc791dc4af6373aaa50 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 10:27:14 -0400 Subject: [PATCH 12/53] add patch developer and patch applier --- src/ssvc/dp_groups/v1.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py index 126b63c5..9d830736 100644 --- a/src/ssvc/dp_groups/v1.py +++ b/src/ssvc/dp_groups/v1.py @@ -31,9 +31,34 @@ ], ) +PATCH_DEVELOPER_1 = SsvcDecisionPointGroup( + name="SSVC Patch Developer", + description="The decision points used by the patch developer.", + key="PD", + version="1.0.0", + decision_points=[ + EXPLOITATION, + UTILITY, + TECHNICAL_IMPACT, + VIRULENCE, + VALUE_DENSITY, + SAFETY_IMPACT, + ], +) + +PATCH_APPLIER_1 = SsvcDecisionPointGroup( + name="SSVC Patch Applier", + description="The decision points used by the patch applier.", + key="PA", + version="1.0.0", + decision_points=[EXPLOITATION, EXPOSURE, MISSION_IMPACT, SAFETY_IMPACT], +) + def main(): print(SSVCv1.to_json(indent=2)) + print(PATCH_DEVELOPER_1.to_json(indent=2)) + print(PATCH_APPLIER_1.to_json(indent=2)) if __name__ == "__main__": From 9eb9887fc5f5d5ff6e8acf887f7106784972f662 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 10:29:51 -0400 Subject: [PATCH 13/53] refactor into modules --- src/ssvc/dp_groups/patch_applier.py | 28 ++++++++++++++++++++ src/ssvc/dp_groups/patch_developer.py | 37 +++++++++++++++++++++++++++ src/ssvc/dp_groups/v1.py | 29 +++------------------ 3 files changed, 69 insertions(+), 25 deletions(-) create mode 100644 src/ssvc/dp_groups/patch_applier.py create mode 100644 src/ssvc/dp_groups/patch_developer.py diff --git a/src/ssvc/dp_groups/patch_applier.py b/src/ssvc/dp_groups/patch_applier.py new file mode 100644 index 00000000..646dc89f --- /dev/null +++ b/src/ssvc/dp_groups/patch_applier.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +""" +file: patch_applier +author: adh +created_at: 9/21/23 10:28 AM +""" +from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.exposure import EXPOSURE_1 as EXPOSURE +from ssvc.decision_points.mission_impact import MISSION_IMPACT_1 as MISSION_IMPACT +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT +from ssvc.dp_groups.base import SsvcDecisionPointGroup + + +PATCH_APPLIER_1 = SsvcDecisionPointGroup( + name="SSVC Patch Applier", + description="The decision points used by the patch applier.", + key="PA", + version="1.0.0", + decision_points=[EXPLOITATION, EXPOSURE, MISSION_IMPACT, SAFETY_IMPACT], +) + + +def main(): + print(PATCH_APPLIER_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/patch_developer.py b/src/ssvc/dp_groups/patch_developer.py new file mode 100644 index 00000000..c44639c5 --- /dev/null +++ b/src/ssvc/dp_groups/patch_developer.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +""" +file: patch_developer +author: adh +created_at: 9/21/23 10:27 AM +""" +from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT +from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT +from ssvc.decision_points.utility import UTILITY_1 as UTILITY +from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY +from ssvc.decision_points.virulence import VIRULENCE_1 as VIRULENCE +from ssvc.dp_groups.base import SsvcDecisionPointGroup + + +PATCH_DEVELOPER_1 = SsvcDecisionPointGroup( + name="SSVC Patch Developer", + description="The decision points used by the patch developer.", + key="PD", + version="1.0.0", + decision_points=[ + EXPLOITATION, + UTILITY, + TECHNICAL_IMPACT, + VIRULENCE, + VALUE_DENSITY, + SAFETY_IMPACT, + ], +) + + +def main(): + print(PATCH_DEVELOPER_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py index 9d830736..bf84020e 100644 --- a/src/ssvc/dp_groups/v1.py +++ b/src/ssvc/dp_groups/v1.py @@ -14,6 +14,10 @@ from ssvc.dp_groups.base import SsvcDecisionPointGroup from ssvc.decision_points.utility import UTILITY_1 as UTILITY +# convenience imports +from ssvc.dp_groups.patch_applier import PATCH_APPLIER_1 as PATCH_APPLIER # noqa +from ssvc.dp_groups.patch_developer import PATCH_DEVELOPER_1 as PATCH_DEVELOPER # noqa + SSVCv1 = SsvcDecisionPointGroup( name="SSVCv1", description="The first version of the SSVC.", @@ -31,34 +35,9 @@ ], ) -PATCH_DEVELOPER_1 = SsvcDecisionPointGroup( - name="SSVC Patch Developer", - description="The decision points used by the patch developer.", - key="PD", - version="1.0.0", - decision_points=[ - EXPLOITATION, - UTILITY, - TECHNICAL_IMPACT, - VIRULENCE, - VALUE_DENSITY, - SAFETY_IMPACT, - ], -) - -PATCH_APPLIER_1 = SsvcDecisionPointGroup( - name="SSVC Patch Applier", - description="The decision points used by the patch applier.", - key="PA", - version="1.0.0", - decision_points=[EXPLOITATION, EXPOSURE, MISSION_IMPACT, SAFETY_IMPACT], -) - def main(): print(SSVCv1.to_json(indent=2)) - print(PATCH_DEVELOPER_1.to_json(indent=2)) - print(PATCH_APPLIER_1.to_json(indent=2)) if __name__ == "__main__": From 6567cf52b9bbfc5d08d92bb0437085015109797f Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 11:17:27 -0400 Subject: [PATCH 14/53] add ssvc2 changes --- src/ssvc/decision_points/automatable.py | 35 +++++++++ src/ssvc/decision_points/exposure.py | 23 ++++++ src/ssvc/decision_points/human_impact.py | 44 +++++++++++ .../decision_points/public_safety_impact.py | 32 ++++++++ .../decision_points/public_value_added.py | 39 ++++++++++ .../decision_points/report_credibility.py | 35 +++++++++ src/ssvc/decision_points/report_public.py | 35 +++++++++ src/ssvc/decision_points/safety_impact.py | 2 + .../decision_points/supplier_cardinality.py | 35 +++++++++ .../decision_points/supplier_contacted.py | 34 +++++++++ .../decision_points/supplier_engagement.py | 35 +++++++++ .../decision_points/supplier_involvement.py | 40 ++++++++++ src/ssvc/decision_points/technical_impact.py | 2 +- src/ssvc/decision_points/utility.py | 22 ++++++ src/ssvc/dp_groups/coordinator_publication.py | 28 +++++++ src/ssvc/dp_groups/coordinator_triage.py | 36 +++++++++ src/ssvc/dp_groups/deployer.py | 38 ++++++++++ src/ssvc/dp_groups/supplier.py | 36 +++++++++ src/ssvc/dp_groups/v2.py | 76 +++++++++++++++++++ 19 files changed, 626 insertions(+), 1 deletion(-) create mode 100644 src/ssvc/decision_points/automatable.py create mode 100644 src/ssvc/decision_points/human_impact.py create mode 100644 src/ssvc/decision_points/public_safety_impact.py create mode 100644 src/ssvc/decision_points/public_value_added.py create mode 100644 src/ssvc/decision_points/report_credibility.py create mode 100644 src/ssvc/decision_points/report_public.py create mode 100644 src/ssvc/decision_points/supplier_cardinality.py create mode 100644 src/ssvc/decision_points/supplier_contacted.py create mode 100644 src/ssvc/decision_points/supplier_engagement.py create mode 100644 src/ssvc/decision_points/supplier_involvement.py create mode 100644 src/ssvc/dp_groups/coordinator_publication.py create mode 100644 src/ssvc/dp_groups/coordinator_triage.py create mode 100644 src/ssvc/dp_groups/deployer.py create mode 100644 src/ssvc/dp_groups/supplier.py create mode 100644 src/ssvc/dp_groups/v2.py diff --git a/src/ssvc/decision_points/automatable.py b/src/ssvc/decision_points/automatable.py new file mode 100644 index 00000000..7a6ddb3a --- /dev/null +++ b/src/ssvc/decision_points/automatable.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +""" +file: automatable +author: adh +created_at: 9/21/23 10:37 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + + +AUTOMATABLE_1 = SsvcDecisionPoint( + name="Automatable", + description="Can an attacker reliably automate creating exploitation events for this vulnerability?", + key="A", + version="1.0.0", + values=[ + SsvcValue( + name="No", + key="N", + description="Attackers cannot reliably automate steps 1-4 of the kill chain for this vulnerability. These steps are (1) reconnaissance, (2) weaponization, (3) delivery, and (4) exploitation.", + ), + SsvcValue( + name="Yes", + key="Y", + description="Attackers can reliably automate steps 1-4 of the kill chain.", + ), + ], +) + + +def main(): + print(AUTOMATABLE_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/exposure.py b/src/ssvc/decision_points/exposure.py index 66ccee8d..024c8b46 100644 --- a/src/ssvc/decision_points/exposure.py +++ b/src/ssvc/decision_points/exposure.py @@ -4,6 +4,8 @@ author: adh created_at: 9/21/23 10:16 AM """ +from copy import deepcopy + # Exposure # The Accessible Attack Surface of the Affected System or Service @@ -41,9 +43,30 @@ ], ) +EXPOSURE_1_0_1 = deepcopy(EXPOSURE_1) +EXPOSURE_1_0_1.version = "1.0.1" +EXPOSURE_1_0_1.values = [ + SsvcValue( + name="Small", + key="S", + description="Local service or program; highly controlled network", + ), + SsvcValue( + name="Controlled", + key="C", + description="Networked service with some access restrictions or mitigations already in place (whether locally or on the network). A successful mitigation must reliably interrupt the adversary’s attack, which requires the attack is detectable both reliably and quickly enough to respond. Controlled covers the situation in which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then exposure should be small.", + ), + SsvcValue( + name="Open", + key="O", + description="Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers)", + ), +] + def main(): print(EXPOSURE_1.to_json(indent=2)) + print(EXPOSURE_1_0_1.to_json(indent=2)) if __name__ == "__main__": diff --git a/src/ssvc/decision_points/human_impact.py b/src/ssvc/decision_points/human_impact.py new file mode 100644 index 00000000..c451a56b --- /dev/null +++ b/src/ssvc/decision_points/human_impact.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +""" +file: human_impact +author: adh +created_at: 9/21/23 10:49 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +HUMAN_IMPACT_1 = SsvcDecisionPoint( + name="Human Impact", + description="Human Impact", + key="HI", + version="1.0.0", + values=[ + SsvcValue( + name="Low", + key="L", + description="Safety=None/Minor, Mission=None/Degraded/Crippled", + ), + SsvcValue( + name="Medium", + key="M", + description="Safety=None/Minor, Mission=MEF Failure OR Safety=Major, Mission=None/Degraded/Crippled", + ), + SsvcValue( + name="High", + key="H", + description="Safety=Hazardous, Mission=None/Degraded/Crippled/MEF Failure OR Safety=Major, Mission=MEF Failure", + ), + SsvcValue( + name="Very High", + key="VH", + description="Safety=Catastrophic OR Mission=Mission Failure", + ), + ], +) + + +def main(): + print(HUMAN_IMPACT_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/public_safety_impact.py b/src/ssvc/decision_points/public_safety_impact.py new file mode 100644 index 00000000..f4e4b68d --- /dev/null +++ b/src/ssvc/decision_points/public_safety_impact.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +""" +file: public_safety_impact +author: adh +created_at: 9/21/23 10:43 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +PUBLIC_SAFETY_IMPACT_1 = SsvcDecisionPoint( + name="Public Safety Impact", + description="A coarse-grained representation of impact to public safety.", + key="PSI", + version="1.0.0", + values=[ + SsvcValue( + name="Minimal", description="Safety impact of None or Minor.", key="M" + ), + SsvcValue( + name="Significant", + description="Safety impact of Major, Hazardous, or Catastrophic.", + key="S", + ), + ], +) + + +def main(): + print(PUBLIC_SAFETY_IMPACT_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/public_value_added.py b/src/ssvc/decision_points/public_value_added.py new file mode 100644 index 00000000..05193b60 --- /dev/null +++ b/src/ssvc/decision_points/public_value_added.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +''' +file: public_value_added +author: adh +created_at: 9/21/23 11:27 AM +''' + +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +PUBLIC_VALUE_ADDED_1 = SsvcDecisionPoint( + name="Public Value Added", + description="How much value would a publication from the coordinator benefit the broader community?", + key="PVA", + version="1.0.0", + values=[ + SsvcValue( + name="Precedence", + key="P", + description="The publication would be the first publicly available, or be coincident with the first publicly available.", + ), + SsvcValue( + name="Ampliative", + key="A", + description="Amplifies and/or augments the existing public information about the vulnerability, for example, adds additional detail, addresses or corrects errors in other public information, draws further attention to the vulnerability, etc.", + ), + SsvcValue( + name="Limited", + key="L", + description="Minimal value added to the existing public information because existing information is already high quality and in multiple outlets.", + ), + ], +) + +def main(): + pass + + +if __name__ == '__main__': + main() diff --git a/src/ssvc/decision_points/report_credibility.py b/src/ssvc/decision_points/report_credibility.py new file mode 100644 index 00000000..e5080cd5 --- /dev/null +++ b/src/ssvc/decision_points/report_credibility.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +''' +file: report_credibility +author: adh +created_at: 9/21/23 11:24 AM +''' + +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +REPORT_CREDIBILITY_1 = SsvcDecisionPoint( + name="Report Credibility", + description="Is the report credible?", + key="RC", + version="1.0.0", + values=[ + SsvcValue( + name="Credible", + key="C", + description="The report is credible.", + ), + SsvcValue( + name="Not Credible", + key="NC", + description="The report is not credible.", + ), + ], +) + +def main(): + print(REPORT_CREDIBILITY_1.to_json(indent=2)) + + + +if __name__ == '__main__': + main() diff --git a/src/ssvc/decision_points/report_public.py b/src/ssvc/decision_points/report_public.py new file mode 100644 index 00000000..b1754a61 --- /dev/null +++ b/src/ssvc/decision_points/report_public.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +""" +file: report_public +author: adh +created_at: 9/21/23 11:15 AM +""" + +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +REPORT_PUBLIC_1 = SsvcDecisionPoint( + name="Report Public", + description="Is a viable report of the details of the vulnerability already publicly available?", + key="RP", + version="1.0.0", + values=[ + SsvcValue( + name="No", + key="N", + description="No public report of the vulnerability exists.", + ), + SsvcValue( + name="Yes", + key="Y", + description="A public report of the vulnerability exists.", + ), + ], +) + + +def main(): + print(REPORT_PUBLIC_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/safety_impact.py b/src/ssvc/decision_points/safety_impact.py index cbd2927e..aabd7efc 100644 --- a/src/ssvc/decision_points/safety_impact.py +++ b/src/ssvc/decision_points/safety_impact.py @@ -4,6 +4,8 @@ author: adh created_at: 9/21/23 10:05 AM """ +from copy import deepcopy + from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue # Safety Impact16F diff --git a/src/ssvc/decision_points/supplier_cardinality.py b/src/ssvc/decision_points/supplier_cardinality.py new file mode 100644 index 00000000..7d47ac48 --- /dev/null +++ b/src/ssvc/decision_points/supplier_cardinality.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +""" +file: supplier_cardinality +author: adh +created_at: 9/21/23 11:20 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +SUPPLIER_CARDINALITY_1 = SsvcDecisionPoint( + name="Supplier Cardinality", + description="How many suppliers are responsible for the vulnerable component and its remediation or mitigation plan?", + key="SC", + version="1.0.0", + values=[ + # One, Multiple + SsvcValue( + name="One", + key="O", + description="There is only one supplier of the vulnerable component.", + ), + SsvcValue( + name="Multiple", + key="M", + description="There are multiple suppliers of the vulnerable component.", + ), + ], +) + + +def main(): + print(SUPPLIER_CARDINALITY_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/supplier_contacted.py b/src/ssvc/decision_points/supplier_contacted.py new file mode 100644 index 00000000..322efce7 --- /dev/null +++ b/src/ssvc/decision_points/supplier_contacted.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +""" +file: supplier_contacted +author: adh +created_at: 9/21/23 11:17 AM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +SUPPLIER_CONTACTED_1 = SsvcDecisionPoint( + name="Supplier Contacted", + description="Has the reporter made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method?", + key="SC", + version="1.0.0", + values=[ + SsvcValue( + name="No", + key="N", + description="The supplier has not been contacted.", + ), + SsvcValue( + name="Yes", + key="Y", + description="The supplier has been contacted.", + ), + ], +) + + +def main(): + print(SUPPLIER_CONTACTED_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/supplier_engagement.py b/src/ssvc/decision_points/supplier_engagement.py new file mode 100644 index 00000000..e766e421 --- /dev/null +++ b/src/ssvc/decision_points/supplier_engagement.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +''' +file: supplier_engagement +author: adh +created_at: 9/21/23 11:22 AM +''' + +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +SUPPLIER_ENGAGEMENT_1 = SsvcDecisionPoint( + name='Supplier Engagement', + description='Is the supplier responding to the reporter’s contact effort and actively participating in the coordination effort?', + key='SE', + version='1.0.0', + values=[ + SsvcValue( + name='Active', + key='A', + description='The supplier is responding to the reporter’s contact effort and actively participating in the coordination effort.', + ), + SsvcValue( + name='Unresponsive', + key='U', + description='The supplier is not responding to the reporter’s contact effort and not actively participating in the coordination effort.', + ), + ], +) + + +def main(): + print(SUPPLIER_ENGAGEMENT_1.to_json(indent=2)) + + +if __name__ == '__main__': + main() diff --git a/src/ssvc/decision_points/supplier_involvement.py b/src/ssvc/decision_points/supplier_involvement.py new file mode 100644 index 00000000..019d0bce --- /dev/null +++ b/src/ssvc/decision_points/supplier_involvement.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +''' +file: supplier_involvement +author: adh +created_at: 9/21/23 11:28 AM +''' + +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +SUPPLIER_INVOLVEMENT_1 = SsvcDecisionPoint( + name="Supplier Involvement", + description="What is the state of the supplier’s work on addressing the vulnerability?", + key="SI", + version="1.0.0", + values=[ + SsvcValue( + name="Fix Ready", + key="FR", + description="The supplier has provided a patch or fix.", + ), + SsvcValue( + name="Cooperative", + key="C", + description="The supplier is actively generating a patch or fix; they may or may not have provided a mitigation or work-around in the mean time.", + ), + SsvcValue( + name="Uncooperative/Unresponsive", + key="UU", + description="The supplier has not responded, declined to generate a remediation, or no longer exists.", + ), + ], +) + + +def main(): + print(SUPPLIER_INVOLVEMENT_1.to_json(indent=2)) + + +if __name__ == '__main__': + main() diff --git a/src/ssvc/decision_points/technical_impact.py b/src/ssvc/decision_points/technical_impact.py index 1fcb563c..634d3a89 100644 --- a/src/ssvc/decision_points/technical_impact.py +++ b/src/ssvc/decision_points/technical_impact.py @@ -21,7 +21,7 @@ SsvcValue( name="Partial", key="P", - description="The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control. In this context, “low” means that the attacker cannot reasonably make enough attempts to overcome the low chance of each attempt not working. Denial of service is a form of limited control over the behavior of the vulnerable component.", + description="The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control.", ), SsvcValue( name="Total", diff --git a/src/ssvc/decision_points/utility.py b/src/ssvc/decision_points/utility.py index 1b7f49c1..ff99e659 100644 --- a/src/ssvc/decision_points/utility.py +++ b/src/ssvc/decision_points/utility.py @@ -4,6 +4,8 @@ author: adh created_at: 9/21/23 9:55 AM """ +from copy import deepcopy + from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue UTILITY_1 = SsvcDecisionPoint( @@ -28,9 +30,29 @@ ], ) +# SSVC v2 keeps same options but changes descriptions +UTILITY_1_0_1 = deepcopy(UTILITY_1) +UTILITY_1_0_1.version = "1.0.1" +UTILITY_1_0_1.values = [ + SsvcValue( + name="Laborious", key="L", description="No to automatable and diffuse value" + ), + SsvcValue( + name="Efficient", + key="E", + description="Yes to automatable and diffuse value OR No to automatable and concentrated value", + ), + SsvcValue( + name="Super Effective", + key="S", + description="Yes to automatable and concentrated value", + ), +] + def main(): print(UTILITY_1.to_json(indent=2)) + print(UTILITY_1_0_1.to_json(indent=2)) if __name__ == "__main__": diff --git a/src/ssvc/dp_groups/coordinator_publication.py b/src/ssvc/dp_groups/coordinator_publication.py new file mode 100644 index 00000000..4eed0520 --- /dev/null +++ b/src/ssvc/dp_groups/coordinator_publication.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +''' +file: coordinator_publication +author: adh +created_at: 9/21/23 11:40 AM +''' +from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.public_value_added import PUBLIC_VALUE_ADDED_1 as PUBLIC_VALUE_ADDED +from ssvc.decision_points.supplier_involvement import SUPPLIER_INVOLVEMENT_1 as SUPPLIER_INVOLVEMENT +from ssvc.dp_groups.base import SsvcDecisionPointGroup + + +def main(): + pass + +if __name__=='__main__': + main() +COORDINATOR_PUBLICATION_1 = SsvcDecisionPointGroup( + name="Coordinator Publication", + description="The decision points used by the coordinator during publication.", + key="CP", + version="1.0.0", + decision_points=[ + SUPPLIER_INVOLVEMENT, + EXPLOITATION, + PUBLIC_VALUE_ADDED, + ], +) diff --git a/src/ssvc/dp_groups/coordinator_triage.py b/src/ssvc/dp_groups/coordinator_triage.py new file mode 100644 index 00000000..f4445616 --- /dev/null +++ b/src/ssvc/dp_groups/coordinator_triage.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +''' +file: coordinator_triage +author: adh +created_at: 9/21/23 11:40 AM +''' +from ssvc.decision_points.public_safety_impact import PUBLIC_SAFETY_IMPACT_1 as PUBLIC_SAFETY_IMPACT +from ssvc.decision_points.report_credibility import REPORT_CREDIBILITY_1 as REPORT_CREDIBILITY +from ssvc.decision_points.report_public import REPORT_PUBLIC_1 as REPORT_PUBLIC +from ssvc.decision_points.supplier_cardinality import SUPPLIER_CARDINALITY_1 as SUPPLIER_CARDINALITY +from ssvc.decision_points.supplier_contacted import SUPPLIER_CONTACTED_1 as SUPPLIER_CONTACTED +from ssvc.decision_points.supplier_engagement import SUPPLIER_ENGAGEMENT_1 as SUPPLIER_ENGAGEMENT +from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY +from ssvc.dp_groups.base import SsvcDecisionPointGroup + + +def main(): + pass + +if __name__=='__main__': + main() +COORDINATOR_TRIAGE_1 = SsvcDecisionPointGroup( + name="Coordinator Triage", + description="The decision points used by the coordinator during triage.", + key="CT", + version="1.0.0", + decision_points=[ + REPORT_PUBLIC, + SUPPLIER_CONTACTED, + REPORT_CREDIBILITY, + SUPPLIER_CARDINALITY, + SUPPLIER_ENGAGEMENT, + UTILITY, + PUBLIC_SAFETY_IMPACT, + ], +) diff --git a/src/ssvc/dp_groups/deployer.py b/src/ssvc/dp_groups/deployer.py new file mode 100644 index 00000000..ec35acb8 --- /dev/null +++ b/src/ssvc/dp_groups/deployer.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +''' +file: deployer +author: adh +created_at: 9/21/23 11:40 AM +''' +from ssvc.decision_points.automatable import AUTOMATABLE_1 as AUTOMATABLE +from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.exposure import EXPOSURE_1_0_1 as EXPOSURE +from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 as HUMAN_IMPACT +from ssvc.decision_points.mission_impact import MISSION_IMPACT_1 as MISSION_IMPACT +from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY +from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY +from ssvc.dp_groups.base import SsvcDecisionPointGroup +from ssvc.dp_groups.v2 import SITUATED_SAFETY_IMPACT + + +def main(): + pass + +if __name__=='__main__': + main() +DEPLOYER_1 = SsvcDecisionPointGroup( + name="Deployer", + description="The decision points used by the deployer.", + key="D", + version="1.0.0", + decision_points=[ + EXPLOITATION, + EXPOSURE, + UTILITY, + AUTOMATABLE, + VALUE_DENSITY, + HUMAN_IMPACT, + SITUATED_SAFETY_IMPACT, + MISSION_IMPACT, + ], +) diff --git a/src/ssvc/dp_groups/supplier.py b/src/ssvc/dp_groups/supplier.py new file mode 100644 index 00000000..bdb9dd28 --- /dev/null +++ b/src/ssvc/dp_groups/supplier.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +''' +file: supplier +author: adh +created_at: 9/21/23 11:41 AM +''' +from ssvc.decision_points.automatable import AUTOMATABLE_1 as AUTOMATABLE +from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.public_safety_impact import PUBLIC_SAFETY_IMPACT_1 as PUBLIC_SAFETY_IMPACT +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT +from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT +from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY +from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY +from ssvc.dp_groups.base import SsvcDecisionPointGroup + + +def main(): + pass + +if __name__=='__main__': + main() +SUPPLIER_1 = SsvcDecisionPointGroup( + name="Supplier", + description="The decision points used by the supplier.", + key="S", + version="1.0.0", + decision_points=[ + EXPLOITATION, + UTILITY, + AUTOMATABLE, + VALUE_DENSITY, + TECHNICAL_IMPACT, + PUBLIC_SAFETY_IMPACT, + SAFETY_IMPACT + ], +) diff --git a/src/ssvc/dp_groups/v2.py b/src/ssvc/dp_groups/v2.py new file mode 100644 index 00000000..576ee921 --- /dev/null +++ b/src/ssvc/dp_groups/v2.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +""" +file: v2 +author: adh +created_at: 9/21/23 10:31 AM +""" +from copy import deepcopy + +from ssvc.decision_points.automatable import AUTOMATABLE_1 as AUTOMATABLE +from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.exposure import EXPOSURE_1_0_1 as EXPOSURE +from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 as HUMAN_IMPACT +from ssvc.decision_points.mission_impact import MISSION_IMPACT_1 as MISSION_IMPACT +from ssvc.decision_points.public_safety_impact import ( + PUBLIC_SAFETY_IMPACT_1 as PUBLIC_SAFETY_IMPACT, +) +from ssvc.decision_points.public_value_added import PUBLIC_VALUE_ADDED_1 as PUBLIC_VALUE_ADDED +from ssvc.decision_points.report_credibility import REPORT_CREDIBILITY_1 as REPORT_CREDIBILITY +from ssvc.decision_points.report_public import REPORT_PUBLIC_1 as REPORT_PUBLIC +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT +from ssvc.decision_points.supplier_cardinality import ( + SUPPLIER_CARDINALITY_1 as SUPPLIER_CARDINALITY, +) +from ssvc.decision_points.supplier_contacted import ( + SUPPLIER_CONTACTED_1 as SUPPLIER_CONTACTED, +) +from ssvc.decision_points.supplier_engagement import SUPPLIER_ENGAGEMENT_1 as SUPPLIER_ENGAGEMENT +from ssvc.decision_points.supplier_involvement import SUPPLIER_INVOLVEMENT_1 as SUPPLIER_INVOLVEMENT +from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT +from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY +from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY +from ssvc.dp_groups.base import SsvcDecisionPointGroup +from ssvc.dp_groups.coordinator_publication import COORDINATOR_PUBLICATION_1 +from ssvc.dp_groups.coordinator_triage import COORDINATOR_TRIAGE_1 +from ssvc.dp_groups.deployer import DEPLOYER_1 +from ssvc.dp_groups.supplier import SUPPLIER_1 + +SITUATED_SAFETY_IMPACT = deepcopy(SAFETY_IMPACT) + +SSVCv2 = SsvcDecisionPointGroup( + name="SSVCv2", + description="The second version of the SSVC.", + key="SSVCv2", + version="2.0.0", + decision_points=[ + EXPLOITATION, + TECHNICAL_IMPACT, + UTILITY, + AUTOMATABLE, + VALUE_DENSITY, + SAFETY_IMPACT, + PUBLIC_SAFETY_IMPACT, + SITUATED_SAFETY_IMPACT, + MISSION_IMPACT, + HUMAN_IMPACT, + EXPOSURE, + REPORT_PUBLIC, + SUPPLIER_CONTACTED, + SUPPLIER_CARDINALITY, + SUPPLIER_ENGAGEMENT, + REPORT_CREDIBILITY, + PUBLIC_VALUE_ADDED, + SUPPLIER_INVOLVEMENT, + ], +) + + +def main(): + print(SSVCv2.to_json(indent=2)) + print(SUPPLIER_1.to_json(indent=2)) + print(DEPLOYER_1.to_json(indent=2)) + print(COORDINATOR_TRIAGE_1.to_json(indent=2)) + print(COORDINATOR_PUBLICATION_1.to_json(indent=2)) + +if __name__ == "__main__": + main() From 4dc6a566558cc1bfc488e06c9d9a062852fb4eb2 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 12:16:53 -0400 Subject: [PATCH 15/53] condense patch applier and deployer --- src/ssvc/decision_points/mission_impact.py | 28 +++++++ src/ssvc/dp_groups/deployer.py | 85 +++++++++++++++------- src/ssvc/dp_groups/patch_applier.py | 28 ------- src/ssvc/dp_groups/v1.py | 2 +- src/ssvc/dp_groups/v2.py | 12 +-- src/ssvc/dp_groups/v2_1.py | 82 +++++++++++++++++++++ 6 files changed, 176 insertions(+), 61 deletions(-) delete mode 100644 src/ssvc/dp_groups/patch_applier.py create mode 100644 src/ssvc/dp_groups/v2_1.py diff --git a/src/ssvc/decision_points/mission_impact.py b/src/ssvc/decision_points/mission_impact.py index de271977..41b77785 100644 --- a/src/ssvc/decision_points/mission_impact.py +++ b/src/ssvc/decision_points/mission_impact.py @@ -4,6 +4,8 @@ author: adh created_at: 9/21/23 10:20 AM """ +from copy import deepcopy + # Impact on Mission Essential Functions of the Organization # None Little to no impact @@ -50,9 +52,35 @@ ], ) +# SSVC v2.1 combined None and Non-Essential Degraded into a single value +MISSION_IMPACT_2 = deepcopy(MISSION_IMPACT_1) +MISSION_IMPACT_2.version = "2.0.0" +MISSION_IMPACT_2.values = [ + SsvcValue( + name="Degraded", + key="D", + description="Little to no impact up to degradation of non-essential functions; chronic degradation would eventually harm essential functions", + ), + SsvcValue( + name="MEF Support Crippled", + key="MSC", + description="Activities that directly support essential functions are crippled; essential functions continue for a time", + ), + SsvcValue( + name="MEF Failure", + key="MEF", + description="Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time", + ), + SsvcValue( + name="Mission Failure", + key="MF", + description="Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails", + ), +] def main(): print(MISSION_IMPACT_1.to_json(indent=2)) + print(MISSION_IMPACT_2.to_json(indent=2)) if __name__ == "__main__": diff --git a/src/ssvc/dp_groups/deployer.py b/src/ssvc/dp_groups/deployer.py index ec35acb8..c9dc4539 100644 --- a/src/ssvc/dp_groups/deployer.py +++ b/src/ssvc/dp_groups/deployer.py @@ -4,35 +4,66 @@ author: adh created_at: 9/21/23 11:40 AM ''' -from ssvc.decision_points.automatable import AUTOMATABLE_1 as AUTOMATABLE -from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION -from ssvc.decision_points.exposure import EXPOSURE_1_0_1 as EXPOSURE -from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 as HUMAN_IMPACT -from ssvc.decision_points.mission_impact import MISSION_IMPACT_1 as MISSION_IMPACT -from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY -from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY +from copy import deepcopy + +from ssvc.decision_points.automatable import AUTOMATABLE_1 +from ssvc.decision_points.exploitation import EXPLOITATION_1 +from ssvc.decision_points.exposure import EXPOSURE_1, EXPOSURE_1_0_1 +from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 +from ssvc.decision_points.mission_impact import MISSION_IMPACT_1, MISSION_IMPACT_2 +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 +from ssvc.decision_points.utility import UTILITY_1_0_1 +from ssvc.decision_points.value_density import VALUE_DENSITY_1 from ssvc.dp_groups.base import SsvcDecisionPointGroup -from ssvc.dp_groups.v2 import SITUATED_SAFETY_IMPACT -def main(): - pass - -if __name__=='__main__': - main() -DEPLOYER_1 = SsvcDecisionPointGroup( - name="Deployer", - description="The decision points used by the deployer.", - key="D", +PATCH_APPLIER_1 = SsvcDecisionPointGroup( + name="SSVC Patch Applier", + description="The decision points used by the patch applier.", + key="PA", version="1.0.0", - decision_points=[ - EXPLOITATION, - EXPOSURE, - UTILITY, - AUTOMATABLE, - VALUE_DENSITY, - HUMAN_IMPACT, - SITUATED_SAFETY_IMPACT, - MISSION_IMPACT, - ], + decision_points=[EXPLOITATION_1, EXPOSURE_1, MISSION_IMPACT_1, SAFETY_IMPACT_1], ) + +# alias for forward compatibility +DEPLOYER_1 = PATCH_APPLIER_1 + +# SSVC v2 +DEPLOYER_2 = deepcopy(DEPLOYER_1) +# change name to SSVC Deployer +DEPLOYER_2.name = "SSVC Deployer" +DEPLOYER_2.key = "D" +DEPLOYER_2.version = "2.0.0" +# update exposure +DEPLOYER_2.decision_points.remove(EXPOSURE_1) +DEPLOYER_2.decision_points.append(EXPOSURE_1_0_1) +# add UTILITY (AUTOMATABLE + VALUE DENSITY) +DEPLOYER_2.decision_points.append(UTILITY_1_0_1) +DEPLOYER_2.decision_points.append(AUTOMATABLE_1) +DEPLOYER_2.decision_points.append(VALUE_DENSITY_1) +# condense MISSION_IMPACT_1 and SAFETY_IMPACT_1 into HUMAN_IMPACT_1 +DEPLOYER_2.decision_points.append(HUMAN_IMPACT_1) + + +# SSVC v2.1 +DEPLOYER_3 = deepcopy(DEPLOYER_2) +DEPLOYER_3.version = "3.0.0" +# replace UTILITY (AUTOMATABLE + VALUE DENSITY) with just AUTOMATABLE +DEPLOYER_3.decision_points.remove(UTILITY_1_0_1) +DEPLOYER_3.decision_points.remove(VALUE_DENSITY_1) + +# update MISSION_IMPACT_1 to MISSION_IMPACT_2 +DEPLOYER_3.decision_points.remove(MISSION_IMPACT_1) +DEPLOYER_3.decision_points.append(MISSION_IMPACT_2) + + + +def main(): + print(PATCH_APPLIER_1.to_json(indent=2)) + print(DEPLOYER_2.to_json(indent=2)) + print(DEPLOYER_3.to_json(indent=2)) + + + +if __name__ == '__main__': + main() diff --git a/src/ssvc/dp_groups/patch_applier.py b/src/ssvc/dp_groups/patch_applier.py deleted file mode 100644 index 646dc89f..00000000 --- a/src/ssvc/dp_groups/patch_applier.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -""" -file: patch_applier -author: adh -created_at: 9/21/23 10:28 AM -""" -from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION -from ssvc.decision_points.exposure import EXPOSURE_1 as EXPOSURE -from ssvc.decision_points.mission_impact import MISSION_IMPACT_1 as MISSION_IMPACT -from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT -from ssvc.dp_groups.base import SsvcDecisionPointGroup - - -PATCH_APPLIER_1 = SsvcDecisionPointGroup( - name="SSVC Patch Applier", - description="The decision points used by the patch applier.", - key="PA", - version="1.0.0", - decision_points=[EXPLOITATION, EXPOSURE, MISSION_IMPACT, SAFETY_IMPACT], -) - - -def main(): - print(PATCH_APPLIER_1.to_json(indent=2)) - - -if __name__ == "__main__": - main() diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py index bf84020e..1a2b63c2 100644 --- a/src/ssvc/dp_groups/v1.py +++ b/src/ssvc/dp_groups/v1.py @@ -15,7 +15,7 @@ from ssvc.decision_points.utility import UTILITY_1 as UTILITY # convenience imports -from ssvc.dp_groups.patch_applier import PATCH_APPLIER_1 as PATCH_APPLIER # noqa +from ssvc.dp_groups.deployer import PATCH_APPLIER_1 as PATCH_APPLIER # noqa from ssvc.dp_groups.patch_developer import PATCH_DEVELOPER_1 as PATCH_DEVELOPER # noqa SSVCv1 = SsvcDecisionPointGroup( diff --git a/src/ssvc/dp_groups/v2.py b/src/ssvc/dp_groups/v2.py index 576ee921..b8c27573 100644 --- a/src/ssvc/dp_groups/v2.py +++ b/src/ssvc/dp_groups/v2.py @@ -30,10 +30,12 @@ from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY from ssvc.dp_groups.base import SsvcDecisionPointGroup -from ssvc.dp_groups.coordinator_publication import COORDINATOR_PUBLICATION_1 -from ssvc.dp_groups.coordinator_triage import COORDINATOR_TRIAGE_1 -from ssvc.dp_groups.deployer import DEPLOYER_1 -from ssvc.dp_groups.supplier import SUPPLIER_1 + +# convenience imports +from ssvc.dp_groups.coordinator_publication import COORDINATOR_PUBLICATION_1 # noqa +from ssvc.dp_groups.coordinator_triage import COORDINATOR_TRIAGE_1 # noqa +from ssvc.dp_groups.deployer import DEPLOYER_2 # noqa +from ssvc.dp_groups.supplier import SUPPLIER_1 # noqa SITUATED_SAFETY_IMPACT = deepcopy(SAFETY_IMPACT) @@ -68,7 +70,7 @@ def main(): print(SSVCv2.to_json(indent=2)) print(SUPPLIER_1.to_json(indent=2)) - print(DEPLOYER_1.to_json(indent=2)) + print(DEPLOYER_2.to_json(indent=2)) print(COORDINATOR_TRIAGE_1.to_json(indent=2)) print(COORDINATOR_PUBLICATION_1.to_json(indent=2)) diff --git a/src/ssvc/dp_groups/v2_1.py b/src/ssvc/dp_groups/v2_1.py new file mode 100644 index 00000000..7c4ad329 --- /dev/null +++ b/src/ssvc/dp_groups/v2_1.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +''' +file: v2_1 +author: adh +created_at: 9/21/23 11:45 AM +''' + +from ssvc.decision_points.automatable import AUTOMATABLE_1 as AUTOMATABLE +from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION +from ssvc.decision_points.exposure import EXPOSURE_1_0_1 as EXPOSURE +from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 as HUMAN_IMPACT +from ssvc.decision_points.mission_impact import MISSION_IMPACT_2 as MISSION_IMPACT +from ssvc.decision_points.public_safety_impact import ( + PUBLIC_SAFETY_IMPACT_1 as PUBLIC_SAFETY_IMPACT, +) +from ssvc.decision_points.public_value_added import PUBLIC_VALUE_ADDED_1 as PUBLIC_VALUE_ADDED +from ssvc.decision_points.report_credibility import REPORT_CREDIBILITY_1 as REPORT_CREDIBILITY +from ssvc.decision_points.report_public import REPORT_PUBLIC_1 as REPORT_PUBLIC +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT +from ssvc.decision_points.supplier_cardinality import ( + SUPPLIER_CARDINALITY_1 as SUPPLIER_CARDINALITY, +) +from ssvc.decision_points.supplier_contacted import ( + SUPPLIER_CONTACTED_1 as SUPPLIER_CONTACTED, +) +from ssvc.decision_points.supplier_engagement import SUPPLIER_ENGAGEMENT_1 as SUPPLIER_ENGAGEMENT +from ssvc.decision_points.supplier_involvement import SUPPLIER_INVOLVEMENT_1 as SUPPLIER_INVOLVEMENT +from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT +from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY +from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY +from ssvc.dp_groups.base import SsvcDecisionPointGroup + +# convenience imports +from ssvc.dp_groups.coordinator_publication import COORDINATOR_PUBLICATION_1 +from ssvc.dp_groups.coordinator_triage import COORDINATOR_TRIAGE_1 +from ssvc.dp_groups.deployer import DEPLOYER_3 +from ssvc.dp_groups.supplier import SUPPLIER_1 + +SSVCv2 = SsvcDecisionPointGroup( + name="SSVCv2.1", + description="The second version of the SSVC.", + key="SSVCv2.1", + version="2.1.0", + decision_points=[ + EXPLOITATION, + TECHNICAL_IMPACT, + UTILITY, + AUTOMATABLE, + VALUE_DENSITY, + SAFETY_IMPACT, + PUBLIC_SAFETY_IMPACT, + SAFETY_IMPACT, + MISSION_IMPACT, + HUMAN_IMPACT, + EXPOSURE, + REPORT_PUBLIC, + SUPPLIER_CONTACTED, + SUPPLIER_CARDINALITY, + SUPPLIER_ENGAGEMENT, + REPORT_CREDIBILITY, + PUBLIC_VALUE_ADDED, + SUPPLIER_INVOLVEMENT, + ], +) + + +def main(): + print(SSVCv2.to_json(indent=2)) + print(SUPPLIER_1.to_json(indent=2)) + print(DEPLOYER_3.to_json(indent=2)) + print(COORDINATOR_TRIAGE_1.to_json(indent=2)) + print(COORDINATOR_PUBLICATION_1.to_json(indent=2)) + +if __name__ == "__main__": + main() + +def main(): + pass + + +if __name__ == '__main__': + main() From 708d7d3c597950aa5ccfc44af483d5d9f5a1b9ea Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 13:16:55 -0400 Subject: [PATCH 16/53] abstract out values, use tuples --- src/ssvc/decision_points/automatable.py | 26 +-- src/ssvc/decision_points/exploitation.py | 46 +++-- src/ssvc/decision_points/exposure.py | 77 ++++---- src/ssvc/decision_points/human_impact.py | 52 ++--- src/ssvc/decision_points/mission_impact.py | 104 +++++----- .../decision_points/public_safety_impact.py | 24 ++- .../decision_points/public_value_added.py | 45 +++-- .../decision_points/report_credibility.py | 36 ++-- src/ssvc/decision_points/report_public.py | 29 +-- src/ssvc/decision_points/safety_impact.py | 187 ++++++------------ .../decision_points/supplier_cardinality.py | 29 +-- .../decision_points/supplier_contacted.py | 28 +-- .../decision_points/supplier_engagement.py | 42 ++-- .../decision_points/supplier_involvement.py | 46 +++-- src/ssvc/decision_points/technical_impact.py | 32 ++- src/ssvc/decision_points/utility.py | 72 ++++--- src/ssvc/decision_points/value_density.py | 38 ++-- src/ssvc/decision_points/virulence.py | 34 ++-- src/ssvc/dp_groups/base.py | 24 +++ src/ssvc/dp_groups/patch_developer.py | 37 ---- src/ssvc/dp_groups/supplier.py | 71 ++++--- src/ssvc/dp_groups/v1.py | 27 +-- src/ssvc/dp_groups/v2.py | 61 +----- src/ssvc/dp_groups/v2_1.py | 70 ++----- 24 files changed, 550 insertions(+), 687 deletions(-) delete mode 100644 src/ssvc/dp_groups/patch_developer.py diff --git a/src/ssvc/decision_points/automatable.py b/src/ssvc/decision_points/automatable.py index 7a6ddb3a..41ee35b9 100644 --- a/src/ssvc/decision_points/automatable.py +++ b/src/ssvc/decision_points/automatable.py @@ -7,23 +7,25 @@ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +AUT_NO = SsvcValue( + name="No", + key="N", + description="Attackers cannot reliably automate steps 1-4 of the kill chain for this vulnerability. " + "These steps are (1) reconnaissance, (2) weaponization, (3) delivery, and (4) exploitation.", +) +AUT_YES = SsvcValue( + name="Yes", + key="Y", + description="Attackers can reliably automate steps 1-4 of the kill chain.", +) + + AUTOMATABLE_1 = SsvcDecisionPoint( name="Automatable", description="Can an attacker reliably automate creating exploitation events for this vulnerability?", key="A", version="1.0.0", - values=[ - SsvcValue( - name="No", - key="N", - description="Attackers cannot reliably automate steps 1-4 of the kill chain for this vulnerability. These steps are (1) reconnaissance, (2) weaponization, (3) delivery, and (4) exploitation.", - ), - SsvcValue( - name="Yes", - key="Y", - description="Attackers can reliably automate steps 1-4 of the kill chain.", - ), - ], + values=(AUT_NO, AUT_YES), ) diff --git a/src/ssvc/decision_points/exploitation.py b/src/ssvc/decision_points/exploitation.py index 8233af17..2dad13af 100644 --- a/src/ssvc/decision_points/exploitation.py +++ b/src/ssvc/decision_points/exploitation.py @@ -6,6 +6,27 @@ """ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +ACTIVE = SsvcValue( + name="Active", + key="A", + description="Shared, observable, reliable evidence that the exploit is being" + " used in the wild by real attackers; there is credible public reporting.", +) + +POC = SsvcValue( + name="PoC", + key="P", + description="One of the following cases is true: (1) private evidence of exploitation is attested but not shared; " + "(2) widespread hearsay attests to exploitation; (3) typical public PoC in places such as Metasploit" + " or ExploitDB; or (4) the vulnerability has a well-known method of exploitation.", +) + +EXP_NONE = SsvcValue( + name="None", + key="N", + description="There is no evidence of active exploitation and no public proof of concept (PoC) of how to exploit the vulnerability.", +) + def _strip_spaces(s): return " ".join([x.strip() for x in s.splitlines()]) @@ -16,26 +37,11 @@ def _strip_spaces(s): description="The present state of exploitation of the vulnerability.", key="E", version="1.0.0", - values=[ - SsvcValue( - name="None", - key="N", - description="There is no evidence of active exploitation and no public proof of concept (PoC) of how to exploit the vulnerability.", - ), - SsvcValue( - name="PoC", - key="P", - description="One of the following cases is true: (1) private evidence of exploitation is attested but not shared; " - "(2) widespread hearsay attests to exploitation; (3) typical public PoC in places such as Metasploit" - " or ExploitDB; or (4) the vulnerability has a well-known method of exploitation.", - ), - SsvcValue( - name="Active", - key="A", - description="Shared, observable, reliable evidence that the exploit is being" - " used in the wild by real attackers; there is credible public reporting.", - ), - ], + values=( + EXP_NONE, + POC, + ACTIVE, + ), ) diff --git a/src/ssvc/decision_points/exposure.py b/src/ssvc/decision_points/exposure.py index 024c8b46..f9a650ff 100644 --- a/src/ssvc/decision_points/exposure.py +++ b/src/ssvc/decision_points/exposure.py @@ -5,63 +5,52 @@ created_at: 9/21/23 10:16 AM """ from copy import deepcopy +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +EXP_UNAVOIDABLE = SsvcValue( + name="Unavoidable", + key="U", + description="Internet or another widely accessible network where access cannot plausibly be restricted or " + "controlled (e.g., DNS servers, web servers, VOIP servers, email servers)", +) -# Exposure -# The Accessible Attack Surface of the Affected System or Service +EXP_CONTROLLED = SsvcValue( + name="Controlled", + key="C", + description="Networked service with some access restrictions or mitigations already in place (whether locally or on the network). " + "A successful mitigation must reliably interrupt the adversary’s attack, which requires the attack is detectable " + "both reliably and quickly enough to respond. Controlled covers the situation in which a vulnerability can be " + "exploited through chaining it with other vulnerabilities. The assumption is that the number of steps in the " + "attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably " + "execute it, then exposure should be small.", +) -# Small Local service or program; highly controlled network -# Controlled Networked service with some access restrictions or mitigations already in place (whether locally or on -# the network). A successful mitigation must reliably interrupt the adversary’s attack, which requires the -# attack is detectable both reliably and quickly enough to respond. Controlled covers the situation in -# which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is -# that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then exposure should be small. -# Unavoidable Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers) +EXP_SMALL = SsvcValue( + name="Small", + key="S", + description="Local service or program; highly controlled network", +) -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue EXPOSURE_1 = SsvcDecisionPoint( name="Exposure", description="The Accessible Attack Surface of the Affected System or Service", key="EXP", version="1.0.0", - values=[ - SsvcValue( - name="Small", - key="S", - description="Local service or program; highly controlled network", - ), - SsvcValue( - name="Controlled", - key="C", - description="Networked service with some access restrictions or mitigations already in place (whether locally or on the network). A successful mitigation must reliably interrupt the adversary’s attack, which requires the attack is detectable both reliably and quickly enough to respond. Controlled covers the situation in which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then exposure should be small.", - ), - SsvcValue( - name="Unavoidable", - key="U", - description="Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers)", - ), - ], + values=( + EXP_SMALL, + EXP_CONTROLLED, + EXP_UNAVOIDABLE, + ), ) +EXP_OPEN = deepcopy(EXP_UNAVOIDABLE) +EXP_OPEN.name = "Open" +EXP_OPEN.key = "O" + EXPOSURE_1_0_1 = deepcopy(EXPOSURE_1) EXPOSURE_1_0_1.version = "1.0.1" -EXPOSURE_1_0_1.values = [ - SsvcValue( - name="Small", - key="S", - description="Local service or program; highly controlled network", - ), - SsvcValue( - name="Controlled", - key="C", - description="Networked service with some access restrictions or mitigations already in place (whether locally or on the network). A successful mitigation must reliably interrupt the adversary’s attack, which requires the attack is detectable both reliably and quickly enough to respond. Controlled covers the situation in which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then exposure should be small.", - ), - SsvcValue( - name="Open", - key="O", - description="Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers)", - ), -] +EXPOSURE_1_0_1.values = (EXP_SMALL, EXP_CONTROLLED, EXP_OPEN) def main(): diff --git a/src/ssvc/decision_points/human_impact.py b/src/ssvc/decision_points/human_impact.py index c451a56b..15034deb 100644 --- a/src/ssvc/decision_points/human_impact.py +++ b/src/ssvc/decision_points/human_impact.py @@ -6,33 +6,41 @@ """ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +VERY_HIGH = SsvcValue( + name="Very High", + key="VH", + description="Safety=Catastrophic OR Mission=Mission Failure", +) + +HIGH = SsvcValue( + name="High", + key="H", + description="Safety=Hazardous, Mission=None/Degraded/Crippled/MEF Failure OR Safety=Major, Mission=MEF Failure", +) + +MEDIUM = SsvcValue( + name="Medium", + key="M", + description="Safety=None/Minor, Mission=MEF Failure OR Safety=Major, Mission=None/Degraded/Crippled", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="Safety=None/Minor, Mission=None/Degraded/Crippled", +) + HUMAN_IMPACT_1 = SsvcDecisionPoint( name="Human Impact", description="Human Impact", key="HI", version="1.0.0", - values=[ - SsvcValue( - name="Low", - key="L", - description="Safety=None/Minor, Mission=None/Degraded/Crippled", - ), - SsvcValue( - name="Medium", - key="M", - description="Safety=None/Minor, Mission=MEF Failure OR Safety=Major, Mission=None/Degraded/Crippled", - ), - SsvcValue( - name="High", - key="H", - description="Safety=Hazardous, Mission=None/Degraded/Crippled/MEF Failure OR Safety=Major, Mission=MEF Failure", - ), - SsvcValue( - name="Very High", - key="VH", - description="Safety=Catastrophic OR Mission=Mission Failure", - ), - ], + values=( + LOW, + MEDIUM, + HIGH, + VERY_HIGH, + ), ) diff --git a/src/ssvc/decision_points/mission_impact.py b/src/ssvc/decision_points/mission_impact.py index 41b77785..60c8e3e2 100644 --- a/src/ssvc/decision_points/mission_impact.py +++ b/src/ssvc/decision_points/mission_impact.py @@ -6,77 +6,63 @@ """ from copy import deepcopy -# Impact on Mission Essential Functions of the Organization - -# None Little to no impact -# Non-Essential -# Degraded -# Degradation of non-essential functions; chronic degradation would eventually harm essential -# functions -# MEF Support -# Crippled -# Activities that directly support essential functions are crippled; essential functions continue for a -# time -# MEF Failure Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time -# Mission Failure Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails - from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +MISSION_FAILURE = SsvcValue( + name="Mission Failure", + key="MF", + description="Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails", +) + +MEF_FAILURE = SsvcValue( + name="MEF Failure", + key="MEF", + description="Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time", +) + +MEF_CRIPPLED = SsvcValue( + name="MEF Support Crippled", + key="MSC", + description="Activities that directly support essential functions are crippled; essential functions continue for a time", +) + + +MI_NED = SsvcValue( + name="Non-Essential Degraded", + key="NED", + description="Degradation of non-essential functions; chronic degradation would eventually harm essential functions", +) + +MI_NONE = SsvcValue(name="None", key="N", description="Little to no impact") + +# combine MI_NONE and MI_NED into a single value +DEGRADED = SsvcValue( + name="Degraded", + key="D", + description="Little to no impact up to degradation of non-essential functions; chronic degradation would eventually harm essential functions", +) + + MISSION_IMPACT_1 = SsvcDecisionPoint( name="Mission Impact", description="Impact on Mission Essential Functions of the Organization", key="MI", version="1.0.0", - values=[ - SsvcValue(name="None", key="N", description="Little to no impact"), - SsvcValue( - name="Non-Essential Degraded", - key="NED", - description="Degradation of non-essential functions; chronic degradation would eventually harm essential functions", - ), - SsvcValue( - name="MEF Support Crippled", - key="MSC", - description="Activities that directly support essential functions are crippled; essential functions continue for a time", - ), - SsvcValue( - name="MEF Failure", - key="MEF", - description="Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time", - ), - SsvcValue( - name="Mission Failure", - key="MF", - description="Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails", - ), - ], + values=( + MI_NONE, + MI_NED, + MEF_CRIPPLED, + MEF_FAILURE, + MISSION_FAILURE, + ), ) # SSVC v2.1 combined None and Non-Essential Degraded into a single value MISSION_IMPACT_2 = deepcopy(MISSION_IMPACT_1) MISSION_IMPACT_2.version = "2.0.0" -MISSION_IMPACT_2.values = [ - SsvcValue( - name="Degraded", - key="D", - description="Little to no impact up to degradation of non-essential functions; chronic degradation would eventually harm essential functions", - ), - SsvcValue( - name="MEF Support Crippled", - key="MSC", - description="Activities that directly support essential functions are crippled; essential functions continue for a time", - ), - SsvcValue( - name="MEF Failure", - key="MEF", - description="Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time", - ), - SsvcValue( - name="Mission Failure", - key="MF", - description="Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails", - ), -] +MISSION_IMPACT_2.values = (DEGRADED, MEF_CRIPPLED, MEF_FAILURE, MISSION_FAILURE) + def main(): print(MISSION_IMPACT_1.to_json(indent=2)) diff --git a/src/ssvc/decision_points/public_safety_impact.py b/src/ssvc/decision_points/public_safety_impact.py index f4e4b68d..6f2973fd 100644 --- a/src/ssvc/decision_points/public_safety_impact.py +++ b/src/ssvc/decision_points/public_safety_impact.py @@ -6,21 +6,25 @@ """ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +SIGNIFICANT = SsvcValue( + name="Significant", + description="Safety impact of Major, Hazardous, or Catastrophic.", + key="S", +) + +MINIMAL = SsvcValue( + name="Minimal", description="Safety impact of None or Minor.", key="M" +) + PUBLIC_SAFETY_IMPACT_1 = SsvcDecisionPoint( name="Public Safety Impact", description="A coarse-grained representation of impact to public safety.", key="PSI", version="1.0.0", - values=[ - SsvcValue( - name="Minimal", description="Safety impact of None or Minor.", key="M" - ), - SsvcValue( - name="Significant", - description="Safety impact of Major, Hazardous, or Catastrophic.", - key="S", - ), - ], + values=( + MINIMAL, + SIGNIFICANT, + ), ) diff --git a/src/ssvc/decision_points/public_value_added.py b/src/ssvc/decision_points/public_value_added.py index 05193b60..6800e28a 100644 --- a/src/ssvc/decision_points/public_value_added.py +++ b/src/ssvc/decision_points/public_value_added.py @@ -1,39 +1,42 @@ #!/usr/bin/env python -''' +""" file: public_value_added author: adh created_at: 9/21/23 11:27 AM -''' +""" from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +LIMITED = SsvcValue( + name="Limited", + key="L", + description="Minimal value added to the existing public information because existing information is already high quality and in multiple outlets.", +) + +AMPLIATIVE = SsvcValue( + name="Ampliative", + key="A", + description="Amplifies and/or augments the existing public information about the vulnerability, for example, adds additional detail, addresses or corrects errors in other public information, draws further attention to the vulnerability, etc.", +) + +PRECEDENCE = SsvcValue( + name="Precedence", + key="P", + description="The publication would be the first publicly available, or be coincident with the first publicly available.", +) + PUBLIC_VALUE_ADDED_1 = SsvcDecisionPoint( name="Public Value Added", description="How much value would a publication from the coordinator benefit the broader community?", key="PVA", version="1.0.0", - values=[ - SsvcValue( - name="Precedence", - key="P", - description="The publication would be the first publicly available, or be coincident with the first publicly available.", - ), - SsvcValue( - name="Ampliative", - key="A", - description="Amplifies and/or augments the existing public information about the vulnerability, for example, adds additional detail, addresses or corrects errors in other public information, draws further attention to the vulnerability, etc.", - ), - SsvcValue( - name="Limited", - key="L", - description="Minimal value added to the existing public information because existing information is already high quality and in multiple outlets.", - ), - ], + values=(PRECEDENCE, AMPLIATIVE, LIMITED), ) + def main(): - pass + print(PUBLIC_VALUE_ADDED_1.to_json(indent=2)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/ssvc/decision_points/report_credibility.py b/src/ssvc/decision_points/report_credibility.py index e5080cd5..85482acc 100644 --- a/src/ssvc/decision_points/report_credibility.py +++ b/src/ssvc/decision_points/report_credibility.py @@ -1,35 +1,39 @@ #!/usr/bin/env python -''' +""" file: report_credibility author: adh created_at: 9/21/23 11:24 AM -''' +""" from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +NOT_CREDIBLE = SsvcValue( + name="Not Credible", + key="NC", + description="The report is not credible.", +) + +CREDIBLE = SsvcValue( + name="Credible", + key="C", + description="The report is credible.", +) + REPORT_CREDIBILITY_1 = SsvcDecisionPoint( name="Report Credibility", description="Is the report credible?", key="RC", version="1.0.0", - values=[ - SsvcValue( - name="Credible", - key="C", - description="The report is credible.", - ), - SsvcValue( - name="Not Credible", - key="NC", - description="The report is not credible.", - ), - ], + values=( + CREDIBLE, + NOT_CREDIBLE, + ), ) + def main(): print(REPORT_CREDIBILITY_1.to_json(indent=2)) - -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/ssvc/decision_points/report_public.py b/src/ssvc/decision_points/report_public.py index b1754a61..0031484b 100644 --- a/src/ssvc/decision_points/report_public.py +++ b/src/ssvc/decision_points/report_public.py @@ -4,26 +4,29 @@ author: adh created_at: 9/21/23 11:15 AM """ - from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +YES = SsvcValue( + name="Yes", + key="Y", + description="A public report of the vulnerability exists.", +) + +NO = SsvcValue( + name="No", + key="N", + description="No public report of the vulnerability exists.", +) + REPORT_PUBLIC_1 = SsvcDecisionPoint( name="Report Public", description="Is a viable report of the details of the vulnerability already publicly available?", key="RP", version="1.0.0", - values=[ - SsvcValue( - name="No", - key="N", - description="No public report of the vulnerability exists.", - ), - SsvcValue( - name="Yes", - key="Y", - description="A public report of the vulnerability exists.", - ), - ], + values=( + NO, + YES, + ), ) diff --git a/src/ssvc/decision_points/safety_impact.py b/src/ssvc/decision_points/safety_impact.py index aabd7efc..b65d32fd 100644 --- a/src/ssvc/decision_points/safety_impact.py +++ b/src/ssvc/decision_points/safety_impact.py @@ -8,137 +8,74 @@ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue -# Safety Impact16F -# 16 Type of Harm Description -# None All Does not mean no impact literally; it just means that the effect is below the threshold for all aspects -# described in Minor -# Minor -# (Any one or -# more of these -# conditions -# hold.) -# Physical harm Physical discomfort for users (not operators) of the system -# Operator -# resiliency -# Requires action by system operator to maintain safe system state as a result of exploitation of the -# vulnerability where operator actions would be well within expected operator abilities; OR causes a -# minor occupational safety hazard -# System -# resiliency -# Small reduction in built-in system safety margins; OR small reduction in system functional capabilities that support safe operation -# Environment Minor externalities (property damage, environmental damage, etc.) imposed on other parties -# Financial Financial losses, which are not readily absorbable, to multiple persons -# Psychological Emotional or psychological harm, sufficient to be cause for counselling or therapy, to multiple persons -# Safety Impact16F -# 16 Type of Harm Description -# Major -# (Any one or -# more of these -# conditions -# hold.) -# Physical harm Physical distress and injuries for users (not operators) of the system -# Operator -# resiliency -# Requires action by system operator to maintain safe system state as a result of exploitation of the -# vulnerability where operator actions would be within their capabilities but the actions require their full -# attention and effort; OR significant distraction or discomfort to operators; OR causes significant occupational safety hazard -# System -# resiliency -# System safety margin effectively eliminated but no actual harm; OR failure of system functional capabilities that support safe operation -# Environment Major externalities (property damage, environmental damage, etc.) imposed on other parties -# Financial Financial losses that likely lead to bankruptcy of multiple persons -# Psychological Widespread emotional or psychological harm, sufficient to be cause for counselling or therapy, to -# populations of people -# Hazardous -# (Any one or -# more of these -# conditions -# hold.) -# Physical harm Serious or fatal injuries, where fatalities are plausibly preventable via emergency services or other -# measures -# Operator -# resiliency -# Actions that would keep the system in a safe state are beyond system operator capabilities, resulting -# in adverse conditions; OR great physical distress to system operators such that they cannot be expected to operate the system properly -# System -# resiliency -# Parts of the cyber-physical system break; system’s ability to recover lost functionality remains intact -# Environment Serious externalities (threat to life as well as property, widespread environmental damage, measurable public health risks, etc.) imposed on other parties -# Financial Socio-technical system (elections, financial grid, etc.) of which the affected component is a part is -# actively destabilized and enters unsafe state -# Psychological N/A -# Catastrophic -# (Any one or -# more of these -# conditions -# hold.) -# Physical harm Multiple immediate fatalities (Emergency response probably cannot save the victims.) -# Operator -# resiliency -# Operator incapacitated (includes fatality or otherwise incapacitated) -# System resiliency Total loss of whole cyber-physical system, of which the software is a part -# Environment Extreme externalities (immediate public health threat, environmental damage leading to small ecosystem collapse, etc.) imposed on other parties -# Financial Social systems (elections, financial grid, etc.) supported by the software collapse -# Psychological N/A +CATASTROPHIC = SsvcValue( + name="Catastrophic", + key="C", + description="Any one or more of these conditions hold. " + "Physical harm: Multiple immediate fatalities (Emergency response probably cannot save the victims.) " + "Operator resiliency: Operator incapacitated (includes fatality or otherwise incapacitated). " + "System resiliency: Total loss of whole cyber-physical system, of which the software is a part. " + "Environment: Extreme externalities (immediate public health threat, environmental damage leading to small ecosystem collapse, etc.) imposed on other parties. " + "Financial: Social systems (elections, financial grid, etc.) supported by the software collapse. " + "Psychological: N/A.", +) + +HAZARDOUS = SsvcValue( + name="Hazardous", + key="H", + description="Any one or more of these conditions hold. " + "Physical harm: Serious or fatal injuries, where fatalities are plausibly preventable via emergency services or other measures. " + "Operator resiliency: Actions that would keep the system in a safe state are beyond system operator capabilities, resulting in adverse conditions; OR great physical distress to system operators such that they cannot be expected to operate the system properly. " + "System resiliency: Parts of the cyber-physical system break; system’s ability to recover lost functionality remains intact. " + "Environment: Serious externalities (threat to life as well as property, widespread environmental damage, measurable public health risks, etc.) imposed on other parties. " + "Financial: Socio-technical system (elections, financial grid, etc.) of which the affected component is a part is actively destabilized and enters unsafe state. " + "Psychological: N/A.", +) + +MAJOR = SsvcValue( + name="Major", + key="J", + description="Any one or more of these conditions hold. " + "Physical harm: Physical distress and injuries for users (not operators) of the system. " + "Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the " + "vulnerability where operator actions would be within their capabilities but the actions require their full attention and effort; OR significant distraction or discomfort to operators; OR causes significant occupational safety hazard. " + "System resiliency: System safety margin effectively eliminated but no actual harm; OR failure of system functional capabilities that support safe operation. " + "Environment: Major externalities (property damage, environmental damage, etc.) imposed on other parties. " + "Financial: Financial losses that likely lead to bankruptcy of multiple persons. " + "Psychological: Widespread emotional or psychological harm, sufficient to be cause for counselling or therapy, to populations of people.", +) + +MINOR = SsvcValue( + name="Minor", + key="M", + description="Any one or more of these conditions hold. " + "Physical harm: Physical discomfort for users (not operators) of the system. " + "Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the " + "vulnerability where operator actions would be well within expected operator abilities; OR causes a minor occupational safety hazard. " + "System resiliency: Small reduction in built-in system safety margins; OR small reduction in system functional capabilities that support safe operation. " + "Environment Minor externalities (property damage, environmental damage, etc.) imposed on other parties. " + "Financial Financial losses, which are not readily absorbable, to multiple persons. " + "Psychological: Emotional or psychological harm, sufficient to be cause for counselling or therapy, to multiple persons.", +) + +SAF_NONE = SsvcValue( + name="None", + key="N", + description="The effect is below the threshold for all aspects described in Minor.", +) SAFETY_IMPACT_1 = SsvcDecisionPoint( name="Safety Impact", description="The safety impact of the vulnerability.", key="SI", version="1.0.0", - values=[ - SsvcValue( - name="None", - key="N", - description="The effect is below the threshold for all aspects described in Minor.", - ), - SsvcValue( - name="Minor", - key="M", - description="Any one or more of these conditions hold. " - "Physical harm: Physical discomfort for users (not operators) of the system. " - "Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the " - "vulnerability where operator actions would be well within expected operator abilities; OR causes a minor occupational safety hazard. " - "System resiliency: Small reduction in built-in system safety margins; OR small reduction in system functional capabilities that support safe operation. " - "Environment Minor externalities (property damage, environmental damage, etc.) imposed on other parties. " - "Financial Financial losses, which are not readily absorbable, to multiple persons. " - "Psychological: Emotional or psychological harm, sufficient to be cause for counselling or therapy, to multiple persons.", - ), - SsvcValue( - name="Major", - key="J", - description="Any one or more of these conditions hold. " - "Physical harm: Physical distress and injuries for users (not operators) of the system. " - "Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the " - "vulnerability where operator actions would be within their capabilities but the actions require their full attention and effort; OR significant distraction or discomfort to operators; OR causes significant occupational safety hazard. " - "System resiliency: System safety margin effectively eliminated but no actual harm; OR failure of system functional capabilities that support safe operation. " - "Environment: Major externalities (property damage, environmental damage, etc.) imposed on other parties. " - "Financial: Financial losses that likely lead to bankruptcy of multiple persons. " - "Psychological: Widespread emotional or psychological harm, sufficient to be cause for counselling or therapy, to populations of people.", - ), - SsvcValue( - name="Hazardous", - key="H", - description="Any one or more of these conditions hold. " - "Physical harm: Serious or fatal injuries, where fatalities are plausibly preventable via emergency services or other measures. " - "Operator resiliency: Actions that would keep the system in a safe state are beyond system operator capabilities, resulting in adverse conditions; OR great physical distress to system operators such that they cannot be expected to operate the system properly. " - "System resiliency: Parts of the cyber-physical system break; system’s ability to recover lost functionality remains intact. " - "Environment: Serious externalities (threat to life as well as property, widespread environmental damage, measurable public health risks, etc.) imposed on other parties. " - "Financial: Socio-technical system (elections, financial grid, etc.) of which the affected component is a part is actively destabilized and enters unsafe state. " - "Psychological: N/A.", - ), - SsvcValue( - name="Catastrophic", - key="C", - description="Any one or more of these conditions hold. " - "Physical harm: Multiple immediate fatalities (Emergency response probably cannot save the victims.) " - "Operator resiliency: Operator incapacitated (includes fatality or otherwise incapacitated). " - "System resiliency: Total loss of whole cyber-physical system, of which the software is a part. " - "Environment: Extreme externalities (immediate public health threat, environmental damage leading to small ecosystem collapse, etc.) imposed on other parties. " - "Financial: Social systems (elections, financial grid, etc.) supported by the software collapse. " - "Psychological: N/A.", - ), - ], + values=( + SAF_NONE, + MINOR, + MAJOR, + HAZARDOUS, + CATASTROPHIC, + ), ) diff --git a/src/ssvc/decision_points/supplier_cardinality.py b/src/ssvc/decision_points/supplier_cardinality.py index 7d47ac48..669a9ced 100644 --- a/src/ssvc/decision_points/supplier_cardinality.py +++ b/src/ssvc/decision_points/supplier_cardinality.py @@ -6,24 +6,27 @@ """ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +MULTIPLE = SsvcValue( + name="Multiple", + key="M", + description="There are multiple suppliers of the vulnerable component.", +) + +ONE = SsvcValue( + name="One", + key="O", + description="There is only one supplier of the vulnerable component.", +) + SUPPLIER_CARDINALITY_1 = SsvcDecisionPoint( name="Supplier Cardinality", description="How many suppliers are responsible for the vulnerable component and its remediation or mitigation plan?", key="SC", version="1.0.0", - values=[ - # One, Multiple - SsvcValue( - name="One", - key="O", - description="There is only one supplier of the vulnerable component.", - ), - SsvcValue( - name="Multiple", - key="M", - description="There are multiple suppliers of the vulnerable component.", - ), - ], + values=( + ONE, + MULTIPLE, + ), ) diff --git a/src/ssvc/decision_points/supplier_contacted.py b/src/ssvc/decision_points/supplier_contacted.py index 322efce7..68970527 100644 --- a/src/ssvc/decision_points/supplier_contacted.py +++ b/src/ssvc/decision_points/supplier_contacted.py @@ -6,23 +6,27 @@ """ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +YES = SsvcValue( + name="Yes", + key="Y", + description="The supplier has been contacted.", +) + +NO = SsvcValue( + name="No", + key="N", + description="The supplier has not been contacted.", +) + SUPPLIER_CONTACTED_1 = SsvcDecisionPoint( name="Supplier Contacted", description="Has the reporter made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method?", key="SC", version="1.0.0", - values=[ - SsvcValue( - name="No", - key="N", - description="The supplier has not been contacted.", - ), - SsvcValue( - name="Yes", - key="Y", - description="The supplier has been contacted.", - ), - ], + values=( + NO, + YES, + ), ) diff --git a/src/ssvc/decision_points/supplier_engagement.py b/src/ssvc/decision_points/supplier_engagement.py index e766e421..00f589ec 100644 --- a/src/ssvc/decision_points/supplier_engagement.py +++ b/src/ssvc/decision_points/supplier_engagement.py @@ -1,29 +1,33 @@ #!/usr/bin/env python -''' +""" file: supplier_engagement author: adh created_at: 9/21/23 11:22 AM -''' +""" from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +UNRESPONSIVE = SsvcValue( + name="Unresponsive", + key="U", + description="The supplier is not responding to the reporter’s contact effort and not actively participating in the coordination effort.", +) + +ACTIVE = SsvcValue( + name="Active", + key="A", + description="The supplier is responding to the reporter’s contact effort and actively participating in the coordination effort.", +) + SUPPLIER_ENGAGEMENT_1 = SsvcDecisionPoint( - name='Supplier Engagement', - description='Is the supplier responding to the reporter’s contact effort and actively participating in the coordination effort?', - key='SE', - version='1.0.0', - values=[ - SsvcValue( - name='Active', - key='A', - description='The supplier is responding to the reporter’s contact effort and actively participating in the coordination effort.', - ), - SsvcValue( - name='Unresponsive', - key='U', - description='The supplier is not responding to the reporter’s contact effort and not actively participating in the coordination effort.', - ), - ], + name="Supplier Engagement", + description="Is the supplier responding to the reporter’s contact effort and actively participating in the coordination effort?", + key="SE", + version="1.0.0", + values=( + ACTIVE, + UNRESPONSIVE, + ), ) @@ -31,5 +35,5 @@ def main(): print(SUPPLIER_ENGAGEMENT_1.to_json(indent=2)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/ssvc/decision_points/supplier_involvement.py b/src/ssvc/decision_points/supplier_involvement.py index 019d0bce..f3ec33b7 100644 --- a/src/ssvc/decision_points/supplier_involvement.py +++ b/src/ssvc/decision_points/supplier_involvement.py @@ -1,34 +1,40 @@ #!/usr/bin/env python -''' +""" file: supplier_involvement author: adh created_at: 9/21/23 11:28 AM -''' +""" from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +UNCOOPERATIVE = SsvcValue( + name="Uncooperative/Unresponsive", + key="UU", + description="The supplier has not responded, declined to generate a remediation, or no longer exists.", +) + +COOPERATIVE = SsvcValue( + name="Cooperative", + key="C", + description="The supplier is actively generating a patch or fix; they may or may not have provided a mitigation or work-around in the mean time.", +) + +FIX_READY = SsvcValue( + name="Fix Ready", + key="FR", + description="The supplier has provided a patch or fix.", +) + SUPPLIER_INVOLVEMENT_1 = SsvcDecisionPoint( name="Supplier Involvement", description="What is the state of the supplier’s work on addressing the vulnerability?", key="SI", version="1.0.0", - values=[ - SsvcValue( - name="Fix Ready", - key="FR", - description="The supplier has provided a patch or fix.", - ), - SsvcValue( - name="Cooperative", - key="C", - description="The supplier is actively generating a patch or fix; they may or may not have provided a mitigation or work-around in the mean time.", - ), - SsvcValue( - name="Uncooperative/Unresponsive", - key="UU", - description="The supplier has not responded, declined to generate a remediation, or no longer exists.", - ), - ], + values=( + FIX_READY, + COOPERATIVE, + UNCOOPERATIVE, + ), ) @@ -36,5 +42,5 @@ def main(): print(SUPPLIER_INVOLVEMENT_1.to_json(indent=2)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/ssvc/decision_points/technical_impact.py b/src/ssvc/decision_points/technical_impact.py index 634d3a89..f30f8937 100644 --- a/src/ssvc/decision_points/technical_impact.py +++ b/src/ssvc/decision_points/technical_impact.py @@ -6,29 +6,27 @@ """ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue -# Partial The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control. In this context, “low” means that the attacker cannot reasonably make enough attempts to overcome the low chance of each attempt -# not working. Denial of service is a form of limited control over the behavior of the vulnerable -# component. -# Total The exploit gives the adversary total control over the behavior of the software, or it gives total -# disclosure of all information on the system that contains the vulnerability +TOTAL = SsvcValue( + name="Total", + key="T", + description="The exploit gives the adversary total control over the behavior of the software, or it gives total disclosure of all information on the system that contains the vulnerability.", +) + +PARTIAL = SsvcValue( + name="Partial", + key="P", + description="The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control.", +) TECHNICAL_IMPACT_1 = SsvcDecisionPoint( name="Technical Impact", description="The technical impact of the vulnerability.", key="TI", version="1.0.0", - values=[ - SsvcValue( - name="Partial", - key="P", - description="The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control.", - ), - SsvcValue( - name="Total", - key="T", - description="The exploit gives the adversary total control over the behavior of the software, or it gives total disclosure of all information on the system that contains the vulnerability.", - ), - ], + values=( + PARTIAL, + TOTAL, + ), ) diff --git a/src/ssvc/decision_points/utility.py b/src/ssvc/decision_points/utility.py index ff99e659..08456a3b 100644 --- a/src/ssvc/decision_points/utility.py +++ b/src/ssvc/decision_points/utility.py @@ -8,46 +8,58 @@ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +SUPER_EFFECTIVE_2 = SsvcValue( + name="Super Effective", + key="S", + description="Yes to automatable and concentrated value", +) + +EFFICIENT_2 = SsvcValue( + name="Efficient", + key="E", + description="Yes to automatable and diffuse value OR No to automatable and concentrated value", +) + +LABORIOUS_2 = SsvcValue( + name="Laborious", key="L", description="No to automatable and diffuse value" +) + +SUPER_EFFECTIVE = SsvcValue( + name="Super Effective", + key="S", + description="Rapid virulence and concentrated value", +) + +EFFICIENT = SsvcValue( + name="Efficient", + key="E", + description="Rapid virulence and diffuse value OR Slow virulence and concentrated value", +) + +LABORIOUS = SsvcValue( + name="Laborious", key="L", description="Slow virulence and diffuse value" +) + UTILITY_1 = SsvcDecisionPoint( name="Utility", description="The Usefulness of the Exploit to the Adversary", key="U", version="1.0.0", - values=[ - SsvcValue( - name="Laborious", key="L", description="Slow virulence and diffuse value" - ), - SsvcValue( - name="Efficient", - key="E", - description="Rapid virulence and diffuse value OR Slow virulence and concentrated value", - ), - SsvcValue( - name="Super Effective", - key="S", - description="Rapid virulence and concentrated value", - ), - ], + values=( + LABORIOUS, + EFFICIENT, + SUPER_EFFECTIVE, + ), ) # SSVC v2 keeps same options but changes descriptions UTILITY_1_0_1 = deepcopy(UTILITY_1) UTILITY_1_0_1.version = "1.0.1" -UTILITY_1_0_1.values = [ - SsvcValue( - name="Laborious", key="L", description="No to automatable and diffuse value" - ), - SsvcValue( - name="Efficient", - key="E", - description="Yes to automatable and diffuse value OR No to automatable and concentrated value", - ), - SsvcValue( - name="Super Effective", - key="S", - description="Yes to automatable and concentrated value", - ), -] +UTILITY_1_0_1.values = ( + LABORIOUS_2, + EFFICIENT_2, + SUPER_EFFECTIVE_2, +) def main(): diff --git a/src/ssvc/decision_points/value_density.py b/src/ssvc/decision_points/value_density.py index 82c2978e..afcf6427 100644 --- a/src/ssvc/decision_points/value_density.py +++ b/src/ssvc/decision_points/value_density.py @@ -6,35 +6,27 @@ """ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue -# Diffuse. The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively -# small. Examples of systems with diffuse value are email accounts, most consumer online banking -# accounts, common cell phones, and most personal computing resources owned and maintained by -# users. (A “user” is anyone whose professional task is something other than the maintenance of the -# system or component. As with safety impact, a “system operator” is anyone who is professionally -# responsible for the proper operation or maintenance of a system.) -# • Concentrated. The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of “system operators” rather than users. Examples of concentrated value are database systems, Kerberos servers, web servers hosting login -# pages, and cloud service providers. However, usefulness and uniqueness of the resources on the -# vulnerable system also inform value density. For example, encrypted mobile messaging platforms -# may have concentrated value, not because each phone’s messaging history has a particularly large -# amount of data, but because it is uniquely valuable to law enforcement. +CONCENTRATED = SsvcValue( + name="Concentrated", + key="C", + description="The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of “system operators” rather than users.", +) + +DIFFUSE = SsvcValue( + name="Diffuse", + key="D", + description="The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively small.", +) VALUE_DENSITY_1 = SsvcDecisionPoint( name="Value Density", description="The concentration of value in the target", key="VD", version="1.0.0", - values=[ - SsvcValue( - name="Diffuse", - key="D", - description="The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively small.", - ), - SsvcValue( - name="Concentrated", - key="C", - description="The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of “system operators” rather than users.", - ), - ], + values=( + DIFFUSE, + CONCENTRATED, + ), ) diff --git a/src/ssvc/decision_points/virulence.py b/src/ssvc/decision_points/virulence.py index 42755d5f..d5400d32 100644 --- a/src/ssvc/decision_points/virulence.py +++ b/src/ssvc/decision_points/virulence.py @@ -6,31 +6,27 @@ """ from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue -# Virulence is described as slow or rapid: -# • Slow. Steps 1-4 of the kill chain12F -# 12 cannot be reliably automated for this vulnerability for some -# reason. These steps are reconnaissance, weaponization, delivery, and exploitation. Example reasons for why a step may not be reliably automatable include (1) the vulnerable component is not -# searchable or enumerable on the network, (2) weaponization may require human direction for -# each target, (3) delivery may require channels that widely deployed network security configurations block, and (3) exploitation may be frustrated by adequate exploit-prevention techniques enabled by default; ASLR is an example of an exploit-prevention tool. -# • Rapid. Steps 1-4 of the of the kill chain can be reliably automated. If the vulnerability allows remote code execution or command injection, the default response should be rapid. +RAPID = SsvcValue( + name="Rapid", + key="R", + description="Steps 1-4 of the of the kill chain can be reliably automated. If the vulnerability allows remote code execution or command injection, the default response should be rapid.", +) + +SLOW = SsvcValue( + name="Slow", + key="S", + description="Steps 1-4 of the kill chain cannot be reliably automated for this vulnerability for some reason. These steps are reconnaissance, weaponization, delivery, and exploitation. Example reasons for why a step may not be reliably automatable include (1) the vulnerable component is not searchable or enumerable on the network, (2) weaponization may require human direction for each target, (3) delivery may require channels that widely deployed network security configurations block, and (3) exploitation may be frustrated by adequate exploit-prevention techniques enabled by default; ASLR is an example of an exploit-prevention tool.", +) VIRULENCE_1 = SsvcDecisionPoint( name="Virulence", description="The speed at which the vulnerability can be exploited.", key="V", version="1.0.0", - values=[ - SsvcValue( - name="Slow", - key="S", - description="Steps 1-4 of the kill chain cannot be reliably automated for this vulnerability for some reason. These steps are reconnaissance, weaponization, delivery, and exploitation. Example reasons for why a step may not be reliably automatable include (1) the vulnerable component is not searchable or enumerable on the network, (2) weaponization may require human direction for each target, (3) delivery may require channels that widely deployed network security configurations block, and (3) exploitation may be frustrated by adequate exploit-prevention techniques enabled by default; ASLR is an example of an exploit-prevention tool.", - ), - SsvcValue( - name="Rapid", - key="R", - description="Steps 1-4 of the of the kill chain can be reliably automated. If the vulnerability allows remote code execution or command injection, the default response should be rapid.", - ), - ], + values=( + SLOW, + RAPID, + ), ) diff --git a/src/ssvc/dp_groups/base.py b/src/ssvc/dp_groups/base.py index 26440a37..e6ff3677 100644 --- a/src/ssvc/dp_groups/base.py +++ b/src/ssvc/dp_groups/base.py @@ -23,6 +23,30 @@ class SsvcDecisionPointGroup(_Base, _Versioned): decision_points: List[SsvcDecisionPoint] +def get_all_decision_points_from( + glist: list[SsvcDecisionPointGroup], +) -> list[SsvcDecisionPoint]: + """ + Given a list of SsvcDecisionPointGroup objects, return a list of all + the unique SsvcDecisionPoint objects contained in those groups. + + Args: + groups (list): A list of SsvcDecisionPointGroup objects. + + Returns: + list: A list of SsvcDecisionPoint objects. + """ + dps = [] + for group in glist: + for dp in group.decision_points: + if dp in dps: + # skip duplicates + continue + # keep non-duplicates + dps.append(dp) + return dps + + def main(): pass diff --git a/src/ssvc/dp_groups/patch_developer.py b/src/ssvc/dp_groups/patch_developer.py deleted file mode 100644 index c44639c5..00000000 --- a/src/ssvc/dp_groups/patch_developer.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -""" -file: patch_developer -author: adh -created_at: 9/21/23 10:27 AM -""" -from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION -from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT -from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT -from ssvc.decision_points.utility import UTILITY_1 as UTILITY -from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY -from ssvc.decision_points.virulence import VIRULENCE_1 as VIRULENCE -from ssvc.dp_groups.base import SsvcDecisionPointGroup - - -PATCH_DEVELOPER_1 = SsvcDecisionPointGroup( - name="SSVC Patch Developer", - description="The decision points used by the patch developer.", - key="PD", - version="1.0.0", - decision_points=[ - EXPLOITATION, - UTILITY, - TECHNICAL_IMPACT, - VIRULENCE, - VALUE_DENSITY, - SAFETY_IMPACT, - ], -) - - -def main(): - print(PATCH_DEVELOPER_1.to_json(indent=2)) - - -if __name__ == "__main__": - main() diff --git a/src/ssvc/dp_groups/supplier.py b/src/ssvc/dp_groups/supplier.py index bdb9dd28..535b03cf 100644 --- a/src/ssvc/dp_groups/supplier.py +++ b/src/ssvc/dp_groups/supplier.py @@ -4,33 +4,54 @@ author: adh created_at: 9/21/23 11:41 AM ''' -from ssvc.decision_points.automatable import AUTOMATABLE_1 as AUTOMATABLE -from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION -from ssvc.decision_points.public_safety_impact import PUBLIC_SAFETY_IMPACT_1 as PUBLIC_SAFETY_IMPACT -from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT -from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT -from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY -from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY +from copy import deepcopy + +from ssvc.decision_points.automatable import AUTOMATABLE_1 +from ssvc.decision_points.exploitation import EXPLOITATION_1 +from ssvc.decision_points.public_safety_impact import PUBLIC_SAFETY_IMPACT_1 +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 +from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 +from ssvc.decision_points.utility import UTILITY_1, UTILITY_1_0_1 +from ssvc.decision_points.value_density import VALUE_DENSITY_1 +from ssvc.decision_points.virulence import VIRULENCE_1 from ssvc.dp_groups.base import SsvcDecisionPointGroup + +PATCH_DEVELOPER_1 = SsvcDecisionPointGroup( + name="SSVC Patch Developer", + description="The decision points used by the patch developer.", + key="PD", + version="1.0.0", + decision_points=[ + EXPLOITATION_1, + UTILITY_1, + TECHNICAL_IMPACT_1, + VIRULENCE_1, + VALUE_DENSITY_1, + SAFETY_IMPACT_1, + ], +) + +# alias for forward compatibility +SUPPLIER_1 = PATCH_DEVELOPER_1 + +# SSVC v2 renamed to SSVC Supplier +SUPPLIER_2 = deepcopy(SUPPLIER_1) +SUPPLIER_2.name = "Supplier", +SUPPLIER_2.description = "The decision points used by the supplier.", +SUPPLIER_2.version = "2.0.0" +# replace UTILITY 1 with UTILITY 1.0.1 +SUPPLIER_2.decision_points.remove(UTILITY_1) +SUPPLIER_2.decision_points.append(UTILITY_1_0_1) +# add PUBLIC_SAFETY_IMPACT_1 +SUPPLIER_2.decision_points.append(PUBLIC_SAFETY_IMPACT_1) + + def main(): - pass - -if __name__=='__main__': + print(PATCH_DEVELOPER_1.to_json(indent=2)) + print(SUPPLIER_2.to_json(indent=2)) + + +if __name__ == '__main__': main() -SUPPLIER_1 = SsvcDecisionPointGroup( - name="Supplier", - description="The decision points used by the supplier.", - key="S", - version="1.0.0", - decision_points=[ - EXPLOITATION, - UTILITY, - AUTOMATABLE, - VALUE_DENSITY, - TECHNICAL_IMPACT, - PUBLIC_SAFETY_IMPACT, - SAFETY_IMPACT - ], -) diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py index 1a2b63c2..8decae18 100644 --- a/src/ssvc/dp_groups/v1.py +++ b/src/ssvc/dp_groups/v1.py @@ -4,35 +4,20 @@ author: adh created_at: 9/21/23 9:52 AM """ -from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION -from ssvc.decision_points.exposure import EXPOSURE_1 as EXPOSURE -from ssvc.decision_points.mission_impact import MISSION_IMPACT_1 as MISSION_IMPACT -from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT -from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT -from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY -from ssvc.decision_points.virulence import VIRULENCE_1 as VIRULENCE -from ssvc.dp_groups.base import SsvcDecisionPointGroup -from ssvc.decision_points.utility import UTILITY_1 as UTILITY +from ssvc.dp_groups.base import SsvcDecisionPointGroup, get_all_decision_points_from # convenience imports -from ssvc.dp_groups.deployer import PATCH_APPLIER_1 as PATCH_APPLIER # noqa -from ssvc.dp_groups.patch_developer import PATCH_DEVELOPER_1 as PATCH_DEVELOPER # noqa +from ssvc.dp_groups.deployer import PATCH_APPLIER_1 # noqa +from ssvc.dp_groups.supplier import PATCH_DEVELOPER_1 # noqa + +GROUPS = [PATCH_APPLIER_1, PATCH_DEVELOPER_1] SSVCv1 = SsvcDecisionPointGroup( name="SSVCv1", description="The first version of the SSVC.", key="SSVCv1", version="1.0.0", - decision_points=[ - EXPLOITATION, - TECHNICAL_IMPACT, - UTILITY, - VIRULENCE, - VALUE_DENSITY, - SAFETY_IMPACT, - EXPOSURE, - MISSION_IMPACT, - ], + decision_points=get_all_decision_points_from(GROUPS), ) diff --git a/src/ssvc/dp_groups/v2.py b/src/ssvc/dp_groups/v2.py index b8c27573..e47046ca 100644 --- a/src/ssvc/dp_groups/v2.py +++ b/src/ssvc/dp_groups/v2.py @@ -4,75 +4,34 @@ author: adh created_at: 9/21/23 10:31 AM """ -from copy import deepcopy -from ssvc.decision_points.automatable import AUTOMATABLE_1 as AUTOMATABLE -from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION -from ssvc.decision_points.exposure import EXPOSURE_1_0_1 as EXPOSURE -from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 as HUMAN_IMPACT -from ssvc.decision_points.mission_impact import MISSION_IMPACT_1 as MISSION_IMPACT -from ssvc.decision_points.public_safety_impact import ( - PUBLIC_SAFETY_IMPACT_1 as PUBLIC_SAFETY_IMPACT, -) -from ssvc.decision_points.public_value_added import PUBLIC_VALUE_ADDED_1 as PUBLIC_VALUE_ADDED -from ssvc.decision_points.report_credibility import REPORT_CREDIBILITY_1 as REPORT_CREDIBILITY -from ssvc.decision_points.report_public import REPORT_PUBLIC_1 as REPORT_PUBLIC -from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT -from ssvc.decision_points.supplier_cardinality import ( - SUPPLIER_CARDINALITY_1 as SUPPLIER_CARDINALITY, -) -from ssvc.decision_points.supplier_contacted import ( - SUPPLIER_CONTACTED_1 as SUPPLIER_CONTACTED, -) -from ssvc.decision_points.supplier_engagement import SUPPLIER_ENGAGEMENT_1 as SUPPLIER_ENGAGEMENT -from ssvc.decision_points.supplier_involvement import SUPPLIER_INVOLVEMENT_1 as SUPPLIER_INVOLVEMENT -from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT -from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY -from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY -from ssvc.dp_groups.base import SsvcDecisionPointGroup +from ssvc.dp_groups.base import SsvcDecisionPointGroup, get_all_decision_points_from # convenience imports -from ssvc.dp_groups.coordinator_publication import COORDINATOR_PUBLICATION_1 # noqa -from ssvc.dp_groups.coordinator_triage import COORDINATOR_TRIAGE_1 # noqa -from ssvc.dp_groups.deployer import DEPLOYER_2 # noqa -from ssvc.dp_groups.supplier import SUPPLIER_1 # noqa +from ssvc.dp_groups.coordinator_publication import COORDINATOR_PUBLICATION_1 # noqa +from ssvc.dp_groups.coordinator_triage import COORDINATOR_TRIAGE_1 # noqa +from ssvc.dp_groups.deployer import DEPLOYER_2 # noqa +from ssvc.dp_groups.supplier import SUPPLIER_2 # noqa + +GROUPS = [COORDINATOR_PUBLICATION_1, COORDINATOR_TRIAGE_1, DEPLOYER_2, SUPPLIER_2] -SITUATED_SAFETY_IMPACT = deepcopy(SAFETY_IMPACT) SSVCv2 = SsvcDecisionPointGroup( name="SSVCv2", description="The second version of the SSVC.", key="SSVCv2", version="2.0.0", - decision_points=[ - EXPLOITATION, - TECHNICAL_IMPACT, - UTILITY, - AUTOMATABLE, - VALUE_DENSITY, - SAFETY_IMPACT, - PUBLIC_SAFETY_IMPACT, - SITUATED_SAFETY_IMPACT, - MISSION_IMPACT, - HUMAN_IMPACT, - EXPOSURE, - REPORT_PUBLIC, - SUPPLIER_CONTACTED, - SUPPLIER_CARDINALITY, - SUPPLIER_ENGAGEMENT, - REPORT_CREDIBILITY, - PUBLIC_VALUE_ADDED, - SUPPLIER_INVOLVEMENT, - ], + decision_points=get_all_decision_points_from(GROUPS), ) def main(): print(SSVCv2.to_json(indent=2)) - print(SUPPLIER_1.to_json(indent=2)) + print(SUPPLIER_2.to_json(indent=2)) print(DEPLOYER_2.to_json(indent=2)) print(COORDINATOR_TRIAGE_1.to_json(indent=2)) print(COORDINATOR_PUBLICATION_1.to_json(indent=2)) + if __name__ == "__main__": main() diff --git a/src/ssvc/dp_groups/v2_1.py b/src/ssvc/dp_groups/v2_1.py index 7c4ad329..baebd7a1 100644 --- a/src/ssvc/dp_groups/v2_1.py +++ b/src/ssvc/dp_groups/v2_1.py @@ -1,82 +1,36 @@ #!/usr/bin/env python -''' +""" file: v2_1 author: adh created_at: 9/21/23 11:45 AM -''' +""" -from ssvc.decision_points.automatable import AUTOMATABLE_1 as AUTOMATABLE -from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION -from ssvc.decision_points.exposure import EXPOSURE_1_0_1 as EXPOSURE -from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 as HUMAN_IMPACT -from ssvc.decision_points.mission_impact import MISSION_IMPACT_2 as MISSION_IMPACT -from ssvc.decision_points.public_safety_impact import ( - PUBLIC_SAFETY_IMPACT_1 as PUBLIC_SAFETY_IMPACT, -) -from ssvc.decision_points.public_value_added import PUBLIC_VALUE_ADDED_1 as PUBLIC_VALUE_ADDED -from ssvc.decision_points.report_credibility import REPORT_CREDIBILITY_1 as REPORT_CREDIBILITY -from ssvc.decision_points.report_public import REPORT_PUBLIC_1 as REPORT_PUBLIC -from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 as SAFETY_IMPACT -from ssvc.decision_points.supplier_cardinality import ( - SUPPLIER_CARDINALITY_1 as SUPPLIER_CARDINALITY, -) -from ssvc.decision_points.supplier_contacted import ( - SUPPLIER_CONTACTED_1 as SUPPLIER_CONTACTED, -) -from ssvc.decision_points.supplier_engagement import SUPPLIER_ENGAGEMENT_1 as SUPPLIER_ENGAGEMENT -from ssvc.decision_points.supplier_involvement import SUPPLIER_INVOLVEMENT_1 as SUPPLIER_INVOLVEMENT -from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 as TECHNICAL_IMPACT -from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY -from ssvc.decision_points.value_density import VALUE_DENSITY_1 as VALUE_DENSITY -from ssvc.dp_groups.base import SsvcDecisionPointGroup +from ssvc.dp_groups.base import SsvcDecisionPointGroup, get_all_decision_points_from # convenience imports from ssvc.dp_groups.coordinator_publication import COORDINATOR_PUBLICATION_1 from ssvc.dp_groups.coordinator_triage import COORDINATOR_TRIAGE_1 from ssvc.dp_groups.deployer import DEPLOYER_3 -from ssvc.dp_groups.supplier import SUPPLIER_1 +from ssvc.dp_groups.supplier import SUPPLIER_2 + +GROUPS = [COORDINATOR_PUBLICATION_1, COORDINATOR_TRIAGE_1, DEPLOYER_3, SUPPLIER_2] + SSVCv2 = SsvcDecisionPointGroup( name="SSVCv2.1", description="The second version of the SSVC.", key="SSVCv2.1", version="2.1.0", - decision_points=[ - EXPLOITATION, - TECHNICAL_IMPACT, - UTILITY, - AUTOMATABLE, - VALUE_DENSITY, - SAFETY_IMPACT, - PUBLIC_SAFETY_IMPACT, - SAFETY_IMPACT, - MISSION_IMPACT, - HUMAN_IMPACT, - EXPOSURE, - REPORT_PUBLIC, - SUPPLIER_CONTACTED, - SUPPLIER_CARDINALITY, - SUPPLIER_ENGAGEMENT, - REPORT_CREDIBILITY, - PUBLIC_VALUE_ADDED, - SUPPLIER_INVOLVEMENT, - ], + decision_points=get_all_decision_points_from(GROUPS), ) def main(): + for group in GROUPS: + print(group.to_json(indent=2)) + print() print(SSVCv2.to_json(indent=2)) - print(SUPPLIER_1.to_json(indent=2)) - print(DEPLOYER_3.to_json(indent=2)) - print(COORDINATOR_TRIAGE_1.to_json(indent=2)) - print(COORDINATOR_PUBLICATION_1.to_json(indent=2)) -if __name__ == "__main__": - main() - -def main(): - pass - -if __name__ == '__main__': +if __name__ == "__main__": main() From 9c03070d0d6e8ea3c2217f95beb07b628cc4cf8a Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 13:51:36 -0400 Subject: [PATCH 17/53] abstract out values, use tuples --- src/ssvc/decision_points/base.py | 4 +- .../decision_points/cvss/access_complexity.py | 62 +++++---- .../decision_points/cvss/access_vector.py | 68 +++++----- .../decision_points/cvss/attack_complexity.py | 28 ++-- .../decision_points/cvss/attack_vector.py | 53 ++++---- .../decision_points/cvss/authentication.py | 68 +++++----- .../cvss/availability_impact.py | 4 - .../cvss/availability_requirement.py | 52 ++++---- .../cvss/collateral_damage_potential.py | 117 +++++++++-------- .../cvss/confidentiality_impact.py | 80 +++++++----- .../cvss/confidentiality_requirement.py | 52 ++++---- .../decision_points/cvss/exploitability.py | 120 ++++++++++-------- src/ssvc/decision_points/cvss/impact_bias.py | 53 ++++---- .../decision_points/cvss/integrity_impact.py | 75 ++++++----- .../cvss/integrity_requirement.py | 52 ++++---- .../cvss/privileges_required.py | 41 +++--- .../decision_points/cvss/remediation_level.py | 92 +++++++------- .../decision_points/cvss/report_confidence.py | 97 ++++++++------ src/ssvc/decision_points/cvss/scope.py | 28 ++-- .../cvss/target_distribution.py | 95 +++++++------- .../decision_points/cvss/user_interaction.py | 28 ++-- 21 files changed, 701 insertions(+), 568 deletions(-) diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index 2747288c..c25fc43e 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -6,7 +6,7 @@ """ from dataclasses import dataclass -from typing import List +from typing import Tuple from dataclasses_json import dataclass_json @@ -34,7 +34,7 @@ class SsvcDecisionPoint(_Base, _Versioned, _Namespaced): Models a single decision point as a list of values. """ - values: List[SsvcValue] + values: Tuple[SsvcValue] def main(): diff --git a/src/ssvc/decision_points/cvss/access_complexity.py b/src/ssvc/decision_points/cvss/access_complexity.py index 8e42f078..419d6cdb 100644 --- a/src/ssvc/decision_points/cvss/access_complexity.py +++ b/src/ssvc/decision_points/cvss/access_complexity.py @@ -7,6 +7,34 @@ from copy import deepcopy from ssvc.decision_points.base import SsvcValue + +HIGH_2 = SsvcValue( + name="High", key="H", description="Specialized access conditions exist." +) + +MEDIUM = SsvcValue( + name="Medium", + key="M", + description="The access conditions are somewhat specialized.", +) + +LOW_2 = SsvcValue( + name="Low", + key="L", + description="Specialized access conditions or extenuating circumstances do not exist.", +) + +HIGH = SsvcValue( + name="High", + key="H", + description="Specialized access conditions exist; for example: the system is exploitable during specific windows of time (a race condition), the system is exploitable under specific circumstances (nondefault configurations), or the system is exploitable with victim interaction (vulnerability exploitable only if user opens e-mail)", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="Specialized access conditions or extenuating circumstances do not exist; the system is always exploitable.", +) from ssvc.decision_points.cvss.base import CvssDecisionPoint ACCESS_COMPLEXITY_1 = CvssDecisionPoint( @@ -14,35 +42,19 @@ description="This metric measures the complexity of the attack required to exploit the vulnerability once an attacker has gained access to the target system.", key="AC", version="1.0.0", - values=[ - SsvcValue( - name="Low", - key="L", - description="Specialized access conditions or extenuating circumstances do not exist; the system is always exploitable.", - ), - SsvcValue( - name="High", - key="H", - description="Specialized access conditions exist; for example: the system is exploitable during specific windows of time (a race condition), the system is exploitable under specific circumstances (nondefault configurations), or the system is exploitable with victim interaction (vulnerability exploitable only if user opens e-mail)", - ), - ], + values=( + LOW, + HIGH, + ), ) ACCESS_COMPLEXITY_2 = deepcopy(ACCESS_COMPLEXITY_1) ACCESS_COMPLEXITY_2.version = "2.0.0" -ACCESS_COMPLEXITY_2.values = [ - SsvcValue( - name="Low", - key="L", - description="Specialized access conditions or extenuating circumstances do not exist.", - ), - SsvcValue( - name="Medium", - key="M", - description="The access conditions are somewhat specialized.", - ), - SsvcValue(name="High", key="H", description="Specialized access conditions exist."), -] +ACCESS_COMPLEXITY_2.values = ( + LOW_2, + MEDIUM, + HIGH_2, +) def main(): diff --git a/src/ssvc/decision_points/cvss/access_vector.py b/src/ssvc/decision_points/cvss/access_vector.py index c5ee3e16..3f530575 100644 --- a/src/ssvc/decision_points/cvss/access_vector.py +++ b/src/ssvc/decision_points/cvss/access_vector.py @@ -7,6 +7,36 @@ from copy import deepcopy from ssvc.decision_points.base import SsvcValue + +NETWORK = SsvcValue( + name="Network", + key="N", + description="A vulnerability exploitable with network access means the vulnerable software is bound to the network stack and the attacker does not require local network access or local access. Such a vulnerability is often termed 'remotely exploitable'. An example of a network attack is an RPC buffer overflow.", +) + +ADJACENT = SsvcValue( + name="Adjacent Network", + key="A", + description="A vulnerability exploitable with adjacent network access requires the attacker to have access to either the broadcast or collision domain of the vulnerable software. Examples of local networks include local IP subnet, Bluetooth, IEEE 802.11, and local Ethernet segment.", +) + +LOCAL_2 = SsvcValue( + name="Local", + key="L", + description="A vulnerability exploitable with only local access requires the attacker to have either physical access to the vulnerable system or a local (shell) account. Examples of locally exploitable vulnerabilities are peripheral attacks such as Firewire/USB DMA attacks, and local privilege escalations (e.g., sudo).", +) + +REMOTE = SsvcValue( + name="Remote", + key="R", + description="The vulnerability is exploitable remotely.", +) + +LOCAL = SsvcValue( + name="Local", + key="L", + description="The vulnerability is only exploitable locally (i.e., it requires physical access or authenticated login to the target system)", +) from ssvc.decision_points.cvss.base import CvssDecisionPoint ACCESS_VECTOR_1 = CvssDecisionPoint( @@ -14,39 +44,19 @@ description="This metric measures whether or not the vulnerability is exploitable locally or remotely.", key="AV", version="1.0.0", - values=[ - SsvcValue( - name="Local", - key="L", - description="The vulnerability is only exploitable locally (i.e., it requires physical access or authenticated login to the target system)", - ), - SsvcValue( - name="Remote", - key="R", - description="The vulnerability is exploitable remotely.", - ), - ], + values=( + LOCAL, + REMOTE, + ), ) ACCESS_VECTOR_2 = deepcopy(ACCESS_VECTOR_1) ACCESS_VECTOR_2.version = "2.0.0" -ACCESS_VECTOR_2.values = [ - SsvcValue( - name="Local", - key="L", - description="A vulnerability exploitable with only local access requires the attacker to have either physical access to the vulnerable system or a local (shell) account. Examples of locally exploitable vulnerabilities are peripheral attacks such as Firewire/USB DMA attacks, and local privilege escalations (e.g., sudo).", - ), - SsvcValue( - name="Adjacent Network", - key="A", - description="A vulnerability exploitable with adjacent network access requires the attacker to have access to either the broadcast or collision domain of the vulnerable software. Examples of local networks include local IP subnet, Bluetooth, IEEE 802.11, and local Ethernet segment.", - ), - SsvcValue( - name="Network", - key="N", - description="A vulnerability exploitable with network access means the vulnerable software is bound to the network stack and the attacker does not require local network access or local access. Such a vulnerability is often termed 'remotely exploitable'. An example of a network attack is an RPC buffer overflow.", - ), -] +ACCESS_VECTOR_2.values = ( + LOCAL_2, + ADJACENT, + NETWORK, +) def main(): diff --git a/src/ssvc/decision_points/cvss/attack_complexity.py b/src/ssvc/decision_points/cvss/attack_complexity.py index ce952f8b..fab5dedd 100644 --- a/src/ssvc/decision_points/cvss/attack_complexity.py +++ b/src/ssvc/decision_points/cvss/attack_complexity.py @@ -8,23 +8,27 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +HIGH = SsvcValue( + name="High", + key="H", + description="A successful attack depends on conditions beyond the attacker's control.", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success against the vulnerable component.", +) + ATTACK_COMPLEXITY_1 = CvssDecisionPoint( name="Attack Complexity", description="This metric describes the conditions beyond the attacker's control that must exist in order to exploit the vulnerability.", key="AC", version="1.0.0", - values=[ - SsvcValue( - name="Low", - key="L", - description="Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success against the vulnerable component.", - ), - SsvcValue( - name="High", - key="H", - description="A successful attack depends on conditions beyond the attacker's control.", - ), - ], + values=( + LOW, + HIGH, + ), ) diff --git a/src/ssvc/decision_points/cvss/attack_vector.py b/src/ssvc/decision_points/cvss/attack_vector.py index dcad01cf..6ede790d 100644 --- a/src/ssvc/decision_points/cvss/attack_vector.py +++ b/src/ssvc/decision_points/cvss/attack_vector.py @@ -4,37 +4,44 @@ author: adh created_at: 9/20/23 2:26 PM """ - from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +NETWORK = SsvcValue( + name="Network", + key="N", + description="A vulnerability exploitable with network access means the vulnerable component is bound to the network stack and the attacker's path is through OSI layer 3 (the network layer). Such a vulnerability is often termed 'remotely exploitable' and can be thought of as an attack being exploitable one or more network hops away (e.g. across layer 3 boundaries from routers).", +) + +ADJACENT = SsvcValue( + name="Adjacent", + key="A", + description="A vulnerability exploitable with adjacent network access means the vulnerable component is bound to the network stack, however the attack is limited to the same shared physical (e.g. Bluetooth, IEEE 802.11), or logical (e.g. local IP subnet) network, and cannot be performed across an OSI layer 3 boundary (e.g. a router).", +) + +LOCAL = SsvcValue( + name="Local", + key="L", + description="A vulnerability exploitable with Local access means that the vulnerable component is not bound to the network stack, and the attacker's path is via read/write/execute capabilities. In some cases, the attacker may be logged in locally in order to exploit the vulnerability, otherwise, she may rely on User Interaction to execute a malicious file.", +) + +PHYSICAL = SsvcValue( + name="Physical", + key="P", + description="A vulnerability exploitable with Physical access requires the attacker to physically touch or manipulate the vulnerable component. Physical interaction may be brief (e.g. evil maid attack [1]) or persistent. An example of such an attack is a cold boot attack which allows an attacker to access to disk encryption keys after gaining physical access to the system, or peripheral attacks such as Firewire/USB Direct Memory Access attacks.", +) + ATTACK_VECTOR_1 = CvssDecisionPoint( name="Attack Vector", description="This metric reflects the context by which vulnerability exploitation is possible. ", key="AV", version="1.0.0", - values=[ - SsvcValue( - name="Physical", - key="P", - description="A vulnerability exploitable with Physical access requires the attacker to physically touch or manipulate the vulnerable component. Physical interaction may be brief (e.g. evil maid attack [1]) or persistent. An example of such an attack is a cold boot attack which allows an attacker to access to disk encryption keys after gaining physical access to the system, or peripheral attacks such as Firewire/USB Direct Memory Access attacks.", - ), - SsvcValue( - name="Local", - key="L", - description="A vulnerability exploitable with Local access means that the vulnerable component is not bound to the network stack, and the attacker's path is via read/write/execute capabilities. In some cases, the attacker may be logged in locally in order to exploit the vulnerability, otherwise, she may rely on User Interaction to execute a malicious file.", - ), - SsvcValue( - name="Adjacent", - key="A", - description="A vulnerability exploitable with adjacent network access means the vulnerable component is bound to the network stack, however the attack is limited to the same shared physical (e.g. Bluetooth, IEEE 802.11), or logical (e.g. local IP subnet) network, and cannot be performed across an OSI layer 3 boundary (e.g. a router).", - ), - SsvcValue( - name="Network", - key="N", - description="A vulnerability exploitable with network access means the vulnerable component is bound to the network stack and the attacker's path is through OSI layer 3 (the network layer). Such a vulnerability is often termed 'remotely exploitable' and can be thought of as an attack being exploitable one or more network hops away (e.g. across layer 3 boundaries from routers).", - ), - ], + values=( + PHYSICAL, + LOCAL, + ADJACENT, + NETWORK, + ), ) diff --git a/src/ssvc/decision_points/cvss/authentication.py b/src/ssvc/decision_points/cvss/authentication.py index aa1626e7..7550526e 100644 --- a/src/ssvc/decision_points/cvss/authentication.py +++ b/src/ssvc/decision_points/cvss/authentication.py @@ -7,47 +7,57 @@ from copy import deepcopy from ssvc.decision_points.base import SsvcValue + +AUTH_NONE = SsvcValue( + name="None", + key="N", + description="Authentication is not required to exploit the vulnerability.", +) + +SINGLE = SsvcValue( + name="Single", + key="S", + description="The vulnerability requires an attacker to be logged into the system (such as at a command line or via a desktop session or web interface).", +) + +MULTIPLE = SsvcValue( + name="Multiple", + key="M", + description="Exploiting the vulnerability requires that the attacker authenticate two or more times, even if the same credentials are used each time.", +) from ssvc.decision_points.cvss.base import CvssDecisionPoint +REQUIRED = SsvcValue( + name="Required", + key="R", + description="Authentication is required to access and exploit the vulnerability.", +) + +NOT_REQUIRED = SsvcValue( + name="Not Required", + key="N", + description="Authentication is not required to access or exploit the vulnerability.", +) + AUTHENTICATION_1 = CvssDecisionPoint( name="Authentication", description="This metric measures whether or not an attacker needs to be authenticated to the target system in order to exploit the vulnerability.", key="AU", version="1.0.0", - values=[ - SsvcValue( - name="Not Required", - key="N", - description="Authentication is not required to access or exploit the vulnerability.", - ), - SsvcValue( - name="Required", - key="R", - description="Authentication is required to access and exploit the vulnerability.", - ), - ], + values=( + NOT_REQUIRED, + REQUIRED, + ), ) AUTHENTICATION_2 = deepcopy(AUTHENTICATION_1) AUTHENTICATION_2.version = "2.0.0" AUTHENTICATION_2.description = "This metric measures the number of times an attacker must authenticate to a target in order to exploit a vulnerability. This metric does not gauge the strength or complexity of the authentication process, only that an attacker is required to provide credentials before an exploit may occur. The possible values for this metric are listed in Table 3. The fewer authentication instances that are required, the higher the vulnerability score." -AUTHENTICATION_2.values = [ - SsvcValue( - name="Multiple", - key="M", - description="Exploiting the vulnerability requires that the attacker authenticate two or more times, even if the same credentials are used each time.", - ), - SsvcValue( - name="Single", - key="S", - description="The vulnerability requires an attacker to be logged into the system (such as at a command line or via a desktop session or web interface).", - ), - SsvcValue( - name="None", - key="N", - description="Authentication is not required to exploit the vulnerability.", - ), -] +AUTHENTICATION_2.values = ( + MULTIPLE, + SINGLE, + AUTH_NONE, +) def main(): diff --git a/src/ssvc/decision_points/cvss/availability_impact.py b/src/ssvc/decision_points/cvss/availability_impact.py index c719149c..726877cd 100644 --- a/src/ssvc/decision_points/cvss/availability_impact.py +++ b/src/ssvc/decision_points/cvss/availability_impact.py @@ -5,14 +5,10 @@ created_at: 9/20/23 1:46 PM """ from copy import deepcopy -from dataclasses import dataclass, field - -from dataclasses_json import dataclass_json from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint - AVAILABILITY_IMPACT_1 = CvssDecisionPoint( name="Availability Impact", description="This metric measures the impact on availability a successful exploit of the vulnerability will have on the target system.", diff --git a/src/ssvc/decision_points/cvss/availability_requirement.py b/src/ssvc/decision_points/cvss/availability_requirement.py index 64dcfc04..4059c368 100644 --- a/src/ssvc/decision_points/cvss/availability_requirement.py +++ b/src/ssvc/decision_points/cvss/availability_requirement.py @@ -8,34 +8,42 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +NOT_DEFINED = SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", +) + +HIGH = SsvcValue( + name="High", + key="H", + description="Loss of availability is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", +) + +MEDIUM = SsvcValue( + name="Medium", + key="M", + description="Loss of availability is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="Loss of availability is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", +) + AVAILABILITY_REQUIREMENT_1 = CvssDecisionPoint( name="Availability Requirement", description="This metric measures the impact to the availability of a successfully exploited vulnerability.", key="CR", version="1.0.0", - values=[ - SsvcValue( - name="Low", - key="L", - description="Loss of availability is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", - ), - SsvcValue( - name="Medium", - key="M", - description="Loss of availability is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", - ), - SsvcValue( - name="High", - key="H", - description="Loss of availability is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", - ), - SsvcValue( - name="Not Defined", - key="ND", - description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", - ), - ], + values=( + LOW, + MEDIUM, + HIGH, + NOT_DEFINED, + ), ) diff --git a/src/ssvc/decision_points/cvss/collateral_damage_potential.py b/src/ssvc/decision_points/cvss/collateral_damage_potential.py index bf8955f5..f6a51d4d 100644 --- a/src/ssvc/decision_points/cvss/collateral_damage_potential.py +++ b/src/ssvc/decision_points/cvss/collateral_damage_potential.py @@ -7,71 +7,80 @@ from copy import deepcopy from ssvc.decision_points.base import SsvcValue + +NOT_DEFINED = SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", +) + +MEDIUM_HIGH = SsvcValue( + name="Medium-High", + key="MH", + description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", +) + +LOW_MEDIUM = SsvcValue( + name="Low-Medium", + key="LM", + description="A successful exploit of this vulnerability may result in moderate physical or property damage or loss.", +) + +CDP_NONE_2 = SsvcValue( + name="None", + key="N", + description="There is no potential for loss of life, physical assets, productivity or revenue.", +) + +HIGH = SsvcValue( + name="High", + key="H", + description="A successful exploit of this vulnerability may result in catastrophic physical or property damage and loss. The range of effect may be over a wide area.", +) + +MEDIUM = SsvcValue( + name="Medium", + key="M", + description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="A successful exploit of this vulnerability may result in light physical or property damage or loss. The system itself may be damaged or destroyed.", +) from ssvc.decision_points.cvss.base import CvssDecisionPoint +CDP_NONE = SsvcValue( + name="None", + key="N", + description="There is no potential for physical or property damage.", +) + + COLLATERAL_DAMAGE_POTENTIAL_1 = CvssDecisionPoint( name="Collateral Damage Potential", description="This metric measures the potential for a loss in physical equipment, property damage or loss of life or limb.", key="CDP", version="1.0.0", - values=[ - SsvcValue( - name="None", - key="N", - description="There is no potential for physical or property damage.", - ), - SsvcValue( - name="Low", - key="L", - description="A successful exploit of this vulnerability may result in light physical or property damage or loss. The system itself may be damaged or destroyed.", - ), - SsvcValue( - name="Medium", - key="M", - description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", - ), - SsvcValue( - name="High", - key="H", - description="A successful exploit of this vulnerability may result in catastrophic physical or property damage and loss. The range of effect may be over a wide area.", - ), - ], + values=( + CDP_NONE, + LOW, + MEDIUM, + HIGH, + ), ) COLLATERAL_DAMAGE_POTENTIAL_2 = deepcopy(COLLATERAL_DAMAGE_POTENTIAL_1) COLLATERAL_DAMAGE_POTENTIAL_2.version = "2.0.0" -COLLATERAL_DAMAGE_POTENTIAL_2.values = [ - SsvcValue( - name="None", - key="N", - description="There is no potential for loss of life, physical assets, productivity or revenue.", - ), - SsvcValue( - name="Low", - key="L", - description="A successful exploit of this vulnerability may result in light physical or property damage or loss. The system itself may be damaged or destroyed.", - ), - SsvcValue( - name="Low-Medium", - key="LM", - description="A successful exploit of this vulnerability may result in moderate physical or property damage or loss.", - ), - SsvcValue( - name="Medium-High", - key="MH", - description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", - ), - SsvcValue( - name="High", - key="H", - description="A successful exploit of this vulnerability may result in catastrophic physical or property damage and loss. The range of effect may be over a wide area.", - ), - SsvcValue( - name="Not Defined", - key="ND", - description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", - ), -] +COLLATERAL_DAMAGE_POTENTIAL_2.values = ( + CDP_NONE_2, + LOW, + LOW_MEDIUM, + MEDIUM_HIGH, + HIGH, + NOT_DEFINED, +) def main(): diff --git a/src/ssvc/decision_points/cvss/confidentiality_impact.py b/src/ssvc/decision_points/cvss/confidentiality_impact.py index 6bb6eef4..dca1ab52 100644 --- a/src/ssvc/decision_points/cvss/confidentiality_impact.py +++ b/src/ssvc/decision_points/cvss/confidentiality_impact.py @@ -9,49 +9,61 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +HIGH = SsvcValue( + name="High", + key="H", + description="There is total loss of confidentiality, resulting in all resources within the impacted component being divulged to the attacker. Alternatively, access to only some restricted information is obtained, but the disclosed information presents a direct, serious impact. For example, an attacker steals the administrator's password, or private encryption keys of a web server.", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="There is some loss of confidentiality. Access to some restricted information is obtained, but the attacker does not have control over what information is obtained, or the amount or kind of loss is constrained. The information disclosure does not cause a direct, serious loss to the impacted component.", +) + +CI_NONE_2 = SsvcValue( + name="None", + key="N", + description="There is no loss of confidentiality within the impacted component.", +) + +COMPLETE = SsvcValue( + name="Complete", + key="C", + description="A total compromise of critical system information. A complete loss of system protection resulting in all critical system files being revealed. The attacker has sovereign control to read all of the system's data (memory, files, etc).", +) + +PARTIAL = SsvcValue( + name="Partial", + key="P", + description="There is considerable informational disclosure. Access to critical system files is possible. There is a loss of important information, but the attacker doesn't have control over what is obtainable or the scope of the loss is constrained.", +) + +CI_NONE = SsvcValue( + name="None", + key="N", + description="No impact on confidentiality.", +) + CONFIDENTIALITY_IMPACT_1 = CvssDecisionPoint( name="Confidentiality Impact", description="This metric measures the impact on confidentiality of a successful exploit of the vulnerability on the target system.", key="C", version="1.0.0", - values=[ - SsvcValue( - name="None", - key="N", - description="No impact on confidentiality.", - ), - SsvcValue( - name="Partial", - key="P", - description="There is considerable informational disclosure. Access to critical system files is possible. There is a loss of important information, but the attacker doesn't have control over what is obtainable or the scope of the loss is constrained.", - ), - SsvcValue( - name="Complete", - key="C", - description="A total compromise of critical system information. A complete loss of system protection resulting in all critical system files being revealed. The attacker has sovereign control to read all of the system's data (memory, files, etc).", - ), - ], + values=( + CI_NONE, + PARTIAL, + COMPLETE, + ), ) CONFIDENTIALITY_IMPACT_2 = deepcopy(CONFIDENTIALITY_IMPACT_1) CONFIDENTIALITY_IMPACT_2.version = "2.0.0" -CONFIDENTIALITY_IMPACT_2.values = [ - SsvcValue( - name="None", - key="N", - description="There is no loss of confidentiality within the impacted component.", - ), - SsvcValue( - name="Low", - key="L", - description="There is some loss of confidentiality. Access to some restricted information is obtained, but the attacker does not have control over what information is obtained, or the amount or kind of loss is constrained. The information disclosure does not cause a direct, serious loss to the impacted component.", - ), - SsvcValue( - name="High", - key="H", - description="There is total loss of confidentiality, resulting in all resources within the impacted component being divulged to the attacker. Alternatively, access to only some restricted information is obtained, but the disclosed information presents a direct, serious impact. For example, an attacker steals the administrator's password, or private encryption keys of a web server.", - ), -] +CONFIDENTIALITY_IMPACT_2.values = ( + CI_NONE_2, + LOW, + HIGH, +) def main(): diff --git a/src/ssvc/decision_points/cvss/confidentiality_requirement.py b/src/ssvc/decision_points/cvss/confidentiality_requirement.py index 8af4b8ae..841738c5 100644 --- a/src/ssvc/decision_points/cvss/confidentiality_requirement.py +++ b/src/ssvc/decision_points/cvss/confidentiality_requirement.py @@ -8,33 +8,41 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +NOT_DEFINED = SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", +) + +HIGH = SsvcValue( + name="High", + key="H", + description="Loss of confidentiality is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", +) + +MEDIUM = SsvcValue( + name="Medium", + key="M", + description="Loss of confidentiality is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="Loss of confidentiality is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", +) + CONFIDENTIALITY_REQUIREMENT_1 = CvssDecisionPoint( name="Confidentiality Requirement", description="This metric measures the impact to the confidentiality of a successfully exploited vulnerability.", key="CR", version="1.0.0", - values=[ - SsvcValue( - name="Low", - key="L", - description="Loss of confidentiality is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", - ), - SsvcValue( - name="Medium", - key="M", - description="Loss of confidentiality is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", - ), - SsvcValue( - name="High", - key="H", - description="Loss of confidentiality is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", - ), - SsvcValue( - name="Not Defined", - key="ND", - description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", - ), - ], + values=( + LOW, + MEDIUM, + HIGH, + NOT_DEFINED, + ), ) diff --git a/src/ssvc/decision_points/cvss/exploitability.py b/src/ssvc/decision_points/cvss/exploitability.py index 336d671d..8a611016 100644 --- a/src/ssvc/decision_points/cvss/exploitability.py +++ b/src/ssvc/decision_points/cvss/exploitability.py @@ -10,33 +10,71 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +NOT_DEFINED = SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to a scoring equation to skip this metric.", +) + +HIGH_2 = SsvcValue( + name="High", + key="H", + description="Functional autonomous code exists, or no exploit is required (manual trigger) and details are widely available. Exploit code works in every situation, or is actively being delivered via an autonomous agent (such as a worm or virus). Network-connected systems are likely to encounter scanning or exploitation attempts. Exploit development has reached the level of reliable, widely-available, easy-to-use automated tools.", +) + +FUNCTIONAL_2 = SsvcValue( + name="Functional", + key="F", + description="Functional exploit code is available. The code works in most situations where the vulnerability exists.", +) + +PROOF_OF_CONCEPT_2 = SsvcValue( + name="Proof-of-Concept", + key="POC", + description="Proof-of-concept exploit code is available, or an attack demonstration is not practical for most systems. The code or technique is not functional in all situations and may require substantial modification by a skilled attacker.", +) + +UNPROVEN_2 = SsvcValue( + name="Unproven", + key="U", + description="No exploit code is available, or an exploit is theoretical.", +) + +HIGH = SsvcValue( + name="High", + key="H", + description="Either the vulnerability is exploitable by functional mobile autonomous code or no exploit is required (manual trigger) and the details for the manual technique are widely available. The code works in every situation where the vulnerability is exploitable and/or is actively being delivered via a mobile autonomous agent (a worm or virus).", +) + +FUNCTIONAL = SsvcValue( + name="Functional", + key="F", + description="Functional exploit code is available. The code works in most situations where the vulnerability is exploitable.", +) + +PROOF_OF_CONCEPT = SsvcValue( + name="Proof of Concept", + key="P", + description="Proof of concept exploit code or an attack demonstration that is not practically applicable to deployed systems is available. The code or technique is not functional in all situations and may require substantial hand tuning by a skilled attacker for use against deployed systems.", +) + +UNPROVEN = SsvcValue( + name="Unproven", + key="U", + description="No exploit code is yet available or an exploit method is entirely theoretical.", +) + EXPLOITABILITY_1 = CvssDecisionPoint( name="Exploitability", description="This metric measures the current state of exploit technique or code availability and suggests a likelihood of exploitation.", key="E", version="1.0.0", - values=[ - SsvcValue( - name="Unproven", - key="U", - description="No exploit code is yet available or an exploit method is entirely theoretical.", - ), - SsvcValue( - name="Proof of Concept", - key="P", - description="Proof of concept exploit code or an attack demonstration that is not practically applicable to deployed systems is available. The code or technique is not functional in all situations and may require substantial hand tuning by a skilled attacker for use against deployed systems.", - ), - SsvcValue( - name="Functional", - key="F", - description="Functional exploit code is available. The code works in most situations where the vulnerability is exploitable.", - ), - SsvcValue( - name="High", - key="H", - description="Either the vulnerability is exploitable by functional mobile autonomous code or no exploit is required (manual trigger) and the details for the manual technique are widely available. The code works in every situation where the vulnerability is exploitable and/or is actively being delivered via a mobile autonomous agent (a worm or virus).", - ), - ], + values=( + UNPROVEN, + PROOF_OF_CONCEPT, + FUNCTIONAL, + HIGH, + ), ) @@ -44,7 +82,9 @@ EXPLOITABILITY_1_1 = deepcopy(EXPLOITABILITY_1) EXPLOITABILITY_1_1.version = "1.1.0" nd = SsvcValue(name="Not Defined", key="ND", description="Not Defined") -EXPLOITABILITY_1_1.values.append(nd) +values = list(EXPLOITABILITY_1_1.values) +values.append(nd) +EXPLOITABILITY_1_1.values = tuple(values) # CVSS v3 modified the descriptions of the values. @@ -52,33 +92,13 @@ EXPLOIT_CODE_MATURITY_1_1_1.name = "Exploit Code Maturity" EXPLOIT_CODE_MATURITY_1_1_1.description = "measures the likelihood of the vulnerability being attacked, and is typically based on the current state of exploit techniques, exploit code availability, or active, 'in-the-wild' exploitation" EXPLOIT_CODE_MATURITY_1_1_1.version = "1.1.1" -EXPLOIT_CODE_MATURITY_1_1_1.values = [ - SsvcValue( - name="Unproven", - key="U", - description="No exploit code is available, or an exploit is theoretical.", - ), - SsvcValue( - name="Proof-of-Concept", - key="POC", - description="Proof-of-concept exploit code is available, or an attack demonstration is not practical for most systems. The code or technique is not functional in all situations and may require substantial modification by a skilled attacker.", - ), - SsvcValue( - name="Functional", - key="F", - description="Functional exploit code is available. The code works in most situations where the vulnerability exists.", - ), - SsvcValue( - name="High", - key="H", - description="Functional autonomous code exists, or no exploit is required (manual trigger) and details are widely available. Exploit code works in every situation, or is actively being delivered via an autonomous agent (such as a worm or virus). Network-connected systems are likely to encounter scanning or exploitation attempts. Exploit development has reached the level of reliable, widely-available, easy-to-use automated tools.", - ), - SsvcValue( - name="Not Defined", - key="ND", - description="Assigning this value to the metric will not influence the score. It is a signal to a scoring equation to skip this metric.", - ), -] +EXPLOIT_CODE_MATURITY_1_1_1.values = ( + UNPROVEN_2, + PROOF_OF_CONCEPT_2, + FUNCTIONAL_2, + HIGH_2, + NOT_DEFINED, +) def main(): diff --git a/src/ssvc/decision_points/cvss/impact_bias.py b/src/ssvc/decision_points/cvss/impact_bias.py index d59a4f9b..385a94cf 100644 --- a/src/ssvc/decision_points/cvss/impact_bias.py +++ b/src/ssvc/decision_points/cvss/impact_bias.py @@ -4,37 +4,44 @@ author: adh created_at: 9/20/23 1:47 PM """ - from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +AVAILABILITY = SsvcValue( + name="Availability", + key="A", + description="Availability Impact is assigned greater weight than Confidentiality Impact or Integrity Impact.", +) + +INTEGRITY = SsvcValue( + name="Integrity", + key="I", + description="Integrity Impact is assigned greater weight than Confidentiality Impact or Availability Impact.", +) + +CONFIDENTIALITY = SsvcValue( + name="Confidentiality", + key="C", + description="Confidentiality impact is assigned greater weight than Integrity Impact or Availability Impact.", +) + +NORMAL = SsvcValue( + name="Normal", + key="N", + description="Confidentiality Impact, Integrity Impact, and Availability Impact are all assigned the same weight.", +) + IMPACT_BIAS_1 = CvssDecisionPoint( name="Impact Bias", description="This metric measures the impact bias of the vulnerability.", key="IB", version="1.0.0", - values=[ - SsvcValue( - name="Normal", - key="N", - description="Confidentiality Impact, Integrity Impact, and Availability Impact are all assigned the same weight.", - ), - SsvcValue( - name="Confidentiality", - key="C", - description="Confidentiality impact is assigned greater weight than Integrity Impact or Availability Impact.", - ), - SsvcValue( - name="Integrity", - key="I", - description="Integrity Impact is assigned greater weight than Confidentiality Impact or Availability Impact.", - ), - SsvcValue( - name="Availability", - key="A", - description="Availability Impact is assigned greater weight than Confidentiality Impact or Integrity Impact.", - ), - ], + values=( + NORMAL, + CONFIDENTIALITY, + INTEGRITY, + AVAILABILITY, + ), ) diff --git a/src/ssvc/decision_points/cvss/integrity_impact.py b/src/ssvc/decision_points/cvss/integrity_impact.py index 5e77352d..bcee4ab9 100644 --- a/src/ssvc/decision_points/cvss/integrity_impact.py +++ b/src/ssvc/decision_points/cvss/integrity_impact.py @@ -5,52 +5,61 @@ created_at: 9/20/23 1:46 PM """ from copy import deepcopy -from dataclasses import dataclass, field - -from dataclasses_json import dataclass_json from ssvc.decision_points.base import SsvcValue + from ssvc.decision_points.cvss.base import CvssDecisionPoint +II_HIGH = SsvcValue( + name="High", + key="H", + description="There is a total loss of integrity, or a complete loss of protection. For example, the attacker is able to modify any/all files protected by the impacted component. Alternatively, only some files can be modified, but malicious modification would present a direct, serious consequence to the impacted component.", +) + +II_LOW = SsvcValue( + name="Low", + key="L", + description="Modification of data is possible, but the attacker does not have control over the consequence of a modification, or the amount of modification is constrained. The data modification does not have a direct, serious impact on the impacted component.", +) + +II_NONE_2 = SsvcValue( + name="None", + key="N", + description="There is no impact to the integrity of the system.", +) +COMPLETE = SsvcValue( + name="Complete", + key="C", + description="A total compromise of system integrity. There is a complete loss of system protection resulting in the entire system being compromised. The attacker has sovereign control to modify any system files.", +) + +PARTIAL = SsvcValue( + name="Partial", + key="P", + description="Considerable breach in integrity. Modification of critical system files or information is possible, but the attacker does not have control over what can be modified, or the scope of what the attacker can affect is constrained. For example, key system or program files may be overwritten or modified, but at random or in a limited context or scope.", +) + +II_NONE = SsvcValue(name="None", key="N", description="No impact on integrity.") + INTEGRITY_IMPACT_1 = CvssDecisionPoint( name="Integrity Impact", description="This metric measures the impact on integrity a successful exploit of the vulnerability will have on the target system.", key="I", version="1.0.0", - values=[ - SsvcValue(name="None", key="N", description="No impact on integrity."), - SsvcValue( - name="Partial", - key="P", - description="Considerable breach in integrity. Modification of critical system files or information is possible, but the attacker does not have control over what can be modified, or the scope of what the attacker can affect is constrained. For example, key system or program files may be overwritten or modified, but at random or in a limited context or scope.", - ), - SsvcValue( - name="Complete", - key="C", - description="A total compromise of system integrity. There is a complete loss of system protection resulting in the entire system being compromised. The attacker has sovereign control to modify any system files.", - ), - ], + values=( + II_NONE, + PARTIAL, + COMPLETE, + ), ) INTEGRITY_IMPACT_2 = deepcopy(INTEGRITY_IMPACT_1) INTEGRITY_IMPACT_2.version = "2.0.0" -INTEGRITY_IMPACT_2.values = [ - SsvcValue( - name="None", - key="N", - description="There is no impact to the integrity of the system.", - ), - SsvcValue( - name="Low", - key="L", - description="Modification of data is possible, but the attacker does not have control over the consequence of a modification, or the amount of modification is constrained. The data modification does not have a direct, serious impact on the impacted component.", - ), - SsvcValue( - name="High", - key="H", - description="There is a total loss of integrity, or a complete loss of protection. For example, the attacker is able to modify any/all files protected by the impacted component. Alternatively, only some files can be modified, but malicious modification would present a direct, serious consequence to the impacted component.", - ), -] +INTEGRITY_IMPACT_2.values = ( + II_NONE_2, + II_LOW, + II_HIGH, +) def main(): diff --git a/src/ssvc/decision_points/cvss/integrity_requirement.py b/src/ssvc/decision_points/cvss/integrity_requirement.py index 9cb1606d..1a18e3cc 100644 --- a/src/ssvc/decision_points/cvss/integrity_requirement.py +++ b/src/ssvc/decision_points/cvss/integrity_requirement.py @@ -8,33 +8,41 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +NOT_DEFINED = SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", +) + +HIGH = SsvcValue( + name="High", + key="H", + description="Loss of integrity is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", +) + +MEDIUM = SsvcValue( + name="Medium", + key="M", + description="Loss of integrity is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="Loss of integrity is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", +) + INTEGRITY_REQUIREMENT_1 = CvssDecisionPoint( name="Integrity Requirement", description="This metric measures the impact to the integrity of a successfully exploited vulnerability.", key="CR", version="1.0.0", - values=[ - SsvcValue( - name="Low", - key="L", - description="Loss of integrity is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", - ), - SsvcValue( - name="Medium", - key="M", - description="Loss of integrity is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", - ), - SsvcValue( - name="High", - key="H", - description="Loss of integrity is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", - ), - SsvcValue( - name="Not Defined", - key="ND", - description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", - ), - ], + values=( + LOW, + MEDIUM, + HIGH, + NOT_DEFINED, + ), ) diff --git a/src/ssvc/decision_points/cvss/privileges_required.py b/src/ssvc/decision_points/cvss/privileges_required.py index e2af78e5..7e85c706 100644 --- a/src/ssvc/decision_points/cvss/privileges_required.py +++ b/src/ssvc/decision_points/cvss/privileges_required.py @@ -4,32 +4,37 @@ author: adh created_at: 9/20/23 2:38 PM """ - from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +HIGH = SsvcValue( + name="High", + key="H", + description="The attacker is authorized with (i.e. requires) privileges that provide significant (e.g. administrative) control over the vulnerable component that could affect component-wide settings and files.", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="The attacker is authorized with (i.e. requires) privileges that provide basic user capabilities that could normally affect only settings and files owned by a user. Alternatively, an attacker with Low privileges may have the ability to cause an impact only to non-sensitive resources.", +) + +PR_NONE = SsvcValue( + name="None", + key="N", + description="The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files to carry out an attack.", +) + PRIVILEGES_REQUIRED_1 = CvssDecisionPoint( name="Privileges Required", description="This metric describes the level of privileges an attacker must possess before successfully exploiting the vulnerability.", key="PR", version="1.0.0", - values=[ - SsvcValue( - name="None", - key="N", - description="The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files to carry out an attack.", - ), - SsvcValue( - name="Low", - key="L", - description="The attacker is authorized with (i.e. requires) privileges that provide basic user capabilities that could normally affect only settings and files owned by a user. Alternatively, an attacker with Low privileges may have the ability to cause an impact only to non-sensitive resources.", - ), - SsvcValue( - name="High", - key="H", - description="The attacker is authorized with (i.e. requires) privileges that provide significant (e.g. administrative) control over the vulnerable component that could affect component-wide settings and files.", - ), - ], + values=( + PR_NONE, + LOW, + HIGH, + ), ) diff --git a/src/ssvc/decision_points/cvss/remediation_level.py b/src/ssvc/decision_points/cvss/remediation_level.py index 846cc3ea..3f9de3b8 100644 --- a/src/ssvc/decision_points/cvss/remediation_level.py +++ b/src/ssvc/decision_points/cvss/remediation_level.py @@ -9,65 +9,59 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +NOT_DEFINED = SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", +) + +UNAVAILABLE = SsvcValue( + name="Unavailable", + key="U", + description="There is either no solution available or it is impossible to apply.", +) + +WORKAROUND = SsvcValue( + name="Workaround", + key="W", + description="There is an unofficial, non-vendor solution available. In some cases, users of the affected technology will create a patch of their own or provide steps to work around or otherwise mitigate against the vulnerability. When it is generally accepted that these unofficial fixes are adequate in plugging the hole for the mean time and no official remediation is available, this value can be set.", +) + +TEMPORARY_FIX = SsvcValue( + name="Temporary Fix", + key="TF", + description="There is an official but temporary fix available. This includes instances where the vendor issues a temporary hotfix, tool or official workaround.", +) + +OFFICIAL_FIX = SsvcValue( + name="Official Fix", + key="OF", + description="A complete vendor solution is available. Either the vendor has issued the final, official patch which eliminates the vulnerability or an upgrade that is not vulnerable is available.", +) + REMEDIATION_LEVEL_1 = CvssDecisionPoint( name="Remediation Level", description="This metric measures the remediation status of a vulnerability.", key="RL", version="1.0.0", - values=[ - SsvcValue( - name="Official Fix", - key="OF", - description="A complete vendor solution is available. Either the vendor has issued the final, official patch which eliminates the vulnerability or an upgrade that is not vulnerable is available.", - ), - SsvcValue( - name="Temporary Fix", - key="TF", - description="There is an official but temporary fix available. This includes instances where the vendor issues a temporary hotfix, tool or official workaround.", - ), - SsvcValue( - name="Workaround", - key="W", - description="There is an unofficial, non-vendor solution available. In some cases, users of the affected technology will create a patch of their own or provide steps to work around or otherwise mitigate against the vulnerability. When it is generally accepted that these unofficial fixes are adequate in plugging the hole for the mean time and no official remediation is available, this value can be set.", - ), - SsvcValue( - name="Unavailable", - key="U", - description="There is either no solution available or it is impossible to apply.", - ), - ], + values=( + OFFICIAL_FIX, + TEMPORARY_FIX, + WORKAROUND, + UNAVAILABLE, + ), ) REMEDIATION_LEVEL_1_1 = deepcopy(REMEDIATION_LEVEL_1) REMEDIATION_LEVEL_1_1.version = "1.1.0" -REMEDIATION_LEVEL_1_1.values = [ - SsvcValue( - name="Official Fix", - key="OF", - description="A complete vendor solution is available. Either the vendor has issued the final, official patch which eliminates the vulnerability or an upgrade that is not vulnerable is available.", - ), - SsvcValue( - name="Temporary Fix", - key="TF", - description="There is an official but temporary fix available. This includes instances where the vendor issues a temporary hotfix, tool or official workaround.", - ), - SsvcValue( - name="Workaround", - key="W", - description="There is an unofficial, non-vendor solution available. In some cases, users of the affected technology will create a patch of their own or provide steps to work around or otherwise mitigate against the vulnerability. When it is generally accepted that these unofficial fixes are adequate in plugging the hole for the mean time and no official remediation is available, this value can be set.", - ), - SsvcValue( - name="Unavailable", - key="U", - description="There is either no solution available or it is impossible to apply.", - ), - SsvcValue( - name="Not Defined", - key="ND", - description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", - ), -] +REMEDIATION_LEVEL_1_1.values = ( + OFFICIAL_FIX, + TEMPORARY_FIX, + WORKAROUND, + UNAVAILABLE, + NOT_DEFINED, +) def main(): diff --git a/src/ssvc/decision_points/cvss/report_confidence.py b/src/ssvc/decision_points/cvss/report_confidence.py index 37e8fcea..7dfba80e 100644 --- a/src/ssvc/decision_points/cvss/report_confidence.py +++ b/src/ssvc/decision_points/cvss/report_confidence.py @@ -9,61 +9,76 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +NOT_DEFINED = SsvcValue( + name="Not Defined", + key="ND", + description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", +) + +CONFIRMED_2 = SsvcValue( + name="Confirmed", + key="C", + description="Detailed reports exist, or functional reproduction is possible (functional exploits may provide this). Source code is available to independently verify the assertions of the research, or the author or vendor of the affected code has confirmed the presence of the vulnerability.", +) + +REASONABLE = SsvcValue( + name="Reasonable", + key="R", + description="Significant details are published, but researchers either do not have full confidence in the root cause, or do not have access to source code to fully confirm all of the interactions that may lead to the result. Reasonable confidence exists, however, that the bug is reproducible and at least one impact is able to be verified (proof-of-concept exploits may provide this). An example is a detailed write-up of research into a vulnerability with an explanation (possibly obfuscated or 'left as an exercise to the reader') that gives assurances on how to reproduce the results.", +) + +UNKNOWN = SsvcValue( + name="Unknown", + key="U", + description="There are reports of impacts that indicate a vulnerability is present. The reports indicate that the cause of the vulnerability is unknown, or reports may differ on the cause or impacts of the vulnerability. Reporters are uncertain of the true nature of the vulnerability, and there is little confidence in the validity of the reports or whether a static Base score can be applied given the differences described. An example is a bug report which notes that an intermittent but non-reproducible crash occurs, with evidence of memory corruption suggesting that denial of service, or possible more serious impacts, may result.", +) + +CONFIRMED = SsvcValue( + name="Confirmed", + key="C", + description="Vendor or author of the affected technology has acknowledged that the vulnerability exists. This value may also be set when existence of a vulnerability is confirmed with absolute confidence through some other event, such as publication of functional proof of concept exploit code or widespread exploitation.", +) + +UNCORROBORATED = SsvcValue( + name="Uncorroborated", + key="UR", + description="Multiple non-official sources; possibily including independent security companies or research organizations. At this point there may be conflicting technical details or some other lingering ambiguity.", +) + +UNCONFIRMED = SsvcValue( + name="Unconfirmed", + key="UC", + description="A single unconfirmed source or possibly several conflicting reports. There is little confidence in the validity of the report. For example, a rumor that surfaces from the hacker underground.", +) + REPORT_CONFIDENCE_1 = CvssDecisionPoint( name="Report Confidence", description="This metric measures the degree of confidence in the existence of the vulnerability and the credibility of the known technical details.", key="RC", version="1.0.0", - values=[ - SsvcValue( - name="Unconfirmed", - key="UC", - description="A single unconfirmed source or possibly several conflicting reports. There is little confidence in the validity of the report. For example, a rumor that surfaces from the hacker underground.", - ), - SsvcValue( - name="Uncorroborated", - key="UR", - description="Multiple non-official sources; possibily including independent security companies or research organizations. At this point there may be conflicting technical details or some other lingering ambiguity.", - ), - SsvcValue( - name="Confirmed", - key="C", - description="Vendor or author of the affected technology has acknowledged that the vulnerability exists. This value may also be set when existence of a vulnerability is confirmed with absolute confidence through some other event, such as publication of functional proof of concept exploit code or widespread exploitation.", - ), - ], + values=( + UNCONFIRMED, + UNCORROBORATED, + CONFIRMED, + ), ) # CVSS v2 added Not Defined. REPORT_CONFIDENCE_1_1 = deepcopy(REPORT_CONFIDENCE_1) REPORT_CONFIDENCE_1_1.version = "1.1.0" -nd = SsvcValue( - name="Not Defined", - key="ND", - description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", -) -REPORT_CONFIDENCE_1_1.values.append(nd) +values = list(REPORT_CONFIDENCE_1_1.values) +values.append(NOT_DEFINED) +REPORT_CONFIDENCE_1_1.values = tuple(values) # CVSS v3 changed names and descriptions for this metric. REPORT_CONFIDENCE_2 = deepcopy(REPORT_CONFIDENCE_1_1) REPORT_CONFIDENCE_2.version = "2.0.0" -REPORT_CONFIDENCE_2.values = [ - SsvcValue( - name="Unknown", - key="U", - description="There are reports of impacts that indicate a vulnerability is present. The reports indicate that the cause of the vulnerability is unknown, or reports may differ on the cause or impacts of the vulnerability. Reporters are uncertain of the true nature of the vulnerability, and there is little confidence in the validity of the reports or whether a static Base score can be applied given the differences described. An example is a bug report which notes that an intermittent but non-reproducible crash occurs, with evidence of memory corruption suggesting that denial of service, or possible more serious impacts, may result.", - ), - SsvcValue( - name="Reasonable", - key="R", - description="Significant details are published, but researchers either do not have full confidence in the root cause, or do not have access to source code to fully confirm all of the interactions that may lead to the result. Reasonable confidence exists, however, that the bug is reproducible and at least one impact is able to be verified (proof-of-concept exploits may provide this). An example is a detailed write-up of research into a vulnerability with an explanation (possibly obfuscated or 'left as an exercise to the reader') that gives assurances on how to reproduce the results.", - ), - SsvcValue( - name="Confirmed", - key="C", - description="Detailed reports exist, or functional reproduction is possible (functional exploits may provide this). Source code is available to independently verify the assertions of the research, or the author or vendor of the affected code has confirmed the presence of the vulnerability.", - ), - deepcopy(nd), -] +REPORT_CONFIDENCE_2.values = ( + UNKNOWN, + REASONABLE, + CONFIRMED_2, + deepcopy(NOT_DEFINED), +) def main(): diff --git a/src/ssvc/decision_points/cvss/scope.py b/src/ssvc/decision_points/cvss/scope.py index f497d3f2..962bed12 100644 --- a/src/ssvc/decision_points/cvss/scope.py +++ b/src/ssvc/decision_points/cvss/scope.py @@ -8,23 +8,27 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +CHANGED = SsvcValue( + name="Changed", + key="C", + description="An exploited vulnerability can affect resources beyond the authorization privileges intended by the vulnerable component. In this case the vulnerable component and the impacted component are different.", +) + +UNCHANGED = SsvcValue( + name="Unchanged", + key="U", + description="An exploited vulnerability can only affect resources managed by the same authority. In this case the vulnerable component and the impacted component are the same.", +) + SCOPE_1 = CvssDecisionPoint( name="Scope", description="the ability for a vulnerability in one software component to impact resources beyond its means, or privileges", key="S", version="1.0.0", - values=[ - SsvcValue( - name="Unchanged", - key="U", - description="An exploited vulnerability can only affect resources managed by the same authority. In this case the vulnerable component and the impacted component are the same.", - ), - SsvcValue( - name="Changed", - key="C", - description="An exploited vulnerability can affect resources beyond the authorization privileges intended by the vulnerable component. In this case the vulnerable component and the impacted component are different.", - ), - ], + values=( + UNCHANGED, + CHANGED, + ), ) diff --git a/src/ssvc/decision_points/cvss/target_distribution.py b/src/ssvc/decision_points/cvss/target_distribution.py index 2ab3e22d..e0d265c6 100644 --- a/src/ssvc/decision_points/cvss/target_distribution.py +++ b/src/ssvc/decision_points/cvss/target_distribution.py @@ -5,71 +5,62 @@ created_at: 9/20/23 1:48 PM """ from copy import deepcopy -from dataclasses import dataclass, field - -from dataclasses_json import dataclass_json from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +NOT_DEFINED = SsvcValue( + name="Not Defined", + key="ND", + description="This value is not defined in any specification, but is used in the CVSS v3.0 vector string when a value is required by the specification, but cannot be provided. This is a special case value and should be used sparingly.", +) + +HIGH = SsvcValue( + name="High", + key="H", + description="Targets exist inside the environment on a considerable scale. Between 50% - 100% of the total environment is considered at risk.", +) + +MEDIUM = SsvcValue( + name="Medium", + key="M", + description="Targets exist inside the environment, but on a medium scale. Between 16% - 49% of the total environment is at risk.", +) + +LOW = SsvcValue( + name="Low", + key="L", + description="Targets exist inside the environment, but on a small scale. Between 1% - 15% of the total environment is at risk.", +) + +TD_NONE = SsvcValue( + name="None", + key="N", + description="No target systems exist, or targets are so highly specialized that they only exist in a laboratory setting. Effectively 0% of the environment is at risk.", +) + TARGET_DISTRIBUTION_1 = CvssDecisionPoint( name="Target Distribution", description="This metric measures the relative size of the field of target systems susceptible to the vulnerability. It is meant as an environment-specific indicator in order to approximate the percentage of systems within the environment that could be affected by the vulnerability.", key="TD", version="1.0.0", - values=[ - SsvcValue( - name="None", - key="N", - description="No target systems exist, or targets are so highly specialized that they only exist in a laboratory setting. Effectively 0% of the environment is at risk.", - ), - SsvcValue( - name="Low", - key="L", - description="Targets exist inside the environment, but on a small scale. Between 1% - 15% of the total environment is at risk.", - ), - SsvcValue( - name="Medium", - key="M", - description="Targets exist inside the environment, but on a medium scale. Between 16% - 49% of the total environment is at risk.", - ), - SsvcValue( - name="High", - key="H", - description="Targets exist inside the environment on a considerable scale. Between 50% - 100% of the total environment is considered at risk.", - ), - ], + values=( + TD_NONE, + LOW, + MEDIUM, + HIGH, + ), ) TARGET_DISTRIBUTION_1_1 = deepcopy(TARGET_DISTRIBUTION_1) TARGET_DISTRIBUTION_1_1.version = "1.1.0" -TARGET_DISTRIBUTION_1_1.values = [ - SsvcValue( - name="None", - key="N", - description="No target systems exist, or targets are so highly specialized that they only exist in a laboratory setting. Effectively 0% of the environment is at risk.", - ), - SsvcValue( - name="Low", - key="L", - description="Targets exist inside the environment, but on a small scale. Between 1% - 15% of the total environment is at risk.", - ), - SsvcValue( - name="Medium", - key="M", - description="Targets exist inside the environment, but on a medium scale. Between 16% - 49% of the total environment is at risk.", - ), - SsvcValue( - name="High", - key="H", - description="Targets exist inside the environment on a considerable scale. Between 50% - 100% of the total environment is considered at risk.", - ), - SsvcValue( - name="Not Defined", - key="ND", - description="This value is not defined in any specification, but is used in the CVSS v3.0 vector string when a value is required by the specification, but cannot be provided. This is a special case value and should be used sparingly.", - ), -] +TARGET_DISTRIBUTION_1_1.values = ( + TD_NONE, + LOW, + MEDIUM, + HIGH, + NOT_DEFINED, +) def main(): diff --git a/src/ssvc/decision_points/cvss/user_interaction.py b/src/ssvc/decision_points/cvss/user_interaction.py index b3c8a7ce..848eb75a 100644 --- a/src/ssvc/decision_points/cvss/user_interaction.py +++ b/src/ssvc/decision_points/cvss/user_interaction.py @@ -8,24 +8,28 @@ from ssvc.decision_points.base import SsvcValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +REQUIRED = SsvcValue( + name="Required", + key="R", + description="Successful exploitation of this vulnerability requires a user to take some action before the vulnerability can be exploited.", +) + +UI_NONE = SsvcValue( + name="None", + key="N", + description="The vulnerable system can be exploited without interaction from any user.", +) + USER_INTERACTION_1 = CvssDecisionPoint( name="User Interaction", description="This metric captures the requirement for a user, other than the attacker, to participate in the successful compromise of the vulnerable component.", key="UI", version="1.0.0", - values=[ - SsvcValue( - name="None", - key="N", - description="The vulnerable system can be exploited without interaction from any user.", - ), - SsvcValue( - name="Required", - key="R", - description="Successful exploitation of this vulnerability requires a user to take some action before the vulnerability can be exploited.", - ), - ], + values=( + UI_NONE, + REQUIRED, + ), ) From 142752eb69159bbcef80eb67366794fa39e0ca9b Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 13:58:58 -0400 Subject: [PATCH 18/53] drop aliases --- src/ssvc/dp_groups/cvss/v1.py | 51 +++++++++++++------------ src/ssvc/dp_groups/cvss/v2.py | 56 ++++++++++++++-------------- src/ssvc/dp_groups/cvss/v3.py | 70 ++++++++++++++++++----------------- 3 files changed, 89 insertions(+), 88 deletions(-) diff --git a/src/ssvc/dp_groups/cvss/v1.py b/src/ssvc/dp_groups/cvss/v1.py index 56ea9915..d2a11152 100644 --- a/src/ssvc/dp_groups/cvss/v1.py +++ b/src/ssvc/dp_groups/cvss/v1.py @@ -5,38 +5,37 @@ created_at: 9/20/23 12:39 PM """ -from ssvc.dp_groups.base import SsvcDecisionPointGroup from ssvc.decision_points.cvss.access_complexity import ( - ACCESS_COMPLEXITY_1 as ACCESS_COMPLEXITY, + ACCESS_COMPLEXITY_1, ) -from ssvc.decision_points.cvss.access_vector import ACCESS_VECTOR_1 as ACCESS_VECTOR -from ssvc.decision_points.cvss.authentication import AUTHENTICATION_1 as AUTHENTICATION +from ssvc.decision_points.cvss.access_vector import ACCESS_VECTOR_1 +from ssvc.decision_points.cvss.authentication import AUTHENTICATION_1 from ssvc.decision_points.cvss.availability_impact import ( - AVAILABILITY_IMPACT_1 as AVAILABILITY_IMPACT, + AVAILABILITY_IMPACT_1, ) from ssvc.decision_points.cvss.collateral_damage_potential import ( - COLLATERAL_DAMAGE_POTENTIAL_1 as COLLATERAL_DAMAGE_POTENTIAL, + COLLATERAL_DAMAGE_POTENTIAL_1, ) from ssvc.decision_points.cvss.confidentiality_impact import ( - CONFIDENTIALITY_IMPACT_1 as CONFIDENTIALITY_IMPACT, + CONFIDENTIALITY_IMPACT_1, ) from ssvc.decision_points.cvss.exploitability import ( - EXPLOITABILITY_1 as EXPLOITABILITY, + EXPLOITABILITY_1, ) -from ssvc.decision_points.cvss.impact_bias import IMPACT_BIAS +from ssvc.decision_points.cvss.impact_bias import IMPACT_BIAS_1 from ssvc.decision_points.cvss.integrity_impact import ( - INTEGRITY_IMPACT_1 as INTEGRITY_IMPACT, + INTEGRITY_IMPACT_1, ) from ssvc.decision_points.cvss.remediation_level import ( - REMEDIATION_LEVEL_1 as REMEDIATION_LEVEL, + REMEDIATION_LEVEL_1, ) from ssvc.decision_points.cvss.report_confidence import ( - REPORT_CONFIDENCE_1 as REPORT_CONFIDENCE, + REPORT_CONFIDENCE_1, ) from ssvc.decision_points.cvss.target_distribution import ( - TARGET_DISTRIBUTION_1 as TARGET_DISTRIBUTION, + TARGET_DISTRIBUTION_1, ) - +from ssvc.dp_groups.base import SsvcDecisionPointGroup # Instantiate the CVSS v1 decision point group CVSSv1 = SsvcDecisionPointGroup( @@ -45,18 +44,18 @@ key="CVSSv1", description="CVSS v1 decision points", decision_points=[ - ACCESS_VECTOR, - ACCESS_COMPLEXITY, - AUTHENTICATION, - CONFIDENTIALITY_IMPACT, - INTEGRITY_IMPACT, - AVAILABILITY_IMPACT, - IMPACT_BIAS, - EXPLOITABILITY, - REMEDIATION_LEVEL, - REPORT_CONFIDENCE, - COLLATERAL_DAMAGE_POTENTIAL, - TARGET_DISTRIBUTION, + ACCESS_VECTOR_1, + ACCESS_COMPLEXITY_1, + AUTHENTICATION_1, + CONFIDENTIALITY_IMPACT_1, + INTEGRITY_IMPACT_1, + AVAILABILITY_IMPACT_1, + IMPACT_BIAS_1, + EXPLOITABILITY_1, + REMEDIATION_LEVEL_1, + REPORT_CONFIDENCE_1, + COLLATERAL_DAMAGE_POTENTIAL_1, + TARGET_DISTRIBUTION_1, ], ) diff --git a/src/ssvc/dp_groups/cvss/v2.py b/src/ssvc/dp_groups/cvss/v2.py index 1e2d409a..1b2000b4 100644 --- a/src/ssvc/dp_groups/cvss/v2.py +++ b/src/ssvc/dp_groups/cvss/v2.py @@ -6,43 +6,43 @@ """ from ssvc.dp_groups.base import SsvcDecisionPointGroup -from ssvc.decision_points.cvss.access_vector import ACCESS_VECTOR_2 as ACCESS_VECTOR +from ssvc.decision_points.cvss.access_vector import ACCESS_VECTOR_2 from ssvc.decision_points.cvss.access_complexity import ( - ACCESS_COMPLEXITY_2 as ACCESS_COMPLEXITY, + ACCESS_COMPLEXITY_2, ) -from ssvc.decision_points.cvss.authentication import AUTHENTICATION_2 as AUTHENTICATION +from ssvc.decision_points.cvss.authentication import AUTHENTICATION_2 from ssvc.decision_points.cvss.availability_requirement import ( - AVAILABILITY_REQUIREMENT_1 as AVAILABILITY_REQUIREMENT, + AVAILABILITY_REQUIREMENT_1, ) from ssvc.decision_points.cvss.collateral_damage_potential import ( - COLLATERAL_DAMAGE_POTENTIAL_2 as COLLATERAL_DAMAGE_POTENTIAL, + COLLATERAL_DAMAGE_POTENTIAL_2, ) from ssvc.decision_points.cvss.confidentiality_impact import ( - CONFIDENTIALITY_IMPACT_1 as CONFIDENTIALITY_IMPACT, + CONFIDENTIALITY_IMPACT_1, ) from ssvc.decision_points.cvss.confidentiality_requirement import ( - CONFIDENTIALITY_REQUIREMENT_1 as CONFIDENTIALITY_REQUIREMENT, + CONFIDENTIALITY_REQUIREMENT_1, ) from ssvc.decision_points.cvss.exploitability import ( - EXPLOITABILITY_1_1 as EXPLOITABILITY, + EXPLOITABILITY_1_1, ) from ssvc.decision_points.cvss.integrity_impact import ( - INTEGRITY_IMPACT_1 as INTEGRITY_IMPACT, + INTEGRITY_IMPACT_1, ) from ssvc.decision_points.cvss.availability_impact import ( - AVAILABILITY_IMPACT_1 as AVAILABILITY_IMPACT, + AVAILABILITY_IMPACT_1, ) from ssvc.decision_points.cvss.integrity_requirement import ( - INTEGRITY_REQUIREMENT_1 as INTEGRITY_REQUIREMENT, + INTEGRITY_REQUIREMENT_1, ) from ssvc.decision_points.cvss.remediation_level import ( - REMEDIATION_LEVEL_1_1 as REMEDIATION_LEVEL, + REMEDIATION_LEVEL_1_1, ) from ssvc.decision_points.cvss.report_confidence import ( - REPORT_CONFIDENCE_1_1 as REPORT_CONFIDENCE, + REPORT_CONFIDENCE_1_1, ) from ssvc.decision_points.cvss.target_distribution import ( - TARGET_DISTRIBUTION_1_1 as TARGET_DISTRIBUTION, + TARGET_DISTRIBUTION_1_1, ) CVSSv2 = SsvcDecisionPointGroup( @@ -51,20 +51,20 @@ key="CVSSv2", version="2.0", decision_points=[ - ACCESS_VECTOR, - ACCESS_COMPLEXITY, - AUTHENTICATION, - CONFIDENTIALITY_IMPACT, - INTEGRITY_IMPACT, - AVAILABILITY_IMPACT, - EXPLOITABILITY, - REMEDIATION_LEVEL, - REPORT_CONFIDENCE, - COLLATERAL_DAMAGE_POTENTIAL, - TARGET_DISTRIBUTION, - CONFIDENTIALITY_REQUIREMENT, - INTEGRITY_REQUIREMENT, - AVAILABILITY_REQUIREMENT, + ACCESS_VECTOR_2, + ACCESS_COMPLEXITY_2, + AUTHENTICATION_2, + CONFIDENTIALITY_IMPACT_1, + INTEGRITY_IMPACT_1, + AVAILABILITY_IMPACT_1, + EXPLOITABILITY_1_1, + REMEDIATION_LEVEL_1_1, + REPORT_CONFIDENCE_1_1, + COLLATERAL_DAMAGE_POTENTIAL_2, + TARGET_DISTRIBUTION_1_1, + CONFIDENTIALITY_REQUIREMENT_1, + INTEGRITY_REQUIREMENT_1, + AVAILABILITY_REQUIREMENT_1, ], ) diff --git a/src/ssvc/dp_groups/cvss/v3.py b/src/ssvc/dp_groups/cvss/v3.py index a4bd651a..89cac571 100644 --- a/src/ssvc/dp_groups/cvss/v3.py +++ b/src/ssvc/dp_groups/cvss/v3.py @@ -9,42 +9,42 @@ from ssvc.decision_points.base import SsvcValue from ssvc.dp_groups.base import SsvcDecisionPointGroup from ssvc.decision_points.cvss.attack_complexity import ( - ATTACK_COMPLEXITY_1 as ATTACK_COMPLEXITY, + ATTACK_COMPLEXITY_1, ) -from ssvc.decision_points.cvss.attack_vector import ATTACK_VECTOR_1 as ATTACK_VECTOR +from ssvc.decision_points.cvss.attack_vector import ATTACK_VECTOR_1 from ssvc.decision_points.cvss.availability_impact import ( - AVAILABILITY_IMPACT_2 as AVAILABILITY_IMPACT, + AVAILABILITY_IMPACT_2, ) from ssvc.decision_points.cvss.availability_requirement import ( - AVAILABILITY_REQUIREMENT_1 as AVAILABILITY_REQUIREMENT, + AVAILABILITY_REQUIREMENT_1, ) from ssvc.decision_points.cvss.confidentiality_impact import ( - CONFIDENTIALITY_IMPACT_2 as CONFIDENTIALITY_IMPACT, + CONFIDENTIALITY_IMPACT_2, ) from ssvc.decision_points.cvss.confidentiality_requirement import ( - CONFIDENTIALITY_REQUIREMENT_1 as CONFIDENTIALITY_REQUIREMENT, + CONFIDENTIALITY_REQUIREMENT_1, ) from ssvc.decision_points.cvss.exploitability import ( - EXPLOIT_CODE_MATURITY_1_1_1 as EXPLOIT_CODE_MATURITY, + EXPLOIT_CODE_MATURITY_1_1_1, ) from ssvc.decision_points.cvss.integrity_impact import ( - INTEGRITY_IMPACT_2 as INTEGRITY_IMPACT, + INTEGRITY_IMPACT_2, ) from ssvc.decision_points.cvss.integrity_requirement import ( - INTEGRITY_REQUIREMENT_1 as INTEGRITY_REQUIREMENT, + INTEGRITY_REQUIREMENT_1, ) from ssvc.decision_points.cvss.privileges_required import ( - PRIVILEGES_REQUIRED_1 as PRIVILEGES_REQUIRED, + PRIVILEGES_REQUIRED_1, ) from ssvc.decision_points.cvss.remediation_level import ( - REMEDIATION_LEVEL_1_1 as REMEDIATION_LEVEL, + REMEDIATION_LEVEL_1_1, ) from ssvc.decision_points.cvss.report_confidence import ( - REPORT_CONFIDENCE_2 as REPORT_CONFIDENCE, + REPORT_CONFIDENCE_2, ) from ssvc.decision_points.cvss.scope import SCOPE_1 as SCOPE from ssvc.decision_points.cvss.user_interaction import ( - USER_INTERACTION_1 as USER_INTERACTION, + USER_INTERACTION_1, ) @@ -59,18 +59,20 @@ def _modify(obj): o.name = "Modified " + o.name o.key = "M" + o.key nd = SsvcValue(name="Not Defined", key="ND", description="Ignore this value") - o.values.append(nd) + values = list(o.values) + values.append(nd) + o.values = tuple(values) return o -MODIFIED_ATTACK_VECTOR = _modify(ATTACK_VECTOR) -MODIFIED_ATTACK_COMPLEXITY = _modify(ATTACK_COMPLEXITY) -MODIFIED_PRIVILEGES_REQUIRED = _modify(PRIVILEGES_REQUIRED) -MODIFIED_USER_INTERACTION = _modify(USER_INTERACTION) +MODIFIED_ATTACK_VECTOR = _modify(ATTACK_VECTOR_1) +MODIFIED_ATTACK_COMPLEXITY = _modify(ATTACK_COMPLEXITY_1) +MODIFIED_PRIVILEGES_REQUIRED = _modify(PRIVILEGES_REQUIRED_1) +MODIFIED_USER_INTERACTION = _modify(USER_INTERACTION_1) MODIFIED_SCOPE = _modify(SCOPE) -MODIFIED_CONFIDENTIALITY_IMPACT = _modify(CONFIDENTIALITY_IMPACT) -MODIFIED_INTEGRITY_IMPACT = _modify(INTEGRITY_IMPACT) -MODIFIED_AVAILABILITY_IMPACT = _modify(AVAILABILITY_IMPACT) +MODIFIED_CONFIDENTIALITY_IMPACT = _modify(CONFIDENTIALITY_IMPACT_2) +MODIFIED_INTEGRITY_IMPACT = _modify(INTEGRITY_IMPACT_2) +MODIFIED_AVAILABILITY_IMPACT = _modify(AVAILABILITY_IMPACT_2) CVSSv3 = SsvcDecisionPointGroup( @@ -79,20 +81,20 @@ def _modify(obj): key="CVSSv3", version="3.0", decision_points=[ - ATTACK_VECTOR, - ATTACK_COMPLEXITY, - PRIVILEGES_REQUIRED, - USER_INTERACTION, + ATTACK_VECTOR_1, + ATTACK_COMPLEXITY_1, + PRIVILEGES_REQUIRED_1, + USER_INTERACTION_1, SCOPE, - CONFIDENTIALITY_IMPACT, - INTEGRITY_IMPACT, - AVAILABILITY_IMPACT, - EXPLOIT_CODE_MATURITY, - REMEDIATION_LEVEL, - REPORT_CONFIDENCE, - CONFIDENTIALITY_REQUIREMENT, - INTEGRITY_REQUIREMENT, - AVAILABILITY_REQUIREMENT, + CONFIDENTIALITY_IMPACT_2, + INTEGRITY_IMPACT_2, + AVAILABILITY_IMPACT_2, + EXPLOIT_CODE_MATURITY_1_1_1, + REMEDIATION_LEVEL_1_1, + REPORT_CONFIDENCE_2, + CONFIDENTIALITY_REQUIREMENT_1, + INTEGRITY_REQUIREMENT_1, + AVAILABILITY_REQUIREMENT_1, MODIFIED_ATTACK_VECTOR, MODIFIED_ATTACK_COMPLEXITY, MODIFIED_PRIVILEGES_REQUIRED, From 61e09cd04f01c92c57cd9845060f14aedb5a45a2 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 14:12:28 -0400 Subject: [PATCH 19/53] add a nist and ossf criticality decision points --- src/ssvc/decision_points/nist/__init__.py | 14 +++++++ src/ssvc/decision_points/nist/base.py | 21 ++++++++++ src/ssvc/decision_points/nist/eo_critical.py | 40 ++++++++++++++++++++ src/ssvc/decision_points/ossf/__init__.py | 14 +++++++ src/ssvc/decision_points/ossf/base.py | 21 ++++++++++ src/ssvc/decision_points/ossf/critical.py | 39 +++++++++++++++++++ 6 files changed, 149 insertions(+) create mode 100644 src/ssvc/decision_points/nist/__init__.py create mode 100644 src/ssvc/decision_points/nist/base.py create mode 100644 src/ssvc/decision_points/nist/eo_critical.py create mode 100644 src/ssvc/decision_points/ossf/__init__.py create mode 100644 src/ssvc/decision_points/ossf/base.py create mode 100644 src/ssvc/decision_points/ossf/critical.py diff --git a/src/ssvc/decision_points/nist/__init__.py b/src/ssvc/decision_points/nist/__init__.py new file mode 100644 index 00000000..12e8438a --- /dev/null +++ b/src/ssvc/decision_points/nist/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +file: __init__.py +author: adh +created_at: 9/21/23 2:03 PM +""" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/nist/base.py b/src/ssvc/decision_points/nist/base.py new file mode 100644 index 00000000..c5a8225f --- /dev/null +++ b/src/ssvc/decision_points/nist/base.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +""" +file: base +author: adh +created_at: 9/21/23 2:07 PM +""" +from ssvc.decision_points.base import SsvcDecisionPoint + + +@dataclasses_json +@dataclass(kw_only=True) +class NistDecisionPoint(SsvcDecisionPoint): + namespace = "nist" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/nist/eo_critical.py b/src/ssvc/decision_points/nist/eo_critical.py new file mode 100644 index 00000000..aa8094d0 --- /dev/null +++ b/src/ssvc/decision_points/nist/eo_critical.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +""" +file: eo_critical +author: adh +created_at: 9/21/23 2:03 PM +""" +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.nist.base import NistDecisionPoint + +YES = SsvcValue( + name="Yes", + key="Y", + description="System meets NIST's critical software definition.", +) + +NO = SsvcValue( + name="No", + key="N", + description="System does not meet NIST's critical software definition.", +) + +EO_CRITICAL = NistDecisionPoint( + name="EO Critical", + description="Denotes whether a system meets NIST's critical software definition. " + "Based on https://www.nist.gov/itl/executive-order-improving-nations-cybersecurity/critical-software-definition-explanatory", + key="EOCS", + version="1.0.0", + values=( + NO, + YES, + ), +) + + +def main(): + print(EO_CRITICAL.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/ossf/__init__.py b/src/ssvc/decision_points/ossf/__init__.py new file mode 100644 index 00000000..ef76b678 --- /dev/null +++ b/src/ssvc/decision_points/ossf/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +file: __init__.py +author: adh +created_at: 9/21/23 2:08 PM +""" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/ossf/base.py b/src/ssvc/decision_points/ossf/base.py new file mode 100644 index 00000000..cf77074a --- /dev/null +++ b/src/ssvc/decision_points/ossf/base.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +""" +file: base +author: adh +created_at: 9/21/23 2:07 PM +""" +from ssvc.decision_points.base import SsvcDecisionPoint + + +@dataclasses_json +@dataclass(kw_only=True) +class OssfDecisionPoint(SsvcDecisionPoint): + namespace: str = "ossf" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/ossf/critical.py b/src/ssvc/decision_points/ossf/critical.py new file mode 100644 index 00000000..0e7f573d --- /dev/null +++ b/src/ssvc/decision_points/ossf/critical.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +""" +file: critical +author: adh +created_at: 9/21/23 2:09 PM +""" +from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.ossf.base import OssfDecisionPoint + +NO = SsvcValue( + name="No", + key="N", + description="System does not meet the Open Source Software Foundation's critical software definition.", +) +YES = SsvcValue( + name="Yes", + key="Y", + description="System meets the Open Source Software Foundation's critical software definition.", +) + +OSSF_CRITICAL_SOFTWARE = OssfDecisionPoint( + name="OSSF Critical Software", + description="Denotes whether a system meets the Open Source Software Foundation's critical software definition. " + "Based on https://openssf.org/wp-content/uploads/2021/07/OSSF-Criticality-Definition-1.0.pdf", + key="OSSFCS", + version="1.0.0", + values=( + NO, + YES, + ), +) + + +def main(): + print(OSSF_CRITICAL_SOFTWARE.to_json(indent=2)) + + +if __name__ == "__main__": + main() From 386039eab13688dccce27bf6376b825f28ba3bc5 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 15:13:44 -0400 Subject: [PATCH 20/53] playing with generating docs --- docs/reference/decision_points/automatable.md | 25 +++------ .../reference/decision_points/exploitation.md | 23 ++++++--- .../decision_points/mission_impact.md | 25 ++++++--- .../decision_points/public_value_added.md | 25 ++++++--- .../decision_points/report_credibility.md | 20 ++++++-- .../decision_points/report_public.md | 21 +++++--- .../decision_points/safety_impact.md | 51 +++++++++++-------- .../decision_points/supplier_cardinality.md | 21 +++++--- .../decision_points/supplier_contacted.md | 21 +++++--- .../decision_points/supplier_engagement.md | 21 +++++--- .../decision_points/supplier_involvement.md | 23 ++++++--- .../decision_points/system_exposure.md | 22 +++++--- .../decision_points/technical_impact.md | 21 +++++--- .../decision_points/value_density.md | 21 +++++--- docs/reference/examples/automatable.json | 19 +++++++ docs/reference/examples/exploitation.json | 24 +++++++++ docs/reference/examples/human_impact.json | 29 +++++++++++ docs/reference/examples/mission_impact.json | 29 +++++++++++ .../examples/public_safety_impact.json | 19 +++++++ .../examples/public_value_added.json | 24 +++++++++ .../examples/report_credibility.json | 19 +++++++ docs/reference/examples/report_public.json | 19 +++++++ docs/reference/examples/safety_impact.json | 34 +++++++++++++ .../examples/supplier_cardinality.json | 19 +++++++ .../examples/supplier_contacted.json | 19 +++++++ .../examples/supplier_engagement.json | 19 +++++++ .../examples/supplier_involvement.json | 24 +++++++++ docs/reference/examples/system_exposure.json | 24 +++++++++ docs/reference/examples/technical_impact.json | 19 +++++++ docs/reference/examples/utility.json | 24 +++++++++ docs/reference/examples/value_density.json | 19 +++++++ docs/reference/examples/virulence.json | 19 +++++++ mkdocs.yml | 4 ++ requirements.txt | 1 + src/ssvc/decision_points/nist/base.py | 21 -------- src/ssvc/decision_points/nist/eo_critical.py | 6 +-- src/ssvc/decision_points/ossf/base.py | 21 -------- src/ssvc/decision_points/ossf/critical.py | 6 +-- .../{exposure.py => system_exposure.py} | 14 ++--- src/ssvc/dp_groups/base.py | 9 ++++ src/ssvc/dp_groups/deployer.py | 24 +++++---- src/ssvc/dp_groups/v2_1.py | 4 +- 42 files changed, 671 insertions(+), 181 deletions(-) create mode 100644 docs/reference/examples/automatable.json create mode 100644 docs/reference/examples/exploitation.json create mode 100644 docs/reference/examples/human_impact.json create mode 100644 docs/reference/examples/mission_impact.json create mode 100644 docs/reference/examples/public_safety_impact.json create mode 100644 docs/reference/examples/public_value_added.json create mode 100644 docs/reference/examples/report_credibility.json create mode 100644 docs/reference/examples/report_public.json create mode 100644 docs/reference/examples/safety_impact.json create mode 100644 docs/reference/examples/supplier_cardinality.json create mode 100644 docs/reference/examples/supplier_contacted.json create mode 100644 docs/reference/examples/supplier_engagement.json create mode 100644 docs/reference/examples/supplier_involvement.json create mode 100644 docs/reference/examples/system_exposure.json create mode 100644 docs/reference/examples/technical_impact.json create mode 100644 docs/reference/examples/utility.json create mode 100644 docs/reference/examples/value_density.json create mode 100644 docs/reference/examples/virulence.json delete mode 100644 src/ssvc/decision_points/nist/base.py delete mode 100644 src/ssvc/decision_points/ossf/base.py rename src/ssvc/decision_points/{exposure.py => system_exposure.py} (83%) diff --git a/docs/reference/decision_points/automatable.md b/docs/reference/decision_points/automatable.md index 334b7f6d..c2cc63b1 100644 --- a/docs/reference/decision_points/automatable.md +++ b/docs/reference/decision_points/automatable.md @@ -9,28 +9,18 @@ | yes | Attackers can reliably automate steps 1-4 of the kill chain. | === "JSON" - This is a made up example and does not reflect the correct format. - The actual JSON should be generated by the tooling, and this content can be replaced by - an include of the generated JSON. - ```json - { - key: "A", - values = [ - { - value: "no" - description: "Attackers cannot reliably automate steps 1-4 of the kill chain for this vulnerability." - }, - { - value: "yes" - description: "Attackers can reliably automate steps 1-4 of the kill chain." - } - ], - } + {% include-markdown "../examples/automatable.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/automatable.py" %} ``` + + [*Automatable*](#automatable) captures the answer to the question “Can an attacker reliably automate creating exploitation events for this vulnerability?” !!! question "What are Steps 1-4 of the Kill Chain?" @@ -78,3 +68,4 @@ Due to vulnerability chaining, there is some nuance as to whether reconnaissance Furthermore, discovery of a vulnerable service is not automatable in a situation where only two hosts are misconfigured to expose the service out of 2 million hosts that are properly configured. As discussed in in [Reasoning Steps Forward](#reasoning-steps-forward), the analyst should consider *credible* effects based on *known* use cases of the software system to be pragmatic about scope and providing values to decision points. + diff --git a/docs/reference/decision_points/exploitation.md b/docs/reference/decision_points/exploitation.md index a25d2e6b..7e9d12d7 100644 --- a/docs/reference/decision_points/exploitation.md +++ b/docs/reference/decision_points/exploitation.md @@ -1,12 +1,21 @@ !!! note "Exploitation" - Evidence of Active Exploitation of a Vulnerability - - | Value | Definition | - | :--- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - | None | There is no evidence of active exploitation and no public proof of concept (PoC) of how to exploit the vulnerability. | - | PoC
(Proof of Concept) | One of the following cases is true: (1) exploit code is sold or traded on underground or restricted fora; (2) a typical public PoC in places such as Metasploit or ExploitDB; or (3) the vulnerability has a well-known method of exploitation. Some examples of condition (3) are open-source web proxies serve as the PoC code for how to exploit any vulnerability in the vein of improper validation of TLS certificates. As another example, Wireshark serves as a PoC for packet replay attacks on ethernet or WiFi networks. A publicly-known hard-coded or default password would also meet this criteria. | - | Active | Shared, observable, reliable evidence that the exploit is being used in the wild by real attackers; there is credible public reporting. | + === "Text" + Evidence of Active Exploitation of a Vulnerability + + | Value | Definition | + | :--- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | None | There is no evidence of active exploitation and no public proof of concept (PoC) of how to exploit the vulnerability. | + | PoC
(Proof of Concept) | One of the following cases is true: (1) exploit code is sold or traded on underground or restricted fora; (2) a typical public PoC in places such as Metasploit or ExploitDB; or (3) the vulnerability has a well-known method of exploitation. Some examples of condition (3) are open-source web proxies serve as the PoC code for how to exploit any vulnerability in the vein of improper validation of TLS certificates. As another example, Wireshark serves as a PoC for packet replay attacks on ethernet or WiFi networks. A publicly-known hard-coded or default password would also meet this criteria. | + | Active | Shared, observable, reliable evidence that the exploit is being used in the wild by real attackers; there is credible public reporting. | + === "JSON" + ```json + {% include-markdown "../examples/exploitation.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/exploitation.py" %} + ``` The intent of this measure is the present state of exploitation of the vulnerability. The intent is not to predict future exploitation but only to acknowledge the current state of affairs. Predictive systems, such as EPSS, could be used to augment this decision or to notify stakeholders of likely changes [@jacobs2021epss]. diff --git a/docs/reference/decision_points/mission_impact.md b/docs/reference/decision_points/mission_impact.md index bcb443a5..0bf84828 100644 --- a/docs/reference/decision_points/mission_impact.md +++ b/docs/reference/decision_points/mission_impact.md @@ -1,13 +1,22 @@ !!! note "Mission Impact" - Impact on Mission Essential Functions of the Organization - - | Value | Description | - | :--- | :---------- | - | Degraded | Little to no impact up to degradation of non-essential functions; chronic degradation would eventually harm essential functions | - | MEF Support Crippled | Activities that directly support essential functions are crippled; essential functions continue for a time | - | MEF Failure | Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time | - | Mission Failure | Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails | + === "Text" + Impact on Mission Essential Functions of the Organization + + | Value | Description | + | :--- | :---------- | + | Degraded | Little to no impact up to degradation of non-essential functions; chronic degradation would eventually harm essential functions | + | MEF Support Crippled | Activities that directly support essential functions are crippled; essential functions continue for a time | + | MEF Failure | Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time | + | Mission Failure | Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails | + === "JSON" + ```json + {% include-markdown "../examples/mission_impact.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/mission_impact.py" %} + ``` A **mission essential function (MEF)** is a function “directly related to accomplishing the organization’s mission as set forth in its statutory or executive charter” [@FCD2_2017, page A-1]. diff --git a/docs/reference/decision_points/public_value_added.md b/docs/reference/decision_points/public_value_added.md index 7392c64a..b157f6c3 100644 --- a/docs/reference/decision_points/public_value_added.md +++ b/docs/reference/decision_points/public_value_added.md @@ -1,13 +1,22 @@ !!! note "Public Value Added" - This decision point asks how much value a publication from the coordinator would benefit the broader community. - The value is based on the state of existing public information about the vulnerablity. - - | Value | Description | - | :---: | :--- | - | Precedence | The publication would be the first publicly available, or be coincident with the first publicly available. | - | Ampliative | Amplifies and/or augments the existing public information about the vulnerability, for example, adds additional detail, addresses or corrects errors in other public information, draws further attention to the vulnerability, etc. | - | Limited | Minimal value added to the existing public information because existing information is already high quality and in multiple outlets. | + === "Text" + This decision point asks how much value a publication from the coordinator would benefit the broader community. + The value is based on the state of existing public information about the vulnerablity. + + | Value | Description | + | :---: | :--- | + | Precedence | The publication would be the first publicly available, or be coincident with the first publicly available. | + | Ampliative | Amplifies and/or augments the existing public information about the vulnerability, for example, adds additional detail, addresses or corrects errors in other public information, draws further attention to the vulnerability, etc. | + | Limited | Minimal value added to the existing public information because existing information is already high quality and in multiple outlets. | + === "JSON" + ```json + {% include-markdown "../examples/public_value_added.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/public_value_added.py" %} + ``` The intent of the definition is that one rarely if ever transitions from limited to ampliative or ampliative to precedence. diff --git a/docs/reference/decision_points/report_credibility.md b/docs/reference/decision_points/report_credibility.md index c1ea7180..49d7bafb 100644 --- a/docs/reference/decision_points/report_credibility.md +++ b/docs/reference/decision_points/report_credibility.md @@ -1,11 +1,21 @@ !!! note "Report Credibility" - Assessing the credibility of a report is complex, but the assessment must reach a conclusion of either: + === "Text" + Assessing the credibility of a report is complex, but the assessment must reach a conclusion of either: + + | Value | Description | + | :---: | :--- | + | Credible | The report is credible. | + | Not credible | The report is not credible. | + === "JSON" + ```json + {% include-markdown "../examples/report_credibility.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/report_credibility.py" %} + ``` - | Value | Description | - | :---: | :--- | - | Credible | The report is credible. | - | Not credible | The report is not credible. | !!! tip inline "See Also" diff --git a/docs/reference/decision_points/report_public.md b/docs/reference/decision_points/report_public.md index ed8ef39d..9f9cafc4 100644 --- a/docs/reference/decision_points/report_public.md +++ b/docs/reference/decision_points/report_public.md @@ -1,9 +1,18 @@ !!! note "Report Public" - Is a viable report of the details of the vulnerability already publicly available? - - | Value | Description | - | :---: | :--- | - | Yes | A viable report of the details of the vulnerability is already publicly available. | - | No | A viable report of the details of the vulnerability is not already publicly available. | + === "Text" + Is a viable report of the details of the vulnerability already publicly available? + + | Value | Description | + | :---: | :--- | + | Yes | A viable report of the details of the vulnerability is already publicly available. | + | No | A viable report of the details of the vulnerability is not already publicly available. | + === "JSON" + ```json + {% include-markdown "../examples/report_public.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/report_public.py" %} + ``` diff --git a/docs/reference/decision_points/safety_impact.md b/docs/reference/decision_points/safety_impact.md index 3b723990..de6683d4 100644 --- a/docs/reference/decision_points/safety_impact.md +++ b/docs/reference/decision_points/safety_impact.md @@ -1,26 +1,35 @@ !!! note "Safety Impact" - Safety Impacts of Affected System Compromise - - | Value | Type of Harm | Definition | - | --: | :-- | :----------- | - | None | All | Does *not* mean no impact *literally*; the effect is below the threshold for all aspects described in Minor | - | Minor | Physical Harm | Physical discomfort for users of the system OR a minor occupational safety hazard OR reduction in physical system safety margins | - | Minor | Environment | Minor externalities (property damage, environmental damage, etc.) imposed on other parties | - | Minor | Financial | Financial losses, which are not readily absorbable, to multiple persons | - | Minor | Psychological | Emotional or psychological harm, sufficient to be cause for counseling or therapy, to multiple persons | - | Major | Physical Harm | Physical distress and injuries for users of the system OR a significant occupational safety hazard OR failure of physical system functional capabilities that support safe operation | - | Major | Environment | Major externalities (property damage, environmental damage, etc.) imposed on other parties | - | Major | Financial | Financial losses that likely lead to bankruptcy of multiple persons | - | Major | Psychological | Widespread emotional or psychological harm, sufficient to be cause for counseling or therapy, to populations of people | - | Hazardous | Physical Harm | Serious or fatal injuries, where fatalities are plausibly preventable via emergency services or other measures OR parts of the cyber-physical system that support safe operation break | - | Hazardous | Environment | Serious externalities (threat to life as well as property, widespread environmental damage, measurable public health risks, etc.) imposed on other parties | - | Hazardous | Financial | Socio-technical system (elections, financial grid, etc.) of which the affected component is a part is actively destabilized and enters unsafe state | - | Hazardous | Psychological | N/A | - | Catastrophic | Physical Harm | Multiple immediate fatalities (emergency response probably cannot save the victims.) | - | Catastrophic | Environment | Extreme externalities (immediate public health threat, environmental damage leading to small ecosystem collapse, etc.) imposed on other parties | - | Catastrophic | Financial | Social systems (elections, financial grid, etc.) supported by the software collapse | - | Catastrophic | Psychological | N/A | + === "Text" + Safety Impacts of Affected System Compromise + + | Value | Type of Harm | Definition | + | --: | :-- | :----------- | + | None | All | Does *not* mean no impact *literally*; the effect is below the threshold for all aspects described in Minor | + | Minor | Physical Harm | Physical discomfort for users of the system OR a minor occupational safety hazard OR reduction in physical system safety margins | + | Minor | Environment | Minor externalities (property damage, environmental damage, etc.) imposed on other parties | + | Minor | Financial | Financial losses, which are not readily absorbable, to multiple persons | + | Minor | Psychological | Emotional or psychological harm, sufficient to be cause for counseling or therapy, to multiple persons | + | Major | Physical Harm | Physical distress and injuries for users of the system OR a significant occupational safety hazard OR failure of physical system functional capabilities that support safe operation | + | Major | Environment | Major externalities (property damage, environmental damage, etc.) imposed on other parties | + | Major | Financial | Financial losses that likely lead to bankruptcy of multiple persons | + | Major | Psychological | Widespread emotional or psychological harm, sufficient to be cause for counseling or therapy, to populations of people | + | Hazardous | Physical Harm | Serious or fatal injuries, where fatalities are plausibly preventable via emergency services or other measures OR parts of the cyber-physical system that support safe operation break | + | Hazardous | Environment | Serious externalities (threat to life as well as property, widespread environmental damage, measurable public health risks, etc.) imposed on other parties | + | Hazardous | Financial | Socio-technical system (elections, financial grid, etc.) of which the affected component is a part is actively destabilized and enters unsafe state | + | Hazardous | Psychological | N/A | + | Catastrophic | Physical Harm | Multiple immediate fatalities (emergency response probably cannot save the victims.) | + | Catastrophic | Environment | Extreme externalities (immediate public health threat, environmental damage leading to small ecosystem collapse, etc.) imposed on other parties | + | Catastrophic | Financial | Social systems (elections, financial grid, etc.) supported by the software collapse | + | Catastrophic | Psychological | N/A | + === "JSON" + ```json + {% include-markdown "../examples/safety_impact.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/safety_impact.py" %} + ``` diff --git a/docs/reference/decision_points/supplier_cardinality.md b/docs/reference/decision_points/supplier_cardinality.md index 887642cb..114c27ab 100644 --- a/docs/reference/decision_points/supplier_cardinality.md +++ b/docs/reference/decision_points/supplier_cardinality.md @@ -1,8 +1,17 @@ !!! note "Supplier Cardinality" - How many suppliers are responsible for the vulnerable component and its remediation or mitigation plan? - - | Value | Description | - | :---: | :--- | - | One | One supplier is responsible for the vulnerable component and its remediation or mitigation plan. | - | Multiple | Multiple suppliers are responsible for the vulnerable component and its remediation or mitigation plan. | + === "Text" + How many suppliers are responsible for the vulnerable component and its remediation or mitigation plan? + + | Value | Description | + | :---: | :--- | + | One | One supplier is responsible for the vulnerable component and its remediation or mitigation plan. | + | Multiple | Multiple suppliers are responsible for the vulnerable component and its remediation or mitigation plan. | + === "JSON" + ```json + {% include-markdown "../examples/supplier_cardinality.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/supplier_cardinality.py" %} + ``` diff --git a/docs/reference/decision_points/supplier_contacted.md b/docs/reference/decision_points/supplier_contacted.md index 40e473e7..ff1dd556 100644 --- a/docs/reference/decision_points/supplier_contacted.md +++ b/docs/reference/decision_points/supplier_contacted.md @@ -5,10 +5,19 @@ !!! note "Supplier Contacted" - Has the reporter made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method? - - | Value | Description | - | :---: | :--- | - | Yes | The reporter has made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method. | - | No | The reporter has not made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method. | + === "Text" + Has the reporter made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method? + + | Value | Description | + | :---: | :--- | + | Yes | The reporter has made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method. | + | No | The reporter has not made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method. | + === "JSON" + ```json + {% include-markdown "../examples/supplier_contacted.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/supplier_contacted.py" %} + ``` diff --git a/docs/reference/decision_points/supplier_engagement.md b/docs/reference/decision_points/supplier_engagement.md index ddb820cb..d553481c 100644 --- a/docs/reference/decision_points/supplier_engagement.md +++ b/docs/reference/decision_points/supplier_engagement.md @@ -1,9 +1,18 @@ !!! note "Supplier Engagement" - Is the supplier responding to the reporter's contact effort and actively participating in the coordination effort? - - | Value | Description | - | :---: | :--- | - | Active | The supplier is responding to the reporter's contact effort and actively participating in the coordination effort. | - | Unresponsive | The supplier is not responding to the reporter's contact effort and is not actively participating in the coordination effort. | + === "Text" + Is the supplier responding to the reporter's contact effort and actively participating in the coordination effort? + + | Value | Description | + | :---: | :--- | + | Active | The supplier is responding to the reporter's contact effort and actively participating in the coordination effort. | + | Unresponsive | The supplier is not responding to the reporter's contact effort and is not actively participating in the coordination effort. | + === "JSON" + ```json + {% include-markdown "../examples/supplier_engagement.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/supplier_engagement.py" %} + ``` diff --git a/docs/reference/decision_points/supplier_involvement.md b/docs/reference/decision_points/supplier_involvement.md index 3a0ee4b3..26f1f4f9 100644 --- a/docs/reference/decision_points/supplier_involvement.md +++ b/docs/reference/decision_points/supplier_involvement.md @@ -1,10 +1,19 @@ !!! note "Supplier Involvement" - This decision point accounts for the state of the supplier's work on addressing the vulnerability. - - | Value | Description | - | :---: | :--- | - | Fix Ready | The supplier has provided a patch or fix. | - | Cooperative | The supplier is actively generating a patch or fix; they may or may not have provided a mitigation or work-around in the mean time. | - | Uncooperative/Unresponsive | The supplier has not responded, declined to generate a remediation, or no longer exists. | + === "Text" + This decision point accounts for the state of the supplier's work on addressing the vulnerability. + + | Value | Description | + | :--- | :--- | + | Fix Ready | The supplier has provided a patch or fix. | + | Cooperative | The supplier is actively generating a patch or fix; they may or may not have provided a mitigation or work-around in the mean time. | + | Uncooperative/Unresponsive | The supplier has not responded, declined to generate a remediation, or no longer exists. | + === "JSON" + ```json + {% include-markdown "../examples/supplier_involvement.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/supplier_involvement.py" %} + ``` diff --git a/docs/reference/decision_points/system_exposure.md b/docs/reference/decision_points/system_exposure.md index aae67216..a989cadd 100644 --- a/docs/reference/decision_points/system_exposure.md +++ b/docs/reference/decision_points/system_exposure.md @@ -1,12 +1,22 @@ !!! note "System Exposure" - The Accessible Attack Surface of the Affected System or Service + === "Text" + The Accessible Attack Surface of the Affected System or Service + + | Value | Description | + | :--- | :------------ | + | Small | Local service or program; highly controlled network | + | Controlled | Networked service with some access restrictions or mitigations already in place (whether locally or on the network). A successful mitigation must reliably interrupt the adversary’s attack, which requires the attack is detectable both reliably and quickly enough to respond. *Controlled* covers the situation in which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then *exposure* should be *small*. | + | Open | Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers) | + === "JSON" + ```json + {% include-markdown "../examples/system_exposure.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/system_exposure.py" %} + ``` - | Value | Description | - | :--- | :------------ | - | Small | Local service or program; highly controlled network | - | Controlled | Networked service with some access restrictions or mitigations already in place (whether locally or on the network). A successful mitigation must reliably interrupt the adversary’s attack, which requires the attack is detectable both reliably and quickly enough to respond. *Controlled* covers the situation in which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then *exposure* should be *small*. | - | Open | Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers) | Measuring the attack surface precisely is difficult, and we do not propose to perfectly delineate between small and controlled access. Exposure should be judged against the system in its deployed context, which may differ from how it is commonly expected to be deployed. diff --git a/docs/reference/decision_points/technical_impact.md b/docs/reference/decision_points/technical_impact.md index 847e00f4..8c81212d 100644 --- a/docs/reference/decision_points/technical_impact.md +++ b/docs/reference/decision_points/technical_impact.md @@ -1,11 +1,20 @@ !!! note "Technical Impact" - Technical Impact of Exploiting the Vulnerability - - | Value | Definition | - | :--- | :------------- | - | Partial | The exploit gives the adversary *limited* control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control. In this context, “low” means that the attacker cannot reasonably make enough attempts to overcome the low chance of each attempt not working. Denial of service is a form of limited control over the behavior of the vulnerable component. | - | Total | The exploit gives the adversary *total* control over the behavior of the software, or it gives total disclosure of all information on the system that contains the vulnerability | + === "Text" + Technical Impact of Exploiting the Vulnerability + + | Value | Definition | + | :--- | :------------- | + | Partial | The exploit gives the adversary *limited* control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control. In this context, “low” means that the attacker cannot reasonably make enough attempts to overcome the low chance of each attempt not working. Denial of service is a form of limited control over the behavior of the vulnerable component. | + | Total | The exploit gives the adversary *total* control over the behavior of the software, or it gives total disclosure of all information on the system that contains the vulnerability | + === "JSON" + ```json + {% include-markdown "../examples/technical_impact.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/technical_impact.py" %} + ``` When evaluating [*Technical Impact*](#technical-impact), recall the scope definition in the [Scope Section](#scope). Total control is relative to the affected component where the vulnerability resides. diff --git a/docs/reference/decision_points/value_density.md b/docs/reference/decision_points/value_density.md index f0ca68a6..622b6735 100644 --- a/docs/reference/decision_points/value_density.md +++ b/docs/reference/decision_points/value_density.md @@ -1,11 +1,20 @@ !!! note "Value Density" - [*Value Density*](#value-density) is described as *diffuse* or *concentrated* based on the resources that the adversary will gain control over with a single exploitation event: - - | Value | Definition | - | :--- | :---------- | - | diffuse | The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively small.| - | concentrated | The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of “system operators” rather than users. | + === "Text" + [*Value Density*](#value-density) is described as *diffuse* or *concentrated* based on the resources that the adversary will gain control over with a single exploitation event: + + | Value | Definition | + | :--- | :---------- | + | Diffuse | The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively small.| + | Concentrated | The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of “system operators” rather than users. | + === "JSON" + ```json + {% include-markdown "../examples/value_density.json" %} + ``` + === "Python" + ```python + {% include-markdown "../../../src/ssvc/decision_points/value_density.py" %} + ``` !!! info inline end "User vs. System Operator" diff --git a/docs/reference/examples/automatable.json b/docs/reference/examples/automatable.json new file mode 100644 index 00000000..589590fb --- /dev/null +++ b/docs/reference/examples/automatable.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Automatable", + "description": "Can an attacker reliably automate creating exploitation events for this vulnerability?", + "key": "A", + "values": [ + { + "name": "No", + "description": "Attackers cannot reliably automate steps 1-4 of the kill chain for this vulnerability. These steps are (1) reconnaissance, (2) weaponization, (3) delivery, and (4) exploitation.", + "key": "N" + }, + { + "name": "Yes", + "description": "Attackers can reliably automate steps 1-4 of the kill chain.", + "key": "Y" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/exploitation.json b/docs/reference/examples/exploitation.json new file mode 100644 index 00000000..83e140f3 --- /dev/null +++ b/docs/reference/examples/exploitation.json @@ -0,0 +1,24 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Exploitation", + "description": "The present state of exploitation of the vulnerability.", + "key": "E", + "values": [ + { + "name": "None", + "description": "There is no evidence of active exploitation and no public proof of concept (PoC) of how to exploit the vulnerability.", + "key": "N" + }, + { + "name": "PoC", + "description": "One of the following cases is true: (1) private evidence of exploitation is attested but not shared; (2) widespread hearsay attests to exploitation; (3) typical public PoC in places such as Metasploit or ExploitDB; or (4) the vulnerability has a well-known method of exploitation.", + "key": "P" + }, + { + "name": "Active", + "description": "Shared, observable, reliable evidence that the exploit is being used in the wild by real attackers; there is credible public reporting.", + "key": "A" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/human_impact.json b/docs/reference/examples/human_impact.json new file mode 100644 index 00000000..25ad3eb4 --- /dev/null +++ b/docs/reference/examples/human_impact.json @@ -0,0 +1,29 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Human Impact", + "description": "Human Impact", + "key": "HI", + "values": [ + { + "name": "Low", + "description": "Safety=None/Minor, Mission=None/Degraded/Crippled", + "key": "L" + }, + { + "name": "Medium", + "description": "Safety=None/Minor, Mission=MEF Failure OR Safety=Major, Mission=None/Degraded/Crippled", + "key": "M" + }, + { + "name": "High", + "description": "Safety=Hazardous, Mission=None/Degraded/Crippled/MEF Failure OR Safety=Major, Mission=MEF Failure", + "key": "H" + }, + { + "name": "Very High", + "description": "Safety=Catastrophic OR Mission=Mission Failure", + "key": "VH" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/mission_impact.json b/docs/reference/examples/mission_impact.json new file mode 100644 index 00000000..fa9f1303 --- /dev/null +++ b/docs/reference/examples/mission_impact.json @@ -0,0 +1,29 @@ +{ + "namespace": "ssvc", + "version": "2.0.0", + "name": "Mission Impact", + "description": "Impact on Mission Essential Functions of the Organization", + "key": "MI", + "values": [ + { + "name": "Degraded", + "description": "Little to no impact up to degradation of non-essential functions; chronic degradation would eventually harm essential functions", + "key": "D" + }, + { + "name": "MEF Support Crippled", + "description": "Activities that directly support essential functions are crippled; essential functions continue for a time", + "key": "MSC" + }, + { + "name": "MEF Failure", + "description": "Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time", + "key": "MEF" + }, + { + "name": "Mission Failure", + "description": "Multiple or all mission essential functions fail; ability to recover those functions degraded; organization\u2019s ability to deliver its overall mission fails", + "key": "MF" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/public_safety_impact.json b/docs/reference/examples/public_safety_impact.json new file mode 100644 index 00000000..7cfe1a47 --- /dev/null +++ b/docs/reference/examples/public_safety_impact.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Public Safety Impact", + "description": "A coarse-grained representation of impact to public safety.", + "key": "PSI", + "values": [ + { + "name": "Minimal", + "description": "Safety impact of None or Minor.", + "key": "M" + }, + { + "name": "Significant", + "description": "Safety impact of Major, Hazardous, or Catastrophic.", + "key": "S" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/public_value_added.json b/docs/reference/examples/public_value_added.json new file mode 100644 index 00000000..19642c67 --- /dev/null +++ b/docs/reference/examples/public_value_added.json @@ -0,0 +1,24 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Public Value Added", + "description": "How much value would a publication from the coordinator benefit the broader community?", + "key": "PVA", + "values": [ + { + "name": "Precedence", + "description": "The publication would be the first publicly available, or be coincident with the first publicly available.", + "key": "P" + }, + { + "name": "Ampliative", + "description": "Amplifies and/or augments the existing public information about the vulnerability, for example, adds additional detail, addresses or corrects errors in other public information, draws further attention to the vulnerability, etc.", + "key": "A" + }, + { + "name": "Limited", + "description": "Minimal value added to the existing public information because existing information is already high quality and in multiple outlets.", + "key": "L" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/report_credibility.json b/docs/reference/examples/report_credibility.json new file mode 100644 index 00000000..1dedf078 --- /dev/null +++ b/docs/reference/examples/report_credibility.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Report Credibility", + "description": "Is the report credible?", + "key": "RC", + "values": [ + { + "name": "Credible", + "description": "The report is credible.", + "key": "C" + }, + { + "name": "Not Credible", + "description": "The report is not credible.", + "key": "NC" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/report_public.json b/docs/reference/examples/report_public.json new file mode 100644 index 00000000..238443be --- /dev/null +++ b/docs/reference/examples/report_public.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Report Public", + "description": "Is a viable report of the details of the vulnerability already publicly available?", + "key": "RP", + "values": [ + { + "name": "No", + "description": "No public report of the vulnerability exists.", + "key": "N" + }, + { + "name": "Yes", + "description": "A public report of the vulnerability exists.", + "key": "Y" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/safety_impact.json b/docs/reference/examples/safety_impact.json new file mode 100644 index 00000000..c74f0080 --- /dev/null +++ b/docs/reference/examples/safety_impact.json @@ -0,0 +1,34 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Safety Impact", + "description": "The safety impact of the vulnerability.", + "key": "SI", + "values": [ + { + "name": "None", + "description": "The effect is below the threshold for all aspects described in Minor.", + "key": "N" + }, + { + "name": "Minor", + "description": "Any one or more of these conditions hold. Physical harm: Physical discomfort for users (not operators) of the system. Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the vulnerability where operator actions would be well within expected operator abilities; OR causes a minor occupational safety hazard. System resiliency: Small reduction in built-in system safety margins; OR small reduction in system functional capabilities that support safe operation. Environment Minor externalities (property damage, environmental damage, etc.) imposed on other parties. Financial Financial losses, which are not readily absorbable, to multiple persons. Psychological: Emotional or psychological harm, sufficient to be cause for counselling or therapy, to multiple persons.", + "key": "M" + }, + { + "name": "Major", + "description": "Any one or more of these conditions hold. Physical harm: Physical distress and injuries for users (not operators) of the system. Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the vulnerability where operator actions would be within their capabilities but the actions require their full attention and effort; OR significant distraction or discomfort to operators; OR causes significant occupational safety hazard. System resiliency: System safety margin effectively eliminated but no actual harm; OR failure of system functional capabilities that support safe operation. Environment: Major externalities (property damage, environmental damage, etc.) imposed on other parties. Financial: Financial losses that likely lead to bankruptcy of multiple persons. Psychological: Widespread emotional or psychological harm, sufficient to be cause for counselling or therapy, to populations of people.", + "key": "J" + }, + { + "name": "Hazardous", + "description": "Any one or more of these conditions hold. Physical harm: Serious or fatal injuries, where fatalities are plausibly preventable via emergency services or other measures. Operator resiliency: Actions that would keep the system in a safe state are beyond system operator capabilities, resulting in adverse conditions; OR great physical distress to system operators such that they cannot be expected to operate the system properly. System resiliency: Parts of the cyber-physical system break; system\u2019s ability to recover lost functionality remains intact. Environment: Serious externalities (threat to life as well as property, widespread environmental damage, measurable public health risks, etc.) imposed on other parties. Financial: Socio-technical system (elections, financial grid, etc.) of which the affected component is a part is actively destabilized and enters unsafe state. Psychological: N/A.", + "key": "H" + }, + { + "name": "Catastrophic", + "description": "Any one or more of these conditions hold. Physical harm: Multiple immediate fatalities (Emergency response probably cannot save the victims.) Operator resiliency: Operator incapacitated (includes fatality or otherwise incapacitated). System resiliency: Total loss of whole cyber-physical system, of which the software is a part. Environment: Extreme externalities (immediate public health threat, environmental damage leading to small ecosystem collapse, etc.) imposed on other parties. Financial: Social systems (elections, financial grid, etc.) supported by the software collapse. Psychological: N/A.", + "key": "C" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/supplier_cardinality.json b/docs/reference/examples/supplier_cardinality.json new file mode 100644 index 00000000..fb26d3b3 --- /dev/null +++ b/docs/reference/examples/supplier_cardinality.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Supplier Cardinality", + "description": "How many suppliers are responsible for the vulnerable component and its remediation or mitigation plan?", + "key": "SC", + "values": [ + { + "name": "One", + "description": "There is only one supplier of the vulnerable component.", + "key": "O" + }, + { + "name": "Multiple", + "description": "There are multiple suppliers of the vulnerable component.", + "key": "M" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/supplier_contacted.json b/docs/reference/examples/supplier_contacted.json new file mode 100644 index 00000000..85c6364b --- /dev/null +++ b/docs/reference/examples/supplier_contacted.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Supplier Contacted", + "description": "Has the reporter made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method?", + "key": "SC", + "values": [ + { + "name": "No", + "description": "The supplier has not been contacted.", + "key": "N" + }, + { + "name": "Yes", + "description": "The supplier has been contacted.", + "key": "Y" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/supplier_engagement.json b/docs/reference/examples/supplier_engagement.json new file mode 100644 index 00000000..66ee28ca --- /dev/null +++ b/docs/reference/examples/supplier_engagement.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Supplier Engagement", + "description": "Is the supplier responding to the reporter\u2019s contact effort and actively participating in the coordination effort?", + "key": "SE", + "values": [ + { + "name": "Active", + "description": "The supplier is responding to the reporter\u2019s contact effort and actively participating in the coordination effort.", + "key": "A" + }, + { + "name": "Unresponsive", + "description": "The supplier is not responding to the reporter\u2019s contact effort and not actively participating in the coordination effort.", + "key": "U" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/supplier_involvement.json b/docs/reference/examples/supplier_involvement.json new file mode 100644 index 00000000..af0b4e65 --- /dev/null +++ b/docs/reference/examples/supplier_involvement.json @@ -0,0 +1,24 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Supplier Involvement", + "description": "What is the state of the supplier\u2019s work on addressing the vulnerability?", + "key": "SI", + "values": [ + { + "name": "Fix Ready", + "description": "The supplier has provided a patch or fix.", + "key": "FR" + }, + { + "name": "Cooperative", + "description": "The supplier is actively generating a patch or fix; they may or may not have provided a mitigation or work-around in the mean time.", + "key": "C" + }, + { + "name": "Uncooperative/Unresponsive", + "description": "The supplier has not responded, declined to generate a remediation, or no longer exists.", + "key": "UU" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/system_exposure.json b/docs/reference/examples/system_exposure.json new file mode 100644 index 00000000..763df0f2 --- /dev/null +++ b/docs/reference/examples/system_exposure.json @@ -0,0 +1,24 @@ +{ + "namespace": "ssvc", + "version": "1.0.1", + "name": "System Exposure", + "description": "The Accessible Attack Surface of the Affected System or Service", + "key": "EXP", + "values": [ + { + "name": "Small", + "description": "Local service or program; highly controlled network", + "key": "S" + }, + { + "name": "Controlled", + "description": "Networked service with some access restrictions or mitigations already in place (whether locally or on the network). A successful mitigation must reliably interrupt the adversary\u2019s attack, which requires the attack is detectable both reliably and quickly enough to respond. Controlled covers the situation in which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then exposure should be small.", + "key": "C" + }, + { + "name": "Open", + "description": "Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers)", + "key": "O" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/technical_impact.json b/docs/reference/examples/technical_impact.json new file mode 100644 index 00000000..82f85e1e --- /dev/null +++ b/docs/reference/examples/technical_impact.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Technical Impact", + "description": "The technical impact of the vulnerability.", + "key": "TI", + "values": [ + { + "name": "Partial", + "description": "The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control.", + "key": "P" + }, + { + "name": "Total", + "description": "The exploit gives the adversary total control over the behavior of the software, or it gives total disclosure of all information on the system that contains the vulnerability.", + "key": "T" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/utility.json b/docs/reference/examples/utility.json new file mode 100644 index 00000000..ec2f1cde --- /dev/null +++ b/docs/reference/examples/utility.json @@ -0,0 +1,24 @@ +{ + "namespace": "ssvc", + "version": "1.0.1", + "name": "Utility", + "description": "The Usefulness of the Exploit to the Adversary", + "key": "U", + "values": [ + { + "name": "Laborious", + "description": "No to automatable and diffuse value", + "key": "L" + }, + { + "name": "Efficient", + "description": "Yes to automatable and diffuse value OR No to automatable and concentrated value", + "key": "E" + }, + { + "name": "Super Effective", + "description": "Yes to automatable and concentrated value", + "key": "S" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/value_density.json b/docs/reference/examples/value_density.json new file mode 100644 index 00000000..adae9dbc --- /dev/null +++ b/docs/reference/examples/value_density.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Value Density", + "description": "The concentration of value in the target", + "key": "VD", + "values": [ + { + "name": "Diffuse", + "description": "The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively small.", + "key": "D" + }, + { + "name": "Concentrated", + "description": "The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of \u201csystem operators\u201d rather than users.", + "key": "C" + } + ] +} \ No newline at end of file diff --git a/docs/reference/examples/virulence.json b/docs/reference/examples/virulence.json new file mode 100644 index 00000000..0b0d1a4f --- /dev/null +++ b/docs/reference/examples/virulence.json @@ -0,0 +1,19 @@ +{ + "namespace": "ssvc", + "version": "1.0.0", + "name": "Virulence", + "description": "The speed at which the vulnerability can be exploited.", + "key": "V", + "values": [ + { + "name": "Slow", + "description": "Steps 1-4 of the kill chain cannot be reliably automated for this vulnerability for some reason. These steps are reconnaissance, weaponization, delivery, and exploitation. Example reasons for why a step may not be reliably automatable include (1) the vulnerable component is not searchable or enumerable on the network, (2) weaponization may require human direction for each target, (3) delivery may require channels that widely deployed network security configurations block, and (3) exploitation may be frustrated by adequate exploit-prevention techniques enabled by default; ASLR is an example of an exploit-prevention tool.", + "key": "S" + }, + { + "name": "Rapid", + "description": "Steps 1-4 of the of the kill chain can be reliably automated. If the vulnerability allows remote code execution or command injection, the default response should be rapid.", + "key": "R" + } + ] +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 73b046c1..d4776ff9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -99,6 +99,10 @@ plugins: - table-reader - bibtex: bib_file: 'doc/md_src_files/sources_ssvc.bib' + - mkdocstrings: + handlers: + python: + paths: [ 'src' ] repo_url: 'https://github.com/CERTCC/SSVC' repo_name: 'CERTCC/SSVC' markdown_extensions: diff --git a/requirements.txt b/requirements.txt index 9e3b5be4..51f703f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ mkdocs-include-markdown-plugin mkdocs-table-reader-plugin mkdocs-material mkdocs-material-extensions +mkdocstrings dataclasses-json diff --git a/src/ssvc/decision_points/nist/base.py b/src/ssvc/decision_points/nist/base.py deleted file mode 100644 index c5a8225f..00000000 --- a/src/ssvc/decision_points/nist/base.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -""" -file: base -author: adh -created_at: 9/21/23 2:07 PM -""" -from ssvc.decision_points.base import SsvcDecisionPoint - - -@dataclasses_json -@dataclass(kw_only=True) -class NistDecisionPoint(SsvcDecisionPoint): - namespace = "nist" - - -def main(): - pass - - -if __name__ == "__main__": - main() diff --git a/src/ssvc/decision_points/nist/eo_critical.py b/src/ssvc/decision_points/nist/eo_critical.py index aa8094d0..491649ba 100644 --- a/src/ssvc/decision_points/nist/eo_critical.py +++ b/src/ssvc/decision_points/nist/eo_critical.py @@ -4,8 +4,7 @@ author: adh created_at: 9/21/23 2:03 PM """ -from ssvc.decision_points.base import SsvcValue -from ssvc.decision_points.nist.base import NistDecisionPoint +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue YES = SsvcValue( name="Yes", @@ -19,10 +18,11 @@ description="System does not meet NIST's critical software definition.", ) -EO_CRITICAL = NistDecisionPoint( +EO_CRITICAL = SsvcDecisionPoint( name="EO Critical", description="Denotes whether a system meets NIST's critical software definition. " "Based on https://www.nist.gov/itl/executive-order-improving-nations-cybersecurity/critical-software-definition-explanatory", + namespace="nist", key="EOCS", version="1.0.0", values=( diff --git a/src/ssvc/decision_points/ossf/base.py b/src/ssvc/decision_points/ossf/base.py deleted file mode 100644 index cf77074a..00000000 --- a/src/ssvc/decision_points/ossf/base.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -""" -file: base -author: adh -created_at: 9/21/23 2:07 PM -""" -from ssvc.decision_points.base import SsvcDecisionPoint - - -@dataclasses_json -@dataclass(kw_only=True) -class OssfDecisionPoint(SsvcDecisionPoint): - namespace: str = "ossf" - - -def main(): - pass - - -if __name__ == "__main__": - main() diff --git a/src/ssvc/decision_points/ossf/critical.py b/src/ssvc/decision_points/ossf/critical.py index 0e7f573d..859a9ad4 100644 --- a/src/ssvc/decision_points/ossf/critical.py +++ b/src/ssvc/decision_points/ossf/critical.py @@ -4,8 +4,7 @@ author: adh created_at: 9/21/23 2:09 PM """ -from ssvc.decision_points.base import SsvcValue -from ssvc.decision_points.ossf.base import OssfDecisionPoint +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue NO = SsvcValue( name="No", @@ -18,10 +17,11 @@ description="System meets the Open Source Software Foundation's critical software definition.", ) -OSSF_CRITICAL_SOFTWARE = OssfDecisionPoint( +OSSF_CRITICAL_SOFTWARE = SsvcDecisionPoint( name="OSSF Critical Software", description="Denotes whether a system meets the Open Source Software Foundation's critical software definition. " "Based on https://openssf.org/wp-content/uploads/2021/07/OSSF-Criticality-Definition-1.0.pdf", + namespace="ossf", key="OSSFCS", version="1.0.0", values=( diff --git a/src/ssvc/decision_points/exposure.py b/src/ssvc/decision_points/system_exposure.py similarity index 83% rename from src/ssvc/decision_points/exposure.py rename to src/ssvc/decision_points/system_exposure.py index f9a650ff..ecd6a12a 100644 --- a/src/ssvc/decision_points/exposure.py +++ b/src/ssvc/decision_points/system_exposure.py @@ -32,8 +32,8 @@ ) -EXPOSURE_1 = SsvcDecisionPoint( - name="Exposure", +SYSTEM_EXPOSURE_1 = SsvcDecisionPoint( + name="System Exposure", description="The Accessible Attack Surface of the Affected System or Service", key="EXP", version="1.0.0", @@ -48,14 +48,14 @@ EXP_OPEN.name = "Open" EXP_OPEN.key = "O" -EXPOSURE_1_0_1 = deepcopy(EXPOSURE_1) -EXPOSURE_1_0_1.version = "1.0.1" -EXPOSURE_1_0_1.values = (EXP_SMALL, EXP_CONTROLLED, EXP_OPEN) +SYSTEM_EXPOSURE_1_0_1 = deepcopy(SYSTEM_EXPOSURE_1) +SYSTEM_EXPOSURE_1_0_1.version = "1.0.1" +SYSTEM_EXPOSURE_1_0_1.values = (EXP_SMALL, EXP_CONTROLLED, EXP_OPEN) def main(): - print(EXPOSURE_1.to_json(indent=2)) - print(EXPOSURE_1_0_1.to_json(indent=2)) + print(SYSTEM_EXPOSURE_1.to_json(indent=2)) + print(SYSTEM_EXPOSURE_1_0_1.to_json(indent=2)) if __name__ == "__main__": diff --git a/src/ssvc/dp_groups/base.py b/src/ssvc/dp_groups/base.py index e6ff3677..9d9c70c8 100644 --- a/src/ssvc/dp_groups/base.py +++ b/src/ssvc/dp_groups/base.py @@ -47,6 +47,15 @@ def get_all_decision_points_from( return dps +def group_to_jsonfiles(group: SsvcDecisionPointGroup, path: str = ".") -> None: + for dp in group.decision_points: + basename = dp.name.strip().lower().replace(" ", "_") + fname = f"{path}/{basename}.json" + with open(fname, "w") as f: + print(f"Writing {fname}") + f.write(dp.to_json(indent=2)) + + def main(): pass diff --git a/src/ssvc/dp_groups/deployer.py b/src/ssvc/dp_groups/deployer.py index c9dc4539..dbcdcfd9 100644 --- a/src/ssvc/dp_groups/deployer.py +++ b/src/ssvc/dp_groups/deployer.py @@ -1,14 +1,17 @@ #!/usr/bin/env python -''' +""" file: deployer author: adh created_at: 9/21/23 11:40 AM -''' +""" from copy import deepcopy from ssvc.decision_points.automatable import AUTOMATABLE_1 from ssvc.decision_points.exploitation import EXPLOITATION_1 -from ssvc.decision_points.exposure import EXPOSURE_1, EXPOSURE_1_0_1 +from ssvc.decision_points.system_exposure import ( + SYSTEM_EXPOSURE_1, + SYSTEM_EXPOSURE_1_0_1, +) from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 from ssvc.decision_points.mission_impact import MISSION_IMPACT_1, MISSION_IMPACT_2 from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 @@ -22,7 +25,12 @@ description="The decision points used by the patch applier.", key="PA", version="1.0.0", - decision_points=[EXPLOITATION_1, EXPOSURE_1, MISSION_IMPACT_1, SAFETY_IMPACT_1], + decision_points=[ + EXPLOITATION_1, + SYSTEM_EXPOSURE_1, + MISSION_IMPACT_1, + SAFETY_IMPACT_1, + ], ) # alias for forward compatibility @@ -35,8 +43,8 @@ DEPLOYER_2.key = "D" DEPLOYER_2.version = "2.0.0" # update exposure -DEPLOYER_2.decision_points.remove(EXPOSURE_1) -DEPLOYER_2.decision_points.append(EXPOSURE_1_0_1) +DEPLOYER_2.decision_points.remove(SYSTEM_EXPOSURE_1) +DEPLOYER_2.decision_points.append(SYSTEM_EXPOSURE_1_0_1) # add UTILITY (AUTOMATABLE + VALUE DENSITY) DEPLOYER_2.decision_points.append(UTILITY_1_0_1) DEPLOYER_2.decision_points.append(AUTOMATABLE_1) @@ -57,13 +65,11 @@ DEPLOYER_3.decision_points.append(MISSION_IMPACT_2) - def main(): print(PATCH_APPLIER_1.to_json(indent=2)) print(DEPLOYER_2.to_json(indent=2)) print(DEPLOYER_3.to_json(indent=2)) - -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/ssvc/dp_groups/v2_1.py b/src/ssvc/dp_groups/v2_1.py index baebd7a1..ad3d7303 100644 --- a/src/ssvc/dp_groups/v2_1.py +++ b/src/ssvc/dp_groups/v2_1.py @@ -16,7 +16,7 @@ GROUPS = [COORDINATOR_PUBLICATION_1, COORDINATOR_TRIAGE_1, DEPLOYER_3, SUPPLIER_2] -SSVCv2 = SsvcDecisionPointGroup( +SSVCv2_1 = SsvcDecisionPointGroup( name="SSVCv2.1", description="The second version of the SSVC.", key="SSVCv2.1", @@ -29,7 +29,7 @@ def main(): for group in GROUPS: print(group.to_json(indent=2)) print() - print(SSVCv2.to_json(indent=2)) + print(SSVCv2_1.to_json(indent=2)) if __name__ == "__main__": From dc3bf343ff36e7c2d9ad347f70ae3bf78065d3cc Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 15:17:47 -0400 Subject: [PATCH 21/53] uncenter column --- docs/reference/decision_points/public_value_added.md | 2 +- docs/reference/decision_points/report_credibility.md | 2 +- docs/reference/decision_points/report_public.md | 2 +- docs/reference/decision_points/safety_impact.md | 2 +- docs/reference/decision_points/supplier_cardinality.md | 2 +- docs/reference/decision_points/supplier_contacted.md | 2 +- docs/reference/decision_points/supplier_engagement.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/reference/decision_points/public_value_added.md b/docs/reference/decision_points/public_value_added.md index b157f6c3..f2d222d4 100644 --- a/docs/reference/decision_points/public_value_added.md +++ b/docs/reference/decision_points/public_value_added.md @@ -5,7 +5,7 @@ The value is based on the state of existing public information about the vulnerablity. | Value | Description | - | :---: | :--- | + | :--- | :--- | | Precedence | The publication would be the first publicly available, or be coincident with the first publicly available. | | Ampliative | Amplifies and/or augments the existing public information about the vulnerability, for example, adds additional detail, addresses or corrects errors in other public information, draws further attention to the vulnerability, etc. | | Limited | Minimal value added to the existing public information because existing information is already high quality and in multiple outlets. | diff --git a/docs/reference/decision_points/report_credibility.md b/docs/reference/decision_points/report_credibility.md index 49d7bafb..22ae6f1c 100644 --- a/docs/reference/decision_points/report_credibility.md +++ b/docs/reference/decision_points/report_credibility.md @@ -4,7 +4,7 @@ Assessing the credibility of a report is complex, but the assessment must reach a conclusion of either: | Value | Description | - | :---: | :--- | + | :--- | :--- | | Credible | The report is credible. | | Not credible | The report is not credible. | === "JSON" diff --git a/docs/reference/decision_points/report_public.md b/docs/reference/decision_points/report_public.md index 9f9cafc4..a6386fa0 100644 --- a/docs/reference/decision_points/report_public.md +++ b/docs/reference/decision_points/report_public.md @@ -4,7 +4,7 @@ Is a viable report of the details of the vulnerability already publicly available? | Value | Description | - | :---: | :--- | + | :--- | :--- | | Yes | A viable report of the details of the vulnerability is already publicly available. | | No | A viable report of the details of the vulnerability is not already publicly available. | === "JSON" diff --git a/docs/reference/decision_points/safety_impact.md b/docs/reference/decision_points/safety_impact.md index de6683d4..c7b98fd9 100644 --- a/docs/reference/decision_points/safety_impact.md +++ b/docs/reference/decision_points/safety_impact.md @@ -4,7 +4,7 @@ Safety Impacts of Affected System Compromise | Value | Type of Harm | Definition | - | --: | :-- | :----------- | + | :--- | :--- | :----------- | | None | All | Does *not* mean no impact *literally*; the effect is below the threshold for all aspects described in Minor | | Minor | Physical Harm | Physical discomfort for users of the system OR a minor occupational safety hazard OR reduction in physical system safety margins | | Minor | Environment | Minor externalities (property damage, environmental damage, etc.) imposed on other parties | diff --git a/docs/reference/decision_points/supplier_cardinality.md b/docs/reference/decision_points/supplier_cardinality.md index 114c27ab..06afc600 100644 --- a/docs/reference/decision_points/supplier_cardinality.md +++ b/docs/reference/decision_points/supplier_cardinality.md @@ -4,7 +4,7 @@ How many suppliers are responsible for the vulnerable component and its remediation or mitigation plan? | Value | Description | - | :---: | :--- | + | :--- | :--- | | One | One supplier is responsible for the vulnerable component and its remediation or mitigation plan. | | Multiple | Multiple suppliers are responsible for the vulnerable component and its remediation or mitigation plan. | === "JSON" diff --git a/docs/reference/decision_points/supplier_contacted.md b/docs/reference/decision_points/supplier_contacted.md index ff1dd556..97e132bd 100644 --- a/docs/reference/decision_points/supplier_contacted.md +++ b/docs/reference/decision_points/supplier_contacted.md @@ -10,7 +10,7 @@ Has the reporter made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method? | Value | Description | - | :---: | :--- | + | :--- | :--- | | Yes | The reporter has made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method. | | No | The reporter has not made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method. | === "JSON" diff --git a/docs/reference/decision_points/supplier_engagement.md b/docs/reference/decision_points/supplier_engagement.md index d553481c..9bf5c78e 100644 --- a/docs/reference/decision_points/supplier_engagement.md +++ b/docs/reference/decision_points/supplier_engagement.md @@ -4,7 +4,7 @@ Is the supplier responding to the reporter's contact effort and actively participating in the coordination effort? | Value | Description | - | :---: | :--- | + | :--- | :--- | | Active | The supplier is responding to the reporter's contact effort and actively participating in the coordination effort. | | Unresponsive | The supplier is not responding to the reporter's contact effort and is not actively participating in the coordination effort. | === "JSON" From 2f2903735b5f805670fdcd9051c7e535758ef0e4 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 15:32:42 -0400 Subject: [PATCH 22/53] start adding table generator --- src/ssvc/decision_points/base.py | 14 ++++++++++++++ src/ssvc/dp_groups/base.py | 9 --------- src/ssvc/tools.py | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 src/ssvc/tools.py diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index c25fc43e..b2793bbd 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -36,6 +36,20 @@ class SsvcDecisionPoint(_Base, _Versioned, _Namespaced): values: Tuple[SsvcValue] + def to_table(self): + rows = [] + rows.append(f"{self.description}") + rows.append("") + + headings = ["Value", "Key", "Description"] + rows.append("|".join(headings)) + rows.append("|".join(["---" for _ in headings])) + + for value in self.values: + rows.append("|".join([value.name, value.key, value.description])) + + return "\n".join(rows) + def main(): opt_none = SsvcValue(name="None", key="N", description="No exploit available") diff --git a/src/ssvc/dp_groups/base.py b/src/ssvc/dp_groups/base.py index 9d9c70c8..e6ff3677 100644 --- a/src/ssvc/dp_groups/base.py +++ b/src/ssvc/dp_groups/base.py @@ -47,15 +47,6 @@ def get_all_decision_points_from( return dps -def group_to_jsonfiles(group: SsvcDecisionPointGroup, path: str = ".") -> None: - for dp in group.decision_points: - basename = dp.name.strip().lower().replace(" ", "_") - fname = f"{path}/{basename}.json" - with open(fname, "w") as f: - print(f"Writing {fname}") - f.write(dp.to_json(indent=2)) - - def main(): pass diff --git a/src/ssvc/tools.py b/src/ssvc/tools.py new file mode 100644 index 00000000..2cd451d0 --- /dev/null +++ b/src/ssvc/tools.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +""" +file: tools +author: adh +created_at: 9/21/23 3:20 PM +""" +from ssvc.dp_groups.base import SsvcDecisionPointGroup + + +def group_to_jsonfiles(group: SsvcDecisionPointGroup, path: str = ".") -> None: + for dp in group.decision_points: + basename = dp.name.strip().lower().replace(" ", "_") + + json_fname = f"{path}/{basename}.json" + with open(json_fname, "w") as f: + print(f"Writing {json_fname}") + f.write(dp.to_json(indent=2)) + + table_fname = f"{path}/{basename}.md" + with open(table_fname, "w") as f: + print(f"Writing {table_fname}") + f.write(dp.to_table()) From 07d145a21aeafe985ccae9b5ac68b9a3298066e3 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 15:53:28 -0400 Subject: [PATCH 23/53] let's try generating tables --- docs/reference/decision_points/automatable.md | 9 ++------- docs/reference/decision_points/exploitation.md | 17 ++++++++++------- docs/reference/examples/automatable.md | 6 ++++++ docs/reference/examples/exploitation.md | 7 +++++++ docs/reference/examples/human_impact.md | 8 ++++++++ docs/reference/examples/mission_impact.md | 8 ++++++++ docs/reference/examples/public_safety_impact.md | 6 ++++++ docs/reference/examples/public_value_added.md | 7 +++++++ docs/reference/examples/report_credibility.md | 6 ++++++ docs/reference/examples/report_public.md | 6 ++++++ docs/reference/examples/safety_impact.md | 9 +++++++++ docs/reference/examples/supplier_cardinality.md | 6 ++++++ docs/reference/examples/supplier_contacted.md | 6 ++++++ docs/reference/examples/supplier_engagement.md | 6 ++++++ docs/reference/examples/supplier_involvement.md | 7 +++++++ docs/reference/examples/system_exposure.md | 7 +++++++ docs/reference/examples/technical_impact.md | 6 ++++++ docs/reference/examples/utility.md | 7 +++++++ docs/reference/examples/value_density.md | 6 ++++++ docs/reference/examples/virulence.md | 6 ++++++ src/ssvc/decision_points/base.py | 10 +++++++--- 21 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 docs/reference/examples/automatable.md create mode 100644 docs/reference/examples/exploitation.md create mode 100644 docs/reference/examples/human_impact.md create mode 100644 docs/reference/examples/mission_impact.md create mode 100644 docs/reference/examples/public_safety_impact.md create mode 100644 docs/reference/examples/public_value_added.md create mode 100644 docs/reference/examples/report_credibility.md create mode 100644 docs/reference/examples/report_public.md create mode 100644 docs/reference/examples/safety_impact.md create mode 100644 docs/reference/examples/supplier_cardinality.md create mode 100644 docs/reference/examples/supplier_contacted.md create mode 100644 docs/reference/examples/supplier_engagement.md create mode 100644 docs/reference/examples/supplier_involvement.md create mode 100644 docs/reference/examples/system_exposure.md create mode 100644 docs/reference/examples/technical_impact.md create mode 100644 docs/reference/examples/utility.md create mode 100644 docs/reference/examples/value_density.md create mode 100644 docs/reference/examples/virulence.md diff --git a/docs/reference/decision_points/automatable.md b/docs/reference/decision_points/automatable.md index c2cc63b1..53b090f8 100644 --- a/docs/reference/decision_points/automatable.md +++ b/docs/reference/decision_points/automatable.md @@ -1,12 +1,7 @@ !!! note "Automatable" - === "Text" - Can an attacker reliably automate creating exploitation events for this vulnerability? - - | Value | Definition | - | :--- | :---------- | - | no | Attackers cannot reliably automate steps 1-4 of the kill chain [@hutchins2011intelligence] for this vulnerability. | - | yes | Attackers can reliably automate steps 1-4 of the kill chain. | + === "Text" + {% include-markdown "../examples/automatable.md" %} === "JSON" ```json diff --git a/docs/reference/decision_points/exploitation.md b/docs/reference/decision_points/exploitation.md index 7e9d12d7..2569adef 100644 --- a/docs/reference/decision_points/exploitation.md +++ b/docs/reference/decision_points/exploitation.md @@ -1,13 +1,7 @@ !!! note "Exploitation" === "Text" - Evidence of Active Exploitation of a Vulnerability - - | Value | Definition | - | :--- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - | None | There is no evidence of active exploitation and no public proof of concept (PoC) of how to exploit the vulnerability. | - | PoC
(Proof of Concept) | One of the following cases is true: (1) exploit code is sold or traded on underground or restricted fora; (2) a typical public PoC in places such as Metasploit or ExploitDB; or (3) the vulnerability has a well-known method of exploitation. Some examples of condition (3) are open-source web proxies serve as the PoC code for how to exploit any vulnerability in the vein of improper validation of TLS certificates. As another example, Wireshark serves as a PoC for packet replay attacks on ethernet or WiFi networks. A publicly-known hard-coded or default password would also meet this criteria. | - | Active | Shared, observable, reliable evidence that the exploit is being used in the wild by real attackers; there is credible public reporting. | + {% include-markdown "../examples/exploitation.md" %} === "JSON" ```json {% include-markdown "../examples/exploitation.json" %} @@ -17,6 +11,15 @@ {% include-markdown "../../../src/ssvc/decision_points/exploitation.py" %} ``` +!!! example + + Some examples of condition (3) include + + - open-source web proxies serve as the PoC code for how to exploit any vulnerability in the vein of improper validation of TLS certificates + - Wireshark serves as a PoC for packet replay attacks on ethernet or WiFi networks. + - A publicly-known hard-coded or default password would also meet this criteria. + + The intent of this measure is the present state of exploitation of the vulnerability. The intent is not to predict future exploitation but only to acknowledge the current state of affairs. Predictive systems, such as EPSS, could be used to augment this decision or to notify stakeholders of likely changes [@jacobs2021epss]. diff --git a/docs/reference/examples/automatable.md b/docs/reference/examples/automatable.md new file mode 100644 index 00000000..2b652185 --- /dev/null +++ b/docs/reference/examples/automatable.md @@ -0,0 +1,6 @@ +Can an attacker reliably automate creating exploitation events for this vulnerability? + +| Value | Key | Description | +| --- | --- | --- | +| No | N | Attackers cannot reliably automate steps 1-4 of the kill chain for this vulnerability. These steps are (1) reconnaissance, (2) weaponization, (3) delivery, and (4) exploitation. | +| Yes | Y | Attackers can reliably automate steps 1-4 of the kill chain. | \ No newline at end of file diff --git a/docs/reference/examples/exploitation.md b/docs/reference/examples/exploitation.md new file mode 100644 index 00000000..e1e7a00e --- /dev/null +++ b/docs/reference/examples/exploitation.md @@ -0,0 +1,7 @@ +The present state of exploitation of the vulnerability. + +| Value | Key | Description | +| --- | --- | --- | +| None | N | There is no evidence of active exploitation and no public proof of concept (PoC) of how to exploit the vulnerability. | +| PoC | P | One of the following cases is true: (1) private evidence of exploitation is attested but not shared; (2) widespread hearsay attests to exploitation; (3) typical public PoC in places such as Metasploit or ExploitDB; or (4) the vulnerability has a well-known method of exploitation. | +| Active | A | Shared, observable, reliable evidence that the exploit is being used in the wild by real attackers; there is credible public reporting. | \ No newline at end of file diff --git a/docs/reference/examples/human_impact.md b/docs/reference/examples/human_impact.md new file mode 100644 index 00000000..74721088 --- /dev/null +++ b/docs/reference/examples/human_impact.md @@ -0,0 +1,8 @@ +Human Impact + +| Value | Key | Description | +| --- | --- | --- | +| Low | L | Safety=None/Minor, Mission=None/Degraded/Crippled | +| Medium | M | Safety=None/Minor, Mission=MEF Failure OR Safety=Major, Mission=None/Degraded/Crippled | +| High | H | Safety=Hazardous, Mission=None/Degraded/Crippled/MEF Failure OR Safety=Major, Mission=MEF Failure | +| Very High | VH | Safety=Catastrophic OR Mission=Mission Failure | \ No newline at end of file diff --git a/docs/reference/examples/mission_impact.md b/docs/reference/examples/mission_impact.md new file mode 100644 index 00000000..626226ca --- /dev/null +++ b/docs/reference/examples/mission_impact.md @@ -0,0 +1,8 @@ +Impact on Mission Essential Functions of the Organization + +| Value | Key | Description | +| --- | --- | --- | +| Degraded | D | Little to no impact up to degradation of non-essential functions; chronic degradation would eventually harm essential functions | +| MEF Support Crippled | MSC | Activities that directly support essential functions are crippled; essential functions continue for a time | +| MEF Failure | MEF | Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time | +| Mission Failure | MF | Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails | \ No newline at end of file diff --git a/docs/reference/examples/public_safety_impact.md b/docs/reference/examples/public_safety_impact.md new file mode 100644 index 00000000..7f3ba7e0 --- /dev/null +++ b/docs/reference/examples/public_safety_impact.md @@ -0,0 +1,6 @@ +A coarse-grained representation of impact to public safety. + +| Value | Key | Description | +| --- | --- | --- | +| Minimal | M | Safety impact of None or Minor. | +| Significant | S | Safety impact of Major, Hazardous, or Catastrophic. | \ No newline at end of file diff --git a/docs/reference/examples/public_value_added.md b/docs/reference/examples/public_value_added.md new file mode 100644 index 00000000..5cf0efe6 --- /dev/null +++ b/docs/reference/examples/public_value_added.md @@ -0,0 +1,7 @@ +How much value would a publication from the coordinator benefit the broader community? + +| Value | Key | Description | +| --- | --- | --- | +| Precedence | P | The publication would be the first publicly available, or be coincident with the first publicly available. | +| Ampliative | A | Amplifies and/or augments the existing public information about the vulnerability, for example, adds additional detail, addresses or corrects errors in other public information, draws further attention to the vulnerability, etc. | +| Limited | L | Minimal value added to the existing public information because existing information is already high quality and in multiple outlets. | \ No newline at end of file diff --git a/docs/reference/examples/report_credibility.md b/docs/reference/examples/report_credibility.md new file mode 100644 index 00000000..f1418767 --- /dev/null +++ b/docs/reference/examples/report_credibility.md @@ -0,0 +1,6 @@ +Is the report credible? + +| Value | Key | Description | +| --- | --- | --- | +| Credible | C | The report is credible. | +| Not Credible | NC | The report is not credible. | \ No newline at end of file diff --git a/docs/reference/examples/report_public.md b/docs/reference/examples/report_public.md new file mode 100644 index 00000000..e1e0fac0 --- /dev/null +++ b/docs/reference/examples/report_public.md @@ -0,0 +1,6 @@ +Is a viable report of the details of the vulnerability already publicly available? + +| Value | Key | Description | +| --- | --- | --- | +| No | N | No public report of the vulnerability exists. | +| Yes | Y | A public report of the vulnerability exists. | \ No newline at end of file diff --git a/docs/reference/examples/safety_impact.md b/docs/reference/examples/safety_impact.md new file mode 100644 index 00000000..17a429f5 --- /dev/null +++ b/docs/reference/examples/safety_impact.md @@ -0,0 +1,9 @@ +The safety impact of the vulnerability. + +| Value | Key | Description | +| --- | --- | --- | +| None | N | The effect is below the threshold for all aspects described in Minor. | +| Minor | M | Any one or more of these conditions hold. Physical harm: Physical discomfort for users (not operators) of the system. Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the vulnerability where operator actions would be well within expected operator abilities; OR causes a minor occupational safety hazard. System resiliency: Small reduction in built-in system safety margins; OR small reduction in system functional capabilities that support safe operation. Environment Minor externalities (property damage, environmental damage, etc.) imposed on other parties. Financial Financial losses, which are not readily absorbable, to multiple persons. Psychological: Emotional or psychological harm, sufficient to be cause for counselling or therapy, to multiple persons. | +| Major | J | Any one or more of these conditions hold. Physical harm: Physical distress and injuries for users (not operators) of the system. Operator resiliency: Requires action by system operator to maintain safe system state as a result of exploitation of the vulnerability where operator actions would be within their capabilities but the actions require their full attention and effort; OR significant distraction or discomfort to operators; OR causes significant occupational safety hazard. System resiliency: System safety margin effectively eliminated but no actual harm; OR failure of system functional capabilities that support safe operation. Environment: Major externalities (property damage, environmental damage, etc.) imposed on other parties. Financial: Financial losses that likely lead to bankruptcy of multiple persons. Psychological: Widespread emotional or psychological harm, sufficient to be cause for counselling or therapy, to populations of people. | +| Hazardous | H | Any one or more of these conditions hold. Physical harm: Serious or fatal injuries, where fatalities are plausibly preventable via emergency services or other measures. Operator resiliency: Actions that would keep the system in a safe state are beyond system operator capabilities, resulting in adverse conditions; OR great physical distress to system operators such that they cannot be expected to operate the system properly. System resiliency: Parts of the cyber-physical system break; system’s ability to recover lost functionality remains intact. Environment: Serious externalities (threat to life as well as property, widespread environmental damage, measurable public health risks, etc.) imposed on other parties. Financial: Socio-technical system (elections, financial grid, etc.) of which the affected component is a part is actively destabilized and enters unsafe state. Psychological: N/A. | +| Catastrophic | C | Any one or more of these conditions hold. Physical harm: Multiple immediate fatalities (Emergency response probably cannot save the victims.) Operator resiliency: Operator incapacitated (includes fatality or otherwise incapacitated). System resiliency: Total loss of whole cyber-physical system, of which the software is a part. Environment: Extreme externalities (immediate public health threat, environmental damage leading to small ecosystem collapse, etc.) imposed on other parties. Financial: Social systems (elections, financial grid, etc.) supported by the software collapse. Psychological: N/A. | \ No newline at end of file diff --git a/docs/reference/examples/supplier_cardinality.md b/docs/reference/examples/supplier_cardinality.md new file mode 100644 index 00000000..ee35bd40 --- /dev/null +++ b/docs/reference/examples/supplier_cardinality.md @@ -0,0 +1,6 @@ +How many suppliers are responsible for the vulnerable component and its remediation or mitigation plan? + +| Value | Key | Description | +| --- | --- | --- | +| One | O | There is only one supplier of the vulnerable component. | +| Multiple | M | There are multiple suppliers of the vulnerable component. | \ No newline at end of file diff --git a/docs/reference/examples/supplier_contacted.md b/docs/reference/examples/supplier_contacted.md new file mode 100644 index 00000000..ec465f09 --- /dev/null +++ b/docs/reference/examples/supplier_contacted.md @@ -0,0 +1,6 @@ +Has the reporter made a good-faith effort to contact the supplier of the vulnerable component using a quality contact method? + +| Value | Key | Description | +| --- | --- | --- | +| No | N | The supplier has not been contacted. | +| Yes | Y | The supplier has been contacted. | \ No newline at end of file diff --git a/docs/reference/examples/supplier_engagement.md b/docs/reference/examples/supplier_engagement.md new file mode 100644 index 00000000..611b33d8 --- /dev/null +++ b/docs/reference/examples/supplier_engagement.md @@ -0,0 +1,6 @@ +Is the supplier responding to the reporter’s contact effort and actively participating in the coordination effort? + +| Value | Key | Description | +| --- | --- | --- | +| Active | A | The supplier is responding to the reporter’s contact effort and actively participating in the coordination effort. | +| Unresponsive | U | The supplier is not responding to the reporter’s contact effort and not actively participating in the coordination effort. | \ No newline at end of file diff --git a/docs/reference/examples/supplier_involvement.md b/docs/reference/examples/supplier_involvement.md new file mode 100644 index 00000000..7738fcad --- /dev/null +++ b/docs/reference/examples/supplier_involvement.md @@ -0,0 +1,7 @@ +What is the state of the supplier’s work on addressing the vulnerability? + +| Value | Key | Description | +| --- | --- | --- | +| Fix Ready | FR | The supplier has provided a patch or fix. | +| Cooperative | C | The supplier is actively generating a patch or fix; they may or may not have provided a mitigation or work-around in the mean time. | +| Uncooperative/Unresponsive | UU | The supplier has not responded, declined to generate a remediation, or no longer exists. | \ No newline at end of file diff --git a/docs/reference/examples/system_exposure.md b/docs/reference/examples/system_exposure.md new file mode 100644 index 00000000..a899691b --- /dev/null +++ b/docs/reference/examples/system_exposure.md @@ -0,0 +1,7 @@ +The Accessible Attack Surface of the Affected System or Service + +| Value | Key | Description | +| --- | --- | --- | +| Small | S | Local service or program; highly controlled network | +| Controlled | C | Networked service with some access restrictions or mitigations already in place (whether locally or on the network). A successful mitigation must reliably interrupt the adversary’s attack, which requires the attack is detectable both reliably and quickly enough to respond. Controlled covers the situation in which a vulnerability can be exploited through chaining it with other vulnerabilities. The assumption is that the number of steps in the attack path is relatively low; if the path is long enough that it is implausible for an adversary to reliably execute it, then exposure should be small. | +| Open | O | Internet or another widely accessible network where access cannot plausibly be restricted or controlled (e.g., DNS servers, web servers, VOIP servers, email servers) | \ No newline at end of file diff --git a/docs/reference/examples/technical_impact.md b/docs/reference/examples/technical_impact.md new file mode 100644 index 00000000..38457e62 --- /dev/null +++ b/docs/reference/examples/technical_impact.md @@ -0,0 +1,6 @@ +The technical impact of the vulnerability. + +| Value | Key | Description | +| --- | --- | --- | +| Partial | P | The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control. | +| Total | T | The exploit gives the adversary total control over the behavior of the software, or it gives total disclosure of all information on the system that contains the vulnerability. | \ No newline at end of file diff --git a/docs/reference/examples/utility.md b/docs/reference/examples/utility.md new file mode 100644 index 00000000..dadc5a34 --- /dev/null +++ b/docs/reference/examples/utility.md @@ -0,0 +1,7 @@ +The Usefulness of the Exploit to the Adversary + +| Value | Key | Description | +| --- | --- | --- | +| Laborious | L | No to automatable and diffuse value | +| Efficient | E | Yes to automatable and diffuse value OR No to automatable and concentrated value | +| Super Effective | S | Yes to automatable and concentrated value | \ No newline at end of file diff --git a/docs/reference/examples/value_density.md b/docs/reference/examples/value_density.md new file mode 100644 index 00000000..da329e8d --- /dev/null +++ b/docs/reference/examples/value_density.md @@ -0,0 +1,6 @@ +The concentration of value in the target + +| Value | Key | Description | +| --- | --- | --- | +| Diffuse | D | The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively small. | +| Concentrated | C | The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of “system operators” rather than users. | \ No newline at end of file diff --git a/docs/reference/examples/virulence.md b/docs/reference/examples/virulence.md new file mode 100644 index 00000000..4d4f28ca --- /dev/null +++ b/docs/reference/examples/virulence.md @@ -0,0 +1,6 @@ +The speed at which the vulnerability can be exploited. + +| Value | Key | Description | +| --- | --- | --- | +| Slow | S | Steps 1-4 of the kill chain cannot be reliably automated for this vulnerability for some reason. These steps are reconnaissance, weaponization, delivery, and exploitation. Example reasons for why a step may not be reliably automatable include (1) the vulnerable component is not searchable or enumerable on the network, (2) weaponization may require human direction for each target, (3) delivery may require channels that widely deployed network security configurations block, and (3) exploitation may be frustrated by adequate exploit-prevention techniques enabled by default; ASLR is an example of an exploit-prevention tool. | +| Rapid | R | Steps 1-4 of the of the kill chain can be reliably automated. If the vulnerability allows remote code execution or command injection, the default response should be rapid. | \ No newline at end of file diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index b2793bbd..8b02a0ef 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -42,11 +42,15 @@ def to_table(self): rows.append("") headings = ["Value", "Key", "Description"] - rows.append("|".join(headings)) - rows.append("|".join(["---" for _ in headings])) + + def make_row(items): + return "| " + " | ".join(items) + " |" + + rows.append(make_row(headings)) + rows.append(make_row(["---" for _ in headings])) for value in self.values: - rows.append("|".join([value.name, value.key, value.description])) + rows.append(make_row([value.name, value.key, value.description])) return "\n".join(rows) From 1f85978adf4176af9e877cdabea5859d36dd5369 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 21 Sep 2023 16:11:13 -0400 Subject: [PATCH 24/53] undo aliases --- src/ssvc/dp_groups/coordinator_publication.py | 29 ++++++------ src/ssvc/dp_groups/coordinator_triage.py | 45 ++++++++++--------- src/ssvc/tools.py | 2 + 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/ssvc/dp_groups/coordinator_publication.py b/src/ssvc/dp_groups/coordinator_publication.py index 4eed0520..83991f32 100644 --- a/src/ssvc/dp_groups/coordinator_publication.py +++ b/src/ssvc/dp_groups/coordinator_publication.py @@ -1,28 +1,31 @@ #!/usr/bin/env python -''' +""" file: coordinator_publication author: adh created_at: 9/21/23 11:40 AM -''' -from ssvc.decision_points.exploitation import EXPLOITATION_1 as EXPLOITATION -from ssvc.decision_points.public_value_added import PUBLIC_VALUE_ADDED_1 as PUBLIC_VALUE_ADDED -from ssvc.decision_points.supplier_involvement import SUPPLIER_INVOLVEMENT_1 as SUPPLIER_INVOLVEMENT +""" +from ssvc.decision_points.exploitation import EXPLOITATION_1 +from ssvc.decision_points.public_value_added import PUBLIC_VALUE_ADDED_1 +from ssvc.decision_points.supplier_involvement import SUPPLIER_INVOLVEMENT_1 from ssvc.dp_groups.base import SsvcDecisionPointGroup -def main(): - pass - -if __name__=='__main__': - main() COORDINATOR_PUBLICATION_1 = SsvcDecisionPointGroup( name="Coordinator Publication", description="The decision points used by the coordinator during publication.", key="CP", version="1.0.0", decision_points=[ - SUPPLIER_INVOLVEMENT, - EXPLOITATION, - PUBLIC_VALUE_ADDED, + SUPPLIER_INVOLVEMENT_1, + EXPLOITATION_1, + PUBLIC_VALUE_ADDED_1, ], ) + + +def main(): + print(COORDINATOR_PUBLICATION_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/dp_groups/coordinator_triage.py b/src/ssvc/dp_groups/coordinator_triage.py index f4445616..578d6baa 100644 --- a/src/ssvc/dp_groups/coordinator_triage.py +++ b/src/ssvc/dp_groups/coordinator_triage.py @@ -1,36 +1,39 @@ #!/usr/bin/env python -''' +""" file: coordinator_triage author: adh created_at: 9/21/23 11:40 AM -''' -from ssvc.decision_points.public_safety_impact import PUBLIC_SAFETY_IMPACT_1 as PUBLIC_SAFETY_IMPACT -from ssvc.decision_points.report_credibility import REPORT_CREDIBILITY_1 as REPORT_CREDIBILITY -from ssvc.decision_points.report_public import REPORT_PUBLIC_1 as REPORT_PUBLIC -from ssvc.decision_points.supplier_cardinality import SUPPLIER_CARDINALITY_1 as SUPPLIER_CARDINALITY -from ssvc.decision_points.supplier_contacted import SUPPLIER_CONTACTED_1 as SUPPLIER_CONTACTED -from ssvc.decision_points.supplier_engagement import SUPPLIER_ENGAGEMENT_1 as SUPPLIER_ENGAGEMENT -from ssvc.decision_points.utility import UTILITY_1_0_1 as UTILITY +""" +from ssvc.decision_points.public_safety_impact import PUBLIC_SAFETY_IMPACT_1 +from ssvc.decision_points.report_credibility import REPORT_CREDIBILITY_1 +from ssvc.decision_points.report_public import REPORT_PUBLIC_1 +from ssvc.decision_points.supplier_cardinality import SUPPLIER_CARDINALITY_1 +from ssvc.decision_points.supplier_contacted import SUPPLIER_CONTACTED_1 +from ssvc.decision_points.supplier_engagement import SUPPLIER_ENGAGEMENT_1 +from ssvc.decision_points.utility import UTILITY_1_0_1 from ssvc.dp_groups.base import SsvcDecisionPointGroup -def main(): - pass - -if __name__=='__main__': - main() COORDINATOR_TRIAGE_1 = SsvcDecisionPointGroup( name="Coordinator Triage", description="The decision points used by the coordinator during triage.", key="CT", version="1.0.0", decision_points=[ - REPORT_PUBLIC, - SUPPLIER_CONTACTED, - REPORT_CREDIBILITY, - SUPPLIER_CARDINALITY, - SUPPLIER_ENGAGEMENT, - UTILITY, - PUBLIC_SAFETY_IMPACT, + REPORT_PUBLIC_1, + SUPPLIER_CONTACTED_1, + REPORT_CREDIBILITY_1, + SUPPLIER_CARDINALITY_1, + SUPPLIER_ENGAGEMENT_1, + UTILITY_1_0_1, + PUBLIC_SAFETY_IMPACT_1, ], ) + + +def main(): + print(COORDINATOR_TRIAGE_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/tools.py b/src/ssvc/tools.py index 2cd451d0..28c6c6f1 100644 --- a/src/ssvc/tools.py +++ b/src/ssvc/tools.py @@ -15,8 +15,10 @@ def group_to_jsonfiles(group: SsvcDecisionPointGroup, path: str = ".") -> None: with open(json_fname, "w") as f: print(f"Writing {json_fname}") f.write(dp.to_json(indent=2)) + f.write("\n") table_fname = f"{path}/{basename}.md" with open(table_fname, "w") as f: print(f"Writing {table_fname}") f.write(dp.to_table()) + f.write("\n") From 8fb72499b25dc2580e004db6fb2ffb8429259ba5 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Mon, 25 Sep 2023 10:28:25 -0400 Subject: [PATCH 25/53] make replace_in_list function --- src/ssvc/dp_groups/deployer.py | 12 +++++----- src/ssvc/dp_groups/supplier.py | 40 ++++++++++++++++------------------ src/ssvc/tools.py | 6 +++++ 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/ssvc/dp_groups/deployer.py b/src/ssvc/dp_groups/deployer.py index dbcdcfd9..84add0ce 100644 --- a/src/ssvc/dp_groups/deployer.py +++ b/src/ssvc/dp_groups/deployer.py @@ -18,7 +18,7 @@ from ssvc.decision_points.utility import UTILITY_1_0_1 from ssvc.decision_points.value_density import VALUE_DENSITY_1 from ssvc.dp_groups.base import SsvcDecisionPointGroup - +from ssvc.tools import replace_in_list PATCH_APPLIER_1 = SsvcDecisionPointGroup( name="SSVC Patch Applier", @@ -43,8 +43,9 @@ DEPLOYER_2.key = "D" DEPLOYER_2.version = "2.0.0" # update exposure -DEPLOYER_2.decision_points.remove(SYSTEM_EXPOSURE_1) -DEPLOYER_2.decision_points.append(SYSTEM_EXPOSURE_1_0_1) +replace_in_list( + DEPLOYER_2.decision_points, old=SYSTEM_EXPOSURE_1, new=SYSTEM_EXPOSURE_1_0_1 +) # add UTILITY (AUTOMATABLE + VALUE DENSITY) DEPLOYER_2.decision_points.append(UTILITY_1_0_1) DEPLOYER_2.decision_points.append(AUTOMATABLE_1) @@ -61,8 +62,9 @@ DEPLOYER_3.decision_points.remove(VALUE_DENSITY_1) # update MISSION_IMPACT_1 to MISSION_IMPACT_2 -DEPLOYER_3.decision_points.remove(MISSION_IMPACT_1) -DEPLOYER_3.decision_points.append(MISSION_IMPACT_2) +replace_in_list( + lst=DEPLOYER_3.decision_points, old=MISSION_IMPACT_1, new=MISSION_IMPACT_2 +) def main(): diff --git a/src/ssvc/dp_groups/supplier.py b/src/ssvc/dp_groups/supplier.py index 535b03cf..d9900812 100644 --- a/src/ssvc/dp_groups/supplier.py +++ b/src/ssvc/dp_groups/supplier.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -''' +""" file: supplier author: adh created_at: 9/21/23 11:41 AM -''' +""" from copy import deepcopy from ssvc.decision_points.automatable import AUTOMATABLE_1 @@ -15,22 +15,21 @@ from ssvc.decision_points.value_density import VALUE_DENSITY_1 from ssvc.decision_points.virulence import VIRULENCE_1 from ssvc.dp_groups.base import SsvcDecisionPointGroup - - +from ssvc.tools import replace_in_list PATCH_DEVELOPER_1 = SsvcDecisionPointGroup( - name="SSVC Patch Developer", - description="The decision points used by the patch developer.", - key="PD", - version="1.0.0", - decision_points=[ - EXPLOITATION_1, - UTILITY_1, - TECHNICAL_IMPACT_1, - VIRULENCE_1, - VALUE_DENSITY_1, - SAFETY_IMPACT_1, - ], + name="SSVC Patch Developer", + description="The decision points used by the patch developer.", + key="PD", + version="1.0.0", + decision_points=[ + EXPLOITATION_1, + UTILITY_1, + TECHNICAL_IMPACT_1, + VIRULENCE_1, + VALUE_DENSITY_1, + SAFETY_IMPACT_1, + ], ) # alias for forward compatibility @@ -38,12 +37,11 @@ # SSVC v2 renamed to SSVC Supplier SUPPLIER_2 = deepcopy(SUPPLIER_1) -SUPPLIER_2.name = "Supplier", -SUPPLIER_2.description = "The decision points used by the supplier.", +SUPPLIER_2.name = ("Supplier",) +SUPPLIER_2.description = ("The decision points used by the supplier.",) SUPPLIER_2.version = "2.0.0" # replace UTILITY 1 with UTILITY 1.0.1 -SUPPLIER_2.decision_points.remove(UTILITY_1) -SUPPLIER_2.decision_points.append(UTILITY_1_0_1) +replace_in_list(SUPPLIER_2.decision_points, old=UTILITY_1, new=UTILITY_1_0_1) # add PUBLIC_SAFETY_IMPACT_1 SUPPLIER_2.decision_points.append(PUBLIC_SAFETY_IMPACT_1) @@ -53,5 +51,5 @@ def main(): print(SUPPLIER_2.to_json(indent=2)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/ssvc/tools.py b/src/ssvc/tools.py index 28c6c6f1..ee949fb8 100644 --- a/src/ssvc/tools.py +++ b/src/ssvc/tools.py @@ -22,3 +22,9 @@ def group_to_jsonfiles(group: SsvcDecisionPointGroup, path: str = ".") -> None: print(f"Writing {table_fname}") f.write(dp.to_table()) f.write("\n") + + +def replace_in_list(lst, old, new): + idx = lst.index(old) + lst[idx] = new + return lst From 4a4bb47f16981ab9982e62825227af6cb74d6415 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Tue, 26 Sep 2023 09:51:05 -0400 Subject: [PATCH 26/53] add dirs to watch & change port --- mkdocs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index d4776ff9..feb03263 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -156,3 +156,7 @@ extra_javascript: - javascripts/tablesort.js extra_css: - stylesheets/extra.css +watch: + - docs + - src +dev_addr: 127.0.0.1:8001 \ No newline at end of file From d565ddb40a32580d291db0ba91898af1c884353d Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 27 Sep 2023 11:00:23 -0400 Subject: [PATCH 27/53] new versions rebuild from scratch, add docstrings --- src/ssvc/dp_groups/deployer.py | 91 +++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/src/ssvc/dp_groups/deployer.py b/src/ssvc/dp_groups/deployer.py index 84add0ce..86d38312 100644 --- a/src/ssvc/dp_groups/deployer.py +++ b/src/ssvc/dp_groups/deployer.py @@ -32,39 +32,84 @@ SAFETY_IMPACT_1, ], ) +""" +In SSVC v1, Patch Applier v1 represents the decision points used by the patch applier. +It includes decision points: + +- Exploitation v1.0.0 +- System Exposure v1.0.0 +- Mission Impact v1.0.0 +- Safety Impact v1.0.0. +""" + # alias for forward compatibility DEPLOYER_1 = PATCH_APPLIER_1 # SSVC v2 -DEPLOYER_2 = deepcopy(DEPLOYER_1) -# change name to SSVC Deployer -DEPLOYER_2.name = "SSVC Deployer" -DEPLOYER_2.key = "D" -DEPLOYER_2.version = "2.0.0" -# update exposure -replace_in_list( - DEPLOYER_2.decision_points, old=SYSTEM_EXPOSURE_1, new=SYSTEM_EXPOSURE_1_0_1 +DEPLOYER_2 = SsvcDecisionPointGroup( + name="SSVC Deployer", + description="The decision points used by the deployer.", + key="D", + version="2.0.0", + decision_points=[ + EXPLOITATION_1, + SYSTEM_EXPOSURE_1_0_1, + MISSION_IMPACT_1, + SAFETY_IMPACT_1, + UTILITY_1_0_1, + AUTOMATABLE_1, + VALUE_DENSITY_1, + HUMAN_IMPACT_1, + ], ) -# add UTILITY (AUTOMATABLE + VALUE DENSITY) -DEPLOYER_2.decision_points.append(UTILITY_1_0_1) -DEPLOYER_2.decision_points.append(AUTOMATABLE_1) -DEPLOYER_2.decision_points.append(VALUE_DENSITY_1) -# condense MISSION_IMPACT_1 and SAFETY_IMPACT_1 into HUMAN_IMPACT_1 -DEPLOYER_2.decision_points.append(HUMAN_IMPACT_1) +""" +Deployer v2.0.0 is renamed from Patch Applier v1.0.0. +It includes decision points: +- Exploitation v1.0.0 +- System Exposure v1.0.1 +- Human Impact v1.0.0 (consolidate Mission Impact v1.0.0 and Safety Impact v1.0.0) + - Safety Impact v1.0.0 + - Mission Impact v1.0.0 +- Utility v1.0.1 (consolidate Automatable v1.0.0 and Value Density v1.0.0) + - Automatable v1.0.0 + - Value Density v1.0.0 -# SSVC v2.1 -DEPLOYER_3 = deepcopy(DEPLOYER_2) -DEPLOYER_3.version = "3.0.0" -# replace UTILITY (AUTOMATABLE + VALUE DENSITY) with just AUTOMATABLE -DEPLOYER_3.decision_points.remove(UTILITY_1_0_1) -DEPLOYER_3.decision_points.remove(VALUE_DENSITY_1) +Changes from Patch Applier v1.0.0: +- System Exposure v1.0.0 -> v1.0.1 +- Utility v1.0.1 is added, which depends on Automatable v1.0.0 and Value Density v1.0.0 +- Human Impact v1.0.0 is added, which depends on Mission Impact v1.0.0 and Safety Impact v1.0.0 +""" -# update MISSION_IMPACT_1 to MISSION_IMPACT_2 -replace_in_list( - lst=DEPLOYER_3.decision_points, old=MISSION_IMPACT_1, new=MISSION_IMPACT_2 +DEPLOYER_3 = SsvcDecisionPointGroup( + name="SSVC Deployer", + description="The decision points used by the deployer.", + key="D", + version="3.0.0", + decision_points=[ + EXPLOITATION_1, + SYSTEM_EXPOSURE_1_0_1, + MISSION_IMPACT_2, + SAFETY_IMPACT_1, + AUTOMATABLE_1, + HUMAN_IMPACT_1, + ], ) +""" +In SSVC 2.1, Deployer 3.0.0 includes decision points: + +- Exploitation 1.0.0 +- System Exposure 1.0.1 +- Automatable 1.0.0 +- Human Impact 1.0.0 + - Safety Impact 1.0.0 + - Mission Impact 2.0.0 + +Changes from v2.0.0: +- removes Utility v1.0.1 in favor of Automatable v1.0.0. +- Mission Impact v1.0.0 -> v2.0.0 +""" def main(): From c5a8e60dfd0a742e69cc08f02afbaabe242dc164 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 27 Sep 2023 14:46:44 -0400 Subject: [PATCH 28/53] rebuild changes from scratch, add docstrings --- src/ssvc/dp_groups/supplier.py | 53 +++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/ssvc/dp_groups/supplier.py b/src/ssvc/dp_groups/supplier.py index d9900812..737ef979 100644 --- a/src/ssvc/dp_groups/supplier.py +++ b/src/ssvc/dp_groups/supplier.py @@ -31,19 +31,56 @@ SAFETY_IMPACT_1, ], ) +""" +In SSVC v1, Patch Developer v1 represents the decision points used by the patch developer. + +It includes decision points: + +- Exploitation v1.0.0 +- Utility v1.0.0 + - Virulence v1.0.0 + - Value Density v1.0.0 +- Technical Impact v1.0.0 +- Safety Impact v1.0.0 +""" # alias for forward compatibility SUPPLIER_1 = PATCH_DEVELOPER_1 # SSVC v2 renamed to SSVC Supplier -SUPPLIER_2 = deepcopy(SUPPLIER_1) -SUPPLIER_2.name = ("Supplier",) -SUPPLIER_2.description = ("The decision points used by the supplier.",) -SUPPLIER_2.version = "2.0.0" -# replace UTILITY 1 with UTILITY 1.0.1 -replace_in_list(SUPPLIER_2.decision_points, old=UTILITY_1, new=UTILITY_1_0_1) -# add PUBLIC_SAFETY_IMPACT_1 -SUPPLIER_2.decision_points.append(PUBLIC_SAFETY_IMPACT_1) +SUPPLIER_2 = SsvcDecisionPointGroup( + name="SSVC Supplier", + description="The decision points used by the supplier.", + key="S", + version="2.0.0", + decision_points=[ + EXPLOITATION_1, + UTILITY_1_0_1, + TECHNICAL_IMPACT_1, + AUTOMATABLE_1, + VALUE_DENSITY_1, + SAFETY_IMPACT_1, + ], +) +""" +In SSVC v2, Supplier v2 represents the decision points used by the supplier. +It includes decision points: + +- Exploitation v1.0.0 +- Utility v1.0.1 + - Automatable v1.0.0 + - Value Density v1.0.0 +- Technical Impact v1.0.0 +- Public Safety Impact v1.0.0 + - Safety Impact v1.0.0 + +Changes from Patch Developer v1: + +- Name change from Patch Developer v1 -> Supplier v2 +- Utility v1.0.0 -> v1.0.1 +- Virulence v1.0.0 replaced by Automatable v1.0.0 +- Public Safety Impact v1.0.0 added, which subsumes Safety Impact v1.0.0 +""" def main(): From e5ed54e2ff3774d85bf2894cb993b1f871d867c2 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 27 Sep 2023 14:51:01 -0400 Subject: [PATCH 29/53] add docstrings --- src/ssvc/dp_groups/coordinator_publication.py | 9 ++++++++ src/ssvc/dp_groups/coordinator_triage.py | 22 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/ssvc/dp_groups/coordinator_publication.py b/src/ssvc/dp_groups/coordinator_publication.py index 83991f32..40f6770f 100644 --- a/src/ssvc/dp_groups/coordinator_publication.py +++ b/src/ssvc/dp_groups/coordinator_publication.py @@ -21,6 +21,15 @@ PUBLIC_VALUE_ADDED_1, ], ) +""" +Added in SSVC v2, the Coordinator Publication v1.0.0 decision points are used by the coordinator during the publication process. + +It includes decision points: + +- Supplier Involvement v1.0.0 +- Exploitation v1.0.0 +- Public Value Added v1.0.0 +""" def main(): diff --git a/src/ssvc/dp_groups/coordinator_triage.py b/src/ssvc/dp_groups/coordinator_triage.py index 578d6baa..4d67d0bb 100644 --- a/src/ssvc/dp_groups/coordinator_triage.py +++ b/src/ssvc/dp_groups/coordinator_triage.py @@ -4,13 +4,16 @@ author: adh created_at: 9/21/23 11:40 AM """ +from ssvc.decision_points.automatable import AUTOMATABLE_1 from ssvc.decision_points.public_safety_impact import PUBLIC_SAFETY_IMPACT_1 from ssvc.decision_points.report_credibility import REPORT_CREDIBILITY_1 from ssvc.decision_points.report_public import REPORT_PUBLIC_1 +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 from ssvc.decision_points.supplier_cardinality import SUPPLIER_CARDINALITY_1 from ssvc.decision_points.supplier_contacted import SUPPLIER_CONTACTED_1 from ssvc.decision_points.supplier_engagement import SUPPLIER_ENGAGEMENT_1 from ssvc.decision_points.utility import UTILITY_1_0_1 +from ssvc.decision_points.value_density import VALUE_DENSITY_1 from ssvc.dp_groups.base import SsvcDecisionPointGroup @@ -26,9 +29,28 @@ SUPPLIER_CARDINALITY_1, SUPPLIER_ENGAGEMENT_1, UTILITY_1_0_1, + AUTOMATABLE_1, + VALUE_DENSITY_1, PUBLIC_SAFETY_IMPACT_1, + SAFETY_IMPACT_1, ], ) +""" +Added in SSVC v2, the Coordinator Triage v1.0.0 decision points are used by the coordinator during the intake and triage process. + +It includes decision points: + +- Report Public v1.0.0 +- Supplier Contacted v1.0.0 +- Report Credibility v1.0.0 +- Supplier Cardinality v1.0.0 +- Supplier Engagement v1.0.0 +- Utility v1.0.1, which depends on + - Value Density v1.0.0 + - Automatable v1.0.0 +- Public Safety Impact v1.0.0. which depends on + - Safety Impact v1.0.0 +""" def main(): From de90b327e321fdd1e3c2877a1e7bffd417df6839 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 27 Sep 2023 14:56:05 -0400 Subject: [PATCH 30/53] don't use deepcopy, rebuild from scratch instead --- src/ssvc/decision_points/system_exposure.py | 15 ++++++++++++--- src/ssvc/decision_points/utility.py | 17 ++++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/ssvc/decision_points/system_exposure.py b/src/ssvc/decision_points/system_exposure.py index ecd6a12a..70afad63 100644 --- a/src/ssvc/decision_points/system_exposure.py +++ b/src/ssvc/decision_points/system_exposure.py @@ -44,13 +44,22 @@ ), ) +# EXP_OPEN is just a rename of EXP_UNAVOIDABLE EXP_OPEN = deepcopy(EXP_UNAVOIDABLE) EXP_OPEN.name = "Open" EXP_OPEN.key = "O" -SYSTEM_EXPOSURE_1_0_1 = deepcopy(SYSTEM_EXPOSURE_1) -SYSTEM_EXPOSURE_1_0_1.version = "1.0.1" -SYSTEM_EXPOSURE_1_0_1.values = (EXP_SMALL, EXP_CONTROLLED, EXP_OPEN) +SYSTEM_EXPOSURE_1_0_1 = SsvcDecisionPoint( + name="System Exposure", + description="The Accessible Attack Surface of the Affected System or Service", + key="EXP", + version="1.0.1", + values=( + EXP_SMALL, + EXP_CONTROLLED, + EXP_OPEN, + ), +) def main(): diff --git a/src/ssvc/decision_points/utility.py b/src/ssvc/decision_points/utility.py index 08456a3b..c90f6fdd 100644 --- a/src/ssvc/decision_points/utility.py +++ b/src/ssvc/decision_points/utility.py @@ -52,13 +52,16 @@ ), ) -# SSVC v2 keeps same options but changes descriptions -UTILITY_1_0_1 = deepcopy(UTILITY_1) -UTILITY_1_0_1.version = "1.0.1" -UTILITY_1_0_1.values = ( - LABORIOUS_2, - EFFICIENT_2, - SUPER_EFFECTIVE_2, +UTILITY_1_0_1 = SsvcDecisionPoint( + name="Utility", + description="The Usefulness of the Exploit to the Adversary", + key="U", + version="1.0.1", + values=( + LABORIOUS_2, + EFFICIENT_2, + SUPER_EFFECTIVE_2, + ), ) From 291af9cd4f23f0807230219a300232dc44d5f76f Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 27 Sep 2023 15:02:05 -0400 Subject: [PATCH 31/53] we don't need replace in list anymore --- src/ssvc/dp_groups/deployer.py | 8 +++----- src/ssvc/dp_groups/supplier.py | 3 --- src/ssvc/tools.py | 6 ------ 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/ssvc/dp_groups/deployer.py b/src/ssvc/dp_groups/deployer.py index 86d38312..700d92dd 100644 --- a/src/ssvc/dp_groups/deployer.py +++ b/src/ssvc/dp_groups/deployer.py @@ -4,21 +4,19 @@ author: adh created_at: 9/21/23 11:40 AM """ -from copy import deepcopy from ssvc.decision_points.automatable import AUTOMATABLE_1 from ssvc.decision_points.exploitation import EXPLOITATION_1 +from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 +from ssvc.decision_points.mission_impact import MISSION_IMPACT_1, MISSION_IMPACT_2 +from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 from ssvc.decision_points.system_exposure import ( SYSTEM_EXPOSURE_1, SYSTEM_EXPOSURE_1_0_1, ) -from ssvc.decision_points.human_impact import HUMAN_IMPACT_1 -from ssvc.decision_points.mission_impact import MISSION_IMPACT_1, MISSION_IMPACT_2 -from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 from ssvc.decision_points.utility import UTILITY_1_0_1 from ssvc.decision_points.value_density import VALUE_DENSITY_1 from ssvc.dp_groups.base import SsvcDecisionPointGroup -from ssvc.tools import replace_in_list PATCH_APPLIER_1 = SsvcDecisionPointGroup( name="SSVC Patch Applier", diff --git a/src/ssvc/dp_groups/supplier.py b/src/ssvc/dp_groups/supplier.py index 737ef979..9ad8bd58 100644 --- a/src/ssvc/dp_groups/supplier.py +++ b/src/ssvc/dp_groups/supplier.py @@ -4,18 +4,15 @@ author: adh created_at: 9/21/23 11:41 AM """ -from copy import deepcopy from ssvc.decision_points.automatable import AUTOMATABLE_1 from ssvc.decision_points.exploitation import EXPLOITATION_1 -from ssvc.decision_points.public_safety_impact import PUBLIC_SAFETY_IMPACT_1 from ssvc.decision_points.safety_impact import SAFETY_IMPACT_1 from ssvc.decision_points.technical_impact import TECHNICAL_IMPACT_1 from ssvc.decision_points.utility import UTILITY_1, UTILITY_1_0_1 from ssvc.decision_points.value_density import VALUE_DENSITY_1 from ssvc.decision_points.virulence import VIRULENCE_1 from ssvc.dp_groups.base import SsvcDecisionPointGroup -from ssvc.tools import replace_in_list PATCH_DEVELOPER_1 = SsvcDecisionPointGroup( name="SSVC Patch Developer", diff --git a/src/ssvc/tools.py b/src/ssvc/tools.py index ee949fb8..28c6c6f1 100644 --- a/src/ssvc/tools.py +++ b/src/ssvc/tools.py @@ -22,9 +22,3 @@ def group_to_jsonfiles(group: SsvcDecisionPointGroup, path: str = ".") -> None: print(f"Writing {table_fname}") f.write(dp.to_table()) f.write("\n") - - -def replace_in_list(lst, old, new): - idx = lst.index(old) - lst[idx] = new - return lst From 1c24518c4ad013b0ef751974344ae04793fda0f0 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 27 Sep 2023 15:02:33 -0400 Subject: [PATCH 32/53] fix #319 make critical software decision point generic --- src/ssvc/decision_points/critical_software.py | 39 ++++++++++++++++++ src/ssvc/decision_points/nist/__init__.py | 14 ------- src/ssvc/decision_points/nist/eo_critical.py | 40 ------------------- src/ssvc/decision_points/ossf/__init__.py | 14 ------- src/ssvc/decision_points/ossf/critical.py | 39 ------------------ 5 files changed, 39 insertions(+), 107 deletions(-) create mode 100644 src/ssvc/decision_points/critical_software.py delete mode 100644 src/ssvc/decision_points/nist/__init__.py delete mode 100644 src/ssvc/decision_points/nist/eo_critical.py delete mode 100644 src/ssvc/decision_points/ossf/__init__.py delete mode 100644 src/ssvc/decision_points/ossf/critical.py diff --git a/src/ssvc/decision_points/critical_software.py b/src/ssvc/decision_points/critical_software.py new file mode 100644 index 00000000..d3bbde4e --- /dev/null +++ b/src/ssvc/decision_points/critical_software.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +""" +file: eo_critical +author: adh +created_at: 9/21/23 2:03 PM +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue + +YES = SsvcValue( + name="Yes", + key="Y", + description="System meets a critical software definition.", +) + +NO = SsvcValue( + name="No", + key="N", + description="System does not meet a critical software definition.", +) + +CRITICAL_SOFTWARE_1 = SsvcDecisionPoint( + name="Critical Software", + description="Denotes whether a system meets a critical software definition.", + namespace="SSVC", + key="CS", + version="1.0.0", + values=( + NO, + YES, + ), +) + + +def main(): + print(CRITICAL_SOFTWARE_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/decision_points/nist/__init__.py b/src/ssvc/decision_points/nist/__init__.py deleted file mode 100644 index 12e8438a..00000000 --- a/src/ssvc/decision_points/nist/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -""" -file: __init__.py -author: adh -created_at: 9/21/23 2:03 PM -""" - - -def main(): - pass - - -if __name__ == "__main__": - main() diff --git a/src/ssvc/decision_points/nist/eo_critical.py b/src/ssvc/decision_points/nist/eo_critical.py deleted file mode 100644 index 491649ba..00000000 --- a/src/ssvc/decision_points/nist/eo_critical.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -""" -file: eo_critical -author: adh -created_at: 9/21/23 2:03 PM -""" -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue - -YES = SsvcValue( - name="Yes", - key="Y", - description="System meets NIST's critical software definition.", -) - -NO = SsvcValue( - name="No", - key="N", - description="System does not meet NIST's critical software definition.", -) - -EO_CRITICAL = SsvcDecisionPoint( - name="EO Critical", - description="Denotes whether a system meets NIST's critical software definition. " - "Based on https://www.nist.gov/itl/executive-order-improving-nations-cybersecurity/critical-software-definition-explanatory", - namespace="nist", - key="EOCS", - version="1.0.0", - values=( - NO, - YES, - ), -) - - -def main(): - print(EO_CRITICAL.to_json(indent=2)) - - -if __name__ == "__main__": - main() diff --git a/src/ssvc/decision_points/ossf/__init__.py b/src/ssvc/decision_points/ossf/__init__.py deleted file mode 100644 index ef76b678..00000000 --- a/src/ssvc/decision_points/ossf/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -""" -file: __init__.py -author: adh -created_at: 9/21/23 2:08 PM -""" - - -def main(): - pass - - -if __name__ == "__main__": - main() diff --git a/src/ssvc/decision_points/ossf/critical.py b/src/ssvc/decision_points/ossf/critical.py deleted file mode 100644 index 859a9ad4..00000000 --- a/src/ssvc/decision_points/ossf/critical.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -""" -file: critical -author: adh -created_at: 9/21/23 2:09 PM -""" -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue - -NO = SsvcValue( - name="No", - key="N", - description="System does not meet the Open Source Software Foundation's critical software definition.", -) -YES = SsvcValue( - name="Yes", - key="Y", - description="System meets the Open Source Software Foundation's critical software definition.", -) - -OSSF_CRITICAL_SOFTWARE = SsvcDecisionPoint( - name="OSSF Critical Software", - description="Denotes whether a system meets the Open Source Software Foundation's critical software definition. " - "Based on https://openssf.org/wp-content/uploads/2021/07/OSSF-Criticality-Definition-1.0.pdf", - namespace="ossf", - key="OSSFCS", - version="1.0.0", - values=( - NO, - YES, - ), -) - - -def main(): - print(OSSF_CRITICAL_SOFTWARE.to_json(indent=2)) - - -if __name__ == "__main__": - main() From a7a7ee0f9f02f32dcc249876f572ff87ded4a2cb Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 27 Sep 2023 16:33:15 -0400 Subject: [PATCH 33/53] test mixins --- src/ssvc/_mixins.py | 16 ++-- src/test/__init__.py | 14 ++++ src/test/test_mixins.py | 157 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 src/test/__init__.py create mode 100644 src/test/test_mixins.py diff --git a/src/ssvc/_mixins.py b/src/ssvc/_mixins.py index 744a855f..de567635 100644 --- a/src/ssvc/_mixins.py +++ b/src/ssvc/_mixins.py @@ -9,14 +9,6 @@ from dataclasses_json import dataclass_json -def main(): - pass - - -if __name__ == "__main__": - main() - - @dataclass_json @dataclass(kw_only=True) class _Versioned: @@ -47,3 +39,11 @@ class _Base: name: str description: str key: str + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/test/__init__.py b/src/test/__init__.py new file mode 100644 index 00000000..5c4aa405 --- /dev/null +++ b/src/test/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +file: __init__.py +author: adh +created_at: 9/27/23 3:56 PM +""" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/test/test_mixins.py b/src/test/test_mixins.py new file mode 100644 index 00000000..b3d1c6e7 --- /dev/null +++ b/src/test/test_mixins.py @@ -0,0 +1,157 @@ +import unittest +from dataclasses import dataclass + +from dataclasses_json import dataclass_json + +from ssvc._mixins import _Base, _Versioned, _Namespaced + + +class TestMixins(unittest.TestCase): + def setUp(self) -> None: + self.obj = _Base(name="foo", key="bar", description="baz") + + def test_ssvc_base_bad_create(self): + # empty + self.assertRaises(TypeError, _Base) + # no name + self.assertRaises(TypeError, _Base, key="bar", description="baz") + # no key + self.assertRaises(TypeError, _Base, name="foo", description="baz") + # no description + self.assertRaises(TypeError, _Base, name="foo", key="bar") + + def test_ssvc_base_create(self): + obj = self.obj + self.assertEqual(obj.name, "foo") + self.assertEqual(obj.key, "bar") + self.assertEqual(obj.description, "baz") + + def test_json_roundtrip(self): + obj = self.obj + json = obj.to_json() + # is it a string? + self.assertIsInstance(json, str) + # does it look right? + self.assertEqual(json, '{"name": "foo", "description": "baz", "key": "bar"}') + + # modify the raw json string + json = json.replace("foo", "quux") + self.assertEqual(json, '{"name": "quux", "description": "baz", "key": "bar"}') + + # does it load? + obj2 = _Base.from_json(json) + self.assertEqual(obj2.name, "quux") + self.assertEqual(obj2.key, "bar") + self.assertEqual(obj2.description, "baz") + + def test_asdict_roundtrip(self): + from dataclasses import asdict + + obj = self.obj + d = asdict(obj) + + self.assertIsInstance(d, dict) + self.assertEqual(d["name"], "foo") + self.assertEqual(d["key"], "bar") + self.assertEqual(d["description"], "baz") + + # modify the dict + d["name"] = "quux" + + # does it load? + obj2 = _Base(**d) + self.assertEqual(obj2.name, "quux") + self.assertEqual(obj2.key, "bar") + self.assertEqual(obj2.description, "baz") + + def test_namespaced_create(self): + obj = _Namespaced() + self.assertEqual(obj.namespace, "ssvc") + + obj = _Namespaced(namespace="quux") + self.assertEqual(obj.namespace, "quux") + + def test_base_namespaced_mix(self): + # We need a dataclass to mix in with _Namespaced + @dataclass_json + @dataclass(kw_only=True) + class Foo(_Base, _Namespaced): + pass + + # empty + self.assertRaises(TypeError, Foo) + # no name + self.assertRaises(TypeError, Foo, key="bar", description="baz") + # no key + self.assertRaises(TypeError, Foo, name="foo", description="baz") + # no description + self.assertRaises(TypeError, Foo, name="foo", key="bar") + # no namespace (should default to ssvc) + obj = Foo(name="foo", key="bar", description="baz") + self.assertEqual(obj.namespace, "ssvc") + + # with namespace + obj = Foo(name="foo", key="bar", description="baz", namespace="quux") + self.assertEqual(obj.namespace, "quux") + + def test_versioned_create(self): + obj = _Versioned() + self.assertEqual(obj.version, "0.0.0") + + obj = _Versioned(version="1.2.3") + self.assertEqual(obj.version, "1.2.3") + + def test_base_versioned_mix(self): + # We need a dataclass to mix in with _Versioned + @dataclass_json + @dataclass(kw_only=True) + class Foo(_Base, _Versioned): + pass + + # empty + self.assertRaises(TypeError, Foo) + # no name + self.assertRaises(TypeError, Foo, key="bar", description="baz") + # no key + self.assertRaises(TypeError, Foo, name="foo", description="baz") + # no description + self.assertRaises(TypeError, Foo, name="foo", key="bar") + # no version (should default to 0.0.0) + obj = Foo(name="foo", key="bar", description="baz") + self.assertEqual(obj.version, "0.0.0") + + # with version + obj = Foo(name="foo", key="bar", description="baz", version="1.2.3") + self.assertEqual(obj.version, "1.2.3") + + def test_base_versioned_namespaced_mix(self): + # We need a dataclass to mix in with _Versioned and _Namespaced + @dataclass_json + @dataclass(kw_only=True) + class Foo(_Base, _Versioned, _Namespaced): + pass + + # empty + self.assertRaises(TypeError, Foo) + # no name + self.assertRaises(TypeError, Foo, key="bar", description="baz") + # no key + self.assertRaises(TypeError, Foo, name="foo", description="baz") + # no description + self.assertRaises(TypeError, Foo, name="foo", key="bar") + # no version (should default to 0.0.0) + obj = Foo(name="foo", key="bar", description="baz") + self.assertEqual(obj.version, "0.0.0") + # no namespace (should default to ssvc) + self.assertEqual(obj.namespace, "ssvc") + + # with version + obj = Foo( + name="foo", key="bar", description="baz", version="1.2.3", namespace="quux" + ) + self.assertEqual(obj.version, "1.2.3") + self.assertEqual(obj.namespace, "quux") + + +if __name__ == "__main__": + unittest.main() From a254e9c926385049162c1f9284072d9dfab5c451 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 28 Sep 2023 11:44:52 -0400 Subject: [PATCH 34/53] test base classes --- src/ssvc/decision_points/base.py | 2 +- src/test/test_dp_base.py | 61 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/test/test_dp_base.py diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index 8b02a0ef..6c9d6483 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -50,7 +50,7 @@ def make_row(items): rows.append(make_row(["---" for _ in headings])) for value in self.values: - rows.append(make_row([value.name, value.key, value.description])) + rows.append(makess_row([value.name, value.key, value.description])) return "\n".join(rows) diff --git a/src/test/test_dp_base.py b/src/test/test_dp_base.py new file mode 100644 index 00000000..f86c5cd0 --- /dev/null +++ b/src/test/test_dp_base.py @@ -0,0 +1,61 @@ +import unittest +import ssvc.decision_points.base as base + + +class MyTestCase(unittest.TestCase): + def setUp(self) -> None: + self.value = base.SsvcValue(name="foo", key="bar", description="baz") + + self.dp = base.SsvcDecisionPoint( + name="foo", + key="bar", + description="baz", + version="1.0.0", + namespace="ns", + values=(self.value,), + ) + + def test_ssvc_value(self): + obj = self.value + # should have name, key, description + self.assertEqual(obj.name, "foo") + self.assertEqual(obj.key, "bar") + self.assertEqual(obj.description, "baz") + + # should not have namespace, version + self.assertFalse(hasattr(obj, "namespace")) + self.assertFalse(hasattr(obj, "version")) + + def test_ssvc_decision_point(self): + obj = self.dp + # should have name, key, description, values, version, namespace + self.assertEqual(obj.name, "foo") + self.assertEqual(obj.key, "bar") + self.assertEqual(obj.description, "baz") + self.assertEqual(obj.version, "1.0.0") + self.assertEqual(obj.namespace, "ns") + self.assertEqual(len(obj.values), 1) + + def test_ssvc_value_json_roundtrip(self): + obj = self.value + + json = obj.to_json() + self.assertIsInstance(json, str) + self.assertGreater(len(json), 0) + + obj2 = base.SsvcValue.from_json(json) + self.assertEqual(obj, obj2) + + def test_ssvc_decision_point_json_roundtrip(self): + obj = self.dp + + json = obj.to_json() + self.assertIsInstance(json, str) + self.assertGreater(len(json), 0) + + obj2 = base.SsvcDecisionPoint.from_json(json) + self.assertEqual(obj, obj2) + + +if __name__ == "__main__": + unittest.main() From 57609f1a8b7f7c597576b2fb9cd48685b22fa6a9 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 4 Oct 2023 11:11:36 -0400 Subject: [PATCH 35/53] refactor out a _Keyed mixin --- src/ssvc/_mixins.py | 11 +- src/ssvc/decision_points/base.py | 6 +- src/test/test_mixins.py | 180 +++++++++++++++---------------- 3 files changed, 102 insertions(+), 95 deletions(-) diff --git a/src/ssvc/_mixins.py b/src/ssvc/_mixins.py index de567635..5460b8ea 100644 --- a/src/ssvc/_mixins.py +++ b/src/ssvc/_mixins.py @@ -29,6 +29,16 @@ class _Namespaced: namespace: str = "ssvc" +@dataclass_json +@dataclass(kw_only=True) +class _Keyed: + """ + Mixin class for keyed SSVC objects. + """ + + key: str + + @dataclass_json @dataclass(kw_only=True) class _Base: @@ -38,7 +48,6 @@ class _Base: name: str description: str - key: str def main(): diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index 6c9d6483..bb303685 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -10,7 +10,7 @@ from dataclasses_json import dataclass_json -from ssvc._mixins import _Base, _Namespaced, _Versioned +from ssvc._mixins import _Base, _Keyed, _Namespaced, _Versioned ## notes @@ -19,7 +19,7 @@ @dataclass_json @dataclass(kw_only=True) -class SsvcValue(_Base): +class SsvcValue(_Base, _Keyed): """ Models a single value option for a decision point. """ @@ -29,7 +29,7 @@ class SsvcValue(_Base): @dataclass_json @dataclass(kw_only=True) -class SsvcDecisionPoint(_Base, _Versioned, _Namespaced): +class SsvcDecisionPoint(_Base, _Keyed, _Versioned, _Namespaced): """ Models a single decision point as a list of values. """ diff --git a/src/test/test_mixins.py b/src/test/test_mixins.py index b3d1c6e7..53e7c517 100644 --- a/src/test/test_mixins.py +++ b/src/test/test_mixins.py @@ -3,28 +3,24 @@ from dataclasses_json import dataclass_json -from ssvc._mixins import _Base, _Versioned, _Namespaced +from ssvc._mixins import _Base, _Keyed, _Versioned, _Namespaced class TestMixins(unittest.TestCase): def setUp(self) -> None: - self.obj = _Base(name="foo", key="bar", description="baz") + self.obj = _Base(name="foo", description="baz") + + def test_ssvc_base_create(self): + obj = _Base(name="foo", description="baz") + self.assertEqual(obj.name, "foo") + self.assertEqual(obj.description, "baz") - def test_ssvc_base_bad_create(self): # empty self.assertRaises(TypeError, _Base) # no name - self.assertRaises(TypeError, _Base, key="bar", description="baz") - # no key - self.assertRaises(TypeError, _Base, name="foo", description="baz") + self.assertRaises(TypeError, _Base, description="baz") # no description - self.assertRaises(TypeError, _Base, name="foo", key="bar") - - def test_ssvc_base_create(self): - obj = self.obj - self.assertEqual(obj.name, "foo") - self.assertEqual(obj.key, "bar") - self.assertEqual(obj.description, "baz") + self.assertRaises(TypeError, _Base, name="foo") def test_json_roundtrip(self): obj = self.obj @@ -32,16 +28,15 @@ def test_json_roundtrip(self): # is it a string? self.assertIsInstance(json, str) # does it look right? - self.assertEqual(json, '{"name": "foo", "description": "baz", "key": "bar"}') + self.assertEqual(json, '{"name": "foo", "description": "baz"}') # modify the raw json string json = json.replace("foo", "quux") - self.assertEqual(json, '{"name": "quux", "description": "baz", "key": "bar"}') + self.assertEqual(json, '{"name": "quux", "description": "baz"}') # does it load? obj2 = _Base.from_json(json) self.assertEqual(obj2.name, "quux") - self.assertEqual(obj2.key, "bar") self.assertEqual(obj2.description, "baz") def test_asdict_roundtrip(self): @@ -52,7 +47,6 @@ def test_asdict_roundtrip(self): self.assertIsInstance(d, dict) self.assertEqual(d["name"], "foo") - self.assertEqual(d["key"], "bar") self.assertEqual(d["description"], "baz") # modify the dict @@ -61,7 +55,6 @@ def test_asdict_roundtrip(self): # does it load? obj2 = _Base(**d) self.assertEqual(obj2.name, "quux") - self.assertEqual(obj2.key, "bar") self.assertEqual(obj2.description, "baz") def test_namespaced_create(self): @@ -71,29 +64,6 @@ def test_namespaced_create(self): obj = _Namespaced(namespace="quux") self.assertEqual(obj.namespace, "quux") - def test_base_namespaced_mix(self): - # We need a dataclass to mix in with _Namespaced - @dataclass_json - @dataclass(kw_only=True) - class Foo(_Base, _Namespaced): - pass - - # empty - self.assertRaises(TypeError, Foo) - # no name - self.assertRaises(TypeError, Foo, key="bar", description="baz") - # no key - self.assertRaises(TypeError, Foo, name="foo", description="baz") - # no description - self.assertRaises(TypeError, Foo, name="foo", key="bar") - # no namespace (should default to ssvc) - obj = Foo(name="foo", key="bar", description="baz") - self.assertEqual(obj.namespace, "ssvc") - - # with namespace - obj = Foo(name="foo", key="bar", description="baz", namespace="quux") - self.assertEqual(obj.namespace, "quux") - def test_versioned_create(self): obj = _Versioned() self.assertEqual(obj.version, "0.0.0") @@ -101,56 +71,84 @@ def test_versioned_create(self): obj = _Versioned(version="1.2.3") self.assertEqual(obj.version, "1.2.3") - def test_base_versioned_mix(self): - # We need a dataclass to mix in with _Versioned - @dataclass_json - @dataclass(kw_only=True) - class Foo(_Base, _Versioned): - pass - - # empty - self.assertRaises(TypeError, Foo) - # no name - self.assertRaises(TypeError, Foo, key="bar", description="baz") - # no key - self.assertRaises(TypeError, Foo, name="foo", description="baz") - # no description - self.assertRaises(TypeError, Foo, name="foo", key="bar") - # no version (should default to 0.0.0) - obj = Foo(name="foo", key="bar", description="baz") - self.assertEqual(obj.version, "0.0.0") - - # with version - obj = Foo(name="foo", key="bar", description="baz", version="1.2.3") - self.assertEqual(obj.version, "1.2.3") - - def test_base_versioned_namespaced_mix(self): - # We need a dataclass to mix in with _Versioned and _Namespaced - @dataclass_json - @dataclass(kw_only=True) - class Foo(_Base, _Versioned, _Namespaced): - pass - - # empty - self.assertRaises(TypeError, Foo) - # no name - self.assertRaises(TypeError, Foo, key="bar", description="baz") - # no key - self.assertRaises(TypeError, Foo, name="foo", description="baz") - # no description - self.assertRaises(TypeError, Foo, name="foo", key="bar") - # no version (should default to 0.0.0) - obj = Foo(name="foo", key="bar", description="baz") - self.assertEqual(obj.version, "0.0.0") - # no namespace (should default to ssvc) - self.assertEqual(obj.namespace, "ssvc") - - # with version - obj = Foo( - name="foo", key="bar", description="baz", version="1.2.3", namespace="quux" - ) - self.assertEqual(obj.version, "1.2.3") - self.assertEqual(obj.namespace, "quux") + def test_keyed_create(self): + obj = _Keyed(key="foo") + self.assertEqual(obj.key, "foo") + + self.assertRaises(TypeError, _Keyed) + + def test_mixin_combos(self): + # We need to test all the combinations + mixins = [ + {"class": _Keyed, "args": {"key": "fizz"}, "has_default": False}, + {"class": _Namespaced, "args": {"namespace": "buzz"}, "has_default": True}, + {"class": _Versioned, "args": {"version": "1.2.3"}, "has_default": True}, + ] + keys_with_defaults = [x["args"].keys() for x in mixins if x["has_default"]] + # flatten the list + keys_with_defaults = [ + item for sublist in keys_with_defaults for item in sublist + ] + + import itertools + + max_len = len(mixins) + for i in range(max_len): + for combo in itertools.combinations(mixins, i + 1): + classes = [x["class"] for x in combo] + args = {k: v for x in combo for k, v in x["args"].items()} + + # create an object with the mixins + @dataclass_json + @dataclass(kw_only=True) + class Foo(_Base, *classes): + pass + + # make sure it breaks if we leave out a required arg + for k in args.keys(): + args_copy = args.copy() + del args_copy[k] + + if k in keys_with_defaults: + # expect success + obj = Foo(name="foo", description="baz", **args_copy) + # make sure the key is defaulted + self.assertEqual(getattr(Foo, k), getattr(obj, k)) + else: + self.assertRaises( + TypeError, Foo, name="foo", description="baz", **args_copy + ) + + # instantiate the object + obj = Foo(name="foo", description="baz", **args) + self.assertEqual(obj.name, "foo") + self.assertEqual(obj.description, "baz") + # make sure the args are set + for k, v in args.items(): + self.assertEqual(getattr(obj, k), v) + + # test json roundtrip + json = obj.to_json() + # is it a string? + self.assertIsInstance(json, str) + # does it look right? + self.assertIn('"name": "foo"', json) + self.assertIn('"description": "baz"', json) + for k, v in args.items(): + self.assertIn(f'"{k}": "{v}"', json) + # change the name and description + json = json.replace("foo", "quux") + json = json.replace("baz", "fizz") + # does it load? + obj2 = Foo.from_json(json) + self.assertEqual(obj2.name, "quux") + self.assertEqual(obj2.description, "fizz") + # make sure the args are set + for k, v in args.items(): + self.assertEqual(getattr(obj2, k), v) + # make sure unchanged attributes match from the original object + for k in args.keys(): + self.assertEqual(getattr(obj2, k), getattr(obj, k)) if __name__ == "__main__": From 63a3ca11ccf4e71cc10566d238620eaf89d64793 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 4 Oct 2023 11:12:48 -0400 Subject: [PATCH 36/53] drop key from dp_groups objects --- src/ssvc/dp_groups/coordinator_publication.py | 1 - src/ssvc/dp_groups/coordinator_triage.py | 1 - src/ssvc/dp_groups/cvss/v1.py | 1 - src/ssvc/dp_groups/cvss/v2.py | 1 - src/ssvc/dp_groups/cvss/v3.py | 1 - src/ssvc/dp_groups/deployer.py | 3 --- src/ssvc/dp_groups/supplier.py | 2 -- src/ssvc/dp_groups/v1.py | 1 - src/ssvc/dp_groups/v2.py | 1 - src/ssvc/dp_groups/v2_1.py | 1 - 10 files changed, 13 deletions(-) diff --git a/src/ssvc/dp_groups/coordinator_publication.py b/src/ssvc/dp_groups/coordinator_publication.py index 40f6770f..d73f6e0f 100644 --- a/src/ssvc/dp_groups/coordinator_publication.py +++ b/src/ssvc/dp_groups/coordinator_publication.py @@ -13,7 +13,6 @@ COORDINATOR_PUBLICATION_1 = SsvcDecisionPointGroup( name="Coordinator Publication", description="The decision points used by the coordinator during publication.", - key="CP", version="1.0.0", decision_points=[ SUPPLIER_INVOLVEMENT_1, diff --git a/src/ssvc/dp_groups/coordinator_triage.py b/src/ssvc/dp_groups/coordinator_triage.py index 4d67d0bb..d4e67964 100644 --- a/src/ssvc/dp_groups/coordinator_triage.py +++ b/src/ssvc/dp_groups/coordinator_triage.py @@ -20,7 +20,6 @@ COORDINATOR_TRIAGE_1 = SsvcDecisionPointGroup( name="Coordinator Triage", description="The decision points used by the coordinator during triage.", - key="CT", version="1.0.0", decision_points=[ REPORT_PUBLIC_1, diff --git a/src/ssvc/dp_groups/cvss/v1.py b/src/ssvc/dp_groups/cvss/v1.py index d2a11152..6f4d4e70 100644 --- a/src/ssvc/dp_groups/cvss/v1.py +++ b/src/ssvc/dp_groups/cvss/v1.py @@ -41,7 +41,6 @@ CVSSv1 = SsvcDecisionPointGroup( name="CVSS", version="1.0", - key="CVSSv1", description="CVSS v1 decision points", decision_points=[ ACCESS_VECTOR_1, diff --git a/src/ssvc/dp_groups/cvss/v2.py b/src/ssvc/dp_groups/cvss/v2.py index 1b2000b4..59f3543a 100644 --- a/src/ssvc/dp_groups/cvss/v2.py +++ b/src/ssvc/dp_groups/cvss/v2.py @@ -48,7 +48,6 @@ CVSSv2 = SsvcDecisionPointGroup( name="CVSS Version 2", description="The Common Vulnerability Scoring System (CVSS) is a free and open industry standard for assessing the severity of computer system security vulnerabilities. CVSS attempts to assign severity scores to vulnerabilities, allowing responders to prioritize responses and resources according to threat. Scores are calculated based on a formula that depends on several metrics that approximate ease of exploit and the impact of exploit. Scores range from 0 to 10, with 10 being the most severe.", - key="CVSSv2", version="2.0", decision_points=[ ACCESS_VECTOR_2, diff --git a/src/ssvc/dp_groups/cvss/v3.py b/src/ssvc/dp_groups/cvss/v3.py index 89cac571..105d7896 100644 --- a/src/ssvc/dp_groups/cvss/v3.py +++ b/src/ssvc/dp_groups/cvss/v3.py @@ -78,7 +78,6 @@ def _modify(obj): CVSSv3 = SsvcDecisionPointGroup( name="CVSS Version 3", description="The Common Vulnerability Scoring System (CVSS) is a free and open industry standard for assessing the severity of computer system security vulnerabilities. CVSS attempts to assign severity scores to vulnerabilities, allowing responders to prioritize responses and resources according to threat. Scores are calculated based on a formula that depends on several metrics that approximate ease of exploit and the impact of exploit. Scores range from 0 to 10, with 10 being the most severe.", - key="CVSSv3", version="3.0", decision_points=[ ATTACK_VECTOR_1, diff --git a/src/ssvc/dp_groups/deployer.py b/src/ssvc/dp_groups/deployer.py index 700d92dd..79e36d34 100644 --- a/src/ssvc/dp_groups/deployer.py +++ b/src/ssvc/dp_groups/deployer.py @@ -21,7 +21,6 @@ PATCH_APPLIER_1 = SsvcDecisionPointGroup( name="SSVC Patch Applier", description="The decision points used by the patch applier.", - key="PA", version="1.0.0", decision_points=[ EXPLOITATION_1, @@ -48,7 +47,6 @@ DEPLOYER_2 = SsvcDecisionPointGroup( name="SSVC Deployer", description="The decision points used by the deployer.", - key="D", version="2.0.0", decision_points=[ EXPLOITATION_1, @@ -83,7 +81,6 @@ DEPLOYER_3 = SsvcDecisionPointGroup( name="SSVC Deployer", description="The decision points used by the deployer.", - key="D", version="3.0.0", decision_points=[ EXPLOITATION_1, diff --git a/src/ssvc/dp_groups/supplier.py b/src/ssvc/dp_groups/supplier.py index 9ad8bd58..4bd68427 100644 --- a/src/ssvc/dp_groups/supplier.py +++ b/src/ssvc/dp_groups/supplier.py @@ -17,7 +17,6 @@ PATCH_DEVELOPER_1 = SsvcDecisionPointGroup( name="SSVC Patch Developer", description="The decision points used by the patch developer.", - key="PD", version="1.0.0", decision_points=[ EXPLOITATION_1, @@ -48,7 +47,6 @@ SUPPLIER_2 = SsvcDecisionPointGroup( name="SSVC Supplier", description="The decision points used by the supplier.", - key="S", version="2.0.0", decision_points=[ EXPLOITATION_1, diff --git a/src/ssvc/dp_groups/v1.py b/src/ssvc/dp_groups/v1.py index 8decae18..049b34db 100644 --- a/src/ssvc/dp_groups/v1.py +++ b/src/ssvc/dp_groups/v1.py @@ -15,7 +15,6 @@ SSVCv1 = SsvcDecisionPointGroup( name="SSVCv1", description="The first version of the SSVC.", - key="SSVCv1", version="1.0.0", decision_points=get_all_decision_points_from(GROUPS), ) diff --git a/src/ssvc/dp_groups/v2.py b/src/ssvc/dp_groups/v2.py index e47046ca..636217e0 100644 --- a/src/ssvc/dp_groups/v2.py +++ b/src/ssvc/dp_groups/v2.py @@ -19,7 +19,6 @@ SSVCv2 = SsvcDecisionPointGroup( name="SSVCv2", description="The second version of the SSVC.", - key="SSVCv2", version="2.0.0", decision_points=get_all_decision_points_from(GROUPS), ) diff --git a/src/ssvc/dp_groups/v2_1.py b/src/ssvc/dp_groups/v2_1.py index ad3d7303..f2409de5 100644 --- a/src/ssvc/dp_groups/v2_1.py +++ b/src/ssvc/dp_groups/v2_1.py @@ -19,7 +19,6 @@ SSVCv2_1 = SsvcDecisionPointGroup( name="SSVCv2.1", description="The second version of the SSVC.", - key="SSVCv2.1", version="2.1.0", decision_points=get_all_decision_points_from(GROUPS), ) From 99e7a4e54bc5d38aea62384de13bced3cc67bd93 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 4 Oct 2023 11:44:06 -0400 Subject: [PATCH 37/53] rename SsvcValue to SsvcDecisionPointValue to clarify what the object represents --- src/ssvc/decision_points/automatable.py | 6 ++--- src/ssvc/decision_points/base.py | 12 +++++----- src/ssvc/decision_points/critical_software.py | 6 ++--- .../decision_points/cvss/access_complexity.py | 12 +++++----- .../decision_points/cvss/access_vector.py | 12 +++++----- .../decision_points/cvss/attack_complexity.py | 6 ++--- .../decision_points/cvss/attack_vector.py | 10 ++++----- .../decision_points/cvss/authentication.py | 12 +++++----- .../cvss/availability_impact.py | 16 ++++++++------ .../cvss/availability_requirement.py | 10 ++++----- .../cvss/collateral_damage_potential.py | 18 +++++++-------- .../cvss/confidentiality_impact.py | 14 ++++++------ .../cvss/confidentiality_requirement.py | 10 ++++----- .../decision_points/cvss/exploitability.py | 22 +++++++++---------- src/ssvc/decision_points/cvss/impact_bias.py | 10 ++++----- .../decision_points/cvss/integrity_impact.py | 16 ++++++++------ .../cvss/integrity_requirement.py | 10 ++++----- .../cvss/privileges_required.py | 8 +++---- .../decision_points/cvss/remediation_level.py | 12 +++++----- .../decision_points/cvss/report_confidence.py | 16 +++++++------- src/ssvc/decision_points/cvss/scope.py | 6 ++--- .../cvss/target_distribution.py | 12 +++++----- .../decision_points/cvss/user_interaction.py | 6 ++--- src/ssvc/decision_points/exploitation.py | 8 +++---- src/ssvc/decision_points/human_impact.py | 10 ++++----- src/ssvc/decision_points/mission_impact.py | 16 ++++++++------ .../decision_points/public_safety_impact.py | 6 ++--- .../decision_points/public_value_added.py | 8 +++---- .../decision_points/report_credibility.py | 6 ++--- src/ssvc/decision_points/report_public.py | 6 ++--- src/ssvc/decision_points/safety_impact.py | 12 +++++----- .../decision_points/supplier_cardinality.py | 6 ++--- .../decision_points/supplier_contacted.py | 6 ++--- .../decision_points/supplier_engagement.py | 6 ++--- .../decision_points/supplier_involvement.py | 8 +++---- src/ssvc/decision_points/system_exposure.py | 8 +++---- src/ssvc/decision_points/technical_impact.py | 6 ++--- src/ssvc/decision_points/utility.py | 14 ++++++------ src/ssvc/decision_points/value_density.py | 6 ++--- src/ssvc/decision_points/virulence.py | 6 ++--- src/ssvc/dp_groups/cvss/v3.py | 6 +++-- src/test/test_dp_base.py | 6 +++-- 42 files changed, 212 insertions(+), 200 deletions(-) diff --git a/src/ssvc/decision_points/automatable.py b/src/ssvc/decision_points/automatable.py index 41ee35b9..4068acfe 100644 --- a/src/ssvc/decision_points/automatable.py +++ b/src/ssvc/decision_points/automatable.py @@ -4,16 +4,16 @@ author: adh created_at: 9/21/23 10:37 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -AUT_NO = SsvcValue( +AUT_NO = SsvcDecisionPointValue( name="No", key="N", description="Attackers cannot reliably automate steps 1-4 of the kill chain for this vulnerability. " "These steps are (1) reconnaissance, (2) weaponization, (3) delivery, and (4) exploitation.", ) -AUT_YES = SsvcValue( +AUT_YES = SsvcDecisionPointValue( name="Yes", key="Y", description="Attackers can reliably automate steps 1-4 of the kill chain.", diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index bb303685..25580482 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -19,7 +19,7 @@ @dataclass_json @dataclass(kw_only=True) -class SsvcValue(_Base, _Keyed): +class SsvcDecisionPointValue(_Base, _Keyed): """ Models a single value option for a decision point. """ @@ -34,7 +34,7 @@ class SsvcDecisionPoint(_Base, _Keyed, _Versioned, _Namespaced): Models a single decision point as a list of values. """ - values: Tuple[SsvcValue] + values: Tuple[SsvcDecisionPointValue] def to_table(self): rows = [] @@ -56,11 +56,13 @@ def make_row(items): def main(): - opt_none = SsvcValue(name="None", key="N", description="No exploit available") - opt_poc = SsvcValue( + opt_none = SsvcDecisionPointValue( + name="None", key="N", description="No exploit available" + ) + opt_poc = SsvcDecisionPointValue( name="PoC", key="P", description="Proof of concept exploit available" ) - opt_active = SsvcValue( + opt_active = SsvcDecisionPointValue( name="Active", key="A", description="Active exploitation observed" ) opts = [opt_none, opt_poc, opt_active] diff --git a/src/ssvc/decision_points/critical_software.py b/src/ssvc/decision_points/critical_software.py index d3bbde4e..2bab998d 100644 --- a/src/ssvc/decision_points/critical_software.py +++ b/src/ssvc/decision_points/critical_software.py @@ -4,15 +4,15 @@ author: adh created_at: 9/21/23 2:03 PM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -YES = SsvcValue( +YES = SsvcDecisionPointValue( name="Yes", key="Y", description="System meets a critical software definition.", ) -NO = SsvcValue( +NO = SsvcDecisionPointValue( name="No", key="N", description="System does not meet a critical software definition.", diff --git a/src/ssvc/decision_points/cvss/access_complexity.py b/src/ssvc/decision_points/cvss/access_complexity.py index 419d6cdb..e06cc3a5 100644 --- a/src/ssvc/decision_points/cvss/access_complexity.py +++ b/src/ssvc/decision_points/cvss/access_complexity.py @@ -6,31 +6,31 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue -HIGH_2 = SsvcValue( +HIGH_2 = SsvcDecisionPointValue( name="High", key="H", description="Specialized access conditions exist." ) -MEDIUM = SsvcValue( +MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="The access conditions are somewhat specialized.", ) -LOW_2 = SsvcValue( +LOW_2 = SsvcDecisionPointValue( name="Low", key="L", description="Specialized access conditions or extenuating circumstances do not exist.", ) -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="Specialized access conditions exist; for example: the system is exploitable during specific windows of time (a race condition), the system is exploitable under specific circumstances (nondefault configurations), or the system is exploitable with victim interaction (vulnerability exploitable only if user opens e-mail)", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="Specialized access conditions or extenuating circumstances do not exist; the system is always exploitable.", diff --git a/src/ssvc/decision_points/cvss/access_vector.py b/src/ssvc/decision_points/cvss/access_vector.py index 3f530575..28a7f12e 100644 --- a/src/ssvc/decision_points/cvss/access_vector.py +++ b/src/ssvc/decision_points/cvss/access_vector.py @@ -6,33 +6,33 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue -NETWORK = SsvcValue( +NETWORK = SsvcDecisionPointValue( name="Network", key="N", description="A vulnerability exploitable with network access means the vulnerable software is bound to the network stack and the attacker does not require local network access or local access. Such a vulnerability is often termed 'remotely exploitable'. An example of a network attack is an RPC buffer overflow.", ) -ADJACENT = SsvcValue( +ADJACENT = SsvcDecisionPointValue( name="Adjacent Network", key="A", description="A vulnerability exploitable with adjacent network access requires the attacker to have access to either the broadcast or collision domain of the vulnerable software. Examples of local networks include local IP subnet, Bluetooth, IEEE 802.11, and local Ethernet segment.", ) -LOCAL_2 = SsvcValue( +LOCAL_2 = SsvcDecisionPointValue( name="Local", key="L", description="A vulnerability exploitable with only local access requires the attacker to have either physical access to the vulnerable system or a local (shell) account. Examples of locally exploitable vulnerabilities are peripheral attacks such as Firewire/USB DMA attacks, and local privilege escalations (e.g., sudo).", ) -REMOTE = SsvcValue( +REMOTE = SsvcDecisionPointValue( name="Remote", key="R", description="The vulnerability is exploitable remotely.", ) -LOCAL = SsvcValue( +LOCAL = SsvcDecisionPointValue( name="Local", key="L", description="The vulnerability is only exploitable locally (i.e., it requires physical access or authenticated login to the target system)", diff --git a/src/ssvc/decision_points/cvss/attack_complexity.py b/src/ssvc/decision_points/cvss/attack_complexity.py index fab5dedd..4430fd00 100644 --- a/src/ssvc/decision_points/cvss/attack_complexity.py +++ b/src/ssvc/decision_points/cvss/attack_complexity.py @@ -5,16 +5,16 @@ created_at: 9/20/23 2:32 PM """ -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="A successful attack depends on conditions beyond the attacker's control.", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success against the vulnerable component.", diff --git a/src/ssvc/decision_points/cvss/attack_vector.py b/src/ssvc/decision_points/cvss/attack_vector.py index 6ede790d..88d4f06d 100644 --- a/src/ssvc/decision_points/cvss/attack_vector.py +++ b/src/ssvc/decision_points/cvss/attack_vector.py @@ -4,28 +4,28 @@ author: adh created_at: 9/20/23 2:26 PM """ -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NETWORK = SsvcValue( +NETWORK = SsvcDecisionPointValue( name="Network", key="N", description="A vulnerability exploitable with network access means the vulnerable component is bound to the network stack and the attacker's path is through OSI layer 3 (the network layer). Such a vulnerability is often termed 'remotely exploitable' and can be thought of as an attack being exploitable one or more network hops away (e.g. across layer 3 boundaries from routers).", ) -ADJACENT = SsvcValue( +ADJACENT = SsvcDecisionPointValue( name="Adjacent", key="A", description="A vulnerability exploitable with adjacent network access means the vulnerable component is bound to the network stack, however the attack is limited to the same shared physical (e.g. Bluetooth, IEEE 802.11), or logical (e.g. local IP subnet) network, and cannot be performed across an OSI layer 3 boundary (e.g. a router).", ) -LOCAL = SsvcValue( +LOCAL = SsvcDecisionPointValue( name="Local", key="L", description="A vulnerability exploitable with Local access means that the vulnerable component is not bound to the network stack, and the attacker's path is via read/write/execute capabilities. In some cases, the attacker may be logged in locally in order to exploit the vulnerability, otherwise, she may rely on User Interaction to execute a malicious file.", ) -PHYSICAL = SsvcValue( +PHYSICAL = SsvcDecisionPointValue( name="Physical", key="P", description="A vulnerability exploitable with Physical access requires the attacker to physically touch or manipulate the vulnerable component. Physical interaction may be brief (e.g. evil maid attack [1]) or persistent. An example of such an attack is a cold boot attack which allows an attacker to access to disk encryption keys after gaining physical access to the system, or peripheral attacks such as Firewire/USB Direct Memory Access attacks.", diff --git a/src/ssvc/decision_points/cvss/authentication.py b/src/ssvc/decision_points/cvss/authentication.py index 7550526e..a76c787f 100644 --- a/src/ssvc/decision_points/cvss/authentication.py +++ b/src/ssvc/decision_points/cvss/authentication.py @@ -6,34 +6,34 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue -AUTH_NONE = SsvcValue( +AUTH_NONE = SsvcDecisionPointValue( name="None", key="N", description="Authentication is not required to exploit the vulnerability.", ) -SINGLE = SsvcValue( +SINGLE = SsvcDecisionPointValue( name="Single", key="S", description="The vulnerability requires an attacker to be logged into the system (such as at a command line or via a desktop session or web interface).", ) -MULTIPLE = SsvcValue( +MULTIPLE = SsvcDecisionPointValue( name="Multiple", key="M", description="Exploiting the vulnerability requires that the attacker authenticate two or more times, even if the same credentials are used each time.", ) from ssvc.decision_points.cvss.base import CvssDecisionPoint -REQUIRED = SsvcValue( +REQUIRED = SsvcDecisionPointValue( name="Required", key="R", description="Authentication is required to access and exploit the vulnerability.", ) -NOT_REQUIRED = SsvcValue( +NOT_REQUIRED = SsvcDecisionPointValue( name="Not Required", key="N", description="Authentication is not required to access or exploit the vulnerability.", diff --git a/src/ssvc/decision_points/cvss/availability_impact.py b/src/ssvc/decision_points/cvss/availability_impact.py index 726877cd..9e0bcb20 100644 --- a/src/ssvc/decision_points/cvss/availability_impact.py +++ b/src/ssvc/decision_points/cvss/availability_impact.py @@ -6,7 +6,7 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint AVAILABILITY_IMPACT_1 = CvssDecisionPoint( @@ -15,13 +15,15 @@ key="A", version="1.0.0", values=[ - SsvcValue(name="None", key="N", description="No impact on availability."), - SsvcValue( + SsvcDecisionPointValue( + name="None", key="N", description="No impact on availability." + ), + SsvcDecisionPointValue( name="Partial", key="P", description="Considerable lag in or interruptions in resource availability. For example, a network-based flood attack that reduces available bandwidth to a web server farm to such an extent that only a small number of connections successfully complete.", ), - SsvcValue( + SsvcDecisionPointValue( name="Complete", key="C", description="Total shutdown of the affected resource. The attacker can render the resource completely unavailable.", @@ -33,17 +35,17 @@ AVAILABILITY_IMPACT_2.version = "2.0.0" AVAILABILITY_IMPACT_2.description = "This metric measures the impact to availability of a successfully exploited vulnerability." AVAILABILITY_IMPACT_2.values = [ - SsvcValue( + SsvcDecisionPointValue( name="None", key="N", description="There is no impact to the availability of the system.", ), - SsvcValue( + SsvcDecisionPointValue( name="Low", key="L", description="There is reduced performance or interruptions in resource availability.", ), - SsvcValue( + SsvcDecisionPointValue( name="High", key="H", description="There is total loss of availability, resulting in the attacker being able to fully deny access to resources in the impacted component; this loss is either sustained (while the attacker continues to deliver the attack) or persistent (the condition persists even after the attack has completed).", diff --git a/src/ssvc/decision_points/cvss/availability_requirement.py b/src/ssvc/decision_points/cvss/availability_requirement.py index 4059c368..887f2a2d 100644 --- a/src/ssvc/decision_points/cvss/availability_requirement.py +++ b/src/ssvc/decision_points/cvss/availability_requirement.py @@ -5,28 +5,28 @@ created_at: 9/20/23 2:11 PM """ -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcValue( +NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="Loss of availability is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -MEDIUM = SsvcValue( +MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="Loss of availability is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="Loss of availability is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", diff --git a/src/ssvc/decision_points/cvss/collateral_damage_potential.py b/src/ssvc/decision_points/cvss/collateral_damage_potential.py index f6a51d4d..2f5ce643 100644 --- a/src/ssvc/decision_points/cvss/collateral_damage_potential.py +++ b/src/ssvc/decision_points/cvss/collateral_damage_potential.py @@ -6,52 +6,52 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue -NOT_DEFINED = SsvcValue( +NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -MEDIUM_HIGH = SsvcValue( +MEDIUM_HIGH = SsvcDecisionPointValue( name="Medium-High", key="MH", description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", ) -LOW_MEDIUM = SsvcValue( +LOW_MEDIUM = SsvcDecisionPointValue( name="Low-Medium", key="LM", description="A successful exploit of this vulnerability may result in moderate physical or property damage or loss.", ) -CDP_NONE_2 = SsvcValue( +CDP_NONE_2 = SsvcDecisionPointValue( name="None", key="N", description="There is no potential for loss of life, physical assets, productivity or revenue.", ) -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="A successful exploit of this vulnerability may result in catastrophic physical or property damage and loss. The range of effect may be over a wide area.", ) -MEDIUM = SsvcValue( +MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="A successful exploit of this vulnerability may result in light physical or property damage or loss. The system itself may be damaged or destroyed.", ) from ssvc.decision_points.cvss.base import CvssDecisionPoint -CDP_NONE = SsvcValue( +CDP_NONE = SsvcDecisionPointValue( name="None", key="N", description="There is no potential for physical or property damage.", diff --git a/src/ssvc/decision_points/cvss/confidentiality_impact.py b/src/ssvc/decision_points/cvss/confidentiality_impact.py index dca1ab52..52aa897f 100644 --- a/src/ssvc/decision_points/cvss/confidentiality_impact.py +++ b/src/ssvc/decision_points/cvss/confidentiality_impact.py @@ -6,40 +6,40 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="There is total loss of confidentiality, resulting in all resources within the impacted component being divulged to the attacker. Alternatively, access to only some restricted information is obtained, but the disclosed information presents a direct, serious impact. For example, an attacker steals the administrator's password, or private encryption keys of a web server.", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="There is some loss of confidentiality. Access to some restricted information is obtained, but the attacker does not have control over what information is obtained, or the amount or kind of loss is constrained. The information disclosure does not cause a direct, serious loss to the impacted component.", ) -CI_NONE_2 = SsvcValue( +CI_NONE_2 = SsvcDecisionPointValue( name="None", key="N", description="There is no loss of confidentiality within the impacted component.", ) -COMPLETE = SsvcValue( +COMPLETE = SsvcDecisionPointValue( name="Complete", key="C", description="A total compromise of critical system information. A complete loss of system protection resulting in all critical system files being revealed. The attacker has sovereign control to read all of the system's data (memory, files, etc).", ) -PARTIAL = SsvcValue( +PARTIAL = SsvcDecisionPointValue( name="Partial", key="P", description="There is considerable informational disclosure. Access to critical system files is possible. There is a loss of important information, but the attacker doesn't have control over what is obtainable or the scope of the loss is constrained.", ) -CI_NONE = SsvcValue( +CI_NONE = SsvcDecisionPointValue( name="None", key="N", description="No impact on confidentiality.", diff --git a/src/ssvc/decision_points/cvss/confidentiality_requirement.py b/src/ssvc/decision_points/cvss/confidentiality_requirement.py index 841738c5..b329deab 100644 --- a/src/ssvc/decision_points/cvss/confidentiality_requirement.py +++ b/src/ssvc/decision_points/cvss/confidentiality_requirement.py @@ -5,28 +5,28 @@ created_at: 9/20/23 2:11 PM """ -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcValue( +NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="Loss of confidentiality is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -MEDIUM = SsvcValue( +MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="Loss of confidentiality is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="Loss of confidentiality is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", diff --git a/src/ssvc/decision_points/cvss/exploitability.py b/src/ssvc/decision_points/cvss/exploitability.py index 8a611016..9bb2913a 100644 --- a/src/ssvc/decision_points/cvss/exploitability.py +++ b/src/ssvc/decision_points/cvss/exploitability.py @@ -7,58 +7,58 @@ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcValue( +NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to a scoring equation to skip this metric.", ) -HIGH_2 = SsvcValue( +HIGH_2 = SsvcDecisionPointValue( name="High", key="H", description="Functional autonomous code exists, or no exploit is required (manual trigger) and details are widely available. Exploit code works in every situation, or is actively being delivered via an autonomous agent (such as a worm or virus). Network-connected systems are likely to encounter scanning or exploitation attempts. Exploit development has reached the level of reliable, widely-available, easy-to-use automated tools.", ) -FUNCTIONAL_2 = SsvcValue( +FUNCTIONAL_2 = SsvcDecisionPointValue( name="Functional", key="F", description="Functional exploit code is available. The code works in most situations where the vulnerability exists.", ) -PROOF_OF_CONCEPT_2 = SsvcValue( +PROOF_OF_CONCEPT_2 = SsvcDecisionPointValue( name="Proof-of-Concept", key="POC", description="Proof-of-concept exploit code is available, or an attack demonstration is not practical for most systems. The code or technique is not functional in all situations and may require substantial modification by a skilled attacker.", ) -UNPROVEN_2 = SsvcValue( +UNPROVEN_2 = SsvcDecisionPointValue( name="Unproven", key="U", description="No exploit code is available, or an exploit is theoretical.", ) -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="Either the vulnerability is exploitable by functional mobile autonomous code or no exploit is required (manual trigger) and the details for the manual technique are widely available. The code works in every situation where the vulnerability is exploitable and/or is actively being delivered via a mobile autonomous agent (a worm or virus).", ) -FUNCTIONAL = SsvcValue( +FUNCTIONAL = SsvcDecisionPointValue( name="Functional", key="F", description="Functional exploit code is available. The code works in most situations where the vulnerability is exploitable.", ) -PROOF_OF_CONCEPT = SsvcValue( +PROOF_OF_CONCEPT = SsvcDecisionPointValue( name="Proof of Concept", key="P", description="Proof of concept exploit code or an attack demonstration that is not practically applicable to deployed systems is available. The code or technique is not functional in all situations and may require substantial hand tuning by a skilled attacker for use against deployed systems.", ) -UNPROVEN = SsvcValue( +UNPROVEN = SsvcDecisionPointValue( name="Unproven", key="U", description="No exploit code is yet available or an exploit method is entirely theoretical.", @@ -81,7 +81,7 @@ # CVSS v2 introduced the concept of "Not Defined" values without changing the other values. EXPLOITABILITY_1_1 = deepcopy(EXPLOITABILITY_1) EXPLOITABILITY_1_1.version = "1.1.0" -nd = SsvcValue(name="Not Defined", key="ND", description="Not Defined") +nd = SsvcDecisionPointValue(name="Not Defined", key="ND", description="Not Defined") values = list(EXPLOITABILITY_1_1.values) values.append(nd) EXPLOITABILITY_1_1.values = tuple(values) diff --git a/src/ssvc/decision_points/cvss/impact_bias.py b/src/ssvc/decision_points/cvss/impact_bias.py index 385a94cf..05d50494 100644 --- a/src/ssvc/decision_points/cvss/impact_bias.py +++ b/src/ssvc/decision_points/cvss/impact_bias.py @@ -4,28 +4,28 @@ author: adh created_at: 9/20/23 1:47 PM """ -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -AVAILABILITY = SsvcValue( +AVAILABILITY = SsvcDecisionPointValue( name="Availability", key="A", description="Availability Impact is assigned greater weight than Confidentiality Impact or Integrity Impact.", ) -INTEGRITY = SsvcValue( +INTEGRITY = SsvcDecisionPointValue( name="Integrity", key="I", description="Integrity Impact is assigned greater weight than Confidentiality Impact or Availability Impact.", ) -CONFIDENTIALITY = SsvcValue( +CONFIDENTIALITY = SsvcDecisionPointValue( name="Confidentiality", key="C", description="Confidentiality impact is assigned greater weight than Integrity Impact or Availability Impact.", ) -NORMAL = SsvcValue( +NORMAL = SsvcDecisionPointValue( name="Normal", key="N", description="Confidentiality Impact, Integrity Impact, and Availability Impact are all assigned the same weight.", diff --git a/src/ssvc/decision_points/cvss/integrity_impact.py b/src/ssvc/decision_points/cvss/integrity_impact.py index bcee4ab9..618f4598 100644 --- a/src/ssvc/decision_points/cvss/integrity_impact.py +++ b/src/ssvc/decision_points/cvss/integrity_impact.py @@ -6,40 +6,42 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -II_HIGH = SsvcValue( +II_HIGH = SsvcDecisionPointValue( name="High", key="H", description="There is a total loss of integrity, or a complete loss of protection. For example, the attacker is able to modify any/all files protected by the impacted component. Alternatively, only some files can be modified, but malicious modification would present a direct, serious consequence to the impacted component.", ) -II_LOW = SsvcValue( +II_LOW = SsvcDecisionPointValue( name="Low", key="L", description="Modification of data is possible, but the attacker does not have control over the consequence of a modification, or the amount of modification is constrained. The data modification does not have a direct, serious impact on the impacted component.", ) -II_NONE_2 = SsvcValue( +II_NONE_2 = SsvcDecisionPointValue( name="None", key="N", description="There is no impact to the integrity of the system.", ) -COMPLETE = SsvcValue( +COMPLETE = SsvcDecisionPointValue( name="Complete", key="C", description="A total compromise of system integrity. There is a complete loss of system protection resulting in the entire system being compromised. The attacker has sovereign control to modify any system files.", ) -PARTIAL = SsvcValue( +PARTIAL = SsvcDecisionPointValue( name="Partial", key="P", description="Considerable breach in integrity. Modification of critical system files or information is possible, but the attacker does not have control over what can be modified, or the scope of what the attacker can affect is constrained. For example, key system or program files may be overwritten or modified, but at random or in a limited context or scope.", ) -II_NONE = SsvcValue(name="None", key="N", description="No impact on integrity.") +II_NONE = SsvcDecisionPointValue( + name="None", key="N", description="No impact on integrity." +) INTEGRITY_IMPACT_1 = CvssDecisionPoint( name="Integrity Impact", diff --git a/src/ssvc/decision_points/cvss/integrity_requirement.py b/src/ssvc/decision_points/cvss/integrity_requirement.py index 1a18e3cc..80e39b81 100644 --- a/src/ssvc/decision_points/cvss/integrity_requirement.py +++ b/src/ssvc/decision_points/cvss/integrity_requirement.py @@ -5,28 +5,28 @@ created_at: 9/20/23 2:11 PM """ -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcValue( +NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="Loss of integrity is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -MEDIUM = SsvcValue( +MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="Loss of integrity is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="Loss of integrity is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", diff --git a/src/ssvc/decision_points/cvss/privileges_required.py b/src/ssvc/decision_points/cvss/privileges_required.py index 7e85c706..ccf5fe27 100644 --- a/src/ssvc/decision_points/cvss/privileges_required.py +++ b/src/ssvc/decision_points/cvss/privileges_required.py @@ -4,22 +4,22 @@ author: adh created_at: 9/20/23 2:38 PM """ -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="The attacker is authorized with (i.e. requires) privileges that provide significant (e.g. administrative) control over the vulnerable component that could affect component-wide settings and files.", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="The attacker is authorized with (i.e. requires) privileges that provide basic user capabilities that could normally affect only settings and files owned by a user. Alternatively, an attacker with Low privileges may have the ability to cause an impact only to non-sensitive resources.", ) -PR_NONE = SsvcValue( +PR_NONE = SsvcDecisionPointValue( name="None", key="N", description="The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files to carry out an attack.", diff --git a/src/ssvc/decision_points/cvss/remediation_level.py b/src/ssvc/decision_points/cvss/remediation_level.py index 3f9de3b8..b214cbb9 100644 --- a/src/ssvc/decision_points/cvss/remediation_level.py +++ b/src/ssvc/decision_points/cvss/remediation_level.py @@ -6,34 +6,34 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcValue( +NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -UNAVAILABLE = SsvcValue( +UNAVAILABLE = SsvcDecisionPointValue( name="Unavailable", key="U", description="There is either no solution available or it is impossible to apply.", ) -WORKAROUND = SsvcValue( +WORKAROUND = SsvcDecisionPointValue( name="Workaround", key="W", description="There is an unofficial, non-vendor solution available. In some cases, users of the affected technology will create a patch of their own or provide steps to work around or otherwise mitigate against the vulnerability. When it is generally accepted that these unofficial fixes are adequate in plugging the hole for the mean time and no official remediation is available, this value can be set.", ) -TEMPORARY_FIX = SsvcValue( +TEMPORARY_FIX = SsvcDecisionPointValue( name="Temporary Fix", key="TF", description="There is an official but temporary fix available. This includes instances where the vendor issues a temporary hotfix, tool or official workaround.", ) -OFFICIAL_FIX = SsvcValue( +OFFICIAL_FIX = SsvcDecisionPointValue( name="Official Fix", key="OF", description="A complete vendor solution is available. Either the vendor has issued the final, official patch which eliminates the vulnerability or an upgrade that is not vulnerable is available.", diff --git a/src/ssvc/decision_points/cvss/report_confidence.py b/src/ssvc/decision_points/cvss/report_confidence.py index 7dfba80e..abde43e8 100644 --- a/src/ssvc/decision_points/cvss/report_confidence.py +++ b/src/ssvc/decision_points/cvss/report_confidence.py @@ -6,46 +6,46 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcValue( +NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -CONFIRMED_2 = SsvcValue( +CONFIRMED_2 = SsvcDecisionPointValue( name="Confirmed", key="C", description="Detailed reports exist, or functional reproduction is possible (functional exploits may provide this). Source code is available to independently verify the assertions of the research, or the author or vendor of the affected code has confirmed the presence of the vulnerability.", ) -REASONABLE = SsvcValue( +REASONABLE = SsvcDecisionPointValue( name="Reasonable", key="R", description="Significant details are published, but researchers either do not have full confidence in the root cause, or do not have access to source code to fully confirm all of the interactions that may lead to the result. Reasonable confidence exists, however, that the bug is reproducible and at least one impact is able to be verified (proof-of-concept exploits may provide this). An example is a detailed write-up of research into a vulnerability with an explanation (possibly obfuscated or 'left as an exercise to the reader') that gives assurances on how to reproduce the results.", ) -UNKNOWN = SsvcValue( +UNKNOWN = SsvcDecisionPointValue( name="Unknown", key="U", description="There are reports of impacts that indicate a vulnerability is present. The reports indicate that the cause of the vulnerability is unknown, or reports may differ on the cause or impacts of the vulnerability. Reporters are uncertain of the true nature of the vulnerability, and there is little confidence in the validity of the reports or whether a static Base score can be applied given the differences described. An example is a bug report which notes that an intermittent but non-reproducible crash occurs, with evidence of memory corruption suggesting that denial of service, or possible more serious impacts, may result.", ) -CONFIRMED = SsvcValue( +CONFIRMED = SsvcDecisionPointValue( name="Confirmed", key="C", description="Vendor or author of the affected technology has acknowledged that the vulnerability exists. This value may also be set when existence of a vulnerability is confirmed with absolute confidence through some other event, such as publication of functional proof of concept exploit code or widespread exploitation.", ) -UNCORROBORATED = SsvcValue( +UNCORROBORATED = SsvcDecisionPointValue( name="Uncorroborated", key="UR", description="Multiple non-official sources; possibily including independent security companies or research organizations. At this point there may be conflicting technical details or some other lingering ambiguity.", ) -UNCONFIRMED = SsvcValue( +UNCONFIRMED = SsvcDecisionPointValue( name="Unconfirmed", key="UC", description="A single unconfirmed source or possibly several conflicting reports. There is little confidence in the validity of the report. For example, a rumor that surfaces from the hacker underground.", diff --git a/src/ssvc/decision_points/cvss/scope.py b/src/ssvc/decision_points/cvss/scope.py index 962bed12..23bfc20a 100644 --- a/src/ssvc/decision_points/cvss/scope.py +++ b/src/ssvc/decision_points/cvss/scope.py @@ -5,16 +5,16 @@ created_at: 9/20/23 2:47 PM """ -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -CHANGED = SsvcValue( +CHANGED = SsvcDecisionPointValue( name="Changed", key="C", description="An exploited vulnerability can affect resources beyond the authorization privileges intended by the vulnerable component. In this case the vulnerable component and the impacted component are different.", ) -UNCHANGED = SsvcValue( +UNCHANGED = SsvcDecisionPointValue( name="Unchanged", key="U", description="An exploited vulnerability can only affect resources managed by the same authority. In this case the vulnerable component and the impacted component are the same.", diff --git a/src/ssvc/decision_points/cvss/target_distribution.py b/src/ssvc/decision_points/cvss/target_distribution.py index e0d265c6..2b428d90 100644 --- a/src/ssvc/decision_points/cvss/target_distribution.py +++ b/src/ssvc/decision_points/cvss/target_distribution.py @@ -6,34 +6,34 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcValue( +NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="This value is not defined in any specification, but is used in the CVSS v3.0 vector string when a value is required by the specification, but cannot be provided. This is a special case value and should be used sparingly.", ) -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="Targets exist inside the environment on a considerable scale. Between 50% - 100% of the total environment is considered at risk.", ) -MEDIUM = SsvcValue( +MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="Targets exist inside the environment, but on a medium scale. Between 16% - 49% of the total environment is at risk.", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="Targets exist inside the environment, but on a small scale. Between 1% - 15% of the total environment is at risk.", ) -TD_NONE = SsvcValue( +TD_NONE = SsvcDecisionPointValue( name="None", key="N", description="No target systems exist, or targets are so highly specialized that they only exist in a laboratory setting. Effectively 0% of the environment is at risk.", diff --git a/src/ssvc/decision_points/cvss/user_interaction.py b/src/ssvc/decision_points/cvss/user_interaction.py index 848eb75a..4fffadee 100644 --- a/src/ssvc/decision_points/cvss/user_interaction.py +++ b/src/ssvc/decision_points/cvss/user_interaction.py @@ -5,16 +5,16 @@ created_at: 9/20/23 2:38 PM """ -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -REQUIRED = SsvcValue( +REQUIRED = SsvcDecisionPointValue( name="Required", key="R", description="Successful exploitation of this vulnerability requires a user to take some action before the vulnerability can be exploited.", ) -UI_NONE = SsvcValue( +UI_NONE = SsvcDecisionPointValue( name="None", key="N", description="The vulnerable system can be exploited without interaction from any user.", diff --git a/src/ssvc/decision_points/exploitation.py b/src/ssvc/decision_points/exploitation.py index 2dad13af..050ea51e 100644 --- a/src/ssvc/decision_points/exploitation.py +++ b/src/ssvc/decision_points/exploitation.py @@ -4,16 +4,16 @@ author: adh created_at: 9/20/23 11:41 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -ACTIVE = SsvcValue( +ACTIVE = SsvcDecisionPointValue( name="Active", key="A", description="Shared, observable, reliable evidence that the exploit is being" " used in the wild by real attackers; there is credible public reporting.", ) -POC = SsvcValue( +POC = SsvcDecisionPointValue( name="PoC", key="P", description="One of the following cases is true: (1) private evidence of exploitation is attested but not shared; " @@ -21,7 +21,7 @@ " or ExploitDB; or (4) the vulnerability has a well-known method of exploitation.", ) -EXP_NONE = SsvcValue( +EXP_NONE = SsvcDecisionPointValue( name="None", key="N", description="There is no evidence of active exploitation and no public proof of concept (PoC) of how to exploit the vulnerability.", diff --git a/src/ssvc/decision_points/human_impact.py b/src/ssvc/decision_points/human_impact.py index 15034deb..355f524a 100644 --- a/src/ssvc/decision_points/human_impact.py +++ b/src/ssvc/decision_points/human_impact.py @@ -4,27 +4,27 @@ author: adh created_at: 9/21/23 10:49 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -VERY_HIGH = SsvcValue( +VERY_HIGH = SsvcDecisionPointValue( name="Very High", key="VH", description="Safety=Catastrophic OR Mission=Mission Failure", ) -HIGH = SsvcValue( +HIGH = SsvcDecisionPointValue( name="High", key="H", description="Safety=Hazardous, Mission=None/Degraded/Crippled/MEF Failure OR Safety=Major, Mission=MEF Failure", ) -MEDIUM = SsvcValue( +MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="Safety=None/Minor, Mission=MEF Failure OR Safety=Major, Mission=None/Degraded/Crippled", ) -LOW = SsvcValue( +LOW = SsvcDecisionPointValue( name="Low", key="L", description="Safety=None/Minor, Mission=None/Degraded/Crippled", diff --git a/src/ssvc/decision_points/mission_impact.py b/src/ssvc/decision_points/mission_impact.py index 60c8e3e2..d0f29808 100644 --- a/src/ssvc/decision_points/mission_impact.py +++ b/src/ssvc/decision_points/mission_impact.py @@ -6,38 +6,40 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -MISSION_FAILURE = SsvcValue( +MISSION_FAILURE = SsvcDecisionPointValue( name="Mission Failure", key="MF", description="Multiple or all mission essential functions fail; ability to recover those functions degraded; organization’s ability to deliver its overall mission fails", ) -MEF_FAILURE = SsvcValue( +MEF_FAILURE = SsvcDecisionPointValue( name="MEF Failure", key="MEF", description="Any one mission essential function fails for period of time longer than acceptable; overall mission of the organization degraded but can still be accomplished for a time", ) -MEF_CRIPPLED = SsvcValue( +MEF_CRIPPLED = SsvcDecisionPointValue( name="MEF Support Crippled", key="MSC", description="Activities that directly support essential functions are crippled; essential functions continue for a time", ) -MI_NED = SsvcValue( +MI_NED = SsvcDecisionPointValue( name="Non-Essential Degraded", key="NED", description="Degradation of non-essential functions; chronic degradation would eventually harm essential functions", ) -MI_NONE = SsvcValue(name="None", key="N", description="Little to no impact") +MI_NONE = SsvcDecisionPointValue( + name="None", key="N", description="Little to no impact" +) # combine MI_NONE and MI_NED into a single value -DEGRADED = SsvcValue( +DEGRADED = SsvcDecisionPointValue( name="Degraded", key="D", description="Little to no impact up to degradation of non-essential functions; chronic degradation would eventually harm essential functions", diff --git a/src/ssvc/decision_points/public_safety_impact.py b/src/ssvc/decision_points/public_safety_impact.py index 6f2973fd..6a1b6104 100644 --- a/src/ssvc/decision_points/public_safety_impact.py +++ b/src/ssvc/decision_points/public_safety_impact.py @@ -4,15 +4,15 @@ author: adh created_at: 9/21/23 10:43 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -SIGNIFICANT = SsvcValue( +SIGNIFICANT = SsvcDecisionPointValue( name="Significant", description="Safety impact of Major, Hazardous, or Catastrophic.", key="S", ) -MINIMAL = SsvcValue( +MINIMAL = SsvcDecisionPointValue( name="Minimal", description="Safety impact of None or Minor.", key="M" ) diff --git a/src/ssvc/decision_points/public_value_added.py b/src/ssvc/decision_points/public_value_added.py index 6800e28a..31385d57 100644 --- a/src/ssvc/decision_points/public_value_added.py +++ b/src/ssvc/decision_points/public_value_added.py @@ -5,21 +5,21 @@ created_at: 9/21/23 11:27 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -LIMITED = SsvcValue( +LIMITED = SsvcDecisionPointValue( name="Limited", key="L", description="Minimal value added to the existing public information because existing information is already high quality and in multiple outlets.", ) -AMPLIATIVE = SsvcValue( +AMPLIATIVE = SsvcDecisionPointValue( name="Ampliative", key="A", description="Amplifies and/or augments the existing public information about the vulnerability, for example, adds additional detail, addresses or corrects errors in other public information, draws further attention to the vulnerability, etc.", ) -PRECEDENCE = SsvcValue( +PRECEDENCE = SsvcDecisionPointValue( name="Precedence", key="P", description="The publication would be the first publicly available, or be coincident with the first publicly available.", diff --git a/src/ssvc/decision_points/report_credibility.py b/src/ssvc/decision_points/report_credibility.py index 85482acc..38722be5 100644 --- a/src/ssvc/decision_points/report_credibility.py +++ b/src/ssvc/decision_points/report_credibility.py @@ -5,15 +5,15 @@ created_at: 9/21/23 11:24 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -NOT_CREDIBLE = SsvcValue( +NOT_CREDIBLE = SsvcDecisionPointValue( name="Not Credible", key="NC", description="The report is not credible.", ) -CREDIBLE = SsvcValue( +CREDIBLE = SsvcDecisionPointValue( name="Credible", key="C", description="The report is credible.", diff --git a/src/ssvc/decision_points/report_public.py b/src/ssvc/decision_points/report_public.py index 0031484b..4141ae89 100644 --- a/src/ssvc/decision_points/report_public.py +++ b/src/ssvc/decision_points/report_public.py @@ -4,15 +4,15 @@ author: adh created_at: 9/21/23 11:15 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -YES = SsvcValue( +YES = SsvcDecisionPointValue( name="Yes", key="Y", description="A public report of the vulnerability exists.", ) -NO = SsvcValue( +NO = SsvcDecisionPointValue( name="No", key="N", description="No public report of the vulnerability exists.", diff --git a/src/ssvc/decision_points/safety_impact.py b/src/ssvc/decision_points/safety_impact.py index b65d32fd..42466d05 100644 --- a/src/ssvc/decision_points/safety_impact.py +++ b/src/ssvc/decision_points/safety_impact.py @@ -6,9 +6,9 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -CATASTROPHIC = SsvcValue( +CATASTROPHIC = SsvcDecisionPointValue( name="Catastrophic", key="C", description="Any one or more of these conditions hold. " @@ -20,7 +20,7 @@ "Psychological: N/A.", ) -HAZARDOUS = SsvcValue( +HAZARDOUS = SsvcDecisionPointValue( name="Hazardous", key="H", description="Any one or more of these conditions hold. " @@ -32,7 +32,7 @@ "Psychological: N/A.", ) -MAJOR = SsvcValue( +MAJOR = SsvcDecisionPointValue( name="Major", key="J", description="Any one or more of these conditions hold. " @@ -45,7 +45,7 @@ "Psychological: Widespread emotional or psychological harm, sufficient to be cause for counselling or therapy, to populations of people.", ) -MINOR = SsvcValue( +MINOR = SsvcDecisionPointValue( name="Minor", key="M", description="Any one or more of these conditions hold. " @@ -58,7 +58,7 @@ "Psychological: Emotional or psychological harm, sufficient to be cause for counselling or therapy, to multiple persons.", ) -SAF_NONE = SsvcValue( +SAF_NONE = SsvcDecisionPointValue( name="None", key="N", description="The effect is below the threshold for all aspects described in Minor.", diff --git a/src/ssvc/decision_points/supplier_cardinality.py b/src/ssvc/decision_points/supplier_cardinality.py index 669a9ced..ebde9d27 100644 --- a/src/ssvc/decision_points/supplier_cardinality.py +++ b/src/ssvc/decision_points/supplier_cardinality.py @@ -4,15 +4,15 @@ author: adh created_at: 9/21/23 11:20 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -MULTIPLE = SsvcValue( +MULTIPLE = SsvcDecisionPointValue( name="Multiple", key="M", description="There are multiple suppliers of the vulnerable component.", ) -ONE = SsvcValue( +ONE = SsvcDecisionPointValue( name="One", key="O", description="There is only one supplier of the vulnerable component.", diff --git a/src/ssvc/decision_points/supplier_contacted.py b/src/ssvc/decision_points/supplier_contacted.py index 68970527..eff08419 100644 --- a/src/ssvc/decision_points/supplier_contacted.py +++ b/src/ssvc/decision_points/supplier_contacted.py @@ -4,15 +4,15 @@ author: adh created_at: 9/21/23 11:17 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -YES = SsvcValue( +YES = SsvcDecisionPointValue( name="Yes", key="Y", description="The supplier has been contacted.", ) -NO = SsvcValue( +NO = SsvcDecisionPointValue( name="No", key="N", description="The supplier has not been contacted.", diff --git a/src/ssvc/decision_points/supplier_engagement.py b/src/ssvc/decision_points/supplier_engagement.py index 00f589ec..69380931 100644 --- a/src/ssvc/decision_points/supplier_engagement.py +++ b/src/ssvc/decision_points/supplier_engagement.py @@ -5,15 +5,15 @@ created_at: 9/21/23 11:22 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -UNRESPONSIVE = SsvcValue( +UNRESPONSIVE = SsvcDecisionPointValue( name="Unresponsive", key="U", description="The supplier is not responding to the reporter’s contact effort and not actively participating in the coordination effort.", ) -ACTIVE = SsvcValue( +ACTIVE = SsvcDecisionPointValue( name="Active", key="A", description="The supplier is responding to the reporter’s contact effort and actively participating in the coordination effort.", diff --git a/src/ssvc/decision_points/supplier_involvement.py b/src/ssvc/decision_points/supplier_involvement.py index f3ec33b7..afc3ce07 100644 --- a/src/ssvc/decision_points/supplier_involvement.py +++ b/src/ssvc/decision_points/supplier_involvement.py @@ -5,21 +5,21 @@ created_at: 9/21/23 11:28 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -UNCOOPERATIVE = SsvcValue( +UNCOOPERATIVE = SsvcDecisionPointValue( name="Uncooperative/Unresponsive", key="UU", description="The supplier has not responded, declined to generate a remediation, or no longer exists.", ) -COOPERATIVE = SsvcValue( +COOPERATIVE = SsvcDecisionPointValue( name="Cooperative", key="C", description="The supplier is actively generating a patch or fix; they may or may not have provided a mitigation or work-around in the mean time.", ) -FIX_READY = SsvcValue( +FIX_READY = SsvcDecisionPointValue( name="Fix Ready", key="FR", description="The supplier has provided a patch or fix.", diff --git a/src/ssvc/decision_points/system_exposure.py b/src/ssvc/decision_points/system_exposure.py index 70afad63..c5a1978b 100644 --- a/src/ssvc/decision_points/system_exposure.py +++ b/src/ssvc/decision_points/system_exposure.py @@ -5,16 +5,16 @@ created_at: 9/21/23 10:16 AM """ from copy import deepcopy -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -EXP_UNAVOIDABLE = SsvcValue( +EXP_UNAVOIDABLE = SsvcDecisionPointValue( name="Unavoidable", key="U", description="Internet or another widely accessible network where access cannot plausibly be restricted or " "controlled (e.g., DNS servers, web servers, VOIP servers, email servers)", ) -EXP_CONTROLLED = SsvcValue( +EXP_CONTROLLED = SsvcDecisionPointValue( name="Controlled", key="C", description="Networked service with some access restrictions or mitigations already in place (whether locally or on the network). " @@ -25,7 +25,7 @@ "execute it, then exposure should be small.", ) -EXP_SMALL = SsvcValue( +EXP_SMALL = SsvcDecisionPointValue( name="Small", key="S", description="Local service or program; highly controlled network", diff --git a/src/ssvc/decision_points/technical_impact.py b/src/ssvc/decision_points/technical_impact.py index f30f8937..da042f62 100644 --- a/src/ssvc/decision_points/technical_impact.py +++ b/src/ssvc/decision_points/technical_impact.py @@ -4,15 +4,15 @@ author: adh created_at: 9/21/23 9:49 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -TOTAL = SsvcValue( +TOTAL = SsvcDecisionPointValue( name="Total", key="T", description="The exploit gives the adversary total control over the behavior of the software, or it gives total disclosure of all information on the system that contains the vulnerability.", ) -PARTIAL = SsvcValue( +PARTIAL = SsvcDecisionPointValue( name="Partial", key="P", description="The exploit gives the adversary limited control over, or information exposure about, the behavior of the software that contains the vulnerability. Or the exploit gives the adversary an importantly low stochastic opportunity for total control.", diff --git a/src/ssvc/decision_points/utility.py b/src/ssvc/decision_points/utility.py index c90f6fdd..eeb3c591 100644 --- a/src/ssvc/decision_points/utility.py +++ b/src/ssvc/decision_points/utility.py @@ -6,37 +6,37 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -SUPER_EFFECTIVE_2 = SsvcValue( +SUPER_EFFECTIVE_2 = SsvcDecisionPointValue( name="Super Effective", key="S", description="Yes to automatable and concentrated value", ) -EFFICIENT_2 = SsvcValue( +EFFICIENT_2 = SsvcDecisionPointValue( name="Efficient", key="E", description="Yes to automatable and diffuse value OR No to automatable and concentrated value", ) -LABORIOUS_2 = SsvcValue( +LABORIOUS_2 = SsvcDecisionPointValue( name="Laborious", key="L", description="No to automatable and diffuse value" ) -SUPER_EFFECTIVE = SsvcValue( +SUPER_EFFECTIVE = SsvcDecisionPointValue( name="Super Effective", key="S", description="Rapid virulence and concentrated value", ) -EFFICIENT = SsvcValue( +EFFICIENT = SsvcDecisionPointValue( name="Efficient", key="E", description="Rapid virulence and diffuse value OR Slow virulence and concentrated value", ) -LABORIOUS = SsvcValue( +LABORIOUS = SsvcDecisionPointValue( name="Laborious", key="L", description="Slow virulence and diffuse value" ) diff --git a/src/ssvc/decision_points/value_density.py b/src/ssvc/decision_points/value_density.py index afcf6427..eac48a13 100644 --- a/src/ssvc/decision_points/value_density.py +++ b/src/ssvc/decision_points/value_density.py @@ -4,15 +4,15 @@ author: adh created_at: 9/21/23 10:01 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -CONCENTRATED = SsvcValue( +CONCENTRATED = SsvcDecisionPointValue( name="Concentrated", key="C", description="The system that contains the vulnerable component is rich in resources. Heuristically, such systems are often the direct responsibility of “system operators” rather than users.", ) -DIFFUSE = SsvcValue( +DIFFUSE = SsvcDecisionPointValue( name="Diffuse", key="D", description="The system that contains the vulnerable component has limited resources. That is, the resources that the adversary will gain control over with a single exploitation event are relatively small.", diff --git a/src/ssvc/decision_points/virulence.py b/src/ssvc/decision_points/virulence.py index d5400d32..289263b0 100644 --- a/src/ssvc/decision_points/virulence.py +++ b/src/ssvc/decision_points/virulence.py @@ -4,15 +4,15 @@ author: adh created_at: 9/21/23 9:58 AM """ -from ssvc.decision_points.base import SsvcDecisionPoint, SsvcValue +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue -RAPID = SsvcValue( +RAPID = SsvcDecisionPointValue( name="Rapid", key="R", description="Steps 1-4 of the of the kill chain can be reliably automated. If the vulnerability allows remote code execution or command injection, the default response should be rapid.", ) -SLOW = SsvcValue( +SLOW = SsvcDecisionPointValue( name="Slow", key="S", description="Steps 1-4 of the kill chain cannot be reliably automated for this vulnerability for some reason. These steps are reconnaissance, weaponization, delivery, and exploitation. Example reasons for why a step may not be reliably automatable include (1) the vulnerable component is not searchable or enumerable on the network, (2) weaponization may require human direction for each target, (3) delivery may require channels that widely deployed network security configurations block, and (3) exploitation may be frustrated by adequate exploit-prevention techniques enabled by default; ASLR is an example of an exploit-prevention tool.", diff --git a/src/ssvc/dp_groups/cvss/v3.py b/src/ssvc/dp_groups/cvss/v3.py index 105d7896..16236dd3 100644 --- a/src/ssvc/dp_groups/cvss/v3.py +++ b/src/ssvc/dp_groups/cvss/v3.py @@ -6,7 +6,7 @@ """ from copy import deepcopy -from ssvc.decision_points.base import SsvcValue +from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.dp_groups.base import SsvcDecisionPointGroup from ssvc.decision_points.cvss.attack_complexity import ( ATTACK_COMPLEXITY_1, @@ -58,7 +58,9 @@ def _modify(obj): o = deepcopy(obj) o.name = "Modified " + o.name o.key = "M" + o.key - nd = SsvcValue(name="Not Defined", key="ND", description="Ignore this value") + nd = SsvcDecisionPointValue( + name="Not Defined", key="ND", description="Ignore this value" + ) values = list(o.values) values.append(nd) o.values = tuple(values) diff --git a/src/test/test_dp_base.py b/src/test/test_dp_base.py index f86c5cd0..cb1ecaac 100644 --- a/src/test/test_dp_base.py +++ b/src/test/test_dp_base.py @@ -4,7 +4,9 @@ class MyTestCase(unittest.TestCase): def setUp(self) -> None: - self.value = base.SsvcValue(name="foo", key="bar", description="baz") + self.value = base.SsvcDecisionPointValue( + name="foo", key="bar", description="baz" + ) self.dp = base.SsvcDecisionPoint( name="foo", @@ -43,7 +45,7 @@ def test_ssvc_value_json_roundtrip(self): self.assertIsInstance(json, str) self.assertGreater(len(json), 0) - obj2 = base.SsvcValue.from_json(json) + obj2 = base.SsvcDecisionPointValue.from_json(json) self.assertEqual(obj, obj2) def test_ssvc_decision_point_json_roundtrip(self): From 9fcdaa3dce2d753bffb4182d5c217ee0e5bb5635 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 4 Oct 2023 12:47:05 -0400 Subject: [PATCH 38/53] Update docstrings, move away from diff-based construction Also add leading underscores to decision point value constants to discourage importing outside of the module they live in. --- .../decision_points/cvss/access_complexity.py | 44 +++++---- .../decision_points/cvss/access_vector.py | 44 +++++---- .../decision_points/cvss/attack_complexity.py | 15 +-- .../decision_points/cvss/attack_vector.py | 23 ++--- .../decision_points/cvss/authentication.py | 46 +++++---- .../cvss/availability_impact.py | 96 +++++++++++-------- .../cvss/availability_requirement.py | 23 ++--- src/ssvc/decision_points/cvss/base.py | 5 +- .../cvss/collateral_damage_potential.py | 58 ++++++----- .../cvss/confidentiality_impact.py | 45 +++++---- .../cvss/confidentiality_requirement.py | 23 ++--- .../decision_points/cvss/exploitability.py | 94 ++++++++++-------- src/ssvc/decision_points/cvss/impact_bias.py | 23 ++--- .../decision_points/cvss/integrity_impact.py | 45 +++++---- .../cvss/integrity_requirement.py | 23 ++--- .../cvss/privileges_required.py | 19 ++-- .../decision_points/cvss/remediation_level.py | 50 +++++----- .../decision_points/cvss/report_confidence.py | 71 ++++++++------ src/ssvc/decision_points/cvss/scope.py | 15 +-- .../cvss/target_distribution.py | 49 ++++++---- .../decision_points/cvss/user_interaction.py | 15 +-- 21 files changed, 470 insertions(+), 356 deletions(-) diff --git a/src/ssvc/decision_points/cvss/access_complexity.py b/src/ssvc/decision_points/cvss/access_complexity.py index e06cc3a5..08cfb1dc 100644 --- a/src/ssvc/decision_points/cvss/access_complexity.py +++ b/src/ssvc/decision_points/cvss/access_complexity.py @@ -1,41 +1,39 @@ #!/usr/bin/env python """ -file: access_complexity -author: adh -created_at: 9/20/23 1:35 PM +Models the CVSS Access Complexity metric as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint -HIGH_2 = SsvcDecisionPointValue( +_HIGH_2 = SsvcDecisionPointValue( name="High", key="H", description="Specialized access conditions exist." ) -MEDIUM = SsvcDecisionPointValue( +_MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="The access conditions are somewhat specialized.", ) -LOW_2 = SsvcDecisionPointValue( +_LOW_2 = SsvcDecisionPointValue( name="Low", key="L", description="Specialized access conditions or extenuating circumstances do not exist.", ) -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="Specialized access conditions exist; for example: the system is exploitable during specific windows of time (a race condition), the system is exploitable under specific circumstances (nondefault configurations), or the system is exploitable with victim interaction (vulnerability exploitable only if user opens e-mail)", ) -LOW = SsvcDecisionPointValue( +_LOW = SsvcDecisionPointValue( name="Low", key="L", description="Specialized access conditions or extenuating circumstances do not exist; the system is always exploitable.", ) -from ssvc.decision_points.cvss.base import CvssDecisionPoint + ACCESS_COMPLEXITY_1 = CvssDecisionPoint( name="Access Complexity", @@ -43,18 +41,28 @@ key="AC", version="1.0.0", values=( - LOW, - HIGH, + _LOW, + _HIGH, ), ) +""" +Defines LOW and HIGH values for CVSS Access Complexity. +""" -ACCESS_COMPLEXITY_2 = deepcopy(ACCESS_COMPLEXITY_1) -ACCESS_COMPLEXITY_2.version = "2.0.0" -ACCESS_COMPLEXITY_2.values = ( - LOW_2, - MEDIUM, - HIGH_2, +ACCESS_COMPLEXITY_2 = CvssDecisionPoint( + name="Access Complexity", + description="This metric measures the complexity of the attack required to exploit the vulnerability once an attacker has gained access to the target system.", + key="AC", + version="2.0.0", + values=( + _LOW_2, + _MEDIUM, + _HIGH_2, + ), ) +""" +Updates LOW and HIGH definitions for CVSS Access Complexity. Adds MEDIUM value. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/access_vector.py b/src/ssvc/decision_points/cvss/access_vector.py index 28a7f12e..4209f9df 100644 --- a/src/ssvc/decision_points/cvss/access_vector.py +++ b/src/ssvc/decision_points/cvss/access_vector.py @@ -1,43 +1,40 @@ #!/usr/bin/env python """ -file: access_vector -author: adh -created_at: 9/20/23 1:30 PM +Models the CVSS Access Vector metric as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint -NETWORK = SsvcDecisionPointValue( +_NETWORK = SsvcDecisionPointValue( name="Network", key="N", description="A vulnerability exploitable with network access means the vulnerable software is bound to the network stack and the attacker does not require local network access or local access. Such a vulnerability is often termed 'remotely exploitable'. An example of a network attack is an RPC buffer overflow.", ) -ADJACENT = SsvcDecisionPointValue( +_ADJACENT = SsvcDecisionPointValue( name="Adjacent Network", key="A", description="A vulnerability exploitable with adjacent network access requires the attacker to have access to either the broadcast or collision domain of the vulnerable software. Examples of local networks include local IP subnet, Bluetooth, IEEE 802.11, and local Ethernet segment.", ) -LOCAL_2 = SsvcDecisionPointValue( +_LOCAL_2 = SsvcDecisionPointValue( name="Local", key="L", description="A vulnerability exploitable with only local access requires the attacker to have either physical access to the vulnerable system or a local (shell) account. Examples of locally exploitable vulnerabilities are peripheral attacks such as Firewire/USB DMA attacks, and local privilege escalations (e.g., sudo).", ) -REMOTE = SsvcDecisionPointValue( +_REMOTE = SsvcDecisionPointValue( name="Remote", key="R", description="The vulnerability is exploitable remotely.", ) -LOCAL = SsvcDecisionPointValue( +_LOCAL = SsvcDecisionPointValue( name="Local", key="L", description="The vulnerability is only exploitable locally (i.e., it requires physical access or authenticated login to the target system)", ) -from ssvc.decision_points.cvss.base import CvssDecisionPoint ACCESS_VECTOR_1 = CvssDecisionPoint( name="Access Vector", @@ -45,18 +42,29 @@ key="AV", version="1.0.0", values=( - LOCAL, - REMOTE, + _LOCAL, + _REMOTE, ), ) +""" +Defines LOCAL and REMOTE values for CVSS Access Vector. +""" + -ACCESS_VECTOR_2 = deepcopy(ACCESS_VECTOR_1) -ACCESS_VECTOR_2.version = "2.0.0" -ACCESS_VECTOR_2.values = ( - LOCAL_2, - ADJACENT, - NETWORK, +ACCESS_VECTOR_2 = CvssDecisionPoint( + name="Access Vector", + description="This metric reflects the context by which vulnerability exploitation is possible.", + key="AV", + version="2.0.0", + values=( + _LOCAL_2, + _ADJACENT, + _NETWORK, + ), ) +""" +Updates LOCAL definition for CVSS Access Vector. Adds ADJACENT and NETWORK values. Removes REMOTE value. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/attack_complexity.py b/src/ssvc/decision_points/cvss/attack_complexity.py index 4430fd00..81d69268 100644 --- a/src/ssvc/decision_points/cvss/attack_complexity.py +++ b/src/ssvc/decision_points/cvss/attack_complexity.py @@ -1,20 +1,18 @@ #!/usr/bin/env python """ -file: attack_complexity -author: adh -created_at: 9/20/23 2:32 PM +Models the CVSS Attack Complexity metric as an SSVC decision point. """ from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="A successful attack depends on conditions beyond the attacker's control.", ) -LOW = SsvcDecisionPointValue( +_LOW = SsvcDecisionPointValue( name="Low", key="L", description="Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success against the vulnerable component.", @@ -26,10 +24,13 @@ key="AC", version="1.0.0", values=( - LOW, - HIGH, + _LOW, + _HIGH, ), ) +""" +Defines LOW and HIGH values for CVSS Attack Complexity. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/attack_vector.py b/src/ssvc/decision_points/cvss/attack_vector.py index 88d4f06d..dca9a801 100644 --- a/src/ssvc/decision_points/cvss/attack_vector.py +++ b/src/ssvc/decision_points/cvss/attack_vector.py @@ -1,31 +1,29 @@ #!/usr/bin/env python """ -file: attack_vector -author: adh -created_at: 9/20/23 2:26 PM +Models the CVSS Attack Vector metric as an SSVC decision point. """ from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NETWORK = SsvcDecisionPointValue( +_NETWORK = SsvcDecisionPointValue( name="Network", key="N", description="A vulnerability exploitable with network access means the vulnerable component is bound to the network stack and the attacker's path is through OSI layer 3 (the network layer). Such a vulnerability is often termed 'remotely exploitable' and can be thought of as an attack being exploitable one or more network hops away (e.g. across layer 3 boundaries from routers).", ) -ADJACENT = SsvcDecisionPointValue( +_ADJACENT = SsvcDecisionPointValue( name="Adjacent", key="A", description="A vulnerability exploitable with adjacent network access means the vulnerable component is bound to the network stack, however the attack is limited to the same shared physical (e.g. Bluetooth, IEEE 802.11), or logical (e.g. local IP subnet) network, and cannot be performed across an OSI layer 3 boundary (e.g. a router).", ) -LOCAL = SsvcDecisionPointValue( +_LOCAL = SsvcDecisionPointValue( name="Local", key="L", description="A vulnerability exploitable with Local access means that the vulnerable component is not bound to the network stack, and the attacker's path is via read/write/execute capabilities. In some cases, the attacker may be logged in locally in order to exploit the vulnerability, otherwise, she may rely on User Interaction to execute a malicious file.", ) -PHYSICAL = SsvcDecisionPointValue( +_PHYSICAL = SsvcDecisionPointValue( name="Physical", key="P", description="A vulnerability exploitable with Physical access requires the attacker to physically touch or manipulate the vulnerable component. Physical interaction may be brief (e.g. evil maid attack [1]) or persistent. An example of such an attack is a cold boot attack which allows an attacker to access to disk encryption keys after gaining physical access to the system, or peripheral attacks such as Firewire/USB Direct Memory Access attacks.", @@ -37,12 +35,15 @@ key="AV", version="1.0.0", values=( - PHYSICAL, - LOCAL, - ADJACENT, - NETWORK, + _PHYSICAL, + _LOCAL, + _ADJACENT, + _NETWORK, ), ) +""" +Defines PHYSICAL, LOCAL, ADJACENT, and NETWORK values for CVSS Attack Vector. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/authentication.py b/src/ssvc/decision_points/cvss/authentication.py index a76c787f..3f269ad5 100644 --- a/src/ssvc/decision_points/cvss/authentication.py +++ b/src/ssvc/decision_points/cvss/authentication.py @@ -1,39 +1,36 @@ #!/usr/bin/env python """ -file: authentication -author: adh -created_at: 9/20/23 1:39 PM +Models the CVSS Authentication metric as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint -AUTH_NONE = SsvcDecisionPointValue( +_AUTH_NONE = SsvcDecisionPointValue( name="None", key="N", description="Authentication is not required to exploit the vulnerability.", ) -SINGLE = SsvcDecisionPointValue( +_SINGLE = SsvcDecisionPointValue( name="Single", key="S", description="The vulnerability requires an attacker to be logged into the system (such as at a command line or via a desktop session or web interface).", ) -MULTIPLE = SsvcDecisionPointValue( +_MULTIPLE = SsvcDecisionPointValue( name="Multiple", key="M", description="Exploiting the vulnerability requires that the attacker authenticate two or more times, even if the same credentials are used each time.", ) -from ssvc.decision_points.cvss.base import CvssDecisionPoint -REQUIRED = SsvcDecisionPointValue( +_REQUIRED = SsvcDecisionPointValue( name="Required", key="R", description="Authentication is required to access and exploit the vulnerability.", ) -NOT_REQUIRED = SsvcDecisionPointValue( +_NOT_REQUIRED = SsvcDecisionPointValue( name="Not Required", key="N", description="Authentication is not required to access or exploit the vulnerability.", @@ -42,22 +39,31 @@ AUTHENTICATION_1 = CvssDecisionPoint( name="Authentication", description="This metric measures whether or not an attacker needs to be authenticated to the target system in order to exploit the vulnerability.", - key="AU", + key="Au", version="1.0.0", values=( - NOT_REQUIRED, - REQUIRED, + _NOT_REQUIRED, + _REQUIRED, ), ) +""" +Includes NOT_REQUIRED and REQUIRED values for CVSS Authentication. +""" -AUTHENTICATION_2 = deepcopy(AUTHENTICATION_1) -AUTHENTICATION_2.version = "2.0.0" -AUTHENTICATION_2.description = "This metric measures the number of times an attacker must authenticate to a target in order to exploit a vulnerability. This metric does not gauge the strength or complexity of the authentication process, only that an attacker is required to provide credentials before an exploit may occur. The possible values for this metric are listed in Table 3. The fewer authentication instances that are required, the higher the vulnerability score." -AUTHENTICATION_2.values = ( - MULTIPLE, - SINGLE, - AUTH_NONE, +AUTHENTICATION_2 = CvssDecisionPoint( + name="Authentication", + description="This metric measures the number of times an attacker must authenticate to a target in order to exploit a vulnerability. This metric does not gauge the strength or complexity of the authentication process, only that an attacker is required to provide credentials before an exploit may occur. The possible values for this metric are listed in Table 3. The fewer authentication instances that are required, the higher the vulnerability score.", + key="Au", + version="2.0.0", + values=( + _MULTIPLE, + _SINGLE, + _AUTH_NONE, + ), ) +""" +Includes MULTIPLE, SINGLE, and AUTH_NONE values for CVSS Authentication. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/availability_impact.py b/src/ssvc/decision_points/cvss/availability_impact.py index 9e0bcb20..7f7f7441 100644 --- a/src/ssvc/decision_points/cvss/availability_impact.py +++ b/src/ssvc/decision_points/cvss/availability_impact.py @@ -1,56 +1,74 @@ #!/usr/bin/env python """ -file: availability_impact -author: adh -created_at: 9/20/23 1:46 PM +Models the CVSS Availability Impact metric as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint +_HIGH = SsvcDecisionPointValue( + name="High", + key="H", + description="There is total loss of availability, resulting in the attacker being able to fully deny access to resources in the impacted component; this loss is either sustained (while the attacker continues to deliver the attack) or persistent (the condition persists even after the attack has completed).", +) + +_LOW = SsvcDecisionPointValue( + name="Low", + key="L", + description="There is reduced performance or interruptions in resource availability.", +) + +_NONE_2 = SsvcDecisionPointValue( + name="None", + key="N", + description="There is no impact to the availability of the system.", +) + +_COMPLETE = SsvcDecisionPointValue( + name="Complete", + key="C", + description="Total shutdown of the affected resource. The attacker can render the resource completely unavailable.", +) + +_PARTIAL = SsvcDecisionPointValue( + name="Partial", + key="P", + description="Considerable lag in or interruptions in resource availability. For example, a network-based flood attack that reduces available bandwidth to a web server farm to such an extent that only a small number of connections successfully complete.", +) + +_NONE_1 = SsvcDecisionPointValue( + name="None", key="N", description="No impact on availability." +) + AVAILABILITY_IMPACT_1 = CvssDecisionPoint( name="Availability Impact", description="This metric measures the impact on availability a successful exploit of the vulnerability will have on the target system.", key="A", version="1.0.0", - values=[ - SsvcDecisionPointValue( - name="None", key="N", description="No impact on availability." - ), - SsvcDecisionPointValue( - name="Partial", - key="P", - description="Considerable lag in or interruptions in resource availability. For example, a network-based flood attack that reduces available bandwidth to a web server farm to such an extent that only a small number of connections successfully complete.", - ), - SsvcDecisionPointValue( - name="Complete", - key="C", - description="Total shutdown of the affected resource. The attacker can render the resource completely unavailable.", - ), - ], -) - -AVAILABILITY_IMPACT_2 = deepcopy(AVAILABILITY_IMPACT_1) -AVAILABILITY_IMPACT_2.version = "2.0.0" -AVAILABILITY_IMPACT_2.description = "This metric measures the impact to availability of a successfully exploited vulnerability." -AVAILABILITY_IMPACT_2.values = [ - SsvcDecisionPointValue( - name="None", - key="N", - description="There is no impact to the availability of the system.", - ), - SsvcDecisionPointValue( - name="Low", - key="L", - description="There is reduced performance or interruptions in resource availability.", + values=( + _NONE_1, + _PARTIAL, + _COMPLETE, ), - SsvcDecisionPointValue( - name="High", - key="H", - description="There is total loss of availability, resulting in the attacker being able to fully deny access to resources in the impacted component; this loss is either sustained (while the attacker continues to deliver the attack) or persistent (the condition persists even after the attack has completed).", +) +""" +Defines None, Partial, and Complete values for CVSS Availability Impact. +""" + +AVAILABILITY_IMPACT_2 = CvssDecisionPoint( + name="Availability Impact", + description="This metric measures the impact to availability of a successfully exploited vulnerability.", + key="A", + version="2.0.0", + values=( + _NONE_2, + _LOW, + _HIGH, ), -] +) +""" +Updates None. Removes Partial and Complete. Adds Low and High values for CVSS Availability Impact. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/availability_requirement.py b/src/ssvc/decision_points/cvss/availability_requirement.py index 887f2a2d..8d2b2e30 100644 --- a/src/ssvc/decision_points/cvss/availability_requirement.py +++ b/src/ssvc/decision_points/cvss/availability_requirement.py @@ -1,32 +1,30 @@ #!/usr/bin/env python """ -file: availability_requirement -author: adh -created_at: 9/20/23 2:11 PM +Models the CVSS Availability Requirement metric as an SSVC decision point. """ from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcDecisionPointValue( +_NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="Loss of availability is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -MEDIUM = SsvcDecisionPointValue( +_MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="Loss of availability is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -LOW = SsvcDecisionPointValue( +_LOW = SsvcDecisionPointValue( name="Low", key="L", description="Loss of availability is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", @@ -39,12 +37,15 @@ key="CR", version="1.0.0", values=( - LOW, - MEDIUM, - HIGH, - NOT_DEFINED, + _LOW, + _MEDIUM, + _HIGH, + _NOT_DEFINED, ), ) +""" +Defines Low, Medium, High, and Not Defined values for CVSS Availability Requirement. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/base.py b/src/ssvc/decision_points/cvss/base.py index 9a8263a5..a993e7e1 100644 --- a/src/ssvc/decision_points/cvss/base.py +++ b/src/ssvc/decision_points/cvss/base.py @@ -1,12 +1,11 @@ #!/usr/bin/env python """ -file: cvss_dp -author: adh -created_at: 9/20/23 12:08 PM +Provides a base class for modeling CVSS vector metrics as SSVC decision points. """ from dataclasses import dataclass + from dataclasses_json import dataclass_json from ssvc.decision_points.base import SsvcDecisionPoint diff --git a/src/ssvc/decision_points/cvss/collateral_damage_potential.py b/src/ssvc/decision_points/cvss/collateral_damage_potential.py index 2f5ce643..01c7594d 100644 --- a/src/ssvc/decision_points/cvss/collateral_damage_potential.py +++ b/src/ssvc/decision_points/cvss/collateral_damage_potential.py @@ -1,57 +1,54 @@ #!/usr/bin/env python """ -file: collateral_damage_potential -author: adh -created_at: 9/20/23 1:48 PM +Models the CVSS Collateral Damage Potential metric as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue +from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcDecisionPointValue( +_NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -MEDIUM_HIGH = SsvcDecisionPointValue( +_MEDIUM_HIGH = SsvcDecisionPointValue( name="Medium-High", key="MH", description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", ) -LOW_MEDIUM = SsvcDecisionPointValue( +_LOW_MEDIUM = SsvcDecisionPointValue( name="Low-Medium", key="LM", description="A successful exploit of this vulnerability may result in moderate physical or property damage or loss.", ) -CDP_NONE_2 = SsvcDecisionPointValue( +_CDP_NONE_2 = SsvcDecisionPointValue( name="None", key="N", description="There is no potential for loss of life, physical assets, productivity or revenue.", ) -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="A successful exploit of this vulnerability may result in catastrophic physical or property damage and loss. The range of effect may be over a wide area.", ) -MEDIUM = SsvcDecisionPointValue( +_MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="A successful exploit of this vulnerability may result in significant physical or property damage or loss.", ) -LOW = SsvcDecisionPointValue( +_LOW = SsvcDecisionPointValue( name="Low", key="L", description="A successful exploit of this vulnerability may result in light physical or property damage or loss. The system itself may be damaged or destroyed.", ) -from ssvc.decision_points.cvss.base import CvssDecisionPoint -CDP_NONE = SsvcDecisionPointValue( +_CDP_NONE = SsvcDecisionPointValue( name="None", key="N", description="There is no potential for physical or property damage.", @@ -64,23 +61,32 @@ key="CDP", version="1.0.0", values=( - CDP_NONE, - LOW, - MEDIUM, - HIGH, + _CDP_NONE, + _LOW, + _MEDIUM, + _HIGH, ), ) +""" +Defines None, Low, Medium, and High values for CVSS Collateral Damage Potential. +""" -COLLATERAL_DAMAGE_POTENTIAL_2 = deepcopy(COLLATERAL_DAMAGE_POTENTIAL_1) -COLLATERAL_DAMAGE_POTENTIAL_2.version = "2.0.0" -COLLATERAL_DAMAGE_POTENTIAL_2.values = ( - CDP_NONE_2, - LOW, - LOW_MEDIUM, - MEDIUM_HIGH, - HIGH, - NOT_DEFINED, +COLLATERAL_DAMAGE_POTENTIAL_2 = CvssDecisionPoint( + name="Collateral Damage Potential", + description="This metric measures the potential for loss of life or physical assets.", + key="CDP", + version="2.0.0", + values=( + _CDP_NONE_2, + _LOW_MEDIUM, + _MEDIUM_HIGH, + _HIGH, + _NOT_DEFINED, + ), ) +""" +Updates None description. Adds Low-Medium, Medium-High, and Not Defined value. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/confidentiality_impact.py b/src/ssvc/decision_points/cvss/confidentiality_impact.py index 52aa897f..1172cccc 100644 --- a/src/ssvc/decision_points/cvss/confidentiality_impact.py +++ b/src/ssvc/decision_points/cvss/confidentiality_impact.py @@ -1,45 +1,42 @@ #!/usr/bin/env python """ -file: confidentiality_impact -author: adh -created_at: 9/20/23 1:46 PM +Models the CVSS Confidentiality Impact metric as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="There is total loss of confidentiality, resulting in all resources within the impacted component being divulged to the attacker. Alternatively, access to only some restricted information is obtained, but the disclosed information presents a direct, serious impact. For example, an attacker steals the administrator's password, or private encryption keys of a web server.", ) -LOW = SsvcDecisionPointValue( +_LOW = SsvcDecisionPointValue( name="Low", key="L", description="There is some loss of confidentiality. Access to some restricted information is obtained, but the attacker does not have control over what information is obtained, or the amount or kind of loss is constrained. The information disclosure does not cause a direct, serious loss to the impacted component.", ) -CI_NONE_2 = SsvcDecisionPointValue( +_CI_NONE_2 = SsvcDecisionPointValue( name="None", key="N", description="There is no loss of confidentiality within the impacted component.", ) -COMPLETE = SsvcDecisionPointValue( +_COMPLETE = SsvcDecisionPointValue( name="Complete", key="C", description="A total compromise of critical system information. A complete loss of system protection resulting in all critical system files being revealed. The attacker has sovereign control to read all of the system's data (memory, files, etc).", ) -PARTIAL = SsvcDecisionPointValue( +_PARTIAL = SsvcDecisionPointValue( name="Partial", key="P", description="There is considerable informational disclosure. Access to critical system files is possible. There is a loss of important information, but the attacker doesn't have control over what is obtainable or the scope of the loss is constrained.", ) -CI_NONE = SsvcDecisionPointValue( +_CI_NONE = SsvcDecisionPointValue( name="None", key="N", description="No impact on confidentiality.", @@ -51,19 +48,29 @@ key="C", version="1.0.0", values=( - CI_NONE, - PARTIAL, - COMPLETE, + _CI_NONE, + _PARTIAL, + _COMPLETE, ), ) +""" +Defines None, Partial, and Complete values for CVSS Confidentiality Impact. +""" -CONFIDENTIALITY_IMPACT_2 = deepcopy(CONFIDENTIALITY_IMPACT_1) -CONFIDENTIALITY_IMPACT_2.version = "2.0.0" -CONFIDENTIALITY_IMPACT_2.values = ( - CI_NONE_2, - LOW, - HIGH, +CONFIDENTIALITY_IMPACT_2 = CvssDecisionPoint( + name="Confidentiality Impact", + description="This metric measures the impact to the confidentiality of the information resources managed by a software component due to a successfully exploited vulnerability.", + key="C", + version="2.0.0", + values=( + _CI_NONE_2, + _LOW, + _HIGH, + ), ) +""" +Updates None. Removes Partial and Complete. Adds Low and High values for CVSS Confidentiality Impact. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/confidentiality_requirement.py b/src/ssvc/decision_points/cvss/confidentiality_requirement.py index b329deab..26e878fc 100644 --- a/src/ssvc/decision_points/cvss/confidentiality_requirement.py +++ b/src/ssvc/decision_points/cvss/confidentiality_requirement.py @@ -1,32 +1,30 @@ #!/usr/bin/env python """ -file: confidentiality_requirement -author: adh -created_at: 9/20/23 2:11 PM +Models the CVSS Confidentiality Requirement metric as an SSVC decision point. """ from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcDecisionPointValue( +_NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="Loss of confidentiality is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -MEDIUM = SsvcDecisionPointValue( +_MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="Loss of confidentiality is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -LOW = SsvcDecisionPointValue( +_LOW = SsvcDecisionPointValue( name="Low", key="L", description="Loss of confidentiality is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", @@ -38,12 +36,15 @@ key="CR", version="1.0.0", values=( - LOW, - MEDIUM, - HIGH, - NOT_DEFINED, + _LOW, + _MEDIUM, + _HIGH, + _NOT_DEFINED, ), ) +""" +Defines Low, Medium, High, and Not Defined values for CVSS Confidentiality Requirement. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/exploitability.py b/src/ssvc/decision_points/cvss/exploitability.py index 9bb2913a..0bf3c661 100644 --- a/src/ssvc/decision_points/cvss/exploitability.py +++ b/src/ssvc/decision_points/cvss/exploitability.py @@ -1,64 +1,64 @@ #!/usr/bin/env python """ -file: exploitability -author: adh -created_at: 9/20/23 1:47 PM +Model the CVSS Exploitability and Exploit Code Maturity metrics as SSVC decision points. """ -from copy import deepcopy - from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcDecisionPointValue( +_NOT_DEFINED_2 = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to a scoring equation to skip this metric.", ) -HIGH_2 = SsvcDecisionPointValue( +_NOT_DEFINED_1 = SsvcDecisionPointValue( + name="Not Defined", key="ND", description="Not Defined" +) + +_HIGH_2 = SsvcDecisionPointValue( name="High", key="H", description="Functional autonomous code exists, or no exploit is required (manual trigger) and details are widely available. Exploit code works in every situation, or is actively being delivered via an autonomous agent (such as a worm or virus). Network-connected systems are likely to encounter scanning or exploitation attempts. Exploit development has reached the level of reliable, widely-available, easy-to-use automated tools.", ) -FUNCTIONAL_2 = SsvcDecisionPointValue( +_FUNCTIONAL_2 = SsvcDecisionPointValue( name="Functional", key="F", description="Functional exploit code is available. The code works in most situations where the vulnerability exists.", ) -PROOF_OF_CONCEPT_2 = SsvcDecisionPointValue( +_PROOF_OF_CONCEPT_2 = SsvcDecisionPointValue( name="Proof-of-Concept", key="POC", description="Proof-of-concept exploit code is available, or an attack demonstration is not practical for most systems. The code or technique is not functional in all situations and may require substantial modification by a skilled attacker.", ) -UNPROVEN_2 = SsvcDecisionPointValue( +_UNPROVEN_2 = SsvcDecisionPointValue( name="Unproven", key="U", description="No exploit code is available, or an exploit is theoretical.", ) -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="Either the vulnerability is exploitable by functional mobile autonomous code or no exploit is required (manual trigger) and the details for the manual technique are widely available. The code works in every situation where the vulnerability is exploitable and/or is actively being delivered via a mobile autonomous agent (a worm or virus).", ) -FUNCTIONAL = SsvcDecisionPointValue( +_FUNCTIONAL = SsvcDecisionPointValue( name="Functional", key="F", description="Functional exploit code is available. The code works in most situations where the vulnerability is exploitable.", ) -PROOF_OF_CONCEPT = SsvcDecisionPointValue( +_PROOF_OF_CONCEPT = SsvcDecisionPointValue( name="Proof of Concept", key="P", description="Proof of concept exploit code or an attack demonstration that is not practically applicable to deployed systems is available. The code or technique is not functional in all situations and may require substantial hand tuning by a skilled attacker for use against deployed systems.", ) -UNPROVEN = SsvcDecisionPointValue( +_UNPROVEN = SsvcDecisionPointValue( name="Unproven", key="U", description="No exploit code is yet available or an exploit method is entirely theoretical.", @@ -70,35 +70,53 @@ key="E", version="1.0.0", values=( - UNPROVEN, - PROOF_OF_CONCEPT, - FUNCTIONAL, - HIGH, + _UNPROVEN, + _PROOF_OF_CONCEPT, + _FUNCTIONAL, + _HIGH, + ), +) +""" +Defines Unproven, Proof of Concept, Functional, and High values for CVSS Exploitability. +""" + +EXPLOITABILITY_1_1 = CvssDecisionPoint( + name="Exploitability", + description="This metric measures the current state of exploit technique or code availability and suggests a likelihood of exploitation.", + key="E", + version="1.1.0", + values=( + _UNPROVEN, + _PROOF_OF_CONCEPT, + _FUNCTIONAL, + _HIGH, + _NOT_DEFINED_1, ), ) +""" +Introduced Not Defined value. +""" + +# TODO is it correct to inherit the version from Exploitability? +# TODO Or is it better for Exploit Code Maturity to reset to 1.0.0? -# CVSS v2 introduced the concept of "Not Defined" values without changing the other values. -EXPLOITABILITY_1_1 = deepcopy(EXPLOITABILITY_1) -EXPLOITABILITY_1_1.version = "1.1.0" -nd = SsvcDecisionPointValue(name="Not Defined", key="ND", description="Not Defined") -values = list(EXPLOITABILITY_1_1.values) -values.append(nd) -EXPLOITABILITY_1_1.values = tuple(values) - - -# CVSS v3 modified the descriptions of the values. -EXPLOIT_CODE_MATURITY_1_1_1 = deepcopy(EXPLOITABILITY_1_1) -EXPLOIT_CODE_MATURITY_1_1_1.name = "Exploit Code Maturity" -EXPLOIT_CODE_MATURITY_1_1_1.description = "measures the likelihood of the vulnerability being attacked, and is typically based on the current state of exploit techniques, exploit code availability, or active, 'in-the-wild' exploitation" -EXPLOIT_CODE_MATURITY_1_1_1.version = "1.1.1" -EXPLOIT_CODE_MATURITY_1_1_1.values = ( - UNPROVEN_2, - PROOF_OF_CONCEPT_2, - FUNCTIONAL_2, - HIGH_2, - NOT_DEFINED, +EXPLOIT_CODE_MATURITY_1_1_1 = CvssDecisionPoint( + name="Exploit Code Maturity", + description="measures the likelihood of the vulnerability being attacked, and is typically based on the current state of exploit techniques, exploit code availability, or active, 'in-the-wild' exploitation", + key="E", + version="1.1.1", + values=( + _UNPROVEN_2, + _PROOF_OF_CONCEPT_2, + _FUNCTIONAL_2, + _HIGH_2, + _NOT_DEFINED_2, + ), ) +""" +Name changed to Exploit Code Maturity. Updated descriptions of values. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/impact_bias.py b/src/ssvc/decision_points/cvss/impact_bias.py index 05d50494..7ab5a993 100644 --- a/src/ssvc/decision_points/cvss/impact_bias.py +++ b/src/ssvc/decision_points/cvss/impact_bias.py @@ -1,31 +1,29 @@ #!/usr/bin/env python """ -file: impact_bias -author: adh -created_at: 9/20/23 1:47 PM +Model the CVSS Impact Bias as an SSVC decision point. """ from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -AVAILABILITY = SsvcDecisionPointValue( +_AVAILABILITY = SsvcDecisionPointValue( name="Availability", key="A", description="Availability Impact is assigned greater weight than Confidentiality Impact or Integrity Impact.", ) -INTEGRITY = SsvcDecisionPointValue( +_INTEGRITY = SsvcDecisionPointValue( name="Integrity", key="I", description="Integrity Impact is assigned greater weight than Confidentiality Impact or Availability Impact.", ) -CONFIDENTIALITY = SsvcDecisionPointValue( +_CONFIDENTIALITY = SsvcDecisionPointValue( name="Confidentiality", key="C", description="Confidentiality impact is assigned greater weight than Integrity Impact or Availability Impact.", ) -NORMAL = SsvcDecisionPointValue( +_NORMAL = SsvcDecisionPointValue( name="Normal", key="N", description="Confidentiality Impact, Integrity Impact, and Availability Impact are all assigned the same weight.", @@ -37,12 +35,15 @@ key="IB", version="1.0.0", values=( - NORMAL, - CONFIDENTIALITY, - INTEGRITY, - AVAILABILITY, + _NORMAL, + _CONFIDENTIALITY, + _INTEGRITY, + _AVAILABILITY, ), ) +""" +Defines Normal, Confidentiality, Integrity, and Availability values for CVSS Impact Bias. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/integrity_impact.py b/src/ssvc/decision_points/cvss/integrity_impact.py index 618f4598..33586f09 100644 --- a/src/ssvc/decision_points/cvss/integrity_impact.py +++ b/src/ssvc/decision_points/cvss/integrity_impact.py @@ -1,45 +1,42 @@ #!/usr/bin/env python """ -file: integrity_impact -author: adh -created_at: 9/20/23 1:46 PM +Models the CVSS Integrity Impact metric as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -II_HIGH = SsvcDecisionPointValue( +_II_HIGH = SsvcDecisionPointValue( name="High", key="H", description="There is a total loss of integrity, or a complete loss of protection. For example, the attacker is able to modify any/all files protected by the impacted component. Alternatively, only some files can be modified, but malicious modification would present a direct, serious consequence to the impacted component.", ) -II_LOW = SsvcDecisionPointValue( +_II_LOW = SsvcDecisionPointValue( name="Low", key="L", description="Modification of data is possible, but the attacker does not have control over the consequence of a modification, or the amount of modification is constrained. The data modification does not have a direct, serious impact on the impacted component.", ) -II_NONE_2 = SsvcDecisionPointValue( +_II_NONE_2 = SsvcDecisionPointValue( name="None", key="N", description="There is no impact to the integrity of the system.", ) -COMPLETE = SsvcDecisionPointValue( +_COMPLETE = SsvcDecisionPointValue( name="Complete", key="C", description="A total compromise of system integrity. There is a complete loss of system protection resulting in the entire system being compromised. The attacker has sovereign control to modify any system files.", ) -PARTIAL = SsvcDecisionPointValue( +_PARTIAL = SsvcDecisionPointValue( name="Partial", key="P", description="Considerable breach in integrity. Modification of critical system files or information is possible, but the attacker does not have control over what can be modified, or the scope of what the attacker can affect is constrained. For example, key system or program files may be overwritten or modified, but at random or in a limited context or scope.", ) -II_NONE = SsvcDecisionPointValue( +_II_NONE = SsvcDecisionPointValue( name="None", key="N", description="No impact on integrity." ) @@ -49,19 +46,29 @@ key="I", version="1.0.0", values=( - II_NONE, - PARTIAL, - COMPLETE, + _II_NONE, + _PARTIAL, + _COMPLETE, ), ) +""" +Defines None, Partial, and Complete values for CVSS Integrity Impact. +""" -INTEGRITY_IMPACT_2 = deepcopy(INTEGRITY_IMPACT_1) -INTEGRITY_IMPACT_2.version = "2.0.0" -INTEGRITY_IMPACT_2.values = ( - II_NONE_2, - II_LOW, - II_HIGH, +INTEGRITY_IMPACT_2 = CvssDecisionPoint( + name="Integrity Impact", + description="This metric measures the impact to integrity of a successfully exploited vulnerability.", + key="I", + version="2.0.0", + values=( + _II_NONE_2, + _II_LOW, + _II_HIGH, + ), ) +""" +Updates None. Removes Partial and Complete. Adds Low and High values for CVSS Integrity Impact. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/integrity_requirement.py b/src/ssvc/decision_points/cvss/integrity_requirement.py index 80e39b81..bed51253 100644 --- a/src/ssvc/decision_points/cvss/integrity_requirement.py +++ b/src/ssvc/decision_points/cvss/integrity_requirement.py @@ -1,32 +1,30 @@ #!/usr/bin/env python """ -file: integrity_requirement -author: adh -created_at: 9/20/23 2:11 PM +Models the CVSS Integrity Requirement metric as an SSVC decision point. """ from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcDecisionPointValue( +_NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="Loss of integrity is likely to have a catastrophic adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -MEDIUM = SsvcDecisionPointValue( +_MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="Loss of integrity is likely to have a serious adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", ) -LOW = SsvcDecisionPointValue( +_LOW = SsvcDecisionPointValue( name="Low", key="L", description="Loss of integrity is likely to have only a limited adverse effect on the organization or individuals associated with the organization (e.g., employees, customers).", @@ -38,12 +36,15 @@ key="CR", version="1.0.0", values=( - LOW, - MEDIUM, - HIGH, - NOT_DEFINED, + _LOW, + _MEDIUM, + _HIGH, + _NOT_DEFINED, ), ) +""" +Defines Low, Medium, High, and Not Defined values for CVSS Integrity Requirement. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/privileges_required.py b/src/ssvc/decision_points/cvss/privileges_required.py index ccf5fe27..8c71f61b 100644 --- a/src/ssvc/decision_points/cvss/privileges_required.py +++ b/src/ssvc/decision_points/cvss/privileges_required.py @@ -1,25 +1,23 @@ #!/usr/bin/env python """ -file: privileges_required -author: adh -created_at: 9/20/23 2:38 PM +Models the CVSS Privileges Required metric as an SSVC decision point. """ from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="The attacker is authorized with (i.e. requires) privileges that provide significant (e.g. administrative) control over the vulnerable component that could affect component-wide settings and files.", ) -LOW = SsvcDecisionPointValue( +_LOW = SsvcDecisionPointValue( name="Low", key="L", description="The attacker is authorized with (i.e. requires) privileges that provide basic user capabilities that could normally affect only settings and files owned by a user. Alternatively, an attacker with Low privileges may have the ability to cause an impact only to non-sensitive resources.", ) -PR_NONE = SsvcDecisionPointValue( +_PR_NONE = SsvcDecisionPointValue( name="None", key="N", description="The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files to carry out an attack.", @@ -31,11 +29,14 @@ key="PR", version="1.0.0", values=( - PR_NONE, - LOW, - HIGH, + _PR_NONE, + _LOW, + _HIGH, ), ) +""" +Defines None, Low, and High values for CVSS Privileges Required. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/remediation_level.py b/src/ssvc/decision_points/cvss/remediation_level.py index b214cbb9..ad3e6b0c 100644 --- a/src/ssvc/decision_points/cvss/remediation_level.py +++ b/src/ssvc/decision_points/cvss/remediation_level.py @@ -1,39 +1,36 @@ #!/usr/bin/env python """ -file: remediation_level -author: adh -created_at: 9/20/23 1:47 PM +Models the CVSS Remediation Level metric as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcDecisionPointValue( +_NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -UNAVAILABLE = SsvcDecisionPointValue( +_UNAVAILABLE = SsvcDecisionPointValue( name="Unavailable", key="U", description="There is either no solution available or it is impossible to apply.", ) -WORKAROUND = SsvcDecisionPointValue( +_WORKAROUND = SsvcDecisionPointValue( name="Workaround", key="W", description="There is an unofficial, non-vendor solution available. In some cases, users of the affected technology will create a patch of their own or provide steps to work around or otherwise mitigate against the vulnerability. When it is generally accepted that these unofficial fixes are adequate in plugging the hole for the mean time and no official remediation is available, this value can be set.", ) -TEMPORARY_FIX = SsvcDecisionPointValue( +_TEMPORARY_FIX = SsvcDecisionPointValue( name="Temporary Fix", key="TF", description="There is an official but temporary fix available. This includes instances where the vendor issues a temporary hotfix, tool or official workaround.", ) -OFFICIAL_FIX = SsvcDecisionPointValue( +_OFFICIAL_FIX = SsvcDecisionPointValue( name="Official Fix", key="OF", description="A complete vendor solution is available. Either the vendor has issued the final, official patch which eliminates the vulnerability or an upgrade that is not vulnerable is available.", @@ -45,23 +42,32 @@ key="RL", version="1.0.0", values=( - OFFICIAL_FIX, - TEMPORARY_FIX, - WORKAROUND, - UNAVAILABLE, + _OFFICIAL_FIX, + _TEMPORARY_FIX, + _WORKAROUND, + _UNAVAILABLE, ), ) +""" +Defines Official Fix, Temporary Fix, Workaround, and Unavailable values for CVSS Remediation Level. +""" - -REMEDIATION_LEVEL_1_1 = deepcopy(REMEDIATION_LEVEL_1) -REMEDIATION_LEVEL_1_1.version = "1.1.0" -REMEDIATION_LEVEL_1_1.values = ( - OFFICIAL_FIX, - TEMPORARY_FIX, - WORKAROUND, - UNAVAILABLE, - NOT_DEFINED, +REMEDIATION_LEVEL_1_1 = CvssDecisionPoint( + name="Remediation Level", + description="This metric measures the remediation status of a vulnerability.", + key="RL", + version="1.1.0", + values=( + _OFFICIAL_FIX, + _TEMPORARY_FIX, + _WORKAROUND, + _UNAVAILABLE, + _NOT_DEFINED, + ), ) +""" +Adds Not Defined to the CVSS Remediation Level decision point. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/report_confidence.py b/src/ssvc/decision_points/cvss/report_confidence.py index abde43e8..9c4c0a35 100644 --- a/src/ssvc/decision_points/cvss/report_confidence.py +++ b/src/ssvc/decision_points/cvss/report_confidence.py @@ -1,51 +1,48 @@ #!/usr/bin/env python """ -file: report_confidence -author: adh -created_at: 9/20/23 1:48 PM +Models the CVSS Report Confidence metric as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcDecisionPointValue( +_NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="Assigning this value to the metric will not influence the score. It is a signal to the equation to skip this metric.", ) -CONFIRMED_2 = SsvcDecisionPointValue( +_CONFIRMED_2 = SsvcDecisionPointValue( name="Confirmed", key="C", description="Detailed reports exist, or functional reproduction is possible (functional exploits may provide this). Source code is available to independently verify the assertions of the research, or the author or vendor of the affected code has confirmed the presence of the vulnerability.", ) -REASONABLE = SsvcDecisionPointValue( +_REASONABLE = SsvcDecisionPointValue( name="Reasonable", key="R", description="Significant details are published, but researchers either do not have full confidence in the root cause, or do not have access to source code to fully confirm all of the interactions that may lead to the result. Reasonable confidence exists, however, that the bug is reproducible and at least one impact is able to be verified (proof-of-concept exploits may provide this). An example is a detailed write-up of research into a vulnerability with an explanation (possibly obfuscated or 'left as an exercise to the reader') that gives assurances on how to reproduce the results.", ) -UNKNOWN = SsvcDecisionPointValue( +_UNKNOWN = SsvcDecisionPointValue( name="Unknown", key="U", description="There are reports of impacts that indicate a vulnerability is present. The reports indicate that the cause of the vulnerability is unknown, or reports may differ on the cause or impacts of the vulnerability. Reporters are uncertain of the true nature of the vulnerability, and there is little confidence in the validity of the reports or whether a static Base score can be applied given the differences described. An example is a bug report which notes that an intermittent but non-reproducible crash occurs, with evidence of memory corruption suggesting that denial of service, or possible more serious impacts, may result.", ) -CONFIRMED = SsvcDecisionPointValue( +_CONFIRMED = SsvcDecisionPointValue( name="Confirmed", key="C", description="Vendor or author of the affected technology has acknowledged that the vulnerability exists. This value may also be set when existence of a vulnerability is confirmed with absolute confidence through some other event, such as publication of functional proof of concept exploit code or widespread exploitation.", ) -UNCORROBORATED = SsvcDecisionPointValue( +_UNCORROBORATED = SsvcDecisionPointValue( name="Uncorroborated", key="UR", description="Multiple non-official sources; possibily including independent security companies or research organizations. At this point there may be conflicting technical details or some other lingering ambiguity.", ) -UNCONFIRMED = SsvcDecisionPointValue( +_UNCONFIRMED = SsvcDecisionPointValue( name="Unconfirmed", key="UC", description="A single unconfirmed source or possibly several conflicting reports. There is little confidence in the validity of the report. For example, a rumor that surfaces from the hacker underground.", @@ -57,28 +54,46 @@ key="RC", version="1.0.0", values=( - UNCONFIRMED, - UNCORROBORATED, - CONFIRMED, + _UNCONFIRMED, + _UNCORROBORATED, + _CONFIRMED, ), ) +""" +Defines Unconfirmed, Uncorroborated, and Confirmed values for CVSS Report Confidence. +""" -# CVSS v2 added Not Defined. -REPORT_CONFIDENCE_1_1 = deepcopy(REPORT_CONFIDENCE_1) -REPORT_CONFIDENCE_1_1.version = "1.1.0" -values = list(REPORT_CONFIDENCE_1_1.values) -values.append(NOT_DEFINED) -REPORT_CONFIDENCE_1_1.values = tuple(values) +REPORT_CONFIDENCE_1_1 = CvssDecisionPoint( + name="Report Confidence", + description="This metric measures the degree of confidence in the existence of the vulnerability and the credibility of the known technical details.", + key="RC", + version="1.1.0", + values=( + _UNCONFIRMED, + _UNCORROBORATED, + _CONFIRMED, + _NOT_DEFINED, + ), +) +""" +Adds Not Defined to the CVSS Report Confidence decision point. +""" -# CVSS v3 changed names and descriptions for this metric. -REPORT_CONFIDENCE_2 = deepcopy(REPORT_CONFIDENCE_1_1) -REPORT_CONFIDENCE_2.version = "2.0.0" -REPORT_CONFIDENCE_2.values = ( - UNKNOWN, - REASONABLE, - CONFIRMED_2, - deepcopy(NOT_DEFINED), +REPORT_CONFIDENCE_2 = CvssDecisionPoint( + name="Report Confidence", + description="This metric measures the degree of confidence in the existence of the vulnerability and the credibility of the known technical details.", + key="RC", + version="2.0.0", + values=( + _UNKNOWN, + _REASONABLE, + _CONFIRMED_2, + _NOT_DEFINED, + ), ) +""" +Replaces Unconfirmed and Uncorroborated with Unknown and Reasonable. Updated Confirmed. Retains Not Defined. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/scope.py b/src/ssvc/decision_points/cvss/scope.py index 23bfc20a..5da298b8 100644 --- a/src/ssvc/decision_points/cvss/scope.py +++ b/src/ssvc/decision_points/cvss/scope.py @@ -1,20 +1,18 @@ #!/usr/bin/env python """ -file: scope -author: adh -created_at: 9/20/23 2:47 PM +Models CVSS Scope as an SSVC decision point. """ from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -CHANGED = SsvcDecisionPointValue( +_CHANGED = SsvcDecisionPointValue( name="Changed", key="C", description="An exploited vulnerability can affect resources beyond the authorization privileges intended by the vulnerable component. In this case the vulnerable component and the impacted component are different.", ) -UNCHANGED = SsvcDecisionPointValue( +_UNCHANGED = SsvcDecisionPointValue( name="Unchanged", key="U", description="An exploited vulnerability can only affect resources managed by the same authority. In this case the vulnerable component and the impacted component are the same.", @@ -26,10 +24,13 @@ key="S", version="1.0.0", values=( - UNCHANGED, - CHANGED, + _UNCHANGED, + _CHANGED, ), ) +""" +Defines Changed and Unchanged values for CVSS Scope. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/target_distribution.py b/src/ssvc/decision_points/cvss/target_distribution.py index 2b428d90..ba0da97d 100644 --- a/src/ssvc/decision_points/cvss/target_distribution.py +++ b/src/ssvc/decision_points/cvss/target_distribution.py @@ -1,39 +1,36 @@ #!/usr/bin/env python """ -file: target_distribution -author: adh -created_at: 9/20/23 1:48 PM +Models CVSS Target Distribution as an SSVC decision point. """ -from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -NOT_DEFINED = SsvcDecisionPointValue( +_NOT_DEFINED = SsvcDecisionPointValue( name="Not Defined", key="ND", description="This value is not defined in any specification, but is used in the CVSS v3.0 vector string when a value is required by the specification, but cannot be provided. This is a special case value and should be used sparingly.", ) -HIGH = SsvcDecisionPointValue( +_HIGH = SsvcDecisionPointValue( name="High", key="H", description="Targets exist inside the environment on a considerable scale. Between 50% - 100% of the total environment is considered at risk.", ) -MEDIUM = SsvcDecisionPointValue( +_MEDIUM = SsvcDecisionPointValue( name="Medium", key="M", description="Targets exist inside the environment, but on a medium scale. Between 16% - 49% of the total environment is at risk.", ) -LOW = SsvcDecisionPointValue( +_LOW = SsvcDecisionPointValue( name="Low", key="L", description="Targets exist inside the environment, but on a small scale. Between 1% - 15% of the total environment is at risk.", ) -TD_NONE = SsvcDecisionPointValue( +_TD_NONE = SsvcDecisionPointValue( name="None", key="N", description="No target systems exist, or targets are so highly specialized that they only exist in a laboratory setting. Effectively 0% of the environment is at risk.", @@ -45,22 +42,32 @@ key="TD", version="1.0.0", values=( - TD_NONE, - LOW, - MEDIUM, - HIGH, + _TD_NONE, + _LOW, + _MEDIUM, + _HIGH, ), ) +""" +Defines None, Low, Medium, and High values for CVSS Target Distribution. +""" -TARGET_DISTRIBUTION_1_1 = deepcopy(TARGET_DISTRIBUTION_1) -TARGET_DISTRIBUTION_1_1.version = "1.1.0" -TARGET_DISTRIBUTION_1_1.values = ( - TD_NONE, - LOW, - MEDIUM, - HIGH, - NOT_DEFINED, +TARGET_DISTRIBUTION_1_1 = CvssDecisionPoint( + name="Target Distribution", + description="This metric measures the relative size of the field of target systems susceptible to the vulnerability. It is meant as an environment-specific indicator in order to approximate the percentage of systems within the environment that could be affected by the vulnerability.", + key="TD", + version="1.1.0", + values=( + _TD_NONE, + _LOW, + _MEDIUM, + _HIGH, + _NOT_DEFINED, + ), ) +""" +Introduces Not Defined value. +""" def main(): diff --git a/src/ssvc/decision_points/cvss/user_interaction.py b/src/ssvc/decision_points/cvss/user_interaction.py index 4fffadee..97000018 100644 --- a/src/ssvc/decision_points/cvss/user_interaction.py +++ b/src/ssvc/decision_points/cvss/user_interaction.py @@ -1,20 +1,18 @@ #!/usr/bin/env python """ -file: user_interaction -author: adh -created_at: 9/20/23 2:38 PM +Models CVSS User Interaction as an SSVC decision point. """ from ssvc.decision_points.base import SsvcDecisionPointValue from ssvc.decision_points.cvss.base import CvssDecisionPoint -REQUIRED = SsvcDecisionPointValue( +_REQUIRED = SsvcDecisionPointValue( name="Required", key="R", description="Successful exploitation of this vulnerability requires a user to take some action before the vulnerability can be exploited.", ) -UI_NONE = SsvcDecisionPointValue( +_UI_NONE = SsvcDecisionPointValue( name="None", key="N", description="The vulnerable system can be exploited without interaction from any user.", @@ -27,10 +25,13 @@ key="UI", version="1.0.0", values=( - UI_NONE, - REQUIRED, + _UI_NONE, + _REQUIRED, ), ) +""" +Defines None and Required values for CVSS User Interaction. +""" def main(): From 54e5cbeb7bd3607288e1cd18f743ececd8dfb348 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 4 Oct 2023 13:02:03 -0400 Subject: [PATCH 39/53] make decision point group a tuple not list --- src/ssvc/dp_groups/base.py | 9 +++++---- src/ssvc/dp_groups/coordinator_publication.py | 4 ++-- src/ssvc/dp_groups/coordinator_triage.py | 4 ++-- src/ssvc/dp_groups/cvss/v1.py | 4 ++-- src/ssvc/dp_groups/cvss/v2.py | 14 +++++++------- src/ssvc/dp_groups/cvss/v3.py | 11 +++++++---- src/ssvc/dp_groups/deployer.py | 4 ++-- src/ssvc/dp_groups/supplier.py | 4 ++-- 8 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/ssvc/dp_groups/base.py b/src/ssvc/dp_groups/base.py index e6ff3677..eec8b34a 100644 --- a/src/ssvc/dp_groups/base.py +++ b/src/ssvc/dp_groups/base.py @@ -5,7 +5,7 @@ created_at: 9/20/23 4:47 PM """ from dataclasses import dataclass -from typing import List +from typing import Tuple from dataclasses_json import dataclass_json @@ -20,12 +20,12 @@ class SsvcDecisionPointGroup(_Base, _Versioned): Models a group of decision points. """ - decision_points: List[SsvcDecisionPoint] + decision_points: Tuple[SsvcDecisionPoint] def get_all_decision_points_from( glist: list[SsvcDecisionPointGroup], -) -> list[SsvcDecisionPoint]: +) -> Tuple[SsvcDecisionPoint]: """ Given a list of SsvcDecisionPointGroup objects, return a list of all the unique SsvcDecisionPoint objects contained in those groups. @@ -44,7 +44,8 @@ def get_all_decision_points_from( continue # keep non-duplicates dps.append(dp) - return dps + + return tuple(dps) def main(): diff --git a/src/ssvc/dp_groups/coordinator_publication.py b/src/ssvc/dp_groups/coordinator_publication.py index d73f6e0f..63d3a34a 100644 --- a/src/ssvc/dp_groups/coordinator_publication.py +++ b/src/ssvc/dp_groups/coordinator_publication.py @@ -14,11 +14,11 @@ name="Coordinator Publication", description="The decision points used by the coordinator during publication.", version="1.0.0", - decision_points=[ + decision_points=( SUPPLIER_INVOLVEMENT_1, EXPLOITATION_1, PUBLIC_VALUE_ADDED_1, - ], + ), ) """ Added in SSVC v2, the Coordinator Publication v1.0.0 decision points are used by the coordinator during the publication process. diff --git a/src/ssvc/dp_groups/coordinator_triage.py b/src/ssvc/dp_groups/coordinator_triage.py index d4e67964..6d6f352b 100644 --- a/src/ssvc/dp_groups/coordinator_triage.py +++ b/src/ssvc/dp_groups/coordinator_triage.py @@ -21,7 +21,7 @@ name="Coordinator Triage", description="The decision points used by the coordinator during triage.", version="1.0.0", - decision_points=[ + decision_points=( REPORT_PUBLIC_1, SUPPLIER_CONTACTED_1, REPORT_CREDIBILITY_1, @@ -32,7 +32,7 @@ VALUE_DENSITY_1, PUBLIC_SAFETY_IMPACT_1, SAFETY_IMPACT_1, - ], + ), ) """ Added in SSVC v2, the Coordinator Triage v1.0.0 decision points are used by the coordinator during the intake and triage process. diff --git a/src/ssvc/dp_groups/cvss/v1.py b/src/ssvc/dp_groups/cvss/v1.py index 6f4d4e70..cc4daab8 100644 --- a/src/ssvc/dp_groups/cvss/v1.py +++ b/src/ssvc/dp_groups/cvss/v1.py @@ -42,7 +42,7 @@ name="CVSS", version="1.0", description="CVSS v1 decision points", - decision_points=[ + decision_points=( ACCESS_VECTOR_1, ACCESS_COMPLEXITY_1, AUTHENTICATION_1, @@ -55,7 +55,7 @@ REPORT_CONFIDENCE_1, COLLATERAL_DAMAGE_POTENTIAL_1, TARGET_DISTRIBUTION_1, - ], + ), ) diff --git a/src/ssvc/dp_groups/cvss/v2.py b/src/ssvc/dp_groups/cvss/v2.py index 59f3543a..4c9d7f5b 100644 --- a/src/ssvc/dp_groups/cvss/v2.py +++ b/src/ssvc/dp_groups/cvss/v2.py @@ -5,12 +5,14 @@ created_at: 9/20/23 12:54 PM """ -from ssvc.dp_groups.base import SsvcDecisionPointGroup -from ssvc.decision_points.cvss.access_vector import ACCESS_VECTOR_2 from ssvc.decision_points.cvss.access_complexity import ( ACCESS_COMPLEXITY_2, ) +from ssvc.decision_points.cvss.access_vector import ACCESS_VECTOR_2 from ssvc.decision_points.cvss.authentication import AUTHENTICATION_2 +from ssvc.decision_points.cvss.availability_impact import ( + AVAILABILITY_IMPACT_1, +) from ssvc.decision_points.cvss.availability_requirement import ( AVAILABILITY_REQUIREMENT_1, ) @@ -29,9 +31,6 @@ from ssvc.decision_points.cvss.integrity_impact import ( INTEGRITY_IMPACT_1, ) -from ssvc.decision_points.cvss.availability_impact import ( - AVAILABILITY_IMPACT_1, -) from ssvc.decision_points.cvss.integrity_requirement import ( INTEGRITY_REQUIREMENT_1, ) @@ -44,12 +43,13 @@ from ssvc.decision_points.cvss.target_distribution import ( TARGET_DISTRIBUTION_1_1, ) +from ssvc.dp_groups.base import SsvcDecisionPointGroup CVSSv2 = SsvcDecisionPointGroup( name="CVSS Version 2", description="The Common Vulnerability Scoring System (CVSS) is a free and open industry standard for assessing the severity of computer system security vulnerabilities. CVSS attempts to assign severity scores to vulnerabilities, allowing responders to prioritize responses and resources according to threat. Scores are calculated based on a formula that depends on several metrics that approximate ease of exploit and the impact of exploit. Scores range from 0 to 10, with 10 being the most severe.", version="2.0", - decision_points=[ + decision_points=( ACCESS_VECTOR_2, ACCESS_COMPLEXITY_2, AUTHENTICATION_2, @@ -64,7 +64,7 @@ CONFIDENTIALITY_REQUIREMENT_1, INTEGRITY_REQUIREMENT_1, AVAILABILITY_REQUIREMENT_1, - ], + ), ) diff --git a/src/ssvc/dp_groups/cvss/v3.py b/src/ssvc/dp_groups/cvss/v3.py index 16236dd3..19393aa8 100644 --- a/src/ssvc/dp_groups/cvss/v3.py +++ b/src/ssvc/dp_groups/cvss/v3.py @@ -7,7 +7,6 @@ from copy import deepcopy from ssvc.decision_points.base import SsvcDecisionPointValue -from ssvc.dp_groups.base import SsvcDecisionPointGroup from ssvc.decision_points.cvss.attack_complexity import ( ATTACK_COMPLEXITY_1, ) @@ -46,6 +45,7 @@ from ssvc.decision_points.cvss.user_interaction import ( USER_INTERACTION_1, ) +from ssvc.dp_groups.base import SsvcDecisionPointGroup def _modify(obj): @@ -62,7 +62,10 @@ def _modify(obj): name="Not Defined", key="ND", description="Ignore this value" ) values = list(o.values) - values.append(nd) + # if there is no value named "Not Defined" value, add it + names = [v.name for v in values] + if nd.name not in names: + values.append(nd) o.values = tuple(values) return o @@ -81,7 +84,7 @@ def _modify(obj): name="CVSS Version 3", description="The Common Vulnerability Scoring System (CVSS) is a free and open industry standard for assessing the severity of computer system security vulnerabilities. CVSS attempts to assign severity scores to vulnerabilities, allowing responders to prioritize responses and resources according to threat. Scores are calculated based on a formula that depends on several metrics that approximate ease of exploit and the impact of exploit. Scores range from 0 to 10, with 10 being the most severe.", version="3.0", - decision_points=[ + decision_points=( ATTACK_VECTOR_1, ATTACK_COMPLEXITY_1, PRIVILEGES_REQUIRED_1, @@ -104,7 +107,7 @@ def _modify(obj): MODIFIED_CONFIDENTIALITY_IMPACT, MODIFIED_INTEGRITY_IMPACT, MODIFIED_AVAILABILITY_IMPACT, - ], + ), ) diff --git a/src/ssvc/dp_groups/deployer.py b/src/ssvc/dp_groups/deployer.py index 79e36d34..e375510e 100644 --- a/src/ssvc/dp_groups/deployer.py +++ b/src/ssvc/dp_groups/deployer.py @@ -82,14 +82,14 @@ name="SSVC Deployer", description="The decision points used by the deployer.", version="3.0.0", - decision_points=[ + decision_points=( EXPLOITATION_1, SYSTEM_EXPOSURE_1_0_1, MISSION_IMPACT_2, SAFETY_IMPACT_1, AUTOMATABLE_1, HUMAN_IMPACT_1, - ], + ), ) """ In SSVC 2.1, Deployer 3.0.0 includes decision points: diff --git a/src/ssvc/dp_groups/supplier.py b/src/ssvc/dp_groups/supplier.py index 4bd68427..126ec913 100644 --- a/src/ssvc/dp_groups/supplier.py +++ b/src/ssvc/dp_groups/supplier.py @@ -18,14 +18,14 @@ name="SSVC Patch Developer", description="The decision points used by the patch developer.", version="1.0.0", - decision_points=[ + decision_points=( EXPLOITATION_1, UTILITY_1, TECHNICAL_IMPACT_1, VIRULENCE_1, VALUE_DENSITY_1, SAFETY_IMPACT_1, - ], + ), ) """ In SSVC v1, Patch Developer v1 represents the decision points used by the patch developer. From 5881e85c12aaab0c25840b7bfb8909ebb91376d4 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Wed, 4 Oct 2023 13:28:08 -0400 Subject: [PATCH 40/53] make it easier to emit json and md tables from decision point groups --- src/ssvc/decision_points/base.py | 2 +- src/ssvc/tools.py | 62 ++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index 25580482..1faf49cb 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -50,7 +50,7 @@ def make_row(items): rows.append(make_row(["---" for _ in headings])) for value in self.values: - rows.append(makess_row([value.name, value.key, value.description])) + rows.append(make_row([value.name, value.key, value.description])) return "\n".join(rows) diff --git a/src/ssvc/tools.py b/src/ssvc/tools.py index 28c6c6f1..76e5a822 100644 --- a/src/ssvc/tools.py +++ b/src/ssvc/tools.py @@ -4,21 +4,61 @@ author: adh created_at: 9/21/23 3:20 PM """ -from ssvc.dp_groups.base import SsvcDecisionPointGroup +from ssvc.decision_points.base import SsvcDecisionPoint +from ssvc.dp_groups.base import SsvcDecisionPointGroup, get_all_decision_points_from -def group_to_jsonfiles(group: SsvcDecisionPointGroup, path: str = ".") -> None: - for dp in group.decision_points: +def write_json(fname: str, dp: SsvcDecisionPoint) -> None: + with open(fname, "w") as f: + print(f"Writing {fname}") + f.write(dp.to_json(indent=2)) + f.write("\n") + + +def write_markdown_table(fname: str, dp: SsvcDecisionPoint) -> None: + with open(fname, "w") as f: + print(f"Writing {fname}") + f.write(dp.to_table()) + f.write("\n") + + +def dump_dp(dp: SsvcDecisionPoint, path: str = None) -> None: + if path is None: + # act like we're in a json list + lines = [f" {line}" for line in dp.to_json(indent=2).split("\n")] + print("\n".join(lines) + ",") + else: basename = dp.name.strip().lower().replace(" ", "_") json_fname = f"{path}/{basename}.json" - with open(json_fname, "w") as f: - print(f"Writing {json_fname}") - f.write(dp.to_json(indent=2)) - f.write("\n") + write_json(json_fname, dp) table_fname = f"{path}/{basename}.md" - with open(table_fname, "w") as f: - print(f"Writing {table_fname}") - f.write(dp.to_table()) - f.write("\n") + write_markdown_table(table_fname, dp) + + +def group_to_jsonfiles(group: SsvcDecisionPointGroup, path: str = None) -> None: + for dp in group.decision_points: + dump_dp(dp, path) + + +def main(): + from ssvc.dp_groups.v1 import SSVCv1 + from ssvc.dp_groups.v2 import SSVCv2 + from ssvc.dp_groups.v2_1 import SSVCv2_1 + from ssvc.dp_groups.cvss.v1 import CVSSv1 + from ssvc.dp_groups.cvss.v2 import CVSSv2 + from ssvc.dp_groups.cvss.v3 import CVSSv3 + + # extract all decision points from the groups + dps = get_all_decision_points_from( + [SSVCv1, SSVCv2, SSVCv2_1, CVSSv1, CVSSv2, CVSSv3] + ) + print("[") + for dp in dps: + dump_dp(dp, None) + print("]") + + +if __name__ == "__main__": + main() From c7dc2892f8087188bb665bb9e406613fe291cb88 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Mon, 9 Oct 2023 10:22:48 -0400 Subject: [PATCH 41/53] relocate tools --- src/ssvc/doc_helpers/__init__.py | 14 ++++++++++++++ src/ssvc/{ => doc_helpers}/tools.py | 0 2 files changed, 14 insertions(+) create mode 100644 src/ssvc/doc_helpers/__init__.py rename src/ssvc/{ => doc_helpers}/tools.py (100%) diff --git a/src/ssvc/doc_helpers/__init__.py b/src/ssvc/doc_helpers/__init__.py new file mode 100644 index 00000000..dd6c1701 --- /dev/null +++ b/src/ssvc/doc_helpers/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +file: __init__.py +author: adh +created_at: 10/4/23 2:12 PM +""" + + +def main(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/ssvc/tools.py b/src/ssvc/doc_helpers/tools.py similarity index 100% rename from src/ssvc/tools.py rename to src/ssvc/doc_helpers/tools.py From 68b78d7b23628b9c53991998a794b6276ecbda44 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Mon, 9 Oct 2023 10:23:08 -0400 Subject: [PATCH 42/53] add High Value Asset decision point --- src/ssvc/decision_points/high_value_asset.py | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/ssvc/decision_points/high_value_asset.py diff --git a/src/ssvc/decision_points/high_value_asset.py b/src/ssvc/decision_points/high_value_asset.py new file mode 100644 index 00000000..381b00c7 --- /dev/null +++ b/src/ssvc/decision_points/high_value_asset.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +""" +Models a high value asset as a decision point. +""" +from ssvc.decision_points.base import SsvcDecisionPoint, SsvcDecisionPointValue + +YES = SsvcDecisionPointValue( + name="Yes", + key="Y", + description="System meets a high value asset definition.", +) + +NO = SsvcDecisionPointValue( + name="No", + key="N", + description="System does not meet a high value asset definition.", +) + +HIGH_VALUE_ASSET_1 = SsvcDecisionPoint( + name="High Value Asset", + description="Denotes whether a system meets a high value asset definition.", + namespace="SSVC", + key="HVA", + version="1.0.0", + values=( + NO, + YES, + ), +) + + +def main(): + print(HIGH_VALUE_ASSET_1.to_json(indent=2)) + + +if __name__ == "__main__": + main() From 52bf539bc35ac02a89c5f82dadb3cc3a058fd54e Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Tue, 10 Oct 2023 15:25:24 -0400 Subject: [PATCH 43/53] add pyproject.toml file --- src/pyproject.toml | 67 ++++++++++++++++++++++++++++++++++++++++++++ src/ssvc/_version.py | 0 2 files changed, 67 insertions(+) create mode 100644 src/pyproject.toml create mode 100644 src/ssvc/_version.py diff --git a/src/pyproject.toml b/src/pyproject.toml new file mode 100644 index 00000000..1ad7177c --- /dev/null +++ b/src/pyproject.toml @@ -0,0 +1,67 @@ +[build-system] +# SetupTools +requires = ["setuptools>66", "setuptools-scm"] +build-backend = "setuptools.build_meta" +# Flit +#requires = ["flit_core >=3.2,<4"] +#build-backend = "flit_core.buildapi" +# Hatchling +#requires = ["hatchling"] +#build-backend = "hatchling.build" +# PDM-Backend +#requires = ["pdm-backend"] +#build-backend = "pdm.backend" + +[project] +name = "ssvc" +authors = [ + { name = "Allen D. Householder", email="adh@cert.org" }, + { name = "Vijay Sarvepalli", email="vssarvepalli@cert.org"} +] +description = "Tools for working with a Stakeholder Specific Vulnerability Categorization (SSVC)" +readme = {file="README.md", content-type="text/markdown"} +requires-python = ">=3.8" +keywords =["ssvc","vulnerability management","vulnerability management"] +license = {file="LICENSE.md"} +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Topic :: Security", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + "mkdocs","mkdocs-material","mkdocs-material-extensions","mkdocstrings","mkdocstrings-python", + "mkdocs-include-markdown-plugin", "pandas","scipy" +] +dynamic = ["version",] + +[project.scripts] +ssvc_csv_analyzer="ssvc.csv_analyzer:main" + +[project.urls] +"Homepage" = "https://certcc.github.io/SSVC" +"Project" = "https://github.com/CERTCC/SSVC" +"Bug Tracker" = "https://github.com/CERTCC/SSVC/issues" + +[tool.setuptools.packages.find] +where = ["."] # list of folders that contain the packages (["."] by default) +include = ["ssvc*"] # package names should match these glob patterns (["*"] by default) +exclude = ["test*"] # exclude packages matching these glob patterns (empty by default) +#namespaces = false # to disable scanning PEP 420 namespaces (true by default) + +[tool.setuptools_scm] +version_file = "ssvc/_version.py" + +#[tools.setuptools.dynamic] + +[tool.black] +line-length = 79 +target-version = ['py38', 'py39', 'py310', 'py311'] + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-ra -q" +testpaths = [ + "test", +] \ No newline at end of file diff --git a/src/ssvc/_version.py b/src/ssvc/_version.py new file mode 100644 index 00000000..e69de29b From 4e67e02137da4840f5a0cf9a2300ef302cdd9a21 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Tue, 10 Oct 2023 15:36:56 -0400 Subject: [PATCH 44/53] make build work --- src/pyproject.toml | 3 +++ src/ssvc/_version.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/pyproject.toml b/src/pyproject.toml index 1ad7177c..afd13c41 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -52,6 +52,9 @@ exclude = ["test*"] # exclude packages matching these glob patterns (empty by d [tool.setuptools_scm] version_file = "ssvc/_version.py" +root = ".." +relative_to = "pyproject.toml" + #[tools.setuptools.dynamic] diff --git a/src/ssvc/_version.py b/src/ssvc/_version.py index e69de29b..8ba9344e 100644 --- a/src/ssvc/_version.py +++ b/src/ssvc/_version.py @@ -0,0 +1,16 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple, Union + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '2.1.2.dev55+gd1f4663.d20231010' +__version_tuple__ = version_tuple = (2, 1, 2, 'dev55', 'gd1f4663.d20231010') From 83434f90f6dc36aec333492156047f77392836a3 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Tue, 10 Oct 2023 15:41:53 -0400 Subject: [PATCH 45/53] move analyzer to ssvc package --- docs/reference/code/analyze_csv.md | 4 +++- src/ssvc/_version.py | 4 ++-- src/{analyze_csv.py => ssvc/csv_analyzer.py} | 8 ++++---- src/test/test_analyze_csv.py | 4 +--- 4 files changed, 10 insertions(+), 10 deletions(-) rename src/{analyze_csv.py => ssvc/csv_analyzer.py} (98%) diff --git a/docs/reference/code/analyze_csv.md b/docs/reference/code/analyze_csv.md index bb886969..1f47e1ab 100644 --- a/docs/reference/code/analyze_csv.md +++ b/docs/reference/code/analyze_csv.md @@ -1,2 +1,4 @@ -::: analyze_csv +# SSVC CSV Analyzer + +::: ssvc.csv_analyzer diff --git a/src/ssvc/_version.py b/src/ssvc/_version.py index 8ba9344e..c157fc07 100644 --- a/src/ssvc/_version.py +++ b/src/ssvc/_version.py @@ -12,5 +12,5 @@ __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -__version__ = version = '2.1.2.dev55+gd1f4663.d20231010' -__version_tuple__ = version_tuple = (2, 1, 2, 'dev55', 'gd1f4663.d20231010') +__version__ = version = '2.1.2.dev56+g4e67e02.d20231010' +__version_tuple__ = version_tuple = (2, 1, 2, 'dev56', 'g4e67e02.d20231010') diff --git a/src/analyze_csv.py b/src/ssvc/csv_analyzer.py similarity index 98% rename from src/analyze_csv.py rename to src/ssvc/csv_analyzer.py index 21c22a5a..0ddc9525 100644 --- a/src/analyze_csv.py +++ b/src/ssvc/csv_analyzer.py @@ -3,7 +3,7 @@ This module provides a script for analyzing an SSVC tree csv file. ```shell -usage: analyze_csv.py [-h] [--outcol OUTCOL] [--permutation] csvfile +usage: csv_analyzer.py [-h] [--outcol OUTCOL] [--permutation] csvfile Analyze an SSVC tree csv file @@ -27,7 +27,7 @@ ``` Analyze the csv file: ```shell - $ python analyze_csv.py test.csv + $ python csv_analyzer.py test.csv Feature Importance after Dropping Each Feature in test.csv feature feature_importance @@ -41,13 +41,13 @@ """ import argparse +import re import sys import pandas as pd -import re -from sklearn.tree import DecisionTreeClassifier import sklearn.inspection from sklearn.base import clone +from sklearn.tree import DecisionTreeClassifier def _col_norm(c: str) -> str: diff --git a/src/test/test_analyze_csv.py b/src/test/test_analyze_csv.py index 115c460d..742ee25f 100644 --- a/src/test/test_analyze_csv.py +++ b/src/test/test_analyze_csv.py @@ -1,10 +1,8 @@ -import contextlib -import io import unittest import pandas as pd -import analyze_csv as acsv +from ssvc import csv_analyzer as acsv class MyTestCase(unittest.TestCase): From 27e207e93c105d8a222558d6dcb07407c895e1ae Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 12 Oct 2023 10:11:42 -0400 Subject: [PATCH 46/53] add dataclasses-json to requirements --- requirements.txt | 5 +++-- src/pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index e72e7d93..e2c5670e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,6 @@ mkdocs-material-extensions mkdocstrings mkdocstrings-python mkdocs-print-site-plugin -pandas~=2.1.1 -scikit-learn~=1.3.1 \ No newline at end of file +dataclasses-json +pandas +scikit-learn diff --git a/src/pyproject.toml b/src/pyproject.toml index afd13c41..4c5aabc5 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -32,7 +32,7 @@ classifiers = [ ] dependencies = [ "mkdocs","mkdocs-material","mkdocs-material-extensions","mkdocstrings","mkdocstrings-python", - "mkdocs-include-markdown-plugin", "pandas","scipy" + "mkdocs-include-markdown-plugin", "pandas","scipy", "dataclasses-json" ] dynamic = ["version",] From 9c95b650fc4587f18e13f107081896bd7d9d81aa Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Thu, 12 Oct 2023 11:35:14 -0400 Subject: [PATCH 47/53] add new json schemas for decision points and dp groups --- data/schema/Decision_Point.schema.json | 60 +++++++++++++++ data/schema/Decision_Point_Group.schema.json | 79 ++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 data/schema/Decision_Point.schema.json create mode 100644 data/schema/Decision_Point_Group.schema.json diff --git a/data/schema/Decision_Point.schema.json b/data/schema/Decision_Point.schema.json new file mode 100644 index 00000000..eef6d770 --- /dev/null +++ b/data/schema/Decision_Point.schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Decision Point schema definition", + "$id": "https://certcc.github.io/SSVC/docs/reference/schema/Decision_Point.schema.json", + "description": "Decision points are the basic building blocks of SSVC decision functions. Individual decision points describe a single aspect of the input to a decision function.", + "type": "object", + "additionalProperties": false, + "properties": { + "namespace": { + "type": "string", + "description": "Namespace (a short, unique string): For example, \"ssvc\" or \"cvss\" to indicate the source of the decision point" + }, + "version": { + "type": "string", + "description": "Version (a semantic version string) that identifies this object" + }, + "key": { + "type": "string", + "description": "A key (a short, unique string) that can be used to identify the Decision Point/Decision Point value in a shorthand way" + }, + "name": { + "type": "string", + "description": "A short label that captures the description of the Decision Point or the Group of Decision Points." + }, + "description": { + "type": "string", + "description": "Description of the Decision Point or the Group of Decision Points." + }, + "values": { + "description": "Decision Point Values are valid results from a Decision Point", + "uniqueItems": true, + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "A key (a short, unique string) that can be used to identify the Decision Point/Decision Point value in a shorthand way" + }, + "name": { + "type": "string", + "description": "A short label that captures the description of the Decision Point or the Group of Decision Points." + }, + "description": { + "type": "string", + "description": "Description of the Decision Point or the Group of Decision Points." + } + } + } + } + }, + "required": [ + "namespace", + "version", + "key", + "name", + "description", + "values" + ] +} diff --git a/data/schema/Decision_Point_Group.schema.json b/data/schema/Decision_Point_Group.schema.json new file mode 100644 index 00000000..47f43e29 --- /dev/null +++ b/data/schema/Decision_Point_Group.schema.json @@ -0,0 +1,79 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Decision Points Group schema definition", + "$id": "https://certcc.github.io/SSVC/docs/reference/schema/Decision_Point_Group.schema.json", + "type": "object", + "additionalProperties": false, + "properties": { + "version": { + "type": "string", + "description": "Version (a semantic version string) that identifies this object" + }, + "name": { + "type": "string", + "description": "A short label that captures the description of the Decision Point or the Group of Decision Points." + }, + "description": { + "type": "string", + "description": "Description of the Decision Point or the Group of Decision Points." + }, + "decision_points": { + "description": "Decision points are the basic building blocks of SSVC decision functions. Individual decision points describe a single aspect of the input to a decision function.", + "additionalProperties": false, + "type": "array", + "items": { + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "Namespace (a short, unique string): For example, \"ssvc\" or \"cvss\" to indicate the source of the decision point" + }, + "version": { + "type": "string", + "description": "Version (a semantic version string) that identifies this object" + }, + "key": { + "type": "string", + "description": "A key (a short, unique string) that can be used to identify the Decision Point/Decision Point value in a shorthand way" + }, + "name": { + "type": "string", + "description": "A short label that captures the description of the Decision Point or the Group of Decision Points." + }, + "description": { + "type": "string", + "description": "Description of the Decision Point or the Group of Decision Points." + }, + "values": { + "description": "Decision Point Values are valid results from a Decision Point", + "uniqueItems": true, + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "A key (a short, unique string) that can be used to identify the Decision Point/Decision Point value in a shorthand way" + }, + "name": { + "type": "string", + "description": "A short label that captures the description of the Decision Point or the Group of Decision Points." + }, + "description": { + "type": "string", + "description": "Description of the Decision Point or the Group of Decision Points." + } + } + } + } + } + } + } + }, + "required": [ + "version", + "name", + "description", + "decision_points" + ] +} From d35c1d40c215720f96943a46e9252c870492608a Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 12 Oct 2023 14:38:37 -0400 Subject: [PATCH 48/53] clean up example --- src/ssvc/decision_points/base.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index 1faf49cb..1d2ba3dc 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -13,10 +13,6 @@ from ssvc._mixins import _Base, _Keyed, _Namespaced, _Versioned -## notes -# based on https://gist.githubusercontent.com/sei-vsarvepalli/de2cd7dae33e1e1dc906d50846becb45/raw/2a85d08a4028f6dd3cc162d4ace3509e0458426f/Exploitation.json - - @dataclass_json @dataclass(kw_only=True) class SsvcDecisionPointValue(_Base, _Keyed): @@ -56,25 +52,26 @@ def make_row(items): def main(): - opt_none = SsvcDecisionPointValue( - name="None", key="N", description="No exploit available" - ) - opt_poc = SsvcDecisionPointValue( - name="PoC", key="P", description="Proof of concept exploit available" - ) - opt_active = SsvcDecisionPointValue( - name="Active", key="A", description="Active exploitation observed" - ) - opts = [opt_none, opt_poc, opt_active] - dp = SsvcDecisionPoint( - values=opts, + _comment="This is an optional comment that will be included in the object.", name="Exploitation", description="Is there an exploit available?", key="E", version="1.0.0", + values=( + SsvcDecisionPointValue( + name="None", key="N", description="No exploit available" + ), + SsvcDecisionPointValue( + name="PoC", + key="P", + description="Proof of concept exploit available", + ), + SsvcDecisionPointValue( + name="Active", key="A", description="Active exploitation observed" + ), + ), ) - print(dp.to_json(indent=2)) From f6d64a6c896ff14f12bf05f43d64148d1c3b5b3c Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 12 Oct 2023 14:38:51 -0400 Subject: [PATCH 49/53] add optional comment field --- src/ssvc/_mixins.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ssvc/_mixins.py b/src/ssvc/_mixins.py index 5460b8ea..48359e5d 100644 --- a/src/ssvc/_mixins.py +++ b/src/ssvc/_mixins.py @@ -4,9 +4,10 @@ author: adh created_at: 9/20/23 4:51 PM """ -from dataclasses import dataclass +from dataclasses import dataclass, field +from typing import Optional -from dataclasses_json import dataclass_json +from dataclasses_json import config, dataclass_json @dataclass_json @@ -39,6 +40,10 @@ class _Keyed: key: str +def exclude_if_none(value): + return value is None + + @dataclass_json @dataclass(kw_only=True) class _Base: @@ -48,6 +53,9 @@ class _Base: name: str description: str + _comment: Optional[str] = field( + default=None, metadata=config(exclude=exclude_if_none) + ) def main(): From 30af5f8b7f94535465fdea3a4b377b9cbdd0266c Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 12 Oct 2023 14:39:02 -0400 Subject: [PATCH 50/53] use comment field in generated text --- src/ssvc/doc_helpers/tools.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/ssvc/doc_helpers/tools.py b/src/ssvc/doc_helpers/tools.py index 76e5a822..8ba73af6 100644 --- a/src/ssvc/doc_helpers/tools.py +++ b/src/ssvc/doc_helpers/tools.py @@ -7,34 +7,39 @@ from ssvc.decision_points.base import SsvcDecisionPoint from ssvc.dp_groups.base import SsvcDecisionPointGroup, get_all_decision_points_from +_DISCLAIMER = "This file is auto-generated. Do not edit." -def write_json(fname: str, dp: SsvcDecisionPoint) -> None: - with open(fname, "w") as f: - print(f"Writing {fname}") - f.write(dp.to_json(indent=2)) - f.write("\n") +def _html_comment(s: str) -> str: + return f"\n" -def write_markdown_table(fname: str, dp: SsvcDecisionPoint) -> None: + +def write_file(fname: str, blob: str) -> None: with open(fname, "w") as f: print(f"Writing {fname}") - f.write(dp.to_table()) + f.write(blob) f.write("\n") def dump_dp(dp: SsvcDecisionPoint, path: str = None) -> None: + dp._comment = _DISCLAIMER + json_blob = dp.to_json(indent=2) + if path is None: # act like we're in a json list - lines = [f" {line}" for line in dp.to_json(indent=2).split("\n")] + lines = [f" {line}" for line in json_blob.split("\n")] print("\n".join(lines) + ",") else: basename = dp.name.strip().lower().replace(" ", "_") + basename += f"_{dp.version.replace('.', '_')}" json_fname = f"{path}/{basename}.json" - write_json(json_fname, dp) + write_file(json_fname, json_blob) table_fname = f"{path}/{basename}.md" - write_markdown_table(table_fname, dp) + md_str = _html_comment(_DISCLAIMER) + md_str += dp.to_table() + write_file(table_fname, md_str) def group_to_jsonfiles(group: SsvcDecisionPointGroup, path: str = None) -> None: From 004c587c68f741cb0fb58757143abf5b7bd59abf Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 12 Oct 2023 16:30:56 -0400 Subject: [PATCH 51/53] eliminate duplicates in get_all_decision_points_from --- src/ssvc/dp_groups/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ssvc/dp_groups/base.py b/src/ssvc/dp_groups/base.py index eec8b34a..706bec3c 100644 --- a/src/ssvc/dp_groups/base.py +++ b/src/ssvc/dp_groups/base.py @@ -37,13 +37,20 @@ def get_all_decision_points_from( list: A list of SsvcDecisionPoint objects. """ dps = [] + seen = set() + for group in glist: for dp in group.decision_points: if dp in dps: # skip duplicates continue + key = (dp.name, dp.version) + if key in seen: + # skip duplicates + continue # keep non-duplicates dps.append(dp) + seen.add(key) return tuple(dps) From ffdbd3182e892fc70c25fb3b7568179a86d19ca2 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 12 Oct 2023 16:58:25 -0400 Subject: [PATCH 52/53] add decision point registry for testing --- src/ssvc/decision_points/base.py | 34 +++++++++++++++++++++--- src/test/test_schema.py | 45 ++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/test/test_schema.py diff --git a/src/ssvc/decision_points/base.py b/src/ssvc/decision_points/base.py index 1d2ba3dc..c148f841 100644 --- a/src/ssvc/decision_points/base.py +++ b/src/ssvc/decision_points/base.py @@ -5,13 +5,31 @@ created_at: 9/20/23 10:07 AM """ -from dataclasses import dataclass -from typing import Tuple +import logging +from dataclasses import dataclass, field +from typing import ClassVar, Dict, Tuple -from dataclasses_json import dataclass_json +from dataclasses_json import config, dataclass_json from ssvc._mixins import _Base, _Keyed, _Namespaced, _Versioned +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +class _DecisionPoints: + """ + A collection of SSVC decision points. + """ + + registry: ClassVar[Dict[str, "SsvcDecisionPoint"]] = {} + + def __iter__(self): + return iter(self.registry.values()) + + +REGISTERED_DECISION_POINTS = _DecisionPoints() + @dataclass_json @dataclass(kw_only=True) @@ -32,6 +50,16 @@ class SsvcDecisionPoint(_Base, _Keyed, _Versioned, _Namespaced): values: Tuple[SsvcDecisionPointValue] + # this is only for our own use in Python land, exclude it from serialization + _fullname: str = field( + init=False, repr=False, default=None, metadata=config(exclude=lambda x: True) + ) + + def __post_init__(self): + self._fullname = f"{self.namespace} {self.name} v{self.version}" + logging.debug(f"Add {self._fullname} to registry") + REGISTERED_DECISION_POINTS.registry[self._fullname] = self + def to_table(self): rows = [] rows.append(f"{self.description}") diff --git a/src/test/test_schema.py b/src/test/test_schema.py new file mode 100644 index 00000000..3869b871 --- /dev/null +++ b/src/test/test_schema.py @@ -0,0 +1,45 @@ +import json +import unittest + +import jsonschema + +import ssvc.decision_points # noqa F401 +from ssvc.decision_points.base import REGISTERED_DECISION_POINTS + + +class MyTestCase(unittest.TestCase): + def test_validation(self): + schema = json.load(open("../../data/schema/Decision_Point.schema.json")) + import logging + + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + hdlr = logging.StreamHandler() + logger.addHandler(hdlr) + + # importing these causes the decision points to register themselves + from ssvc.dp_groups.v1 import SSVCv1 # noqa + from ssvc.dp_groups.v2 import SSVCv2 # noqa + from ssvc.dp_groups.v2_1 import SSVCv2_1 # noqa + from ssvc.dp_groups.cvss.v1 import CVSSv1 # noqa + from ssvc.dp_groups.cvss.v2 import CVSSv2 # noqa + from ssvc.dp_groups.cvss.v3 import CVSSv3 # noqa + + for dp in REGISTERED_DECISION_POINTS: + exp = None + as_json = dp.to_json() + loaded = json.loads(as_json) + + try: + jsonschema.validate(loaded, schema) + except jsonschema.exceptions.ValidationError as e: + exp = e + + self.assertIsNone(exp, f"Validation failed for {dp.name} {dp.version}") + logger.debug( + f"Validation passed for ({dp.namespace}) {dp.name} v{dp.version}" + ) + + +if __name__ == "__main__": + unittest.main() From 31de409580775cbc65cf1e5271efea9b436d09e1 Mon Sep 17 00:00:00 2001 From: "Allen D. Householder" Date: Thu, 12 Oct 2023 17:00:53 -0400 Subject: [PATCH 53/53] add jsonschema to requirements --- requirements.txt | 1 + src/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e2c5670e..8cd33e14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ mkdocs-print-site-plugin dataclasses-json pandas scikit-learn +jsonschema diff --git a/src/pyproject.toml b/src/pyproject.toml index 4c5aabc5..83b62dbb 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -32,7 +32,7 @@ classifiers = [ ] dependencies = [ "mkdocs","mkdocs-material","mkdocs-material-extensions","mkdocstrings","mkdocstrings-python", - "mkdocs-include-markdown-plugin", "pandas","scipy", "dataclasses-json" + "mkdocs-include-markdown-plugin", "pandas","scipy", "dataclasses-json", "jsonschema" ] dynamic = ["version",]