Pull in updates from master

master
Desai, Kartikey H 2020-03-20 16:52:21 -04:00
commit f37b84a564
15 changed files with 159 additions and 138 deletions

View File

@ -7,10 +7,10 @@ import re
import stix2 import stix2
from .base import _STIXBase from .base import _Observable, _STIXBase
from .exceptions import DuplicateObjectRegistrationError, ParseError from .exceptions import ParseError
from .markings import _MarkingsMixin from .markings import _MarkingsMixin
from .utils import _get_dict from .utils import SCO21_EXT_REGEX, TYPE_REGEX, _get_dict
STIX2_OBJ_MAPS = {} STIX2_OBJ_MAPS = {}
@ -262,22 +262,54 @@ def _register_observable(new_observable, version=None):
OBJ_MAP_OBSERVABLE[new_observable._type] = new_observable 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. """Register a custom extension to a STIX Cyber Observable type.
Args: Args:
observable: An observable object observable: An observable class or instance
new_extension (class): A class to register in the Observables new_extension (class): A class to register in the Observables
Extensions map. Extensions map.
version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If version (str): Which STIX2 version to use. (e.g. "2.0", "2.1").
None, use latest version. Defaults to the latest supported version.
""" """
if version: 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('.', '') v = 'v' + version.replace('.', '')
else:
# Use default version (latest) if no version was provided.
v = 'v' + stix2.DEFAULT_VERSION.replace('.', '')
try: try:
observable_type = observable._type 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'] EXT_MAP = STIX2_OBJ_MAPS[v]['observable-extensions']
try: try:
EXT_MAP[observable_type][new_extension._type] = new_extension EXT_MAP[observable_type][ext_type] = new_extension
except KeyError: except KeyError:
if observable_type not in OBJ_MAP_OBSERVABLE: if observable_type not in OBJ_MAP_OBSERVABLE:
raise ValueError( raise ValueError(
@ -300,7 +332,7 @@ def _register_observable_extension(observable, new_extension, version=None):
% observable_type, % observable_type,
) )
else: else:
EXT_MAP[observable_type] = {new_extension._type: new_extension} EXT_MAP[observable_type] = {ext_type: new_extension}
def _collect_stix2_mappings(): def _collect_stix2_mappings():

View File

@ -1,6 +1,8 @@
from collections import OrderedDict from collections import OrderedDict
import re import re
import six
from .base import _cls_init, _Extension, _Observable, _STIXBase from .base import _cls_init, _Extension, _Observable, _STIXBase
from .core import ( from .core import (
STIXDomainObject, _register_marking, _register_object, 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): 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): 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 _type = type
_properties = OrderedDict(properties) _properties = prop_dict
def __init__(self, **kwargs): def __init__(self, **kwargs):
_Extension.__init__(self, **kwargs) _Extension.__init__(self, **kwargs)

View File

@ -822,27 +822,24 @@ def test_custom_extension_invalid_type_name():
def test_custom_extension_no_properties(): 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) @stix2.v20.CustomExtension(stix2.v20.DomainName, 'x-new-ext2', None)
class BarExtension(): class BarExtension():
pass pass
assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_empty_properties(): 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', []) @stix2.v20.CustomExtension(stix2.v20.DomainName, 'x-new-ext2', [])
class BarExtension(): class BarExtension():
pass pass
assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_dict_properties(): 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', {}) @stix2.v20.CustomExtension(stix2.v20.DomainName, 'x-new-ext2', {})
class BarExtension(): class BarExtension():
pass pass
assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_no_init_1(): def test_custom_extension_no_init_1():

View File

@ -14,14 +14,7 @@ COA_WITH_BIN_JSON = """{
"created": "2016-04-06T20:03:48.000Z", "created": "2016-04-06T20:03:48.000Z",
"modified": "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", "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 ...", "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="
}""" }"""
@ -33,17 +26,7 @@ COA_WITH_REF_JSON = """{
"created": "2016-04-06T20:03:48.000Z", "created": "2016-04-06T20:03:48.000Z",
"modified": "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", "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 ...", "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"
}
}""" }"""
@ -84,15 +67,4 @@ def test_parse_course_of_action(sdo_json, sdo_dict):
assert getattr(coa, attr_name) == cmp_value 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 # TODO: Add other examples

View File

@ -800,7 +800,7 @@ def test_custom_extension_wrong_observable_type():
) )
def test_custom_extension_with_list_and_dict_properties_observable_type(data): def test_custom_extension_with_list_and_dict_properties_observable_type(data):
@stix2.v21.CustomExtension( @stix2.v21.CustomExtension(
stix2.v21.UserAccount, 'some-extension', [ stix2.v21.UserAccount, 'x-some-extension-ext', [
('keys', stix2.properties.ListProperty(stix2.properties.DictionaryProperty, required=True)), ('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(): def test_custom_extension_no_properties():
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError):
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new-ext2', None) @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', None)
class BarExtension(): class BarExtension():
pass pass
assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_empty_properties(): def test_custom_extension_empty_properties():
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError):
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new-ext2', []) @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', [])
class BarExtension(): class BarExtension():
pass pass
assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_dict_properties(): def test_custom_extension_dict_properties():
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError):
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new-ext2', {}) @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', {})
class BarExtension(): class BarExtension():
pass pass
assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_no_init_1(): def test_custom_extension_no_init_1():
@stix2.v21.CustomExtension( @stix2.v21.CustomExtension(
stix2.v21.DomainName, 'x-new-extension', [ stix2.v21.DomainName, 'x-new-extension-ext', [
('property1', stix2.properties.StringProperty(required=True)), ('property1', stix2.properties.StringProperty(required=True)),
], ],
) )
@ -914,7 +911,7 @@ def test_custom_extension_no_init_1():
def test_custom_extension_no_init_2(): def test_custom_extension_no_init_2():
@stix2.v21.CustomExtension( @stix2.v21.CustomExtension(
stix2.v21.DomainName, 'x-new-ext2', [ stix2.v21.DomainName, 'x-new2-ext', [
('property1', stix2.properties.StringProperty(required=True)), ('property1', stix2.properties.StringProperty(required=True)),
], ],
) )
@ -949,14 +946,14 @@ def test_custom_and_spec_extension_mix():
file_obs = stix2.v21.File( file_obs = stix2.v21.File(
name="my_file.dat", name="my_file.dat",
extensions={ extensions={
"x-custom1": { "x-custom1-ext": {
"a": 1, "a": 1,
"b": 2, "b": 2,
}, },
"ntfs-ext": { "ntfs-ext": {
"sid": "S-1-whatever", "sid": "S-1-whatever",
}, },
"x-custom2": { "x-custom2-ext": {
"z": 99.9, "z": 99.9,
"y": False, "y": False,
}, },
@ -969,8 +966,8 @@ def test_custom_and_spec_extension_mix():
allow_custom=True, allow_custom=True,
) )
assert file_obs.extensions["x-custom1"] == {"a": 1, "b": 2} assert file_obs.extensions["x-custom1-ext"] == {"a": 1, "b": 2}
assert file_obs.extensions["x-custom2"] == {"y": False, "z": 99.9} 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["ntfs-ext"].sid == "S-1-whatever"
assert file_obs.extensions["raster-image-ext"].image_height == 1024 assert file_obs.extensions["raster-image-ext"].image_height == 1024

View File

@ -120,3 +120,14 @@ def test_external_reference_source_required():
assert excinfo.value.cls == stix2.v21.ExternalReference assert excinfo.value.cls == stix2.v21.ExternalReference
assert excinfo.value.properties == ["source_name"] 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",
},
)

View File

@ -14,9 +14,6 @@ EXPECTED_INDICATOR = """{
"id": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7", "id": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7",
"created": "2017-01-01T00:00:01.000Z", "created": "2017-01-01T00:00:01.000Z",
"modified": "2017-01-01T00:00:01.000Z", "modified": "2017-01-01T00:00:01.000Z",
"indicator_types": [
"malicious-activity"
],
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
"pattern_type": "stix", "pattern_type": "stix",
"pattern_version": "2.1", "pattern_version": "2.1",
@ -29,7 +26,6 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
id='indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7', id='indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7',
created='2017-01-01T00:00:01.000Z', created='2017-01-01T00:00:01.000Z',
modified='2017-01-01T00:00:01.000Z', modified='2017-01-01T00:00:01.000Z',
indicator_types=['malicious-activity'],
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
pattern_type='stix', pattern_type='stix',
pattern_version='2.1', pattern_version='2.1',
@ -49,7 +45,6 @@ def test_indicator_with_all_required_properties():
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
pattern_type="stix", pattern_type="stix",
valid_from=epoch, valid_from=epoch,
indicator_types=['malicious-activity'],
) )
assert ind.revoked is False assert ind.revoked is False
@ -103,8 +98,8 @@ def test_indicator_required_properties():
stix2.v21.Indicator() stix2.v21.Indicator()
assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.cls == stix2.v21.Indicator
assert excinfo.value.properties == ["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: (indicator_types, 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(): def test_indicator_required_property_pattern():
@ -163,9 +158,6 @@ def test_created_modified_time_are_identical_by_default():
"id": INDICATOR_ID, "id": INDICATOR_ID,
"created": "2017-01-01T00:00:01Z", "created": "2017-01-01T00:00:01Z",
"modified": "2017-01-01T00:00:01Z", "modified": "2017-01-01T00:00:01Z",
"indicator_types": [
"malicious-activity",
],
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
"pattern_type": "stix", "pattern_type": "stix",
"valid_from": "1970-01-01T00:00:01Z", "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.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.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.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']" assert idctr.pattern == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']"

View File

@ -13,10 +13,7 @@ EXPECTED_INFRASTRUCTURE = """{
"id": "infrastructure--3000ae1b-784c-f03d-8abc-0a625b2ff018", "id": "infrastructure--3000ae1b-784c-f03d-8abc-0a625b2ff018",
"created": "2017-01-01T12:34:56.000Z", "created": "2017-01-01T12:34:56.000Z",
"modified": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z",
"name": "Poison Ivy C2", "name": "Poison Ivy C2"
"infrastructure_types": [
"command-and-control"
]
}""" }"""
@ -29,7 +26,6 @@ def test_infrastructure_with_all_required_properties():
created=now, created=now,
modified=now, modified=now,
name="Poison Ivy C2", name="Poison Ivy C2",
infrastructure_types=["command-and-control"],
) )
assert str(infra) == EXPECTED_INFRASTRUCTURE assert str(infra) == EXPECTED_INFRASTRUCTURE
@ -76,7 +72,7 @@ def test_infrastructure_required_properties():
stix2.v21.Infrastructure() stix2.v21.Infrastructure()
assert excinfo.value.cls == 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(): def test_infrastructure_required_property_name():
@ -105,7 +101,6 @@ def test_invalid_kwarg_to_infrastructure():
"id": INFRASTRUCTURE_ID, "id": INFRASTRUCTURE_ID,
"created": "2017-01-01T12:34:56.000Z", "created": "2017-01-01T12:34:56.000Z",
"modified": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z",
"infrastructure_types": ["command-and-control"],
"name": "Poison Ivy C2", "name": "Poison Ivy C2",
}, },
], ],
@ -118,7 +113,6 @@ def test_parse_infrastructure(data):
assert infra.id == INFRASTRUCTURE_ID assert infra.id == INFRASTRUCTURE_ID
assert infra.created == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc) 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.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' assert infra.name == 'Poison Ivy C2'

View File

@ -1,5 +1,5 @@
import datetime as dt import datetime as dt
import re import json
import pytest import pytest
import pytz import pytz
@ -16,9 +16,6 @@ EXPECTED_MALWARE = """{
"created": "2016-05-12T08:17:27.000Z", "created": "2016-05-12T08:17:27.000Z",
"modified": "2016-05-12T08:17:27.000Z", "modified": "2016-05-12T08:17:27.000Z",
"name": "Cryptolocker", "name": "Cryptolocker",
"malware_types": [
"ransomware"
],
"is_family": false "is_family": false
}""" }"""
@ -31,7 +28,6 @@ def test_malware_with_all_required_properties():
id=MALWARE_ID, id=MALWARE_ID,
created=now, created=now,
modified=now, modified=now,
malware_types=["ransomware"],
name="Cryptolocker", name="Cryptolocker",
is_family=False, is_family=False,
) )
@ -80,7 +76,7 @@ def test_malware_required_properties():
stix2.v21.Malware() stix2.v21.Malware()
assert excinfo.value.cls == 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(): def test_malware_required_property_name():
@ -116,7 +112,6 @@ def test_invalid_kwarg_to_malware():
"id": MALWARE_ID, "id": MALWARE_ID,
"created": "2016-05-12T08:17:27.000Z", "created": "2016-05-12T08:17:27.000Z",
"modified": "2016-05-12T08:17:27.000Z", "modified": "2016-05-12T08:17:27.000Z",
"malware_types": ["ransomware"],
"name": "Cryptolocker", "name": "Cryptolocker",
"is_family": False, "is_family": False,
}, },
@ -130,13 +125,14 @@ def test_parse_malware(data):
assert mal.id == MALWARE_ID assert mal.id == MALWARE_ID
assert mal.created == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc) 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.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
assert mal.malware_types == ['ransomware']
assert mal.name == 'Cryptolocker' assert mal.name == 'Cryptolocker'
assert not mal.is_family assert not mal.is_family
def test_parse_malware_invalid_labels(): def test_parse_malware_invalid_types():
data = re.compile('\\[.+\\]', re.DOTALL).sub('1', EXPECTED_MALWARE) data = json.loads(EXPECTED_MALWARE)
data["malware_types"] = 1 # Oops, not a list
data = json.dumps(data)
with pytest.raises(InvalidValueError) as excinfo: with pytest.raises(InvalidValueError) as excinfo:
stix2.parse(data) stix2.parse(data)
assert "Invalid value for Malware 'malware_types'" in str(excinfo.value) 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, "is_family": False,
"malware_types": ["something"], "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"]

