Add proper customization enforcement for open vocabs. This adds

a new OpenVocabProperty class.  It also requires a redesign of
HashesProperty and redoes general library support for hash
algorithms.
pull/1/head
Michael Chisholm 2020-07-09 20:13:53 -04:00
parent 62ee2b2b0a
commit c590de8ea5
27 changed files with 1046 additions and 351 deletions

95
stix2/hashes.py Normal file
View File

@ -0,0 +1,95 @@
"""
Library support for hash algorithms, independent of STIX specs.
"""
import enum
import re
class Hash(enum.Enum):
"""
Instances represent a hash algorithm, independent of STIX spec version.
Different spec versions may have different requirements for naming; this
allows us to refer to and use hash algorithms in a spec-agnostic way.
"""
MD5 = 0
MD6 = 1
RIPEMD160 = 2
SHA1 = 3
SHA224 = 4
SHA256 = 5
SHA384 = 6
SHA512 = 7
SHA3224 = 8
SHA3256 = 9
SHA3384 = 10
SHA3512 = 11
SSDEEP = 12
WHIRLPOOL = 13
TLSH = 14
# Regexes used to sanity check hash values. Could also be combined with the
# enum values themselves using enum definition tricks, but... this seems
# simpler.
_HASH_REGEXES = {
Hash.MD5: r"^[a-f0-9]{32}$",
Hash.MD6: r"^[a-f0-9]{32}|[a-f0-9]{40}|[a-f0-9]{56}|[a-f0-9]{64}|[a-f0-9]{96}|[a-f0-9]{128}$",
Hash.RIPEMD160: r"^[a-f0-9]{40}$",
Hash.SHA1: r"^[a-f0-9]{40}$",
Hash.SHA224: r"^[a-f0-9]{56}$",
Hash.SHA256: r"^[a-f0-9]{64}$",
Hash.SHA384: r"^[a-f0-9]{96}$",
Hash.SHA512: r"^[a-f0-9]{128}$",
Hash.SHA3224: r"^[a-f0-9]{56}$",
Hash.SHA3256: r"^[a-f0-9]{64}$",
Hash.SHA3384: r"^[a-f0-9]{96}$",
Hash.SHA3512: r"^[a-f0-9]{128}$",
Hash.SSDEEP: r"^[a-z0-9/+:.]{1,128}$",
Hash.WHIRLPOOL: r"^[a-f0-9]{128}$",
Hash.TLSH: r"^[a-f0-9]{70}$",
}
# compile all the regexes; be case-insensitive
for hash_, re_str in _HASH_REGEXES.items():
_HASH_REGEXES[hash_] = re.compile(re_str, re.I)
def infer_hash_algorithm(name):
"""
Given a hash algorithm name, try to figure out which hash algorithm it
refers to. This primarily enables some user flexibility in naming hash
algorithms when creating STIX content.
:param name: A hash algorithm name
:return: A Hash enum value if the name was recognized, or None if it was
not recognized.
"""
enum_name = name.replace("-", "").upper()
try:
enum_obj = Hash[enum_name]
except KeyError:
enum_obj = None
return enum_obj
def check_hash(hash_, value):
"""
Sanity check the given hash value, against the given hash algorithm.
:param hash_: The hash algorithm, as one of the Hash enums
:param value: A hash value as string
:return: True if the value seems okay; False if not
"""
# I guess there's no need to require a regex mapping for the algorithm...
# Just assume it's okay if we have no way to check it.
result = True
regex = _HASH_REGEXES.get(hash_)
if regex:
result = bool(regex.match(value))
return result

View File

@ -7,6 +7,9 @@ import inspect
import re
import uuid
import stix2
import stix2.hashes
from .base import _STIXBase
from .exceptions import CustomContentError, DictionaryKeyError, STIXError
from .parsing import parse, parse_observable
@ -417,54 +420,65 @@ class DictionaryProperty(Property):
return dictified, False
HASHES_REGEX = {
"MD5": (r"^[a-fA-F0-9]{32}$", "MD5"),
"MD6": (r"^[a-fA-F0-9]{32}|[a-fA-F0-9]{40}|[a-fA-F0-9]{56}|[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128}$", "MD6"),
"RIPEMD160": (r"^[a-fA-F0-9]{40}$", "RIPEMD-160"),
"SHA1": (r"^[a-fA-F0-9]{40}$", "SHA-1"),
"SHA224": (r"^[a-fA-F0-9]{56}$", "SHA-224"),
"SHA256": (r"^[a-fA-F0-9]{64}$", "SHA-256"),
"SHA384": (r"^[a-fA-F0-9]{96}$", "SHA-384"),
"SHA512": (r"^[a-fA-F0-9]{128}$", "SHA-512"),
"SHA3224": (r"^[a-fA-F0-9]{56}$", "SHA3-224"),
"SHA3256": (r"^[a-fA-F0-9]{64}$", "SHA3-256"),
"SHA3384": (r"^[a-fA-F0-9]{96}$", "SHA3-384"),
"SHA3512": (r"^[a-fA-F0-9]{128}$", "SHA3-512"),
"SSDEEP": (r"^[a-zA-Z0-9/+:.]{1,128}$", "SSDEEP"),
"WHIRLPOOL": (r"^[a-fA-F0-9]{128}$", "WHIRLPOOL"),
"TLSH": (r"^[a-fA-F0-9]{70}$", "TLSH"),
}
class HashesProperty(DictionaryProperty):
def __init__(self, spec_hash_names, **kwargs):
super().__init__(**kwargs)
self.__spec_hash_names = spec_hash_names
# Map hash algorithm enum to the given spec mandated name, for those
# names which are recognized as hash algorithms by this library.
self.__alg_to_spec_name = {}
for spec_hash_name in spec_hash_names:
alg = stix2.hashes.infer_hash_algorithm(spec_hash_name)
if alg:
self.__alg_to_spec_name[alg] = spec_hash_name
def clean(self, value, allow_custom):
# ignore the has_custom return value here; there is no customization
# of DictionaryProperties.
clean_dict, _ = super(HashesProperty, self).clean(
value, allow_custom,
)
clean_dict, _ = super().clean(value, allow_custom)
spec_dict = {}
has_custom = False
for k, v in copy.deepcopy(clean_dict).items():
key = k.upper().replace('-', '')
if key in HASHES_REGEX:
vocab_key = HASHES_REGEX[key][1]
if vocab_key == "SSDEEP" and self.spec_version == "2.0":
vocab_key = vocab_key.lower()
if not re.match(HASHES_REGEX[key][0], v):
raise ValueError("'{0}' is not a valid {1} hash".format(v, vocab_key))
if k != vocab_key:
clean_dict[vocab_key] = clean_dict[k]
del clean_dict[k]
for hash_k, hash_v in clean_dict.items():
hash_alg = stix2.hashes.infer_hash_algorithm(hash_k)
if hash_alg:
# Library-supported hash algorithm: sanity check the value.
if not stix2.hashes.check_hash(hash_alg, hash_v):
raise ValueError(
"'{0}' is not a valid {1} hash".format(
hash_v, hash_alg.name,
),
)
spec_name = self.__alg_to_spec_name.get(hash_alg)
if not spec_name:
# There is library support for the hash algorithm, but it's
# not in the spec. So it's custom. Just use the user's
# name as-is.
has_custom = True
spec_name = hash_k
else:
has_custom = True
# Unrecognized hash algorithm; use as-is. Hash algorithm name
# must be an exact match from spec, or it will be considered
# custom.
spec_name = hash_k
if spec_name not in self.__spec_hash_names:
has_custom = True
if not allow_custom and has_custom:
raise CustomContentError("custom hash found: " + k)
raise CustomContentError(
"custom hash algorithm: " + hash_k,
)
return clean_dict, has_custom
spec_dict[spec_name] = hash_v
return spec_dict, has_custom
class BinaryProperty(Property):
@ -654,19 +668,49 @@ class EmbeddedObjectProperty(Property):
class EnumProperty(StringProperty):
"""
Used for enumeration type properties. Properties of this type do not allow
customization.
"""
def __init__(self, allowed, **kwargs):
if type(allowed) is not list:
allowed = list(allowed)
if isinstance(allowed, str):
allowed = [allowed]
self.allowed = allowed
super(EnumProperty, self).__init__(**kwargs)
def clean(self, value, allow_custom):
cleaned_value, _ = super(EnumProperty, self).clean(value, allow_custom)
if cleaned_value not in self.allowed:
raise ValueError("value '{}' is not valid for this enumeration.".format(cleaned_value))
return cleaned_value, False
class OpenVocabProperty(StringProperty):
"""
Used for open vocab type properties.
"""
def __init__(self, allowed, **kwargs):
super(OpenVocabProperty, self).__init__(**kwargs)
if isinstance(allowed, str):
allowed = [allowed]
self.allowed = allowed
def clean(self, value, allow_custom):
cleaned_value, _ = super(OpenVocabProperty, self).clean(
value, allow_custom,
)
has_custom = cleaned_value not in self.allowed
if not allow_custom and has_custom:
raise ValueError("value '{}' is not valid for this enumeration.".format(cleaned_value))
raise CustomContentError(
"custom value in open vocab: '{}'".format(cleaned_value),
)
return cleaned_value, has_custom

