stidantic 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of stidantic might be problematic. Click here for more details.
- stidantic/bundle.py +29 -0
- stidantic/extension.py +115 -0
- stidantic/extensions/pap.py +66 -0
- stidantic/language.py +35 -0
- stidantic/marking.py +118 -0
- stidantic/sco.py +1526 -0
- stidantic/sdo.py +952 -0
- stidantic/sro.py +159 -0
- stidantic/types.py +441 -0
- stidantic/validators.py +20 -0
- stidantic/vocab.py +512 -0
- stidantic-0.1.3.dist-info/METADATA +203 -0
- stidantic-0.1.3.dist-info/RECORD +15 -0
- stidantic-0.1.3.dist-info/WHEEL +4 -0
- stidantic-0.1.3.dist-info/licenses/LICENSE +21 -0
stidantic/sro.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from typing import Literal, Self, Annotated
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic.functional_validators import model_validator
|
|
5
|
+
from pydantic.types import PositiveInt
|
|
6
|
+
from stidantic.types import StixRelationship, Identifier, StixType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# 5.1 Relationship
|
|
10
|
+
class Relationship(StixRelationship):
|
|
11
|
+
"""
|
|
12
|
+
The Relationship object is used to link together two SDOs or SCOs in order to describe how they are related to
|
|
13
|
+
each other. If SDOs and SCOs are considered "nodes" or "vertices" in the graph, the Relationship Objects (SROs)
|
|
14
|
+
represent "edges".
|
|
15
|
+
|
|
16
|
+
STIX defines many relationship types to link together SDOs and SCOs. These relationships are contained in the
|
|
17
|
+
"Relationships" table under each SDO and SCO definition. Relationship types defined in the specification SHOULD
|
|
18
|
+
be used to ensure consistency. An example of a specification-defined relationship is that an indicator indicates
|
|
19
|
+
a campaign. That relationship type is listed in the Relationships section of the Indicator SDO definition.
|
|
20
|
+
|
|
21
|
+
STIX also allows relationships from any SDO or SCO to any SDO or SCO that have not been defined in this
|
|
22
|
+
specification. These relationships MAY use the related-to relationship type or MAY use a user-defined
|
|
23
|
+
relationship type. As an example, a user might want to link malware directly to a tool.
|
|
24
|
+
They can do so using related-to to say that the Malware is related to the Tool but not describe how,
|
|
25
|
+
or they could use delivered-by (a user-defined name they determined) to indicate more detail.
|
|
26
|
+
|
|
27
|
+
Note that some relationships in STIX may seem like "shortcuts". For example, an Indicator doesn't really detect
|
|
28
|
+
a Campaign: it detects activity (Attack Patterns, Malware, Infrastructure, etc.) that are often used by that
|
|
29
|
+
campaign. While some analysts might want all of the source data and think that shortcuts are misleading,
|
|
30
|
+
in many cases it's helpful to provide just the key points (shortcuts) and leave out the low-level details.
|
|
31
|
+
In other cases, the low-level analysis may not be known or sharable, while the high-level analysis is.
|
|
32
|
+
For these reasons, relationships that might appear to be "shortcuts" are not excluded from STIX.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
type: Literal["relationship"] = "relationship" # pyright: ignore[reportIncompatibleVariableOverride]
|
|
36
|
+
# The name used to identify the type of Relationship. This value SHOULD be an exact value listed in the
|
|
37
|
+
# relationships for the source and target SDO, but MAY be any string.
|
|
38
|
+
relationship_type: StixType
|
|
39
|
+
# A description that provides more details and context about the Relationship,
|
|
40
|
+
# potentially including its purpose and its key characteristics.
|
|
41
|
+
description: str | None = None
|
|
42
|
+
# The id of the source (from) object. The value MUST be an ID reference to an SDO or SCO
|
|
43
|
+
# (i.e., it cannot point to an SRO, Bundle, Language Content, or Marking Definition).
|
|
44
|
+
source_ref: Identifier
|
|
45
|
+
# The id of the target (to) object. The value MUST be an ID reference to an SDO or SCO
|
|
46
|
+
# (i.e., it cannot point to an SRO, Bundle, Language Content, or Marking Definition).
|
|
47
|
+
target_ref: Identifier
|
|
48
|
+
# This optional timestamp represents the earliest time at which the Relationship between the objects exists.
|
|
49
|
+
# If this property is a future timestamp, at the time the start_time property is defined,
|
|
50
|
+
# then this represents an estimate by the producer of the intelligence of the earliest
|
|
51
|
+
# time at which relationship will be asserted to be true.
|
|
52
|
+
# If it is not specified, then the earliest time at which the relationship between the objects exists
|
|
53
|
+
# is not defined.
|
|
54
|
+
start_time: datetime | None = None
|
|
55
|
+
# The latest time at which the Relationship between the objects exists. If this property is a future timestamp,
|
|
56
|
+
# at the time the stop_time property is defined, then this represents an estimate by the producer of the
|
|
57
|
+
# intelligence of the latest time at which relationship will be asserted to be true.
|
|
58
|
+
# If stop_time is not specified, then the latest time at which the relationship between
|
|
59
|
+
# the objects exists is either not known, not disclosed, or has no defined stop time.
|
|
60
|
+
stop_time: datetime | None = None
|
|
61
|
+
|
|
62
|
+
@model_validator(mode="after")
|
|
63
|
+
def validate_start_stop_interval(self) -> Self:
|
|
64
|
+
"""
|
|
65
|
+
If start_time and stop_time are both defined, then stop_time MUST be later than the start_time value.
|
|
66
|
+
"""
|
|
67
|
+
if self.start_time and self.stop_time and self.start_time > self.stop_time:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
"the stop_time property MUST be greater than or equal to the timestamp in the start_time property"
|
|
70
|
+
)
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# 5.2 Sighting
|
|
75
|
+
class Sighting(StixRelationship):
|
|
76
|
+
"""
|
|
77
|
+
A Sighting denotes the belief that something in CTI (e.g., an indicator, malware, tool, threat actor, etc.)
|
|
78
|
+
was seen. Sightings are used to track who and what are being targeted, how attacks are carried out, and to
|
|
79
|
+
track trends in attack behavior.
|
|
80
|
+
|
|
81
|
+
The Sighting relationship object is a special type of SRO; it is a relationship that contains extra properties
|
|
82
|
+
not present on the Generic Relationship object. These extra properties are included to represent data specific
|
|
83
|
+
to sighting relationships (e.g., count, representing how many times something was seen), but for other purposes
|
|
84
|
+
a Sighting can be thought of as a Relationship with a name of "sighting-of". Sighting is captured as a relationship
|
|
85
|
+
because you cannot have a sighting unless you have something that has been sighted.
|
|
86
|
+
Sighting does not make sense without the relationship to what was sighted.
|
|
87
|
+
|
|
88
|
+
Sighting relationships relate three aspects of the sighting:
|
|
89
|
+
● What was sighted, such as the Indicator, Malware, Campaign, or other SDO (sighting_of_ref)
|
|
90
|
+
● Who sighted it and/or where it was sighted, represented as an Identity (where_sighted_refs)
|
|
91
|
+
● What was actually seen on systems and networks, represented as Observed Data (observed_data_refs)
|
|
92
|
+
|
|
93
|
+
What was sighted is required; a sighting does not make sense unless you say what you saw. Who sighted it,
|
|
94
|
+
where it was sighted, and what was actually seen are optional. In many cases it is not necessary to provide
|
|
95
|
+
that level of detail in order to provide value.
|
|
96
|
+
|
|
97
|
+
Sightings are used whenever any SDO has been "seen". In some cases, the object creator wishes to convey very little
|
|
98
|
+
information about the sighting; the details might be sensitive, but the fact that they saw a malware instance or
|
|
99
|
+
threat actor could still be very useful. In other cases, providing the details may be helpful or even necessary;
|
|
100
|
+
saying exactly which of the 1000 IP addresses in an indicator were sighted is helpful when tracking which of those
|
|
101
|
+
IPs is still malicious.
|
|
102
|
+
|
|
103
|
+
Sighting is distinct from Observed Data in that Sighting is an intelligence assertion ("I saw this threat actor")
|
|
104
|
+
while Observed Data is simply information ("I saw this file"). When you combine them by including the linked
|
|
105
|
+
Observed Data (observed_data_refs) from a Sighting, you can say "I saw this file, and that makes me think I saw
|
|
106
|
+
this threat actor".
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
type: Literal["sighting"] = "sighting" # pyright: ignore[reportIncompatibleVariableOverride]
|
|
110
|
+
# A description that provides more details and context about the Sighting.
|
|
111
|
+
description: str | None = None
|
|
112
|
+
# The beginning of the time window during which the SDO referenced by the sighting_of_ref property was sighted.
|
|
113
|
+
first_seen: datetime | None = None
|
|
114
|
+
# The end of the time window during which the SDO referenced by the sighting_of_ref property was sighted.
|
|
115
|
+
last_seen: datetime | None = None
|
|
116
|
+
# If present, this MUST be an integer between 0 and 999,999,999 inclusive and represents the number of times the
|
|
117
|
+
# SDO referenced by the sighting_of_ref property was sighted.
|
|
118
|
+
# Observed Data has a similar property called number_observed, which refers to the number of times the data was
|
|
119
|
+
# observed. These counts refer to different concepts and are distinct.
|
|
120
|
+
# For example, a single sighting of a DDoS bot might have many millions of observations of the network traffic
|
|
121
|
+
# that it generates. Thus, the Sighting count would be 1 (the bot was observed once) but the Observed Data
|
|
122
|
+
# number_observed would be much higher.
|
|
123
|
+
# As another example, a sighting with a count of 0 can be used to express that an indicator was not seen at all.
|
|
124
|
+
count: PositiveInt | None = None
|
|
125
|
+
# An ID reference to the SDO that was sighted (e.g., Indicator or Malware).
|
|
126
|
+
# For example, if this is a Sighting of an Indicator, that Indicator’s ID would be the value of this property.
|
|
127
|
+
# This property MUST reference only an SDO.
|
|
128
|
+
sighting_of_ref: Identifier | None = None
|
|
129
|
+
# A list of ID references to the Observed Data objects that contain the raw cyber data for this Sighting.
|
|
130
|
+
# For example, a Sighting of an Indicator with an IP address could include the Observed Data for the
|
|
131
|
+
# network connection that the Indicator was used to detect.
|
|
132
|
+
# This property MUST reference only Observed Data SDOs.
|
|
133
|
+
observed_data_refs: list[Identifier] | None = None
|
|
134
|
+
# A list of ID references to the Identity or Location objects describing the entities or types of entities
|
|
135
|
+
# that saw the sighting.
|
|
136
|
+
# Omitting the where_sighted_refs property does not imply that the sighting was seen by the object creator.
|
|
137
|
+
# To indicate that the sighting was seen by the object creator, an Identity representing the object creator
|
|
138
|
+
# should be listed in where_sighted_refs.
|
|
139
|
+
# This property MUST reference only Identity or Location SDOs.
|
|
140
|
+
where_sighted_refs: list[Identifier] | None = None
|
|
141
|
+
# The summary property indicates whether the Sighting should be considered summary data.
|
|
142
|
+
# Summary data is an aggregation of previous Sightings reports and should not be considered primary source data.
|
|
143
|
+
# Default value is false.
|
|
144
|
+
summary: bool | None = False
|
|
145
|
+
|
|
146
|
+
@model_validator(mode="after")
|
|
147
|
+
def validate_first_last_interval(self) -> Self:
|
|
148
|
+
"""
|
|
149
|
+
If this property and the first_seen property are both defined, then this property
|
|
150
|
+
MUST be greater than or equal to the timestamp in the first_seen property.
|
|
151
|
+
"""
|
|
152
|
+
if self.first_seen and self.last_seen and self.first_seen > self.last_seen:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"the last_seen property MUST be greater than or equal to the timestamp in the first_seen property"
|
|
155
|
+
)
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
SROs = Annotated[(Relationship | Sighting), Field(discriminator="type")]
|
stidantic/types.py
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from re import compile
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Annotated, Literal, Any, Self, Type, get_args # pyright: ignore[reportDeprecated]
|
|
5
|
+
from annotated_types import Ge, Le
|
|
6
|
+
from typing_extensions import TypedDict
|
|
7
|
+
|
|
8
|
+
from pydantic.functional_validators import AfterValidator, model_validator
|
|
9
|
+
from pydantic.networks import AnyUrl, UrlConstraints
|
|
10
|
+
from pydantic.types import Base64Bytes, StringConstraints
|
|
11
|
+
from pydantic_core import CoreSchema, core_schema
|
|
12
|
+
from pydantic import (
|
|
13
|
+
BaseModel,
|
|
14
|
+
ConfigDict,
|
|
15
|
+
Field,
|
|
16
|
+
GetCoreSchemaHandler,
|
|
17
|
+
SerializeAsAny,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from stidantic.validators import (
|
|
21
|
+
validate_bin_field,
|
|
22
|
+
validate_hex_field,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Common constraints on Stix dictionnary keys.
|
|
27
|
+
StixKeyPattern = compile(r"^[a-zA-Z0-9\-\_]+$")
|
|
28
|
+
StixKeyConstraint = StringConstraints(max_length=250, pattern=StixKeyPattern)
|
|
29
|
+
StixKey = Annotated[str, StixKeyConstraint]
|
|
30
|
+
|
|
31
|
+
# Common constraints on Stix type names.
|
|
32
|
+
StixTypePattern = compile(r"^[a-zA-Z0-9\-]+$")
|
|
33
|
+
StixTypeConstraint = StringConstraints(pattern=StixTypePattern)
|
|
34
|
+
StixType = Annotated[str, StixTypeConstraint]
|
|
35
|
+
|
|
36
|
+
# Common constraints on Stix property names.
|
|
37
|
+
StixPropPattern = compile(r"^[a-zA-Z0-9\_]+$")
|
|
38
|
+
StixPropConstraint = StringConstraints(
|
|
39
|
+
min_length=3, max_length=250, pattern=StixPropPattern
|
|
40
|
+
)
|
|
41
|
+
StixProp = Annotated[str, StixPropConstraint]
|
|
42
|
+
|
|
43
|
+
# 2.1 Binary
|
|
44
|
+
# The binary data type represents a sequence of bytes. In order to allow pattern matching on custom objects,
|
|
45
|
+
# for all properties that use the binary type, the property name MUST end with '_bin'.
|
|
46
|
+
type StixBinary = Annotated[Base64Bytes, AfterValidator(validate_bin_field)]
|
|
47
|
+
|
|
48
|
+
# 2.8 Hexadecimal
|
|
49
|
+
# The hex data type encodes an array of octets (8-bit bytes) as hexadecimal. The string MUST consist of an even number
|
|
50
|
+
# of hexadecimal characters, which are the digits '0' through '9' and the lower-case letters 'a' through 'f'. In order
|
|
51
|
+
# to allow pattern matching on custom objects, for all properties that use the hex type,
|
|
52
|
+
# the property name MUST end with '_hex'.
|
|
53
|
+
type StixHex = Annotated[str, AfterValidator(validate_hex_field)]
|
|
54
|
+
|
|
55
|
+
# 2.3 Dictionnary
|
|
56
|
+
# Dictionary keys MUST be unique in each dictionary, MUST be in ASCII, and are limited to the characters
|
|
57
|
+
# a-z (lowercase ASCII), A-Z (uppercase ASCII), numerals 0-9, hyphen (-), and underscore (_).
|
|
58
|
+
# Dictionary keys MUST be no longer than 250 ASCII characters in length and SHOULD be lowercase.
|
|
59
|
+
type StixDict = dict[
|
|
60
|
+
StixKey,
|
|
61
|
+
Any, # pyright: ignore[reportExplicitAny]
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# A URL reference to an external resource [RFC3986].
|
|
66
|
+
type StixUrl = Annotated[AnyUrl, UrlConstraints(preserve_empty_path=True)]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# 2.9 Identifier
|
|
70
|
+
class Identifier(str):
|
|
71
|
+
@classmethod
|
|
72
|
+
def __get_pydantic_core_schema__(
|
|
73
|
+
cls,
|
|
74
|
+
source_type: Any, # pyright: ignore[reportExplicitAny, reportAny]
|
|
75
|
+
handler: GetCoreSchemaHandler,
|
|
76
|
+
) -> CoreSchema:
|
|
77
|
+
return core_schema.no_info_after_validator_function(cls, handler(str))
|
|
78
|
+
|
|
79
|
+
def get_type(self: str) -> str:
|
|
80
|
+
return self.split("--", maxsplit=1)[0]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class StixCore(BaseModel):
|
|
84
|
+
model_config = ConfigDict( # pyright: ignore[reportUnannotatedClassAttribute]
|
|
85
|
+
use_enum_values=True,
|
|
86
|
+
validate_by_alias=True,
|
|
87
|
+
validate_by_name=True,
|
|
88
|
+
frozen=True,
|
|
89
|
+
extra="allow",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# 2.7 Hashes
|
|
94
|
+
class Hashes(StixCore, extra="allow"):
|
|
95
|
+
"""
|
|
96
|
+
The Hashes type represents one or more cryptographic hashes, as a special set of key/value pairs.
|
|
97
|
+
Accordingly, the name of each hashing algorithm MUST be specified as a key in the dictionary
|
|
98
|
+
and MUST identify the name of the hashing algorithm used to generate the corresponding value.
|
|
99
|
+
This name SHOULD come from one of the values defined in the hash-algorithm-ov open vocabulary.
|
|
100
|
+
|
|
101
|
+
To enhance compatibility, the SHA-256 hash SHOULD be used whenever possible.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# Specifies the MD5 message digest algorithm. The corresponding hash string for this
|
|
105
|
+
# value MUST be a valid MD5 message digest as defined in [RFC1321].
|
|
106
|
+
md5: Annotated[str | None, Field(serialization_alias="MD5")] = None
|
|
107
|
+
# Specifies the SHA-1 (secure-hash algorithm 1) cryptographic hash function.
|
|
108
|
+
# The corresponding hash string for this value MUST be a valid SHA-1 message digest as defined in [RFC3174].
|
|
109
|
+
sha1: Annotated[str | None, Field(serialization_alias="SHA-1")] = None
|
|
110
|
+
# Specifies the SHA-256 cryptographic hash function (part of the SHA2 family).
|
|
111
|
+
# The corresponding hash string for this value MUST be a valid SHA-256 message digest as defined in [RFC6234].
|
|
112
|
+
sha256: Annotated[str | None, Field(serialization_alias="SHA-256")] = None
|
|
113
|
+
# Specifies the SHA-512 cryptographic hash function (part of the SHA2 family).
|
|
114
|
+
# The corresponding hash string for this value MUST be a valid SHA-512 message digest as defined in [RFC6234].
|
|
115
|
+
sha512: Annotated[str | None, Field(serialization_alias="SHA-512")] = None
|
|
116
|
+
# Specifies the SHA3-256 cryptographic hash function. The corresponding hash string
|
|
117
|
+
# for this value MUST be a valid SHA3-256 message digest as defined in [FIPS202].
|
|
118
|
+
sha3_256: Annotated[str | None, Field(serialization_alias="SHA3-256")] = None
|
|
119
|
+
# Specifies the SHA3-512 cryptographic hash function. The corresponding hash string
|
|
120
|
+
# for this value MUST be a valid SHA3-512 message digest as defined in [FIPS202].
|
|
121
|
+
sha3_512: Annotated[str | None, Field(serialization_alias="SHA3-512")] = None
|
|
122
|
+
# Specifies the ssdeep fuzzy hashing algorithm. The corresponding hash string for this
|
|
123
|
+
# value MUST be a valid piecewise hash as defined in the [SSDEEP] specification.
|
|
124
|
+
ssdeep: Annotated[str | None, Field(serialization_alias="SSDEEP")] = None
|
|
125
|
+
# Specifies the TLSH fuzzy hashing algorithm. The corresponding hash string for this
|
|
126
|
+
# value MUST be a valid 35 byte long hash as defined in the [TLSH] specification.
|
|
127
|
+
tlsh: Annotated[str | None, Field(serialization_alias="TLSH")] = None
|
|
128
|
+
|
|
129
|
+
@model_validator(mode="after")
|
|
130
|
+
def lang_or_marking_ref(self) -> "Hashes":
|
|
131
|
+
"""
|
|
132
|
+
Dictionary keys MUST be unique in each hashes property, MUST be in ASCII, and are limited to the
|
|
133
|
+
characters a-z (lowercase ASCII), A-Z (uppercase ASCII), numerals 0-9, hyphen (-), and underscore (_).
|
|
134
|
+
Dictionary keys MUST have a minimum length of 3 ASCII characters
|
|
135
|
+
and MUST be no longer than 250 ASCII characters in length.
|
|
136
|
+
The value MUST be a string in the appropriate format defined by the hash type indicated in the dictionary key.
|
|
137
|
+
"""
|
|
138
|
+
if self.__pydantic_extra__ and any(
|
|
139
|
+
not (len(key) > 250 or len(key) < 3 or StixKeyPattern.match(key))
|
|
140
|
+
for key in self.__pydantic_extra__.keys()
|
|
141
|
+
):
|
|
142
|
+
raise ValueError("Invalid extra hash key.")
|
|
143
|
+
return self
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# 2.5 External Reference
|
|
147
|
+
class ExternalReference(StixCore):
|
|
148
|
+
"""
|
|
149
|
+
External references are used to describe pointers to information represented outside of STIX.
|
|
150
|
+
For example, a Malware object could use an external reference to indicate an ID for that malware
|
|
151
|
+
in an external database or a report could use references to represent source material.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
# The name of the source that the external-reference is defined within (system, registry, organization, etc.).
|
|
155
|
+
source_name: str
|
|
156
|
+
# A human readable description.
|
|
157
|
+
description: str | None = None
|
|
158
|
+
# A URL reference to an external resource [RFC3986].
|
|
159
|
+
url: StixUrl | None = None
|
|
160
|
+
# Specifies a dictionary of hashes for the contents of the url. This SHOULD be provided when the url property is
|
|
161
|
+
# present. Dictionary keys MUST come from one of the entries listed in the hash-algorithm-ov open vocabulary.
|
|
162
|
+
# As stated in Section 2.7, to ensure interoperability, a SHA-256 hash SHOULD be included whenever possible.
|
|
163
|
+
hashes: Hashes | None = None
|
|
164
|
+
# An identifier for the external reference content.
|
|
165
|
+
external_id: str | None = None
|
|
166
|
+
|
|
167
|
+
@model_validator(mode="before")
|
|
168
|
+
def at_least_one(cls, values: dict[str, str]) -> dict[str, str]:
|
|
169
|
+
"""
|
|
170
|
+
In addition to the source_name property, at least one of the description, url,
|
|
171
|
+
or external_id properties MUST be present.
|
|
172
|
+
"""
|
|
173
|
+
for value in values.values():
|
|
174
|
+
if value:
|
|
175
|
+
return values
|
|
176
|
+
raise ValueError("Missing at least one hash value.")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# 2.11 Kill Chain Phase
|
|
180
|
+
class KillChainPhase(StixCore):
|
|
181
|
+
"""
|
|
182
|
+
The kill-chain-phase represents a phase in a kill chain, which describes the
|
|
183
|
+
various phases an attacker may undertake in order to achieve their objectives.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
# The name of the kill chain. The value of this property SHOULD be all lowercase
|
|
187
|
+
# and SHOULD use hyphens instead of spaces or underscores as word separators.
|
|
188
|
+
kill_chain_name: str
|
|
189
|
+
# The name of the phase in the kill chain. The value of this property SHOULD be all
|
|
190
|
+
# lowercase and SHOULD use hyphens instead of spaces or underscores as word separators.
|
|
191
|
+
phase_name: str
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# 7.2.3 Granular Markings
|
|
195
|
+
class GranularMarking(StixCore):
|
|
196
|
+
"""
|
|
197
|
+
The granular-marking type defines how the list of marking-definition objects referenced by
|
|
198
|
+
the marking_refs property to apply to a set of content identified by
|
|
199
|
+
the list of selectors in the selectors property.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
# The lang property identifies the language of the text identified by this marking. The value of the lang property,
|
|
203
|
+
# if present, MUST be an [RFC5646] language code. If the marking_ref property is not present, this property MUST
|
|
204
|
+
# be present. If the marking_ref property is present, this property MUST NOT be present.
|
|
205
|
+
lang: str | None = None
|
|
206
|
+
# The marking_ref property specifies the ID of the marking-definition object that describes the marking.
|
|
207
|
+
# If the lang property is not present, this property MUST be present. If the lang property is present,
|
|
208
|
+
# this property MUST NOT be present.
|
|
209
|
+
marking_ref: Identifier | None = None
|
|
210
|
+
# The selectors property specifies a list of selectors for content contained within the STIX Object in which this
|
|
211
|
+
# property appears. Selectors MUST conform to the syntax defined below. The marking-definition referenced in
|
|
212
|
+
# the marking_ref property is applied to the content selected by the selectors in this list. The [RFC5646] language
|
|
213
|
+
# code specified by the lang property is applied to the content selected by the selectors in this list.
|
|
214
|
+
selectors: list[str] | None = None
|
|
215
|
+
# Selector Syntax:
|
|
216
|
+
# Selectors contained in the selectors list are strings that consist of multiple components that MUST be separated
|
|
217
|
+
# by the . character. Each component MUST be one of:
|
|
218
|
+
# ● A property name or dictionary key, e.g., description, or;
|
|
219
|
+
# ● A zero-based list index, specified as a non-negative integer in square brackets, e.g., [4]
|
|
220
|
+
# Selectors denote path traversals: the root of each selector is the STIX Object
|
|
221
|
+
# that the granular_markings property appears in.
|
|
222
|
+
# Starting from that root, for each component in the selector, properties and list items are traversed.
|
|
223
|
+
# When the complete list has been traversed, the value of the content is considered selected.
|
|
224
|
+
# Selectors MUST refer to properties or list items that are actually present on the marked object.
|
|
225
|
+
|
|
226
|
+
@model_validator(mode="after")
|
|
227
|
+
def lang_or_marking_ref(self) -> "GranularMarking":
|
|
228
|
+
if self.lang and self.marking_ref:
|
|
229
|
+
raise ValueError(
|
|
230
|
+
"Both lang and marking_ref properties MUST NOT be present."
|
|
231
|
+
)
|
|
232
|
+
if not self.lang and not self.marking_ref:
|
|
233
|
+
raise ValueError("Either lang or marking_ref property MUST be present.")
|
|
234
|
+
return self
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# 10.5 Extension Types Enumeration
|
|
238
|
+
class ExtensionType(Enum):
|
|
239
|
+
new_sdo = "new-sdo"
|
|
240
|
+
new_sco = "new-sco"
|
|
241
|
+
new_sro = "new-sro"
|
|
242
|
+
property_extension = "property-extension"
|
|
243
|
+
toplevel_property_extension = "toplevel-property-extension"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class Extension(StixCore):
|
|
247
|
+
extension_type: ExtensionType | None = None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class ExtensionsDict(TypedDict, total=False, extra_items=SerializeAsAny[Extension]): ... # noqa: E701
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# 3.2 Common Properties
|
|
254
|
+
class StixCommon(StixCore):
|
|
255
|
+
"""
|
|
256
|
+
This section defines the common properties that MAY exist on a STIX Objects. While some STIX Objects use all
|
|
257
|
+
of these common properties, not all object types do.
|
|
258
|
+
Each type of STIX Object defines which common properties are required, which are optional,
|
|
259
|
+
and which are not in use.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
# All STIX Objects and the STIX Bundle Object have an id property that uniquely identifies each instance
|
|
263
|
+
# of the object.
|
|
264
|
+
# This id MUST meet the requirements of the identifier type (see section 2.9).
|
|
265
|
+
# For objects that support versioning, all objects with the same id are considered different versions
|
|
266
|
+
# of the same object and the version of the object is identified by its modified property.
|
|
267
|
+
id: Identifier
|
|
268
|
+
# The type property identifies the type of STIX Object.
|
|
269
|
+
type: str
|
|
270
|
+
# The value of this property MUST be 2.1 for STIX Objects defined according to this specification.
|
|
271
|
+
spec_version: Literal["2.1"] = "2.1"
|
|
272
|
+
# The revoked property is only used by STIX Objects that support versioning and indicates whether the object
|
|
273
|
+
# has been revoked.
|
|
274
|
+
revoked: bool | None = None
|
|
275
|
+
# The labels property specifies a set of terms used to describe this object.
|
|
276
|
+
# Where an object has a specific property defined in the specification for characterizing
|
|
277
|
+
# subtypes of that object, the labels property MUST NOT be used for that purpose.
|
|
278
|
+
labels: list[str] | None = None
|
|
279
|
+
# The confidence property identifies the confidence that the creator has in the correctness of their data.
|
|
280
|
+
# The confidence value MUST be a number in the range of 0-100.
|
|
281
|
+
confidence: Annotated[int, Ge(0), Le(100)] | None = None
|
|
282
|
+
# The lang property identifies the language of the text content in this object.
|
|
283
|
+
# When present, it MUST be a language code conformant to [RFC5646].
|
|
284
|
+
# RFC5646 does not enforce ISO 639-1 alpha-2 or ISO 639-3 alpha-3 formats.
|
|
285
|
+
lang: str | None = None
|
|
286
|
+
# The external_references property specifies a list of external references which refers to non-STIX information.
|
|
287
|
+
external_references: list[ExternalReference] | None = None
|
|
288
|
+
# The object_marking_refs property specifies a list of id properties of marking-definition objects that apply
|
|
289
|
+
# to this object.
|
|
290
|
+
object_marking_refs: list[Identifier] | None = None
|
|
291
|
+
# The granular_markings property specifies a list of granular markings applied to this object.
|
|
292
|
+
granular_markings: list[GranularMarking] | None = None
|
|
293
|
+
# Specifies any extensions of the object, as a dictionary.
|
|
294
|
+
# Dictionary keys SHOULD be the id of a STIX Extension object or the name of a predefined object extension found
|
|
295
|
+
# in this specification, depending on the type of extension being used.
|
|
296
|
+
# The corresponding dictionary values MUST contain the contents of the extension instance.
|
|
297
|
+
# Each extension dictionary MAY contain the property extension_type.
|
|
298
|
+
# The value of this property MUST come from the extension-type-enum enumeration.
|
|
299
|
+
# If the extension_type property is not present, then this is a predefined extension which
|
|
300
|
+
# does not use the extension facility described in section 7.3.
|
|
301
|
+
# When this extension facility is used the extension_type property MUST be present.
|
|
302
|
+
extensions: ExtensionsDict | dict[str, Extension] | None = None
|
|
303
|
+
|
|
304
|
+
@classmethod
|
|
305
|
+
def register_new_extension(
|
|
306
|
+
cls,
|
|
307
|
+
definition: "StixCommon", # pyright: ignore[reportUnusedParameter]
|
|
308
|
+
extension: Type[Extension], # pyright: ignore[reportUnusedParameter, reportDeprecated]
|
|
309
|
+
):
|
|
310
|
+
"""
|
|
311
|
+
Dynamically update the extensions property so that new classes get deserialized based on the extension key.
|
|
312
|
+
"""
|
|
313
|
+
annotations = get_args(cls.model_fields["extensions"].annotation)[0] # pyright: ignore[reportAny, reportUnusedVariable]
|
|
314
|
+
CustomExtensionsDict = TypedDict(
|
|
315
|
+
"CustomExtensionsDict",
|
|
316
|
+
{
|
|
317
|
+
definition.id: extension, # pyright: ignore[reportGeneralTypeIssues]
|
|
318
|
+
**annotations.__annotations__, # pyright: ignore[reportGeneralTypeIssues]
|
|
319
|
+
},
|
|
320
|
+
total=False,
|
|
321
|
+
extra_items=SerializeAsAny[Extension],
|
|
322
|
+
)
|
|
323
|
+
cls.model_fields["extensions"].annotation = CustomExtensionsDict | None # pyright: ignore[reportAttributeAccessIssue]
|
|
324
|
+
cls.model_rebuild(force=True) # pyright: ignore[reportUnusedCallResult]
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class StixDomain(StixCommon):
|
|
328
|
+
# The created_by_ref property specifies the id property of the identity object that describes the entity that
|
|
329
|
+
# created this object.
|
|
330
|
+
created_by_ref: Identifier | None = None
|
|
331
|
+
# The created property represents the time at which the object was originally created.
|
|
332
|
+
# The created property MUST NOT be changed when creating a new version of the object.
|
|
333
|
+
created: datetime
|
|
334
|
+
# The modified property is only used by STIX Objects that support versioning and represents
|
|
335
|
+
# the time that this particular version of the object was last modified.
|
|
336
|
+
# Object creators MUST set the modified property when creating a new
|
|
337
|
+
# version of an object if the created property was set.
|
|
338
|
+
modified: datetime
|
|
339
|
+
|
|
340
|
+
@model_validator(mode="after")
|
|
341
|
+
def validate_modified_after_created(self) -> Self:
|
|
342
|
+
"""
|
|
343
|
+
If the created property is defined, then the value of the modified property for a given object version
|
|
344
|
+
MUST be later than or equal to the value of the created property.
|
|
345
|
+
"""
|
|
346
|
+
if self.created > self.modified:
|
|
347
|
+
raise ValueError(
|
|
348
|
+
"The modified property MUST be later than or equal to the value of the created property."
|
|
349
|
+
)
|
|
350
|
+
return self
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class StixObservable(StixCommon):
|
|
354
|
+
# This property defines whether or not the data contained within the object has been defanged.
|
|
355
|
+
defanged: bool | None = None
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class StixRelationship(StixCommon):
|
|
359
|
+
relationship_type: str
|
|
360
|
+
# The created property represents the time at which the object was originally created.
|
|
361
|
+
# The created property MUST NOT be changed when creating a new version of the object.
|
|
362
|
+
created: datetime
|
|
363
|
+
# The modified property is only used by STIX Objects that support versioning and represents
|
|
364
|
+
# the time that this particular version of the object was last modified.
|
|
365
|
+
# Object creators MUST set the modified property when creating a new
|
|
366
|
+
# version of an object if the created property was set.
|
|
367
|
+
modified: datetime
|
|
368
|
+
# The created_by_ref property specifies the id property of the identity object that describes the entity that
|
|
369
|
+
# created this object.
|
|
370
|
+
created_by_ref: Identifier | None = None
|
|
371
|
+
|
|
372
|
+
@model_validator(mode="after")
|
|
373
|
+
def validate_modified_after_created(self) -> Self:
|
|
374
|
+
"""
|
|
375
|
+
If the created property is defined, then the value of the modified property for a given object version
|
|
376
|
+
MUST be later than or equal to the value of the created property.
|
|
377
|
+
"""
|
|
378
|
+
if self.created > self.modified:
|
|
379
|
+
raise ValueError(
|
|
380
|
+
"The modified property MUST be later than or equal to the value of the created property."
|
|
381
|
+
)
|
|
382
|
+
return self
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class StixMeta(StixCommon):
|
|
386
|
+
type: str
|
|
387
|
+
# The created property represents the time at which the object was originally created.
|
|
388
|
+
# The created property MUST NOT be changed when creating a new version of the object.
|
|
389
|
+
created: datetime
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class StixLanguage(StixMeta):
|
|
393
|
+
# The created_by_ref property specifies the id property of the identity object that describes the entity that
|
|
394
|
+
# created this object.
|
|
395
|
+
created_by_ref: Identifier | None = None
|
|
396
|
+
# The modified property is only used by STIX Objects that support versioning and represents
|
|
397
|
+
# the time that this particular version of the object was last modified.
|
|
398
|
+
# Object creators MUST set the modified property when creating a new
|
|
399
|
+
# version of an object if the created property was set.
|
|
400
|
+
modified: datetime
|
|
401
|
+
|
|
402
|
+
@model_validator(mode="after")
|
|
403
|
+
def validate_modified_after_created(self) -> Self:
|
|
404
|
+
"""
|
|
405
|
+
If the created property is defined, then the value of the modified property for a given object version
|
|
406
|
+
MUST be later than or equal to the value of the created property.
|
|
407
|
+
"""
|
|
408
|
+
if self.created > self.modified:
|
|
409
|
+
raise ValueError(
|
|
410
|
+
"The modified property MUST be later than or equal to the value of the created property."
|
|
411
|
+
)
|
|
412
|
+
return self
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class StixMarking(StixMeta):
|
|
416
|
+
# The created_by_ref property specifies the id property of the identity object that describes the entity that
|
|
417
|
+
# created this object.
|
|
418
|
+
created_by_ref: Identifier | None = None
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class StixExtension(StixMeta):
|
|
422
|
+
# The created_by_ref property specifies the id property of the identity object that describes the entity that
|
|
423
|
+
# created this object.
|
|
424
|
+
created_by_ref: Identifier
|
|
425
|
+
# The modified property is only used by STIX Objects that support versioning and represents
|
|
426
|
+
# the time that this particular version of the object was last modified.
|
|
427
|
+
# Object creators MUST set the modified property when creating a new
|
|
428
|
+
# version of an object if the created property was set.
|
|
429
|
+
modified: datetime
|
|
430
|
+
|
|
431
|
+
@model_validator(mode="after")
|
|
432
|
+
def validate_modified_after_created(self) -> Self:
|
|
433
|
+
"""
|
|
434
|
+
If the created property is defined, then the value of the modified property for a given object version
|
|
435
|
+
MUST be later than or equal to the value of the created property.
|
|
436
|
+
"""
|
|
437
|
+
if self.created > self.modified:
|
|
438
|
+
raise ValueError(
|
|
439
|
+
"The modified property MUST be later than or equal to the value of the created property."
|
|
440
|
+
)
|
|
441
|
+
return self
|
stidantic/validators.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from pydantic import ValidationInfo
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def validate_identifier(value: str) -> str:
|
|
5
|
+
_type, _uuid = value.split("--")
|
|
6
|
+
if not _type and not _uuid:
|
|
7
|
+
raise ValueError("Invalid identifier format.")
|
|
8
|
+
return value
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def validate_bin_field(value: str, info: ValidationInfo) -> str:
|
|
12
|
+
if info.field_name and not info.field_name.endswith("_bin"):
|
|
13
|
+
raise ValueError("The property name MUST end with '_bin'.")
|
|
14
|
+
return value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_hex_field(value: str, info: ValidationInfo) -> str:
|
|
18
|
+
if info.field_name and not info.field_name.endswith("_hex"):
|
|
19
|
+
raise ValueError("The property name MUST end with '_hex'.")
|
|
20
|
+
return value
|