View File

@ -34,11 +34,13 @@ MALWARE_ANALYSIS_JSON = """{
"submitted": "2018-11-23T06:45:55.747Z", "submitted": "2018-11-23T06:45:55.747Z",
"analysis_started": "2018-11-29T07:30:03.895Z", "analysis_started": "2018-11-29T07:30:03.895Z",
"analysis_ended": "2018-11-29T08:30:03.895Z", "analysis_ended": "2018-11-29T08:30:03.895Z",
"av_result": "malicious", "result_name": "MegaRansom",
"result": "malicious",
"analysis_sco_refs": [ "analysis_sco_refs": [
"file--fc27e371-6c88-4c5c-868a-4dda0e60b167", "file--fc27e371-6c88-4c5c-868a-4dda0e60b167",
"url--6f7a74cd-8eb2-4b88-a4da-aa878e50ac2e" "url--6f7a74cd-8eb2-4b88-a4da-aa878e50ac2e"
] ],
"sample_ref": "email-addr--499a32d7-74c1-4276-ace9-725ac933e243"
}""" }"""

View File

@ -1300,6 +1300,7 @@ def test_software_example():
s = stix2.v21.Software( s = stix2.v21.Software(
name="Word", name="Word",
cpe="cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*", cpe="cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*",
swid="com.acme.rms-ce-v4-1-5-0",
version="2002", version="2002",
vendor="Microsoft", vendor="Microsoft",
) )

View File

@ -26,6 +26,7 @@ NOW = object()
STIX_UNMOD_PROPERTIES = ['created', 'created_by_ref', 'id', 'type'] STIX_UNMOD_PROPERTIES = ['created', 'created_by_ref', 'id', 'type']
TYPE_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$' TYPE_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$'
SCO21_EXT_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-ext$'
class STIXdatetime(dt.datetime): class STIXdatetime(dt.datetime):

View File

@ -4,6 +4,7 @@ from collections import OrderedDict
from ..base import _STIXBase from ..base import _STIXBase
from ..custom import _custom_marking_builder from ..custom import _custom_marking_builder
from ..exceptions import InvalidValueError
from ..markings import _MarkingsMixin from ..markings import _MarkingsMixin
from ..markings.utils import check_tlp_marking from ..markings.utils import check_tlp_marking
from ..properties import ( from ..properties import (
@ -28,10 +29,26 @@ class ExternalReference(_STIXBase):
('external_id', StringProperty()), ('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): def _check_object_constraints(self):
super(ExternalReference, self)._check_object_constraints() super(ExternalReference, self)._check_object_constraints()
self._check_at_least_one_property(['description', 'external_id', 'url']) 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): class KillChainPhase(_STIXBase):
# TODO: Add link # TODO: Add link

View File

@ -382,7 +382,7 @@ class File(_Observable):
('granular_markings', ListProperty(GranularMarking)), ('granular_markings', ListProperty(GranularMarking)),
('defanged', BooleanProperty(default=lambda: False)), ('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): def _check_object_constraints(self):
super(File, self)._check_object_constraints() super(File, self)._check_object_constraints()
@ -760,6 +760,7 @@ class Software(_Observable):
('id', IDProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')),
('name', StringProperty(required=True)), ('name', StringProperty(required=True)),
('cpe', StringProperty()), ('cpe', StringProperty()),
('swid', StringProperty()),
('languages', ListProperty(StringProperty)), ('languages', ListProperty(StringProperty)),
('vendor', StringProperty()), ('vendor', StringProperty()),
('version', StringProperty()), ('version', StringProperty()),

View File

@ -13,10 +13,9 @@ from ..exceptions import (
InvalidValueError, PropertyPresenceError, STIXDeprecationWarning, InvalidValueError, PropertyPresenceError, STIXDeprecationWarning,
) )
from ..properties import ( from ..properties import (
BinaryProperty, BooleanProperty, EmbeddedObjectProperty, EnumProperty, BooleanProperty, EnumProperty, FloatProperty, IDProperty, IntegerProperty,
FloatProperty, IDProperty, IntegerProperty, ListProperty, ListProperty, ObservableProperty, PatternProperty, ReferenceProperty,
ObservableProperty, PatternProperty, ReferenceProperty, StringProperty, StringProperty, TimestampProperty, TypeProperty,
TimestampProperty, TypeProperty,
) )
from ..utils import NOW from ..utils import NOW
from .common import ExternalReference, GranularMarking, KillChainPhase from .common import ExternalReference, GranularMarking, KillChainPhase
@ -106,10 +105,6 @@ class CourseOfAction(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty(required=True)), ('name', StringProperty(required=True)),
('description', StringProperty()), ('description', StringProperty()),
('action_type', StringProperty()),
('os_execution_envs', ListProperty(StringProperty)),
('action_bin', BinaryProperty()),
('action_reference', EmbeddedObjectProperty(ExternalReference)),
('revoked', BooleanProperty(default=lambda: False)), ('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)), ('labels', ListProperty(StringProperty)),
('confidence', IntegerProperty()), ('confidence', IntegerProperty()),
@ -119,14 +114,6 @@ class CourseOfAction(STIXDomainObject):
('granular_markings', ListProperty(GranularMarking)), ('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): class Grouping(STIXDomainObject):
# TODO: Add link # TODO: Add link
@ -173,7 +160,7 @@ class Identity(STIXDomainObject):
('name', StringProperty(required=True)), ('name', StringProperty(required=True)),
('description', StringProperty()), ('description', StringProperty()),
('roles', ListProperty(StringProperty)), ('roles', ListProperty(StringProperty)),
('identity_class', StringProperty(required=True)), ('identity_class', StringProperty()),
('sectors', ListProperty(StringProperty)), ('sectors', ListProperty(StringProperty)),
('contact_information', StringProperty()), ('contact_information', StringProperty()),
('revoked', BooleanProperty(default=lambda: False)), ('revoked', BooleanProperty(default=lambda: False)),
@ -202,7 +189,7 @@ class Indicator(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty()), ('name', StringProperty()),
('description', StringProperty()), ('description', StringProperty()),
('indicator_types', ListProperty(StringProperty, required=True)), ('indicator_types', ListProperty(StringProperty)),
('pattern', PatternProperty(required=True)), ('pattern', PatternProperty(required=True)),
('pattern_type', StringProperty(required=True)), ('pattern_type', StringProperty(required=True)),
('pattern_version', StringProperty()), ('pattern_version', StringProperty()),
@ -269,7 +256,7 @@ class Infrastructure(STIXDomainObject):
('granular_markings', ListProperty(GranularMarking)), ('granular_markings', ListProperty(GranularMarking)),
('name', StringProperty(required=True)), ('name', StringProperty(required=True)),
('description', StringProperty()), ('description', StringProperty()),
('infrastructure_types', ListProperty(StringProperty, required=True)), ('infrastructure_types', ListProperty(StringProperty)),
('aliases', ListProperty(StringProperty)), ('aliases', ListProperty(StringProperty)),
('kill_chain_phases', ListProperty(KillChainPhase)), ('kill_chain_phases', ListProperty(KillChainPhase)),
('first_seen', TimestampProperty()), ('first_seen', TimestampProperty()),
@ -454,13 +441,13 @@ class Malware(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty()), ('name', StringProperty()),
('description', StringProperty()), ('description', StringProperty()),
('malware_types', ListProperty(StringProperty, required=True)), ('malware_types', ListProperty(StringProperty)),
('is_family', BooleanProperty(required=True)), ('is_family', BooleanProperty(required=True)),
('aliases', ListProperty(StringProperty)), ('aliases', ListProperty(StringProperty)),
('kill_chain_phases', ListProperty(KillChainPhase)), ('kill_chain_phases', ListProperty(KillChainPhase)),
('first_seen', TimestampProperty()), ('first_seen', TimestampProperty()),
('last_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)), ('architecture_execution_envs', ListProperty(StringProperty)),
('implementation_languages', ListProperty(StringProperty)), ('implementation_languages', ListProperty(StringProperty)),
('capabilities', ListProperty(StringProperty)), ('capabilities', ListProperty(StringProperty)),
@ -524,14 +511,16 @@ class MalwareAnalysis(STIXDomainObject):
('submitted', TimestampProperty()), ('submitted', TimestampProperty()),
('analysis_started', TimestampProperty()), ('analysis_started', TimestampProperty()),
('analysis_ended', TimestampProperty()), ('analysis_ended', TimestampProperty()),
('av_result', StringProperty()), ('result_name', StringProperty()),
('result', StringProperty()),
('analysis_sco_refs', ListProperty(ReferenceProperty(valid_types="SCO", spec_version='2.1'))), ('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): def _check_object_constraints(self):
super(MalwareAnalysis, self)._check_object_constraints() 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): class Note(STIXDomainObject):
@ -672,7 +661,7 @@ class Report(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty(required=True)), ('name', StringProperty(required=True)),
('description', StringProperty()), ('description', StringProperty()),
('report_types', ListProperty(StringProperty, required=True)), ('report_types', ListProperty(StringProperty)),
('published', TimestampProperty(required=True)), ('published', TimestampProperty(required=True)),
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)), ('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
('revoked', BooleanProperty(default=lambda: False)), ('revoked', BooleanProperty(default=lambda: False)),
@ -701,7 +690,7 @@ class ThreatActor(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty(required=True)), ('name', StringProperty(required=True)),
('description', StringProperty()), ('description', StringProperty()),
('threat_actor_types', ListProperty(StringProperty, required=True)), ('threat_actor_types', ListProperty(StringProperty)),
('aliases', ListProperty(StringProperty)), ('aliases', ListProperty(StringProperty)),
('first_seen', TimestampProperty()), ('first_seen', TimestampProperty()),
('last_seen', TimestampProperty()), ('last_seen', TimestampProperty()),
@ -748,7 +737,7 @@ class Tool(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty(required=True)), ('name', StringProperty(required=True)),
('description', StringProperty()), ('description', StringProperty()),
('tool_types', ListProperty(StringProperty, required=True)), ('tool_types', ListProperty(StringProperty)),
('aliases', ListProperty(StringProperty)), ('aliases', ListProperty(StringProperty)),
('kill_chain_phases', ListProperty(KillChainPhase)), ('kill_chain_phases', ListProperty(KillChainPhase)),
('tool_version', StringProperty()), ('tool_version', StringProperty()),