View File

@ -9,8 +9,9 @@ from stix2.exceptions import (
)
from stix2.properties import (
BinaryProperty, BooleanProperty, EmbeddedObjectProperty, EnumProperty,
FloatProperty, HexProperty, IntegerProperty, ListProperty, Property,
StringProperty, TimestampProperty, TypeProperty,
FloatProperty, HashesProperty, HexProperty, IntegerProperty, ListProperty,
OpenVocabProperty, Property, StringProperty, TimestampProperty,
TypeProperty,
)
@ -363,8 +364,86 @@ def test_enum_property_invalid():
with pytest.raises(ValueError):
enum_prop.clean('z', False)
with pytest.raises(ValueError):
enum_prop.clean('z', True)
def test_enum_property_custom():
enum_prop = EnumProperty(['a', 'b', 'c'])
result = enum_prop.clean("z", True)
assert result == ("z", True)
@pytest.mark.parametrize(
"vocab", [
['a', 'b', 'c'],
('a', 'b', 'c'),
'b',
],
)
def test_openvocab_property(vocab):
ov_prop = OpenVocabProperty(vocab)
assert ov_prop.clean("b", False) == ("b", False)
assert ov_prop.clean("b", True) == ("b", False)
with pytest.raises(CustomContentError):
ov_prop.clean("d", False)
assert ov_prop.clean("d", True) == ("d", True)
@pytest.mark.parametrize(
"value", [
{"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"},
[('MD5', '2dfb1bcc980200c6706feee399d41b3f'), ('RIPEMD-160', 'b3a8cd8a27c90af79b3c81754f267780f443dfef')],
],
)
def test_hashes_property_valid(value):
hash_prop = HashesProperty(["sha256", "md5", "ripemd160"])
_, has_custom = hash_prop.clean(value, False)
assert not has_custom
@pytest.mark.parametrize(
"value", [
{"MD5": "a"},
{"SHA-256": "2dfb1bcc980200c6706feee399d41b3f"},
],
)
def test_hashes_property_invalid(value):
hash_prop = HashesProperty(["sha256", "md5"])
with pytest.raises(ValueError):
hash_prop.clean(value, False)
def test_hashes_property_custom():
value = {
"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b",
"abc-123": "aaaaaaaaaaaaaaaaaaaaa",
}
expected_cleaned_value = {
# cleaning transforms recognized hash algorithm names to the spec-
# mandated name.
"SHA-256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b",
"abc-123": "aaaaaaaaaaaaaaaaaaaaa",
}
hash_prop = HashesProperty(["SHA-256"])
result = hash_prop.clean(value, True)
assert result == (expected_cleaned_value, True)
with pytest.raises(CustomContentError):
hash_prop.clean(value, False)
def test_hashes_no_library_support():
prop = HashesProperty(["foo"])
result = prop.clean({"foo": "bar"}, False)
assert result == ({"foo": "bar"}, False)
result = prop.clean({"foo": "bar"}, True)
assert result == ({"foo": "bar"}, False)
with pytest.raises(CustomContentError):
# require exact name match for unsupported hash algorithms
prop.clean({"FOO": "bar"}, False)
result = prop.clean({"FOO": "bar"}, True)
assert result == ({"FOO": "bar"}, True)

View File

@ -55,7 +55,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -67,7 +67,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -79,7 +79,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.936Z",
"name": "Malicious site hosting downloader",
@ -91,7 +91,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -103,7 +103,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -156,7 +156,7 @@ def stix_objs2():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-31T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -168,7 +168,7 @@ def stix_objs2():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -180,7 +180,7 @@ def stix_objs2():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",

View File

