Pull in updates from master
commit
f37b84a564
|
@ -7,10 +7,10 @@ import re
|
|||
|
||||
import stix2
|
||||
|
||||
from .base import _STIXBase
|
||||
from .exceptions import DuplicateObjectRegistrationError, ParseError
|
||||
from .base import _Observable, _STIXBase
|
||||
from .exceptions import ParseError
|
||||
from .markings import _MarkingsMixin
|
||||
from .utils import _get_dict
|
||||
from .utils import SCO21_EXT_REGEX, TYPE_REGEX, _get_dict
|
||||
|
||||
STIX2_OBJ_MAPS = {}
|
||||
|
||||
|
@ -262,22 +262,54 @@ def _register_observable(new_observable, version=None):
|
|||
OBJ_MAP_OBSERVABLE[new_observable._type] = new_observable
|
||||
|
||||
|
||||
def _register_observable_extension(observable, new_extension, version=None):
|
||||
def _register_observable_extension(
|
||||
observable, new_extension, version=stix2.DEFAULT_VERSION,
|
||||
):
|
||||
"""Register a custom extension to a STIX Cyber Observable type.
|
||||
|
||||
Args:
|
||||
observable: An observable object
|
||||
observable: An observable class or instance
|
||||
new_extension (class): A class to register in the Observables
|
||||
Extensions map.
|
||||
version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If
|
||||
None, use latest version.
|
||||
version (str): Which STIX2 version to use. (e.g. "2.0", "2.1").
|
||||
Defaults to the latest supported version.
|
||||
|
||||
"""
|
||||
if version:
|
||||
v = 'v' + version.replace('.', '')
|
||||
else:
|
||||
# Use default version (latest) if no version was provided.
|
||||
v = 'v' + stix2.DEFAULT_VERSION.replace('.', '')
|
||||
obs_class = observable if isinstance(observable, type) else \
|
||||
type(observable)
|
||||
ext_type = new_extension._type
|
||||
|
||||
if not issubclass(obs_class, _Observable):
|
||||
raise ValueError("'observable' must be a valid Observable class!")
|
||||
|
||||
if version == "2.0":
|
||||
if not re.match(TYPE_REGEX, ext_type):
|
||||
raise ValueError(
|
||||
"Invalid extension type name '%s': must only contain the "
|
||||
"characters a-z (lowercase ASCII), 0-9, and hyphen (-)." %
|
||||
ext_type,
|
||||
)
|
||||
else: # 2.1+
|
||||
if not re.match(SCO21_EXT_REGEX, ext_type):
|
||||
raise ValueError(
|
||||
"Invalid extension type name '%s': must only contain the "
|
||||
"characters a-z (lowercase ASCII), 0-9, hyphen (-), and end "
|
||||
"with '-ext'." % ext_type,
|
||||
)
|
||||
|
||||
if len(ext_type) < 3 or len(ext_type) > 250:
|
||||
raise ValueError(
|
||||
"Invalid extension type name '%s': must be between 3 and 250"
|
||||
" characters." % ext_type,
|
||||
)
|
||||
|
||||
if not new_extension._properties:
|
||||
raise ValueError(
|
||||
"Invalid extension: must define at least one property: " +
|
||||
ext_type,
|
||||
)
|
||||
|
||||
v = 'v' + version.replace('.', '')
|
||||
|
||||
try:
|
||||
observable_type = observable._type
|
||||
|
@ -291,7 +323,7 @@ def _register_observable_extension(observable, new_extension, version=None):
|
|||
EXT_MAP = STIX2_OBJ_MAPS[v]['observable-extensions']
|
||||
|
||||
try:
|
||||
EXT_MAP[observable_type][new_extension._type] = new_extension
|
||||
EXT_MAP[observable_type][ext_type] = new_extension
|
||||
except KeyError:
|
||||
if observable_type not in OBJ_MAP_OBSERVABLE:
|
||||
raise ValueError(
|
||||
|
@ -300,7 +332,7 @@ def _register_observable_extension(observable, new_extension, version=None):
|
|||
% observable_type,
|
||||
)
|
||||
else:
|
||||
EXT_MAP[observable_type] = {new_extension._type: new_extension}
|
||||
EXT_MAP[observable_type] = {ext_type: new_extension}
|
||||
|
||||
|
||||
def _collect_stix2_mappings():
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from collections import OrderedDict
|
||||
import re
|
||||
|
||||
import six
|
||||
|
||||
from .base import _cls_init, _Extension, _Observable, _STIXBase
|
||||
from .core import (
|
||||
STIXDomainObject, _register_marking, _register_object,
|
||||
|
@ -113,24 +115,23 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props=
|
|||
|
||||
|
||||
def _custom_extension_builder(cls, observable, type, properties, version):
|
||||
if not observable or not issubclass(observable, _Observable):
|
||||
raise ValueError("'observable' must be a valid Observable class!")
|
||||
|
||||
try:
|
||||
prop_dict = OrderedDict(properties)
|
||||
except TypeError as e:
|
||||
six.raise_from(
|
||||
ValueError(
|
||||
"Extension properties must be dict-like, e.g. a list "
|
||||
"containing tuples. For example, "
|
||||
"[('property1', IntegerProperty())]",
|
||||
),
|
||||
e,
|
||||
)
|
||||
|
||||
class _CustomExtension(cls, _Extension):
|
||||
|
||||
if not re.match(TYPE_REGEX, type):
|
||||
raise ValueError(
|
||||
"Invalid extension type name '%s': must only contain the "
|
||||
"characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type,
|
||||
)
|
||||
elif len(type) < 3 or len(type) > 250:
|
||||
raise ValueError("Invalid extension type name '%s': must be between 3 and 250 characters." % type)
|
||||
|
||||
if not properties or not isinstance(properties, list):
|
||||
raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]")
|
||||
|
||||
_type = type
|
||||
_properties = OrderedDict(properties)
|
||||
_properties = prop_dict
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
_Extension.__init__(self, **kwargs)
|
||||
|
|
|
@ -822,27 +822,24 @@ def test_custom_extension_invalid_type_name():
|
|||
|
||||
|
||||
def test_custom_extension_no_properties():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
@stix2.v20.CustomExtension(stix2.v20.DomainName, 'x-new-ext2', None)
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Must supply a list, containing tuples." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_empty_properties():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
@stix2.v20.CustomExtension(stix2.v20.DomainName, 'x-new-ext2', [])
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Must supply a list, containing tuples." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_dict_properties():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
@stix2.v20.CustomExtension(stix2.v20.DomainName, 'x-new-ext2', {})
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Must supply a list, containing tuples." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_no_init_1():
|
||||
|
|
|
@ -14,14 +14,7 @@ COA_WITH_BIN_JSON = """{
|
|||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
|
||||
"action_type": "textual:text/plain",
|
||||
"os_execution_envs": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
],
|
||||
"action_bin": "aGVsbG8gd29ybGQ="
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
|
||||
}"""
|
||||
|
||||
|
||||
|
@ -33,17 +26,7 @@ COA_WITH_REF_JSON = """{
|
|||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
|
||||
"action_type": "textual:text/plain",
|
||||
"os_execution_envs": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
],
|
||||
"action_reference": {
|
||||
"source_name": "a source",
|
||||
"description": "description of a source"
|
||||
}
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
|
||||
}"""
|
||||
|
||||
|
||||
|
@ -84,15 +67,4 @@ def test_parse_course_of_action(sdo_json, sdo_dict):
|
|||
assert getattr(coa, attr_name) == cmp_value
|
||||
|
||||
|
||||
def test_course_of_action_constraint():
|
||||
with pytest.raises(stix2.exceptions.MutuallyExclusivePropertiesError):
|
||||
stix2.v21.CourseOfAction(
|
||||
name="Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
action_bin="aGVsbG8gd29ybGQ=",
|
||||
action_reference=stix2.v21.ExternalReference(
|
||||
source_name="a source",
|
||||
description="description of a source",
|
||||
),
|
||||
)
|
||||
|
||||
# TODO: Add other examples
|
||||
|
|
|
@ -800,7 +800,7 @@ def test_custom_extension_wrong_observable_type():
|
|||
)
|
||||
def test_custom_extension_with_list_and_dict_properties_observable_type(data):
|
||||
@stix2.v21.CustomExtension(
|
||||
stix2.v21.UserAccount, 'some-extension', [
|
||||
stix2.v21.UserAccount, 'x-some-extension-ext', [
|
||||
('keys', stix2.properties.ListProperty(stix2.properties.DictionaryProperty, required=True)),
|
||||
],
|
||||
)
|
||||
|
@ -876,32 +876,29 @@ def test_custom_extension_invalid_type_name():
|
|||
|
||||
|
||||
def test_custom_extension_no_properties():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new-ext2', None)
|
||||
with pytest.raises(ValueError):
|
||||
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', None)
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Must supply a list, containing tuples." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_empty_properties():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new-ext2', [])
|
||||
with pytest.raises(ValueError):
|
||||
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', [])
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Must supply a list, containing tuples." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_dict_properties():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new-ext2', {})
|
||||
with pytest.raises(ValueError):
|
||||
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', {})
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Must supply a list, containing tuples." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_no_init_1():
|
||||
@stix2.v21.CustomExtension(
|
||||
stix2.v21.DomainName, 'x-new-extension', [
|
||||
stix2.v21.DomainName, 'x-new-extension-ext', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
],
|
||||
)
|
||||
|
@ -914,7 +911,7 @@ def test_custom_extension_no_init_1():
|
|||
|
||||
def test_custom_extension_no_init_2():
|
||||
@stix2.v21.CustomExtension(
|
||||
stix2.v21.DomainName, 'x-new-ext2', [
|
||||
stix2.v21.DomainName, 'x-new2-ext', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
],
|
||||
)
|
||||
|
@ -949,14 +946,14 @@ def test_custom_and_spec_extension_mix():
|
|||
file_obs = stix2.v21.File(
|
||||
name="my_file.dat",
|
||||
extensions={
|
||||
"x-custom1": {
|
||||
"x-custom1-ext": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
},
|
||||
"ntfs-ext": {
|
||||
"sid": "S-1-whatever",
|
||||
},
|
||||
"x-custom2": {
|
||||
"x-custom2-ext": {
|
||||
"z": 99.9,
|
||||
"y": False,
|
||||
},
|
||||
|
@ -969,8 +966,8 @@ def test_custom_and_spec_extension_mix():
|
|||
allow_custom=True,
|
||||
)
|
||||
|
||||
assert file_obs.extensions["x-custom1"] == {"a": 1, "b": 2}
|
||||
assert file_obs.extensions["x-custom2"] == {"y": False, "z": 99.9}
|
||||
assert file_obs.extensions["x-custom1-ext"] == {"a": 1, "b": 2}
|
||||
assert file_obs.extensions["x-custom2-ext"] == {"y": False, "z": 99.9}
|
||||
assert file_obs.extensions["ntfs-ext"].sid == "S-1-whatever"
|
||||
assert file_obs.extensions["raster-image-ext"].image_height == 1024
|
||||
|
||||
|
|
|
@ -120,3 +120,14 @@ def test_external_reference_source_required():
|
|||
|
||||
assert excinfo.value.cls == stix2.v21.ExternalReference
|
||||
assert excinfo.value.properties == ["source_name"]
|
||||
|
||||
|
||||
def test_external_reference_bad_hash():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError):
|
||||
stix2.v21.ExternalReference(
|
||||
source_name="ACME Threat Intel",
|
||||
description="Threat report",
|
||||
hashes={
|
||||
"SHA-123": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
},
|
||||
)
|
||||
|
|
|
@ -14,9 +14,6 @@ EXPECTED_INDICATOR = """{
|
|||
"id": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7",
|
||||
"created": "2017-01-01T00:00:01.000Z",
|
||||
"modified": "2017-01-01T00:00:01.000Z",
|
||||
"indicator_types": [
|
||||
"malicious-activity"
|
||||
],
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"pattern_version": "2.1",
|
||||
|
@ -29,7 +26,6 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
|
|||
id='indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7',
|
||||
created='2017-01-01T00:00:01.000Z',
|
||||
modified='2017-01-01T00:00:01.000Z',
|
||||
indicator_types=['malicious-activity'],
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
pattern_type='stix',
|
||||
pattern_version='2.1',
|
||||
|
@ -49,7 +45,6 @@ def test_indicator_with_all_required_properties():
|
|||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
pattern_type="stix",
|
||||
valid_from=epoch,
|
||||
indicator_types=['malicious-activity'],
|
||||
)
|
||||
|
||||
assert ind.revoked is False
|
||||
|
@ -103,8 +98,8 @@ def test_indicator_required_properties():
|
|||
stix2.v21.Indicator()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.properties == ["indicator_types", "pattern", "pattern_type", "valid_from"]
|
||||
assert str(excinfo.value) == "No values for required properties for Indicator: (indicator_types, pattern, pattern_type, valid_from)."
|
||||
assert excinfo.value.properties == ["pattern", "pattern_type", "valid_from"]
|
||||
assert str(excinfo.value) == "No values for required properties for Indicator: (pattern, pattern_type, valid_from)."
|
||||
|
||||
|
||||
def test_indicator_required_property_pattern():
|
||||
|
@ -163,9 +158,6 @@ def test_created_modified_time_are_identical_by_default():
|
|||
"id": INDICATOR_ID,
|
||||
"created": "2017-01-01T00:00:01Z",
|
||||
"modified": "2017-01-01T00:00:01Z",
|
||||
"indicator_types": [
|
||||
"malicious-activity",
|
||||
],
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"valid_from": "1970-01-01T00:00:01Z",
|
||||
|
@ -181,7 +173,6 @@ def test_parse_indicator(data):
|
|||
assert idctr.created == dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
|
||||
assert idctr.modified == dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
|
||||
assert idctr.valid_from == dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
|
||||
assert idctr.indicator_types[0] == "malicious-activity"
|
||||
assert idctr.pattern == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']"
|
||||
|
||||
|
||||
|
|
|
@ -13,10 +13,7 @@ EXPECTED_INFRASTRUCTURE = """{
|
|||
"id": "infrastructure--3000ae1b-784c-f03d-8abc-0a625b2ff018",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Poison Ivy C2",
|
||||
"infrastructure_types": [
|
||||
"command-and-control"
|
||||
]
|
||||
"name": "Poison Ivy C2"
|
||||
}"""
|
||||
|
||||
|
||||
|
@ -29,7 +26,6 @@ def test_infrastructure_with_all_required_properties():
|
|||
created=now,
|
||||
modified=now,
|
||||
name="Poison Ivy C2",
|
||||
infrastructure_types=["command-and-control"],
|
||||
)
|
||||
|
||||
assert str(infra) == EXPECTED_INFRASTRUCTURE
|
||||
|
@ -76,7 +72,7 @@ def test_infrastructure_required_properties():
|
|||
stix2.v21.Infrastructure()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.properties == ["infrastructure_types", "name"]
|
||||
assert excinfo.value.properties == ["name"]
|
||||
|
||||
|
||||
def test_infrastructure_required_property_name():
|
||||
|
@ -105,7 +101,6 @@ def test_invalid_kwarg_to_infrastructure():
|
|||
"id": INFRASTRUCTURE_ID,
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"infrastructure_types": ["command-and-control"],
|
||||
"name": "Poison Ivy C2",
|
||||
},
|
||||
],
|
||||
|
@ -118,7 +113,6 @@ def test_parse_infrastructure(data):
|
|||
assert infra.id == INFRASTRUCTURE_ID
|
||||
assert infra.created == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert infra.modified == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert infra.infrastructure_types == ['command-and-control']
|
||||
assert infra.name == 'Poison Ivy C2'
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import datetime as dt
|
||||
import re
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
@ -16,9 +16,6 @@ EXPECTED_MALWARE = """{
|
|||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"name": "Cryptolocker",
|
||||
"malware_types": [
|
||||
"ransomware"
|
||||
],
|
||||
"is_family": false
|
||||
}"""
|
||||
|
||||
|
@ -31,7 +28,6 @@ def test_malware_with_all_required_properties():
|
|||
id=MALWARE_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
malware_types=["ransomware"],
|
||||
name="Cryptolocker",
|
||||
is_family=False,
|
||||
)
|
||||
|
@ -80,7 +76,7 @@ def test_malware_required_properties():
|
|||
stix2.v21.Malware()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Malware
|
||||
assert excinfo.value.properties == ["is_family", "malware_types"]
|
||||
assert excinfo.value.properties == ["is_family"]
|
||||
|
||||
|
||||
def test_malware_required_property_name():
|
||||
|
@ -116,7 +112,6 @@ def test_invalid_kwarg_to_malware():
|
|||
"id": MALWARE_ID,
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"malware_types": ["ransomware"],
|
||||
"name": "Cryptolocker",
|
||||
"is_family": False,
|
||||
},
|
||||
|
@ -130,13 +125,14 @@ def test_parse_malware(data):
|
|||
assert mal.id == MALWARE_ID
|
||||
assert mal.created == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert mal.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert mal.malware_types == ['ransomware']
|
||||
assert mal.name == 'Cryptolocker'
|
||||
assert not mal.is_family
|
||||
|
||||
|
||||
def test_parse_malware_invalid_labels():
|
||||
data = re.compile('\\[.+\\]', re.DOTALL).sub('1', EXPECTED_MALWARE)
|
||||
def test_parse_malware_invalid_types():
|
||||
data = json.loads(EXPECTED_MALWARE)
|
||||
data["malware_types"] = 1 # Oops, not a list
|
||||
data = json.dumps(data)
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.parse(data)
|
||||
assert "Invalid value for Malware 'malware_types'" in str(excinfo.value)
|
||||
|
@ -197,3 +193,22 @@ def test_malware_non_family_no_name():
|
|||
"is_family": False,
|
||||
"malware_types": ["something"],
|
||||
})
|
||||
|
||||
|
||||
def test_malware_with_os_refs():
|
||||
software = stix2.parse({
|
||||
"type": "software",
|
||||
"name": "SuperOS",
|
||||
"spec_version": "2.1",
|
||||
})
|
||||
|
||||
malware = stix2.parse({
|
||||
"type": "malware",
|
||||
"id": MALWARE_ID,
|
||||
"spec_version": "2.1",
|
||||
"is_family": False,
|
||||
"malware_types": ["something"],
|
||||
"operating_system_refs": [software],
|
||||
})
|
||||
|
||||
assert malware["operating_system_refs"][0] == software["id"]
|
||||
|
|
|
@ -34,11 +34,13 @@ MALWARE_ANALYSIS_JSON = """{
|
|||
"submitted": "2018-11-23T06:45:55.747Z",
|
||||
"analysis_started": "2018-11-29T07:30:03.895Z",
|
||||
"analysis_ended": "2018-11-29T08:30:03.895Z",
|
||||
"av_result": "malicious",
|
||||
"result_name": "MegaRansom",
|
||||
"result": "malicious",
|
||||
"analysis_sco_refs": [
|
||||
"file--fc27e371-6c88-4c5c-868a-4dda0e60b167",
|
||||
"url--6f7a74cd-8eb2-4b88-a4da-aa878e50ac2e"
|
||||
]
|
||||
],
|
||||
"sample_ref": "email-addr--499a32d7-74c1-4276-ace9-725ac933e243"
|
||||
}"""
|
||||
|
||||
|
||||
|
|
|
@ -1300,6 +1300,7 @@ def test_software_example():
|
|||
s = stix2.v21.Software(
|
||||
name="Word",
|
||||
cpe="cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*",
|
||||
swid="com.acme.rms-ce-v4-1-5-0",
|
||||
version="2002",
|
||||
vendor="Microsoft",
|
||||
)
|
||||
|
|
|
@ -26,6 +26,7 @@ NOW = object()
|
|||
STIX_UNMOD_PROPERTIES = ['created', 'created_by_ref', 'id', 'type']
|
||||
|
||||
TYPE_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$'
|
||||
SCO21_EXT_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-ext$'
|
||||
|
||||
|
||||
class STIXdatetime(dt.datetime):
|
||||
|
|
|
@ -4,6 +4,7 @@ from collections import OrderedDict
|
|||
|
||||
from ..base import _STIXBase
|
||||
from ..custom import _custom_marking_builder
|
||||
from ..exceptions import InvalidValueError
|
||||
from ..markings import _MarkingsMixin
|
||||
from ..markings.utils import check_tlp_marking
|
||||
from ..properties import (
|
||||
|
@ -28,10 +29,26 @@ class ExternalReference(_STIXBase):
|
|||
('external_id', StringProperty()),
|
||||
])
|
||||
|
||||
# This is hash-algorithm-ov
|
||||
_LEGAL_HASHES = {
|
||||
"MD5", "SHA-1", "SHA-256", "SHA-512", "SHA3-256", "SHA3-512", "SSDEEP",
|
||||
"TLSH",
|
||||
}
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(ExternalReference, self)._check_object_constraints()
|
||||
self._check_at_least_one_property(['description', 'external_id', 'url'])
|
||||
|
||||
if "hashes" in self:
|
||||
if any(
|
||||
hash_ not in self._LEGAL_HASHES
|
||||
for hash_ in self["hashes"]
|
||||
):
|
||||
raise InvalidValueError(
|
||||
ExternalReference, "hashes",
|
||||
"Hash algorithm names must be members of hash-algorithm-ov",
|
||||
)
|
||||
|
||||
|
||||
class KillChainPhase(_STIXBase):
|
||||
# TODO: Add link
|
||||
|
|
|
@ -382,7 +382,7 @@ class File(_Observable):
|
|||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["hashes", "name", "extensions"]
|
||||
_id_contributing_properties = ["hashes", "name", "parent_directory_ref", "extensions"]
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(File, self)._check_object_constraints()
|
||||
|
@ -760,6 +760,7 @@ class Software(_Observable):
|
|||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('name', StringProperty(required=True)),
|
||||
('cpe', StringProperty()),
|
||||
('swid', StringProperty()),
|
||||
('languages', ListProperty(StringProperty)),
|
||||
('vendor', StringProperty()),
|
||||
('version', StringProperty()),
|
||||
|
|
|
@ -13,10 +13,9 @@ from ..exceptions import (
|
|||
InvalidValueError, PropertyPresenceError, STIXDeprecationWarning,
|
||||
)
|
||||
from ..properties import (
|
||||
BinaryProperty, BooleanProperty, EmbeddedObjectProperty, EnumProperty,
|
||||
FloatProperty, IDProperty, IntegerProperty, ListProperty,
|
||||
ObservableProperty, PatternProperty, ReferenceProperty, StringProperty,
|
||||
TimestampProperty, TypeProperty,
|
||||
BooleanProperty, EnumProperty, FloatProperty, IDProperty, IntegerProperty,
|
||||
ListProperty, ObservableProperty, PatternProperty, ReferenceProperty,
|
||||
StringProperty, TimestampProperty, TypeProperty,
|
||||
)
|
||||
from ..utils import NOW
|
||||
from .common import ExternalReference, GranularMarking, KillChainPhase
|
||||
|
@ -106,10 +105,6 @@ class CourseOfAction(STIXDomainObject):
|
|||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('action_type', StringProperty()),
|
||||
('os_execution_envs', ListProperty(StringProperty)),
|
||||
('action_bin', BinaryProperty()),
|
||||
('action_reference', EmbeddedObjectProperty(ExternalReference)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
|
@ -119,14 +114,6 @@ class CourseOfAction(STIXDomainObject):
|
|||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(CourseOfAction, self)._check_object_constraints()
|
||||
|
||||
self._check_mutually_exclusive_properties(
|
||||
["action_bin", "action_reference"],
|
||||
at_least_one=False,
|
||||
)
|
||||
|
||||
|
||||
class Grouping(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
|
@ -173,7 +160,7 @@ class Identity(STIXDomainObject):
|
|||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('roles', ListProperty(StringProperty)),
|
||||
('identity_class', StringProperty(required=True)),
|
||||
('identity_class', StringProperty()),
|
||||
('sectors', ListProperty(StringProperty)),
|
||||
('contact_information', StringProperty()),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
|
@ -202,7 +189,7 @@ class Indicator(STIXDomainObject):
|
|||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('indicator_types', ListProperty(StringProperty, required=True)),
|
||||
('indicator_types', ListProperty(StringProperty)),
|
||||
('pattern', PatternProperty(required=True)),
|
||||
('pattern_type', StringProperty(required=True)),
|
||||
('pattern_version', StringProperty()),
|
||||
|
@ -269,7 +256,7 @@ class Infrastructure(STIXDomainObject):
|
|||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('infrastructure_types', ListProperty(StringProperty, required=True)),
|
||||
('infrastructure_types', ListProperty(StringProperty)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('first_seen', TimestampProperty()),
|
||||
|
@ -454,13 +441,13 @@ class Malware(STIXDomainObject):
|
|||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('malware_types', ListProperty(StringProperty, required=True)),
|
||||
('malware_types', ListProperty(StringProperty)),
|
||||
('is_family', BooleanProperty(required=True)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
('os_execution_envs', ListProperty(StringProperty)),
|
||||
('operating_system_refs', ListProperty(ReferenceProperty(valid_types='software', spec_version='2.1'))),
|
||||
('architecture_execution_envs', ListProperty(StringProperty)),
|
||||
('implementation_languages', ListProperty(StringProperty)),
|
||||
('capabilities', ListProperty(StringProperty)),
|
||||
|
@ -524,14 +511,16 @@ class MalwareAnalysis(STIXDomainObject):
|
|||
('submitted', TimestampProperty()),
|
||||
('analysis_started', TimestampProperty()),
|
||||
('analysis_ended', TimestampProperty()),
|
||||
('av_result', StringProperty()),
|
||||
('result_name', StringProperty()),
|
||||
('result', StringProperty()),
|
||||
('analysis_sco_refs', ListProperty(ReferenceProperty(valid_types="SCO", spec_version='2.1'))),
|
||||
('sample_ref', ReferenceProperty(valid_types="SCO", spec_version="2.1")),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(MalwareAnalysis, self)._check_object_constraints()
|
||||
|
||||
self._check_at_least_one_property(["av_result", "analysis_sco_refs"])
|
||||
self._check_at_least_one_property(["result", "analysis_sco_refs"])
|
||||
|
||||
|
||||
class Note(STIXDomainObject):
|
||||
|
@ -672,7 +661,7 @@ class Report(STIXDomainObject):
|
|||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('report_types', ListProperty(StringProperty, required=True)),
|
||||
('report_types', ListProperty(StringProperty)),
|
||||
('published', TimestampProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
|
@ -701,7 +690,7 @@ class ThreatActor(STIXDomainObject):
|
|||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('threat_actor_types', ListProperty(StringProperty, required=True)),
|
||||
('threat_actor_types', ListProperty(StringProperty)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
|
@ -748,7 +737,7 @@ class Tool(STIXDomainObject):
|
|||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('tool_types', ListProperty(StringProperty, required=True)),
|
||||
('tool_types', ListProperty(StringProperty)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('tool_version', StringProperty()),
|
||||
|
|
Loading…
Reference in New Issue