@ -20,7 +20,7 @@ stix_objs = [
"created": "2014-05-08T09:00:00.000Z",
"id": "indicator--a932fcc6-e032-476c-826f-cb970a5a1ade",
"labels": [
"file-hash-watchlist",
"compromised",
],
"modified": "2014-05-08T09:00:00.000Z",
"name": "File hash for Poison Ivy variant",

View File

@ -19,7 +19,7 @@ IND1 = {
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -31,7 +31,7 @@ IND2 = {
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -43,7 +43,7 @@ IND3 = {
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.936Z",
"name": "Malicious site hosting downloader",
@ -55,7 +55,7 @@ IND4 = {
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -67,7 +67,7 @@ IND5 = {
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -79,7 +79,7 @@ IND6 = {
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-31T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -91,7 +91,7 @@ IND7 = {
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -103,7 +103,7 @@ IND8 = {
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"labels": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -285,7 +285,7 @@ def test_memory_store_object_creator_of_present(mem_store):
iden = Identity(
id=IDENTITY_ID,
name="Foo Corp.",
identity_class="corporation",
identity_class="organization",
)
mem_store.add(camp)

View File

@ -13,7 +13,7 @@ def test_pickling():
id=IDENTITY_ID,
name="alice",
description="this is a pickle test",
identity_class="some_class",
identity_class="individual",
)
pickle.loads(pickle.dumps(identity))

View File

@ -8,9 +8,8 @@ from stix2.exceptions import (
ExtraPropertiesError, ParseError,
)
from stix2.properties import (
DictionaryProperty, EmbeddedObjectProperty, ExtensionsProperty,
HashesProperty, IDProperty, ListProperty, ObservableProperty,
ReferenceProperty, STIXObjectProperty,
DictionaryProperty, EmbeddedObjectProperty, ExtensionsProperty, IDProperty,
ListProperty, ObservableProperty, ReferenceProperty, STIXObjectProperty,
)
from stix2.v20.common import MarkingProperty
@ -354,45 +353,6 @@ def test_property_list_of_dictionary():
assert test_obj.property1[0]['foo'] == 'bar'
@pytest.mark.parametrize(
"value", [
{"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"},
[('MD5', '2dfb1bcc980200c6706feee399d41b3f'), ('RIPEMD-160', 'b3a8cd8a27c90af79b3c81754f267780f443dfef')],
],
)
def test_hashes_property_valid(value):
hash_prop = HashesProperty()
_, has_custom = hash_prop.clean(value, False)
assert not has_custom
@pytest.mark.parametrize(
"value", [
{"MD5": "a"},
{"SHA-256": "2dfb1bcc980200c6706feee399d41b3f"},
],
)
def test_hashes_property_invalid(value):
hash_prop = HashesProperty()
with pytest.raises(ValueError):
hash_prop.clean(value, False)
def test_hashes_property_custom():
value = {
"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b",
"abc-123": "aaaaaaaaaaaaaaaaaaaaa",
}
hash_prop = HashesProperty()
result = hash_prop.clean(value, True)
assert result == (value, True)
with pytest.raises(CustomContentError):
hash_prop.clean(value, False)
def test_embedded_property():
emb_prop = EmbeddedObjectProperty(type=stix2.v20.EmailMIMEComponent)
mime = stix2.v20.EmailMIMEComponent(

View File

@ -433,7 +433,7 @@ def test_version_marking():
def test_version_disable_custom():
m = stix2.v20.Malware(
name="foo", labels=["label"], description="Steals your identity!",
name="foo", labels=["spyware"], description="Steals your identity!",
x_custom=123, allow_custom=True,
)
@ -450,7 +450,7 @@ def test_version_disable_custom():
def test_version_enable_custom():
m = stix2.v20.Malware(
name="foo", labels=["label"], description="Steals your identity!",
name="foo", labels=["spyware"], description="Steals your identity!",
)
# Add a custom property to an object for which it was previously disallowed
@ -464,7 +464,7 @@ def test_version_enable_custom():
def test_version_propagate_custom():
m = stix2.v20.Malware(
name="foo", labels=["label"],
name="foo", labels=["spyware"],
)
# Remember custom-not-allowed setting from original; produce error
@ -476,7 +476,7 @@ def test_version_propagate_custom():
assert m2.description == "Steals your identity!"
m_custom = stix2.v20.Malware(
name="foo", labels=["label"], x_custom=123, allow_custom=True,
name="foo", labels=["spyware"], x_custom=123, allow_custom=True,
)
# Remember custom-allowed setting from original; should work

View File

@ -66,7 +66,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"indicator_types": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -80,7 +80,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"indicator_types": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -94,7 +94,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"indicator_types": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.936Z",
"name": "Malicious site hosting downloader",
@ -108,7 +108,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"indicator_types": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -122,7 +122,7 @@ def stix_objs1():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"indicator_types": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -183,7 +183,7 @@ def stix_objs2():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000001",
"indicator_types": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-31T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -197,7 +197,7 @@ def stix_objs2():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"indicator_types": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",
@ -211,7 +211,7 @@ def stix_objs2():
"created": "2017-01-27T13:49:53.935Z",
"id": "indicator--00000000-0000-4000-8000-000000000002",
"indicator_types": [
"url-watchlist",
"malicious-activity",
],
"modified": "2017-01-27T13:49:53.935Z",
"name": "Malicious site hosting downloader",

View File

@ -24,7 +24,7 @@ stix_objs = [
"created": "2014-05-08T09:00:00.000Z",
"id": "indicator--a932fcc6-e032-476c-826f-cb970a5a1ade",
"indicator_types": [
"file-hash-watchlist",
"compromised",
],
"modified": "2014-05-08T09:00:00.000Z",
"name": "File hash for Poison Ivy variant",

View File

@ -300,7 +300,7 @@ def test_memory_store_object_creator_of_present(mem_store):
iden = Identity(
id=IDENTITY_ID,
name="Foo Corp.",
identity_class="corporation",
identity_class="organization",
)
mem_store.add(camp)

View File

@ -14,6 +14,7 @@ from stix2.properties import (
TypeProperty,
)
import stix2.v21
from stix2.v21.vocab import HASHING_ALGORITHM
SCO_DET_ID_NAMESPACE = uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7")
@ -175,7 +176,7 @@ def test_empty_hash():
spec_version='2.1', enclosing_type=_type,
),
),
('hashes', HashesProperty()),
('hashes', HashesProperty(HASHING_ALGORITHM)),
))
_id_contributing_properties = ['hashes']

View File

@ -490,7 +490,7 @@ def test_object_similarity_on_same_identity2():
IDEN_KWARGS = dict(
name="John Smith",
identity_class="individual",
sectors=["government", "critical-infrastructure"],
sectors=["government", "infrastructure"],
)
iden1 = stix2.v21.Identity(id=IDENTITY_ID, **IDEN_KWARGS)
iden2 = stix2.v21.Identity(id=IDENTITY_ID, **IDEN_KWARGS)
@ -723,7 +723,7 @@ def test_object_similarity_different_spec_version_raises():
def test_object_similarity_zero_match():
IND_KWARGS = dict(
indicator_types=["malicious-activity", "bar"],
indicator_types=["anomalous-activity"],
pattern="[ipv4-addr:value = '192.168.1.1']",
pattern_type="stix",
valid_from="2019-01-01T12:34:56Z",
@ -743,14 +743,14 @@ def test_object_similarity_zero_match():
ind1 = stix2.v21.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
ind2 = stix2.v21.Indicator(id=INDICATOR_ID, **IND_KWARGS)
env = stix2.Environment().object_similarity(ind1, ind2, **weights)
assert round(env) == 8
assert round(env) == 0
env = stix2.Environment().object_similarity(ind2, ind1, **weights)
assert round(env) == 8
assert round(env) == 0
def test_object_similarity_different_spec_version():
IND_KWARGS = dict(
labels=["APTX"],
labels=["malicious-activity"],
pattern="[ipv4-addr:value = '192.168.1.1']",
)
weights = {

View File

@ -36,7 +36,7 @@ EXPECTED_LOCATION_2 = """{
"id": "location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64",
"created": "2016-04-06T20:03:00.000Z",
"modified": "2016-04-06T20:03:00.000Z",
"region": "north-america"
"region": "northern-america"
}
"""
@ -47,8 +47,7 @@ EXPECTED_LOCATION_2_REPR = "Location(" + " ".join(
id='location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64',
created='2016-04-06T20:03:00.000Z',
modified='2016-04-06T20:03:00.000Z',
region='north-america'""".split(),
) + ")"
region='northern-america'""".split()) + ")"
def test_location_with_some_required_properties():
@ -76,7 +75,7 @@ def test_location_with_some_required_properties():
"id": LOCATION_ID,
"created": "2016-04-06T20:03:00.000Z",
"modified": "2016-04-06T20:03:00.000Z",
"region": "north-america",
"region": "northern-america",
},
],
)
@ -88,7 +87,7 @@ def test_parse_location(data):
assert location.id == LOCATION_ID
assert location.created == dt.datetime(2016, 4, 6, 20, 3, 0, tzinfo=pytz.utc)
assert location.modified == dt.datetime(2016, 4, 6, 20, 3, 0, tzinfo=pytz.utc)
assert location.region == 'north-america'
assert location.region == 'northern-america'
rep = re.sub(r"(\[|=| )u('|\"|\\\'|\\\")", r"\g<1>\g<2>", repr(location))
assert rep == EXPECTED_LOCATION_2_REPR
@ -302,6 +301,7 @@ def test_google_map_url_multiple_props_no_long_lat_provided():
region="North America",
country="United States of America",
street_address="1410 Museum Campus Drive, Chicago, IL 60605",
allow_custom=True,
)
loc_url = loc.to_maps_url()
@ -312,7 +312,7 @@ def test_google_map_url_multiple_props_and_long_lat_provided():
expected_url = "https://www.google.com/maps/search/?api=1&query=41.862401%2C-87.616001"
loc = stix2.v21.Location(
region="North America",
region="northern-america",
country="United States of America",
street_address="1410 Museum Campus Drive, Chicago, IL 60605",
latitude=41.862401,
@ -354,6 +354,7 @@ def test_bing_map_url_multiple_props_no_long_lat_provided():
region="North America",
country="United States of America",
street_address="1410 Museum Campus Drive, Chicago, IL 60605",
allow_custom=True,
)
loc_url = loc.to_maps_url("Bing Maps")
@ -364,7 +365,7 @@ def test_bing_map_url_multiple_props_and_long_lat_provided():
expected_url = "https://bing.com/maps/default.aspx?where1=41.862401%2C-87.616001&lvl=16"
loc = stix2.v21.Location(
region="North America",
region="northern-america",
country="United States of America",
street_address="1410 Museum Campus Drive, Chicago, IL 60605",
latitude=41.862401,

View File

@ -181,7 +181,7 @@ def test_malware_family_no_name():
"id": MALWARE_ID,
"spec_version": "2.1",
"is_family": True,
"malware_types": ["a type"],
"malware_types": ["spyware"],
})
@ -191,7 +191,7 @@ def test_malware_non_family_no_name():
"id": MALWARE_ID,
"spec_version": "2.1",
"is_family": False,
"malware_types": ["something"],
"malware_types": ["spyware"],
})
@ -207,7 +207,7 @@ def test_malware_with_os_refs():
"id": MALWARE_ID,
"spec_version": "2.1",
"is_family": False,
"malware_types": ["something"],
"malware_types": ["spyware"],
"operating_system_refs": [software],
})

View File

@ -13,7 +13,7 @@ def test_pickling():
id=IDENTITY_ID,
name="alice",
description="this is a pickle test",
identity_class="some_class",
identity_class="individual",
)
pickle.loads(pickle.dumps(identity))

View File

@ -6,9 +6,9 @@ from stix2.exceptions import (
ExtraPropertiesError, ParseError,
)
from stix2.properties import (
DictionaryProperty, EmbeddedObjectProperty, ExtensionsProperty,
HashesProperty, IDProperty, ListProperty, ObservableProperty,
ReferenceProperty, STIXObjectProperty, StringProperty,
DictionaryProperty, EmbeddedObjectProperty, ExtensionsProperty, IDProperty,
ListProperty, ObservableProperty, ReferenceProperty, STIXObjectProperty,
StringProperty,
)
from stix2.v21.common import MarkingProperty
@ -367,53 +367,6 @@ def test_property_list_of_dictionary():
assert test_obj.property1[0]['foo'] == 'bar'
@pytest.mark.parametrize(
"value", [
{"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"},
[('MD5', '2dfb1bcc980200c6706feee399d41b3f'), ('RIPEMD-160', 'b3a8cd8a27c90af79b3c81754f267780f443dfef')],
[('TLSH', '6FF02BEF718027B0160B4391212923ED7F1A463D563B1549B86CF62973B197AD2731F8')],
],
)
def test_hashes_property_valid(value):
hash_prop = HashesProperty()
_, has_custom = hash_prop.clean(value, False)
assert not has_custom
_, has_custom = hash_prop.clean(value, True)
assert not has_custom
@pytest.mark.parametrize(
"value", [
{"MD5": "a"},
{"SHA-256": "2dfb1bcc980200c6706feee399d41b3f"},
{"TLSH": "6FF02BEF718027B0160B4391212923ED7F1A463D563B1549B86CF62973B197AD2731F"},
],
)
def test_hashes_property_invalid(value):
hash_prop = HashesProperty()
with pytest.raises(ValueError):
hash_prop.clean(value, False)
with pytest.raises(ValueError):
hash_prop.clean(value, True)
def test_hashes_property_custom():
value = {
"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b",
"abc-123": "aaaaaaaaaaaaaaaaaaaaa",
}
hash_prop = HashesProperty()
result = hash_prop.clean(value, True)
assert result == (value, True)
with pytest.raises(CustomContentError):
hash_prop.clean(value, False)
def test_embedded_property():
emb_prop = EmbeddedObjectProperty(type=stix2.v21.EmailMIMEComponent)
mime = stix2.v21.EmailMIMEComponent(

View File

@ -76,7 +76,7 @@ def test_seen_ordering_constraint():
with pytest.raises(ValueError):
stix2.v21.ThreatActor(
name="Bad Person",
threat_actor_types=["bad person", "evil person"],
threat_actor_types=["hacker", "criminal"],
first_seen="2010-04-21T09:31:11Z",
last_seen="2009-02-06T03:39:31Z",
)
@ -84,7 +84,7 @@ def test_seen_ordering_constraint():
# equal timestamps is okay.
stix2.v21.ThreatActor(
name="Bad Person",
threat_actor_types=["bad person", "evil person"],
threat_actor_types=["hacker", "criminal"],
first_seen="2010-04-21T09:31:11Z",
last_seen="2010-04-21T09:31:11Z",
)

View File

@ -12,6 +12,7 @@ from ..properties import (
)
from ..utils import NOW, _get_dict
from .base import _STIXBase20
from .vocab import HASHING_ALGORITHM
def _should_set_millisecond(cr, marking_type):
@ -38,7 +39,7 @@ class ExternalReference(_STIXBase20):
('source_name', StringProperty(required=True)),
('description', StringProperty()),
('url', StringProperty()),
('hashes', HashesProperty(spec_version='2.0')),
('hashes', HashesProperty(HASHING_ALGORITHM, spec_version='2.0')),
('external_id', StringProperty()),
])

View File

@ -17,6 +17,7 @@ from ..properties import (
ObjectReferenceProperty, StringProperty, TimestampProperty, TypeProperty,
)
from .base import _Extension, _Observable, _STIXBase20
from .vocab import HASHING_ALGORITHM
class Artifact(_Observable):
@ -30,7 +31,7 @@ class Artifact(_Observable):
('mime_type', StringProperty()),
('payload_bin', BinaryProperty()),
('url', StringProperty()),
('hashes', HashesProperty(spec_version='2.0')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
])
@ -173,7 +174,7 @@ class AlternateDataStream(_STIXBase20):
_properties = OrderedDict([
('name', StringProperty(required=True)),
('hashes', HashesProperty(spec_version='2.0')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
('size', IntegerProperty()),
])
@ -256,7 +257,7 @@ class WindowsPEOptionalHeaderType(_STIXBase20):
('size_of_heap_commit', IntegerProperty()),
('loader_flags_hex', HexProperty()),
('number_of_rva_and_sizes', IntegerProperty()),
('hashes', HashesProperty(spec_version='2.0')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
])
def _check_object_constraints(self):
@ -273,7 +274,7 @@ class WindowsPESection(_STIXBase20):
('name', StringProperty(required=True)),
('size', IntegerProperty()),
('entropy', FloatProperty()),
('hashes', HashesProperty(spec_version='2.0')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
])
@ -293,7 +294,7 @@ class WindowsPEBinaryExt(_Extension):
('number_of_symbols', IntegerProperty()),
('size_of_optional_header', IntegerProperty()),
('characteristics_hex', HexProperty()),
('file_header_hashes', HashesProperty(spec_version='2.0')),
('file_header_hashes', HashesProperty(HASHING_ALGORITHM)),
('optional_header', EmbeddedObjectProperty(type=WindowsPEOptionalHeaderType)),
('sections', ListProperty(EmbeddedObjectProperty(type=WindowsPESection))),
])
@ -307,7 +308,7 @@ class File(_Observable):
_type = 'file'
_properties = OrderedDict([
('type', TypeProperty(_type, spec_version='2.0')),
('hashes', HashesProperty(spec_version='2.0')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
('size', IntegerProperty()),
('name', StringProperty()),
('name_enc', StringProperty()),
@ -771,7 +772,7 @@ class X509Certificate(_Observable):
_properties = OrderedDict([
('type', TypeProperty(_type, spec_version='2.0')),
('is_self_signed', BooleanProperty()),
('hashes', HashesProperty(spec_version='2.0')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
('version', StringProperty()),
('serial_number', StringProperty()),
('signature_algorithm', StringProperty()),

View File

@ -9,12 +9,17 @@ from ..custom import _custom_object_builder
from ..exceptions import InvalidValueError
from ..properties import (
BooleanProperty, IDProperty, IntegerProperty, ListProperty,
ObservableProperty, PatternProperty, ReferenceProperty, StringProperty,
TimestampProperty, TypeProperty,
ObservableProperty, OpenVocabProperty, PatternProperty, ReferenceProperty,
StringProperty, TimestampProperty, TypeProperty,
)
from ..utils import NOW
from .base import _DomainObject
from .common import ExternalReference, GranularMarking, KillChainPhase
from .vocab import (
ATTACK_MOTIVATION, ATTACK_RESOURCE_LEVEL, IDENTITY_CLASS, INDICATOR_LABEL,
INDUSTRY_SECTOR, MALWARE_LABEL, REPORT_LABEL, THREAT_ACTOR_LABEL,
THREAT_ACTOR_ROLE, THREAT_ACTOR_SOPHISTICATION, TOOL_LABEL,
)
class AttackPattern(_DomainObject):
@ -102,8 +107,8 @@ class Identity(_DomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty(required=True)),
('description', StringProperty()),
('identity_class', StringProperty(required=True)),
('sectors', ListProperty(StringProperty)),
('identity_class', OpenVocabProperty(IDENTITY_CLASS, required=True)),
('sectors', ListProperty(OpenVocabProperty(INDUSTRY_SECTOR))),
('contact_information', StringProperty()),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)),
@ -132,7 +137,7 @@ class Indicator(_DomainObject):
('valid_until', TimestampProperty()),
('kill_chain_phases', ListProperty(KillChainPhase)),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty, required=True)),
('labels', ListProperty(OpenVocabProperty(INDICATOR_LABEL), required=True)),
('external_references', ListProperty(ExternalReference)),
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
('granular_markings', ListProperty(GranularMarking)),
@ -163,8 +168,8 @@ class IntrusionSet(_DomainObject):
('last_seen', TimestampProperty()),
('goals', ListProperty(StringProperty)),
('resource_level', StringProperty()),
('primary_motivation', StringProperty()),
('secondary_motivations', ListProperty(StringProperty)),
('primary_motivation', OpenVocabProperty(ATTACK_MOTIVATION)),
('secondary_motivations', ListProperty(OpenVocabProperty(ATTACK_MOTIVATION))),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)),
('external_references', ListProperty(ExternalReference)),
@ -189,7 +194,7 @@ class Malware(_DomainObject):
('description', StringProperty()),
('kill_chain_phases', ListProperty(KillChainPhase)),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty, required=True)),
('labels', ListProperty(OpenVocabProperty(MALWARE_LABEL), required=True)),
('external_references', ListProperty(ExternalReference)),
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
('granular_markings', ListProperty(GranularMarking)),
@ -237,7 +242,7 @@ class Report(_DomainObject):
('published', TimestampProperty(required=True)),
('object_refs', ListProperty(ReferenceProperty(invalid_types=[], spec_version='2.0'), required=True)),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty, required=True)),
('labels', ListProperty(OpenVocabProperty(REPORT_LABEL), required=True)),
('external_references', ListProperty(ExternalReference)),
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
('granular_markings', ListProperty(GranularMarking)),
@ -259,15 +264,15 @@ class ThreatActor(_DomainObject):
('name', StringProperty(required=True)),
('description', StringProperty()),
('aliases', ListProperty(StringProperty)),
('roles', ListProperty(StringProperty)),
('roles', ListProperty(OpenVocabProperty(THREAT_ACTOR_ROLE))),
('goals', ListProperty(StringProperty)),
('sophistication', StringProperty()),
('resource_level', StringProperty()),
('primary_motivation', StringProperty()),
('secondary_motivations', ListProperty(StringProperty)),
('personal_motivations', ListProperty(StringProperty)),
('sophistication', OpenVocabProperty(THREAT_ACTOR_SOPHISTICATION)),
('resource_level', OpenVocabProperty(ATTACK_RESOURCE_LEVEL)),
('primary_motivation', OpenVocabProperty(ATTACK_MOTIVATION)),
('secondary_motivations', ListProperty(OpenVocabProperty(ATTACK_MOTIVATION))),
('personal_motivations', ListProperty(OpenVocabProperty(ATTACK_MOTIVATION))),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty, required=True)),
('labels', ListProperty(OpenVocabProperty(THREAT_ACTOR_LABEL), required=True)),
('external_references', ListProperty(ExternalReference)),
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
('granular_markings', ListProperty(GranularMarking)),
@ -291,7 +296,7 @@ class Tool(_DomainObject):
('kill_chain_phases', ListProperty(KillChainPhase)),
('tool_version', StringProperty()),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty, required=True)),
('labels', ListProperty(OpenVocabProperty(TOOL_LABEL), required=True)),
('external_references', ListProperty(ExternalReference)),
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
('granular_markings', ListProperty(GranularMarking)),

178
stix2/v20/vocab.py Normal file
View File

@ -0,0 +1,178 @@
"""
STIX 2.0 open vocabularies and enums
"""
ATTACK_MOTIVATION = [
"accidental",
"coercion",
"dominance",
"ideology",
"notoriety",
"organizational-gain",
"personal-gain",
"personal-satisfaction",
"revenge",
"unpredictable",
]
ATTACK_RESOURCE_LEVEL = [
"individual",
"club",
"contest",
"team",
"organization",
"government",
]
HASHING_ALGORITHM = [
"MD5",
"MD6",
"RIPEMD-160",
"SHA-1",
"SHA-224",
"SHA-256",
"SHA-384",
"SHA-512",
"SHA3-224",
"SHA3-256",
"SHA3-384",
"SHA3-512",
"ssdeep",
"WHIRLPOOL",
]
IDENTITY_CLASS = [
"individual",
"group",
"organization",
"class",
"unknown",
]
INDICATOR_LABEL = [
"anomalous-activity",
"anonymization",
"benign",
"compromised",
"malicious-activity",
"attribution",
]
INDUSTRY_SECTOR = [
"agriculture",
"aerospace",
"automotive",
"communications",
"construction",
"defence",
"education",
"energy",
"entertainment",
"financial-services",
"government-national",
"government-regional",
"government-local",
"government-public-services",
"healthcare",
"hospitality-leisure",
"infrastructure",
"insurance",
"manufacturing",
"mining",
"non-profit",
"pharmaceuticals",
"retail",
"technology",
"telecommunications",
"transportation",
"utilities",
]
MALWARE_LABEL = [
"adware",
"backdoor",
"bot",
"ddos",
"dropper",
"exploit-kit",
"keylogger",
"ransomware",
"remote-access-trojan",
"resource-exploitation",
"rogue-security-software",
"rootkit",
"screen-capture",
"spyware",
"trojan",
"virus",
"worm",
]
REPORT_LABEL = [
"threat-report",
"attack-pattern",
"campaign",
"identity",
"indicator",
"intrusion-set",
"malware",
"observed-data",
"threat-actor",
"tool",
"vulnerability",
]
THREAT_ACTOR_LABEL = [
"activist",
"competitor",
"crime-syndicate",
"criminal",
"hacker",
"insider-accidental",
"insider-disgruntled",
"nation-state",
"sensationalist",
"spy",
"terrorist",
]
THREAT_ACTOR_ROLE = [
"agent",
"director",
"independent",
"infrastructure-architect",
"infrastructure-operator",
"malware-author",
"sponsor",
]
THREAT_ACTOR_SOPHISTICATION = [
"none",
"minimal",
"intermediate",
"advanced",
"expert",
"innovator",
"strategic",
]
TOOL_LABEL = [
"denial-of-service",
"exploitation",
"information-gathering",
"network-capture",
"credential-exploitation",
"remote-access",
"vulnerability-scanning",
]

View File

@ -13,6 +13,7 @@ from ..properties import (
)
from ..utils import NOW, _get_dict
from .base import _STIXBase21
from .vocab import HASHING_ALGORITHM
class ExternalReference(_STIXBase21):
@ -24,7 +25,7 @@ class ExternalReference(_STIXBase21):
('source_name', StringProperty(required=True)),
('description', StringProperty()),
('url', StringProperty()),
('hashes', HashesProperty(spec_version='2.1')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
('external_id', StringProperty()),
])

View File

@ -14,10 +14,17 @@ from ..properties import (
BinaryProperty, BooleanProperty, DictionaryProperty,
EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
HashesProperty, HexProperty, IDProperty, IntegerProperty, ListProperty,
ReferenceProperty, StringProperty, TimestampProperty, TypeProperty,
OpenVocabProperty, ReferenceProperty, StringProperty, TimestampProperty,
TypeProperty,
)
from .base import _Extension, _Observable, _STIXBase21
from .common import GranularMarking
from .vocab import (
ACCOUNT_TYPE, ENCRYPTION_ALGORITHM, HASHING_ALGORITHM,
NETWORK_SOCKET_ADDRESS_FAMILY, NETWORK_SOCKET_TYPE,
WINDOWS_INTEGRITY_LEVEL, WINDOWS_PEBINARY_TYPE, WINDOWS_REGISTRY_DATATYPE,
WINDOWS_SERVICE_START_TYPE, WINDOWS_SERVICE_STATUS, WINDOWS_SERVICE_TYPE,
)
class Artifact(_Observable):
@ -33,8 +40,8 @@ class Artifact(_Observable):
('mime_type', StringProperty()),
('payload_bin', BinaryProperty()),
('url', StringProperty()),
('hashes', HashesProperty(spec_version='2.1')),
('encryption_algorithm', StringProperty()),
('hashes', HashesProperty(HASHING_ALGORITHM)),
('encryption_algorithm', EnumProperty(ENCRYPTION_ALGORITHM)),
('decryption_key', StringProperty()),
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
('granular_markings', ListProperty(GranularMarking)),
@ -212,7 +219,7 @@ class AlternateDataStream(_STIXBase21):
_properties = OrderedDict([
('name', StringProperty(required=True)),
('hashes', HashesProperty(spec_version='2.1')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
('size', IntegerProperty()),
])
@ -294,7 +301,7 @@ class WindowsPEOptionalHeaderType(_STIXBase21):
('size_of_heap_commit', IntegerProperty()),
('loader_flags_hex', HexProperty()),
('number_of_rva_and_sizes', IntegerProperty()),
('hashes', HashesProperty(spec_version='2.1')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
])
def _check_object_constraints(self):
@ -311,7 +318,7 @@ class WindowsPESection(_STIXBase21):
('name', StringProperty(required=True)),
('size', IntegerProperty(min=0)),
('entropy', FloatProperty()),
('hashes', HashesProperty(spec_version='2.1')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
])
@ -322,7 +329,7 @@ class WindowsPEBinaryExt(_Extension):
_type = 'windows-pebinary-ext'
_properties = OrderedDict([
('pe_type', StringProperty(required=True)), # open_vocab
('pe_type', OpenVocabProperty(WINDOWS_PEBINARY_TYPE, required=True)),
('imphash', StringProperty()),
('machine_hex', HexProperty()),
('number_of_sections', IntegerProperty(min=0)),
@ -331,7 +338,7 @@ class WindowsPEBinaryExt(_Extension):
('number_of_symbols', IntegerProperty(min=0)),
('size_of_optional_header', IntegerProperty(min=0)),
('characteristics_hex', HexProperty()),
('file_header_hashes', HashesProperty(spec_version='2.1')),
('file_header_hashes', HashesProperty(HASHING_ALGORITHM)),
('optional_header', EmbeddedObjectProperty(type=WindowsPEOptionalHeaderType)),
('sections', ListProperty(EmbeddedObjectProperty(type=WindowsPESection))),
])
@ -347,7 +354,7 @@ class File(_Observable):
('type', TypeProperty(_type, spec_version='2.1')),
('spec_version', StringProperty(fixed='2.1')),
('id', IDProperty(_type, spec_version='2.1')),
('hashes', HashesProperty(spec_version='2.1')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
('size', IntegerProperty(min=0)),
('name', StringProperty()),
('name_enc', StringProperty()),
@ -487,34 +494,11 @@ class SocketExt(_Extension):
_type = 'socket-ext'
_properties = OrderedDict([
(
'address_family', EnumProperty(
allowed=[
"AF_UNSPEC",
"AF_INET",
"AF_IPX",
"AF_APPLETALK",
"AF_NETBIOS",
"AF_INET6",
"AF_IRDA",
"AF_BTH",
], required=True,
),
),
('address_family', EnumProperty(NETWORK_SOCKET_ADDRESS_FAMILY, required=True)),
('is_blocking', BooleanProperty()),
('is_listening', BooleanProperty()),
('options', DictionaryProperty(spec_version='2.1')),
(
'socket_type', EnumProperty(
allowed=[
"SOCK_STREAM",
"SOCK_DGRAM",
"SOCK_RAW",
"SOCK_RDM",
"SOCK_SEQPACKET",
],
),
),
('socket_type', EnumProperty(NETWORK_SOCKET_TYPE)),
('socket_descriptor', IntegerProperty(min=0)),
('socket_handle', IntegerProperty()),
])
@ -613,16 +597,7 @@ class WindowsProcessExt(_Extension):
('owner_sid', StringProperty()),
('window_title', StringProperty()),
('startup_info', DictionaryProperty(spec_version='2.1')),
(
'integrity_level', EnumProperty(
allowed=[
"low",
"medium",
"high",
"system",
],
),
),
('integrity_level', EnumProperty(WINDOWS_INTEGRITY_LEVEL)),
])
@ -637,41 +612,10 @@ class WindowsServiceExt(_Extension):
('descriptions', ListProperty(StringProperty)),
('display_name', StringProperty()),
('group_name', StringProperty()),
(
'start_type', EnumProperty(
allowed=[
"SERVICE_AUTO_START",
"SERVICE_BOOT_START",
"SERVICE_DEMAND_START",
"SERVICE_DISABLED",
"SERVICE_SYSTEM_ALERT",
],
),
),
('start_type', EnumProperty(WINDOWS_SERVICE_START_TYPE)),
('service_dll_refs', ListProperty(ReferenceProperty(valid_types='file', spec_version="2.1"))),
(
'service_type', EnumProperty(
allowed=[
"SERVICE_KERNEL_DRIVER",
"SERVICE_FILE_SYSTEM_DRIVER",
"SERVICE_WIN32_OWN_PROCESS",
"SERVICE_WIN32_SHARE_PROCESS",
],
),
),
(
'service_status', EnumProperty(
allowed=[
"SERVICE_CONTINUE_PENDING",
"SERVICE_PAUSE_PENDING",
"SERVICE_PAUSED",
"SERVICE_RUNNING",
"SERVICE_START_PENDING",
"SERVICE_STOP_PENDING",
"SERVICE_STOPPED",
],
),
),
('service_type', EnumProperty(WINDOWS_SERVICE_TYPE)),
('service_status', EnumProperty(WINDOWS_SERVICE_STATUS)),
])
@ -789,7 +733,7 @@ class UserAccount(_Observable):
('user_id', StringProperty()),
('credential', StringProperty()),
('account_login', StringProperty()),
('account_type', StringProperty()), # open vocab
('account_type', OpenVocabProperty(ACCOUNT_TYPE)),
('display_name', StringProperty()),
('is_service_account', BooleanProperty()),
('is_privileged', BooleanProperty()),
@ -817,25 +761,7 @@ class WindowsRegistryValueType(_STIXBase21):
_properties = OrderedDict([
('name', StringProperty()),
('data', StringProperty()),
(
'data_type', EnumProperty(
allowed=[
"REG_NONE",
"REG_SZ",
"REG_EXPAND_SZ",
"REG_BINARY",
"REG_DWORD",
"REG_DWORD_BIG_ENDIAN",
"REG_LINK",
"REG_MULTI_SZ",
"REG_RESOURCE_LIST",
"REG_FULL_RESOURCE_DESCRIPTION",
"REG_RESOURCE_REQUIREMENTS_LIST",
"REG_QWORD",
"REG_INVALID_TYPE",
],
),
),
('data_type', EnumProperty(WINDOWS_REGISTRY_DATATYPE)),
])
@ -900,7 +826,7 @@ class X509Certificate(_Observable):
('spec_version', StringProperty(fixed='2.1')),
('id', IDProperty(_type, spec_version='2.1')),
('is_self_signed', BooleanProperty()),
('hashes', HashesProperty(spec_version='2.1')),
('hashes', HashesProperty(HASHING_ALGORITHM)),
('version', StringProperty()),
('serial_number', StringProperty()),
('signature_algorithm', StringProperty()),

View File

@ -13,12 +13,20 @@ from ..exceptions import (
)
from ..properties import (
BooleanProperty, EnumProperty, FloatProperty, IDProperty, IntegerProperty,
ListProperty, ObservableProperty, PatternProperty, ReferenceProperty,
StringProperty, TimestampProperty, TypeProperty,
ListProperty, ObservableProperty, OpenVocabProperty, PatternProperty,
ReferenceProperty, StringProperty, TimestampProperty, TypeProperty,
)
from ..utils import NOW
from .base import _DomainObject
from .common import ExternalReference, GranularMarking, KillChainPhase
from .vocab import (
ATTACK_MOTIVATION, ATTACK_RESOURCE_LEVEL, GROUPING_CONTEXT, IDENTITY_CLASS,
IMPLEMENTATION_LANGUAGE, INDICATOR_TYPE, INDUSTRY_SECTOR,
INFRASTRUCTURE_TYPE, MALWARE_CAPABILITIES, MALWARE_RESULT, MALWARE_TYPE,
OPINION, PATTERN_TYPE, PROCESSOR_ARCHITECTURE, REGION, REPORT_TYPE,
THREAT_ACTOR_ROLE, THREAT_ACTOR_SOPHISTICATION, THREAT_ACTOR_TYPE,
TOOL_TYPE,
)
class AttackPattern(_DomainObject):
@ -127,7 +135,7 @@ class Grouping(_DomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
('name', StringProperty()),
('description', StringProperty()),
('context', StringProperty(required=True)),
('context', OpenVocabProperty(GROUPING_CONTEXT, required=True)),
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)),
@ -155,8 +163,8 @@ class Identity(_DomainObject):
('name', StringProperty(required=True)),
('description', StringProperty()),
('roles', ListProperty(StringProperty)),
('identity_class', StringProperty()),
('sectors', ListProperty(StringProperty)),
('identity_class', OpenVocabProperty(IDENTITY_CLASS)),
('sectors', ListProperty(OpenVocabProperty(INDUSTRY_SECTOR))),
('contact_information', StringProperty()),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)),
@ -183,9 +191,9 @@ class Indicator(_DomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
('name', StringProperty()),
('description', StringProperty()),
('indicator_types', ListProperty(StringProperty)),
('indicator_types', ListProperty(OpenVocabProperty(INDICATOR_TYPE))),
('pattern', PatternProperty(required=True)),
('pattern_type', StringProperty(required=True)),
('pattern_type', OpenVocabProperty(PATTERN_TYPE, required=True)),
('pattern_version', StringProperty()),
('valid_from', TimestampProperty(default=lambda: NOW)),
('valid_until', TimestampProperty()),
@ -242,7 +250,7 @@ class Infrastructure(_DomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
('name', StringProperty(required=True)),
('description', StringProperty()),
('infrastructure_types', ListProperty(StringProperty)),
('infrastructure_types', ListProperty(OpenVocabProperty(INFRASTRUCTURE_TYPE))),
('aliases', ListProperty(StringProperty)),
('kill_chain_phases', ListProperty(KillChainPhase)),
('first_seen', TimestampProperty()),
@ -286,9 +294,9 @@ class IntrusionSet(_DomainObject):
('first_seen', TimestampProperty()),
('last_seen', TimestampProperty()),
('goals', ListProperty(StringProperty)),
('resource_level', StringProperty()),
('primary_motivation', StringProperty()),
('secondary_motivations', ListProperty(StringProperty)),
('resource_level', OpenVocabProperty(ATTACK_RESOURCE_LEVEL)),
('primary_motivation', OpenVocabProperty(ATTACK_MOTIVATION)),
('secondary_motivations', ListProperty(OpenVocabProperty(ATTACK_MOTIVATION))),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)),
('confidence', IntegerProperty()),
@ -327,7 +335,7 @@ class Location(_DomainObject):
('latitude', FloatProperty(min=-90.0, max=90.0)),
('longitude', FloatProperty(min=-180.0, max=180.0)),
('precision', FloatProperty(min=0.0)),
('region', StringProperty()),
('region', OpenVocabProperty(REGION)),
('country', StringProperty()),
('administrative_area', StringProperty()),
('city', StringProperty()),
@ -431,16 +439,16 @@ class Malware(_DomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
('name', StringProperty()),
('description', StringProperty()),
('malware_types', ListProperty(StringProperty)),
('malware_types', ListProperty(OpenVocabProperty(MALWARE_TYPE))),
('is_family', BooleanProperty(required=True)),
('aliases', ListProperty(StringProperty)),
('kill_chain_phases', ListProperty(KillChainPhase)),
('first_seen', TimestampProperty()),
('last_seen', TimestampProperty()),
('operating_system_refs', ListProperty(ReferenceProperty(valid_types='software', spec_version='2.1'))),
('architecture_execution_envs', ListProperty(StringProperty)),
('implementation_languages', ListProperty(StringProperty)),
('capabilities', ListProperty(StringProperty)),
('architecture_execution_envs', ListProperty(OpenVocabProperty(PROCESSOR_ARCHITECTURE))),
('implementation_languages', ListProperty(OpenVocabProperty(IMPLEMENTATION_LANGUAGE))),
('capabilities', ListProperty(OpenVocabProperty(MALWARE_CAPABILITIES))),
('sample_refs', ListProperty(ReferenceProperty(valid_types=['artifact', 'file'], spec_version='2.1'))),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)),
@ -494,7 +502,7 @@ class MalwareAnalysis(_DomainObject):
('analysis_started', TimestampProperty()),
('analysis_ended', TimestampProperty()),
('result_name', StringProperty()),
('result', StringProperty()),
('result', OpenVocabProperty(MALWARE_RESULT)),
('analysis_sco_refs', ListProperty(ReferenceProperty(valid_types="SCO", spec_version='2.1'))),
('sample_ref', ReferenceProperty(valid_types="SCO", spec_version='2.1')),
('revoked', BooleanProperty(default=lambda: False)),
@ -607,17 +615,7 @@ class Opinion(_DomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
('explanation', StringProperty()),
('authors', ListProperty(StringProperty)),
(
'opinion', EnumProperty(
allowed=[
'strongly-disagree',
'disagree',
'neutral',
'agree',
'strongly-agree',
], required=True,
),
),
('opinion', EnumProperty(OPINION, required=True)),
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)),
@ -644,7 +642,7 @@ class Report(_DomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
('name', StringProperty(required=True)),
('description', StringProperty()),
('report_types', ListProperty(StringProperty)),
('report_types', ListProperty(OpenVocabProperty(REPORT_TYPE))),
('published', TimestampProperty(required=True)),
('object_refs', ListProperty(ReferenceProperty(invalid_types=[], spec_version='2.1'), required=True)),
('revoked', BooleanProperty(default=lambda: False)),
@ -672,17 +670,17 @@ class ThreatActor(_DomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
('name', StringProperty(required=True)),
('description', StringProperty()),
('threat_actor_types', ListProperty(StringProperty)),
('threat_actor_types', ListProperty(OpenVocabProperty(THREAT_ACTOR_TYPE))),
('aliases', ListProperty(StringProperty)),
('first_seen', TimestampProperty()),
('last_seen', TimestampProperty()),
('roles', ListProperty(StringProperty)),
('roles', ListProperty(OpenVocabProperty(THREAT_ACTOR_ROLE))),
('goals', ListProperty(StringProperty)),
('sophistication', StringProperty()),
('resource_level', StringProperty()),
('primary_motivation', StringProperty()),
('secondary_motivations', ListProperty(StringProperty)),
('personal_motivations', ListProperty(StringProperty)),
('sophistication', OpenVocabProperty(THREAT_ACTOR_SOPHISTICATION)),
('resource_level', OpenVocabProperty(ATTACK_RESOURCE_LEVEL)),
('primary_motivation', OpenVocabProperty(ATTACK_MOTIVATION)),
('secondary_motivations', ListProperty(OpenVocabProperty(ATTACK_MOTIVATION))),
('personal_motivations', ListProperty(OpenVocabProperty(ATTACK_MOTIVATION))),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)),
('confidence', IntegerProperty()),
@ -718,7 +716,7 @@ class Tool(_DomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
('name', StringProperty(required=True)),
('description', StringProperty()),
('tool_types', ListProperty(StringProperty)),
('tool_types', ListProperty(OpenVocabProperty(TOOL_TYPE))),
('aliases', ListProperty(StringProperty)),
('kill_chain_phases', ListProperty(KillChainPhase)),
('tool_version', StringProperty()),

452
stix2/v21/vocab.py Normal file
View File

@ -0,0 +1,452 @@
"""
STIX 2.1 open vocabularies and enums
"""
ACCOUNT_TYPE = [
"facebook",
"ldap",
"nis",
"openid",
"radius",
"skype",
"tacacs",
"twitter",
"unix",
"windows-local",
"windows-domain",
]
ATTACK_MOTIVATION = [
"accidental",
"coercion",
"dominance",
"ideology",
"notoriety",
"organizational-gain",
"personal-gain",
"personal-satisfaction",
"revenge",
"unpredictable",
]
ATTACK_RESOURCE_LEVEL = [
"individual",
"club",
"contest",
"team",
"organization",
"government",
]
ENCRYPTION_ALGORITHM = [
"AES-256-GCM",
"ChaCha20-Poly1305",
"mime-type-indicated",
]
GROUPING_CONTEXT = [
"suspicious-activity",
"malware-analysis",
"unspecified",
]
HASHING_ALGORITHM = [
"MD5",
"SHA-1",
"SHA-256",
"SHA-512",
"SHA3-256",
"SHA3-512",
"SSDEEP",
"TLSH",
]
IDENTITY_CLASS = [
"individual",
"group",
"system",
"organization",
"class",
"unknown",
]
IMPLEMENTATION_LANGUAGE = [
"applescript",
"bash",
"c",
"c++",
"c#",
"go",
"java",
"javascript",
"lua",
"objective-c",
"perl",
"php",
"powershell",
"python",
"ruby",
"scala",
"swift",
"typescript",
"visual-basic",
"x86-32",
"x86-64",
]
INDICATOR_TYPE = [
"anomalous-activity",
"anonymization",
"benign",
"compromised",
"malicious-activity",
"attribution",
"unknown",
]
INDUSTRY_SECTOR = [
"agriculture",
"aerospace",
"automotive",
"chemical",
"commercial",
"communications",
"construction",
"defense",
"education",
"energy",
"entertainment",
"financial-services",
"government",
"emergency-services",
"government-national",
"government-regional",
"government-local",
"government-public-services",
"healthcare",
"hospitality-leisure",
"infrastructure",
"dams",
"nuclear",
"water",
"insurance",
"manufacturing",
"mining",
"non-profit",
"pharmaceuticals",
"retail",
"technology",
"telecommunications",
"transportation",
"utilities",
]
INFRASTRUCTURE_TYPE = [
"amplification",
"anonymization",
"botnet",
"command-and-control",
"exfiltration",
"hosting-malware",
"hosting-target-lists",
"phishing",
"reconnaissance",
"staging",
"unknown",
]
MALWARE_RESULT = [
"malicious",
"suspicious",
"benign",
"unknown",
]
MALWARE_CAPABILITIES = [
"accesses-remote-machines",
"anti-debugging",
"anti-disassembly",
"anti-emulation",
"anti-memory-forensics",
"anti-sandbox",
"anti-vm",
"captures-input-peripherals",
"captures-output-peripherals",
"captures-system-state-data",
"cleans-traces-of-infection",
"commits-fraud",
"communicates-with-c2",
"compromises-data-availability",
"compromises-data-integrity",
"compromises-system-availability",
"controls-local-machine",
"degrades-security-software",
"degrades-system-updates",
"determines-c2-server",
"emails-spam",
"escalates-privileges",
"evades-av",
"exfiltrates-data",
"fingerprints-host",
"hides-artifacts",
"hides-executing-code",
"infects-files",
"infects-remote-machines",
"installs-other-components",
"persists-after-system-reboot",
"prevents-artifact-access",
"prevents-artifact-deletion",
"probes-network-environment",
"self-modifies",
"steals-authentication-credentials",
"violates-system-operational-integrity",
]
MALWARE_TYPE = [
"adware",
"backdoor",
"bot",
"bootkit",
"ddos",
"downloader",
"dropper",
"exploit-kit",
"keylogger",
"ransomware",
"remote-access-trojan",
"resource-exploitation",
"rogue-security-software",
"rootkit",
"screen-capture",
"spyware",
"trojan",
"unknown",
"virus",
"webshell",
"wiper",
"worm",
]
NETWORK_SOCKET_ADDRESS_FAMILY = [
"AF_UNSPEC",
"AF_INET",
"AF_IPX",
"AF_APPLETALK",
"AF_NETBIOS",
"AF_INET6",
"AF_IRDA",
"AF_BTH",
]
NETWORK_SOCKET_TYPE = [
"SOCK_STREAM",
"SOCK_DGRAM",
"SOCK_RAW",
"SOCK_RDM",
"SOCK_SEQPACKET",
]
OPINION = [
"strongly-disagree",
"disagree",
"neutral",
"agree",
"strongly-agree",
]
PATTERN_TYPE = [
"stix",
"pcre",
"sigma",
"snort",
"suricata",
"yara",
]
PROCESSOR_ARCHITECTURE = [
"alpha",
"arm",
"ia-64",
"mips",
"powerpc",
"sparc",
"x86",
"x86-64",
]
REGION = [
"africa",
"eastern-africa",
"middle-africa",
"northern-africa",
"southern-africa",
"western-africa",
"americas",
"latin-america-caribbean",
"south-america",
"caribbean",
"central-america",
"northern-america",
"asia",
"central-asia",
"eastern-asia",
"southern-asia",
"south-eastern-asia",
"western-asia",
"europe",
"eastern-europe",
"northern-europe",
"southern-europe",
"western-europe",
"oceania",
"antarctica",
"australia-new-zealand",
"melanesia",
"micronesia",
"polynesia",
]
REPORT_TYPE = [
"attack-pattern",
"campaign",
"identity",
"indicator",
"intrusion-set",
"malware",
"observed-data",
"threat-actor",
"threat-report",
"tool",
"vulnerability",
]
THREAT_ACTOR_TYPE = [
"activist",
"competitor",
"crime-syndicate",
"criminal",
"hacker",
"insider-accidental",
"insider-disgruntled",
"nation-state",
"sensationalist",
"spy",
"terrorist",
"unknown",
]
THREAT_ACTOR_ROLE = [
"agent",
"director",
"independent",
"infrastructure-architect",
"infrastructure-operator",
"malware-author",
"sponsor",
]
THREAT_ACTOR_SOPHISTICATION = [
"none",
"minimal",
"intermediate",
"advanced",
"expert",
"innovator",
"strategic",
]
TOOL_TYPE = [
"denial-of-service",
"exploitation",
"information-gathering",
"network-capture",
"credential-exploitation",
"remote-access",
"vulnerability-scanning",
"unknown",
]
WINDOWS_INTEGRITY_LEVEL = [
"low",
"medium",
"high",
"system",
]
WINDOWS_PEBINARY_TYPE = [
"dll",
"exe",
"sys",
]
WINDOWS_REGISTRY_DATATYPE = [
"REG_NONE",
"REG_SZ",
"REG_EXPAND_SZ",
"REG_BINARY",
"REG_DWORD",
"REG_DWORD_BIG_ENDIAN",
"REG_DWORD_LITTLE_ENDIAN",
"REG_LINK",
"REG_MULTI_SZ",
"REG_RESOURCE_LIST",
"REG_FULL_RESOURCE_DESCRIPTION",
"REG_RESOURCE_REQUIREMENTS_LIST",
"REG_QWORD",
"REG_INVALID_TYPE",
]
WINDOWS_SERVICE_START_TYPE = [
"SERVICE_AUTO_START",
"SERVICE_BOOT_START",
"SERVICE_DEMAND_START",
"SERVICE_DISABLED",
"SERVICE_SYSTEM_ALERT",
]
WINDOWS_SERVICE_TYPE = [
"SERVICE_KERNEL_DRIVER",
"SERVICE_FILE_SYSTEM_DRIVER",
"SERVICE_WIN32_OWN_PROCESS",
"SERVICE_WIN32_SHARE_PROCESS",
]
WINDOWS_SERVICE_STATUS = [
"SERVICE_CONTINUE_PENDING",
"SERVICE_PAUSE_PENDING",
"SERVICE_PAUSED",
"SERVICE_RUNNING",
"SERVICE_START_PENDING",
"SERVICE_STOP_PENDING",
"SERVICE_STOPPED",
]