import datetime as dt import re import pytest import pytz import stix2 import stix2.exceptions from .constants import IDENTITY_ID, OBSERVED_DATA_ID OBJECTS_REGEX = re.compile('\"objects\": {(?:.*?)(?:(?:[^{]*?)|(?:{[^{]*?}))*}', re.DOTALL) EXPECTED = """{ "type": "observed-data", "spec_version": "2.1", "id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", "created_by_ref": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c", "created": "2016-04-06T19:58:16.000Z", "modified": "2016-04-06T19:58:16.000Z", "first_observed": "2015-12-21T19:00:00Z", "last_observed": "2015-12-21T19:00:00Z", "number_observed": 50, "objects": { "0": { "type": "file", "id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f", "spec_version": "2.1", "name": "foo.exe" } } }""" def test_observed_data_example(): observed_data = stix2.v21.ObservedData( id=OBSERVED_DATA_ID, created_by_ref=IDENTITY_ID, created="2016-04-06T19:58:16.000Z", modified="2016-04-06T19:58:16.000Z", first_observed="2015-12-21T19:00:00Z", last_observed="2015-12-21T19:00:00Z", number_observed=50, objects={ "0": { "type": "file", "id": "file--7af1312c-4402-5d2f-b169-b118d73b85c4", "name": "foo.exe", }, }, ) assert observed_data.id == "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf" assert observed_data.created_by_ref == "identity--311b2d2d-f010-4473-83ec-1edf84858f4c" assert observed_data.created == observed_data.modified == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc) assert observed_data.first_observed == observed_data.last_observed == dt.datetime(2015, 12, 21, 19, 00, 00, tzinfo=pytz.utc) assert observed_data.number_observed == 50 assert observed_data.objects['0'] == stix2.v21.File(name="foo.exe") EXPECTED_WITH_REF = """{ "type": "observed-data", "spec_version": "2.1", "id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", "created_by_ref": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c", "created": "2016-04-06T19:58:16.000Z", "modified": "2016-04-06T19:58:16.000Z", "first_observed": "2015-12-21T19:00:00Z", "last_observed": "2015-12-21T19:00:00Z", "number_observed": 50, "objects": { "0": { "type": "file", "id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f", "spec_version": "2.1", "name": "foo.exe" }, "1": { "type": "directory", "id": "directory--536a61a4-0934-516b-9aad-fcbb75e0583a", "spec_version": "2.1", "path": "/usr/home", "contains_refs": [ "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f" ] } } }""" def test_observed_data_example_with_refs(): observed_data = stix2.v21.ObservedData( id=OBSERVED_DATA_ID, created_by_ref=IDENTITY_ID, created="2016-04-06T19:58:16.000Z", modified="2016-04-06T19:58:16.000Z", first_observed="2015-12-21T19:00:00Z", last_observed="2015-12-21T19:00:00Z", number_observed=50, objects={ "0": { "type": "file", "id": "file--7af1312c-4402-5d2f-b169-b118d73b85c4", "name": "foo.exe", }, "1": { "type": "directory", "id": "directory--ee97f78e-7e2b-5b3d-bcbd-5692968cacea", "path": "/usr/home", "contains_refs": ["file--5956efbb-a7b0-566d-a7f9-a202eb05c70f"], }, }, ) assert observed_data.id == "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf" assert observed_data.created_by_ref == "identity--311b2d2d-f010-4473-83ec-1edf84858f4c" assert observed_data.created == observed_data.modified == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc) assert observed_data.first_observed == observed_data.last_observed == dt.datetime(2015, 12, 21, 19, 00, 00, tzinfo=pytz.utc) assert observed_data.number_observed == 50 assert observed_data.objects['0'] == stix2.v21.File(name="foo.exe") assert observed_data.objects['1'] == stix2.v21.Directory(path="/usr/home", contains_refs=["file--5956efbb-a7b0-566d-a7f9-a202eb05c70f"]) EXPECTED_OBJECT_REFS = """{ "type": "observed-data", "spec_version": "2.1", "id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", "created": "2016-04-06T19:58:16.000Z", "modified": "2016-04-06T19:58:16.000Z", "first_observed": "2015-12-21T19:00:00Z", "last_observed": "2015-12-21T19:00:00Z", "number_observed": 50, "object_refs": [ "file--758bf2c0-a6f1-56d1-872e-6b727467739a", "url--d97ed5c4-3f33-46d9-b25b-c3d7b94d1457", "mutex--eca0b3ba-8d76-11e9-a1fd-34415dabec0c" ] }""" def test_observed_data_example_with_object_refs(): observed_data = stix2.v21.ObservedData( id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", created="2016-04-06T19:58:16.000Z", modified="2016-04-06T19:58:16.000Z", first_observed="2015-12-21T19:00:00Z", last_observed="2015-12-21T19:00:00Z", number_observed=50, object_refs=[ "file--758bf2c0-a6f1-56d1-872e-6b727467739a", "url--d97ed5c4-3f33-46d9-b25b-c3d7b94d1457", "mutex--eca0b3ba-8d76-11e9-a1fd-34415dabec0c", ], ) assert str(observed_data) == EXPECTED_OBJECT_REFS def test_observed_data_object_constraint(): with pytest.raises(stix2.exceptions.MutuallyExclusivePropertiesError): stix2.v21.ObservedData( id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", created="2016-04-06T19:58:16.000Z", modified="2016-04-06T19:58:16.000Z", first_observed="2015-12-21T19:00:00Z", last_observed="2015-12-21T19:00:00Z", number_observed=50, objects={ "0": { "name": "foo.exe", "type": "file", }, }, object_refs=[ "file--758bf2c0-a6f1-56d1-872e-6b727467739a", "url--d97ed5c4-3f33-46d9-b25b-c3d7b94d1457", "mutex--eca0b3ba-8d76-11e9-a1fd-34415dabec0c", ], ) def test_observed_data_example_with_bad_refs(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.ObservedData( id=OBSERVED_DATA_ID, created_by_ref=IDENTITY_ID, created="2016-04-06T19:58:16.000Z", modified="2016-04-06T19:58:16.000Z", first_observed="2015-12-21T19:00:00Z", last_observed="2015-12-21T19:00:00Z", number_observed=50, objects={ "0": { "type": "file", "id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f", "name": "foo.exe", }, "1": { "type": "directory", "path": "/usr/home", "contains_refs": ["monkey--5956efbb-a7b0-566d-a7f9-a202eb05c70f"], }, }, ) assert excinfo.value.cls == stix2.v21.Directory assert excinfo.value.prop_name == "contains_refs" assert "The type-specifying prefix 'monkey' for this property is not valid" in excinfo.value.reason def test_observed_data_example_with_non_dictionary(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.ObservedData( id=OBSERVED_DATA_ID, created_by_ref=IDENTITY_ID, created="2016-04-06T19:58:16.000Z", modified="2016-04-06T19:58:16.000Z", first_observed="2015-12-21T19:00:00Z", last_observed="2015-12-21T19:00:00Z", number_observed=50, objects="file: foo.exe", ) assert excinfo.value.cls == stix2.v21.ObservedData assert excinfo.value.prop_name == "objects" assert 'must contain a dictionary' in excinfo.value.reason def test_observed_data_example_with_empty_dictionary(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.ObservedData( id=OBSERVED_DATA_ID, created_by_ref=IDENTITY_ID, created="2016-04-06T19:58:16.000Z", modified="2016-04-06T19:58:16.000Z", first_observed="2015-12-21T19:00:00Z", last_observed="2015-12-21T19:00:00Z", number_observed=50, objects={}, ) assert excinfo.value.cls == stix2.v21.ObservedData assert excinfo.value.prop_name == "objects" assert 'must contain a non-empty dictionary' in excinfo.value.reason @pytest.mark.parametrize( "data", [ EXPECTED, { "type": "observed-data", "spec_version": "2.1", "id": OBSERVED_DATA_ID, "created": "2016-04-06T19:58:16.000Z", "created_by_ref": IDENTITY_ID, "first_observed": "2015-12-21T19:00:00Z", "last_observed": "2015-12-21T19:00:00Z", "modified": "2016-04-06T19:58:16.000Z", "number_observed": 50, "objects": { "0": { "name": "foo.exe", "type": "file", "id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f", }, }, }, ], ) def test_parse_observed_data(data): odata = stix2.parse(data, version="2.1") assert odata.type == 'observed-data' assert odata.spec_version == '2.1' assert odata.id == OBSERVED_DATA_ID assert odata.created == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc) assert odata.modified == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc) assert odata.first_observed == dt.datetime(2015, 12, 21, 19, 0, 0, tzinfo=pytz.utc) assert odata.last_observed == dt.datetime(2015, 12, 21, 19, 0, 0, tzinfo=pytz.utc) assert odata.created_by_ref == IDENTITY_ID assert odata.objects["0"].type == "file" @pytest.mark.parametrize( "data", [ """"0": { "type": "artifact", "mime_type": "image/jpeg", "payload_bin": "VBORw0KGgoAAAANSUhEUgAAADI==" }""", """"0": { "type": "artifact", "mime_type": "image/jpeg", "url": "https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg", "hashes": { "MD5": "6826f9a05da08134006557758bb3afbb" } }""", ], ) def test_parse_artifact_valid(data): odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED) odata = stix2.parse(odata_str, version="2.1") assert odata.objects["0"].type == "artifact" @pytest.mark.parametrize( "data", [ """"0": { "type": "artifact", "mime_type": "image/jpeg", "payload_bin": "abcVBORw0KGgoAAAANSUhEUgAAADI==" }""", """"0": { "type": "artifact", "mime_type": "image/jpeg", "url": "https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg", "hashes": { "MD5": "a" } }""", ], ) def test_parse_artifact_invalid(data): odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED) with pytest.raises(stix2.exceptions.InvalidValueError): stix2.parse(odata_str, version="2.1") def test_artifact_example_dependency_error(): with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo: stix2.v21.Artifact(url="http://example.com/sirvizio.exe") assert excinfo.value.dependencies == [("hashes", "url")] assert str(excinfo.value) == "The property dependencies for Artifact: (hashes, url) are not met." @pytest.mark.parametrize( "data", [ """"0": { "type": "autonomous-system", "number": 15139, "name": "Slime Industries", "rir": "ARIN" }""", ], ) def test_parse_autonomous_system_valid(data): odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED) odata = stix2.parse(odata_str, version="2.1") assert odata.objects["0"].type == "autonomous-system" assert odata.objects["0"].number == 15139 assert odata.objects["0"].name == "Slime Industries" assert odata.objects["0"].rir == "ARIN" @pytest.mark.parametrize( "data", [ """{ "type": "email-addr", "value": "john@example.com", "display_name": "John Doe", "belongs_to_ref": "user-account--fc07c1af-6b11-41f8-97a4-47920d866a91" }""", ], ) def test_parse_email_address(data): odata = stix2.parse(data, version='2.1') assert odata.type == "email-addr" odata_str = re.compile( '"belongs_to_ref": "user-account--fc07c1af-6b11-41f8-97a4-47920d866a91"', re.DOTALL, ).sub( '"belongs_to_ref": "mutex--9be6365f-b89c-48c0-9340-6953f6595718"', data, ) with pytest.raises(stix2.exceptions.InvalidValueError): stix2.parse(odata_str, version='2.1') @pytest.mark.parametrize( "data", [ """ { "type": "email-message", "is_multipart": true, "content_type": "multipart/mixed", "date": "2016-06-19T14:20:40.000Z", "from_ref": "email-addr--d4ef7e1f-086d-5ff4-bce4-312ddc3eae76", "to_refs": [ "email-addr--8b0eb924-208c-5efd-80e5-84e2d610e54b" ], "cc_refs": [ "email-addr--1766f860-5cf3-5697-8789-35f1242663d5" ], "subject": "Check out this picture of a cat!", "additional_header_fields": { "Content-Disposition": "inline", "X-Mailer": "Mutt/1.5.23", "X-Originating-IP": "198.51.100.3" }, "body_multipart": [ { "content_type": "text/plain; charset=utf-8", "content_disposition": "inline", "body": "Cats are funny!" }, { "content_type": "image/png", "content_disposition": "attachment; filename=\\"tabby.png\\"", "body_raw_ref": "artifact--80b04ad8-db52-464b-a85a-a44a5f3a60c5" }, { "content_type": "application/zip", "content_disposition": "attachment; filename=\\"tabby_pics.zip\\"", "body_raw_ref": "file--e63474fc-b386-5630-a003-1b555e22f99b" } ] } """, ], ) def test_parse_email_message(data): odata = stix2.parse(data, version='2.1') assert odata.type == "email-message" assert odata.body_multipart[0].content_disposition == "inline" @pytest.mark.parametrize( "data", [ """ { "type": "email-message", "from_ref": "email-addr--d4ef7e1f-086d-5ff4-bce4-312ddc3eae76", "to_refs": ["email-addr--8b0eb924-208c-5efd-80e5-84e2d610e54b"], "is_multipart": true, "date": "1997-11-21T15:55:06.000Z", "subject": "Saying Hello", "body": "Cats are funny!" } """, ], ) def test_parse_email_message_not_multipart(data): with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo: stix2.parse(data, version='2.1') assert excinfo.value.cls == stix2.v21.EmailMessage assert excinfo.value.dependencies == [("is_multipart", "body")] @pytest.mark.parametrize( "data", [ """"0": { "type": "file", "id": "file--ecd47d73-15e4-5250-afda-ef8897b22340", "hashes": { "SHA-256": "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a" } }, "1": { "type": "file", "id": "file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3", "hashes": { "SHA-1": "6e71b3cac15d32fe2d36c270887df9479c25c640" } }, "2": { "type": "file", "id": "file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac", "hashes": { "SHA-512": "b7e98c78c24fb4c2c7b175e90474b21eae0ccf1b5ea4708b4e0f2d2940004419edc7161c18a1e71b2565df099ba017bcaa67a248e2989b6268ce078b88f2e210" } }, "3": { "type": "file", "name": "foo.zip", "hashes": { "SHA3-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f" }, "mime_type": "application/zip", "extensions": { "archive-ext": { "contains_refs": [ "file--ecd47d73-15e4-5250-afda-ef8897b22340", "file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3", "file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac" ] } } }""", ], ) def test_parse_file_archive(data): odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED) odata = stix2.parse(odata_str, version="2.1") assert all(x in odata.objects["3"].extensions['archive-ext'].contains_refs for x in [ "file--ecd47d73-15e4-5250-afda-ef8897b22340", "file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3", "file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac", ]) @pytest.mark.parametrize( "data", [ """ { "type": "email-message", "is_multipart": true, "content_type": "multipart/mixed", "date": "2016-06-19T14:20:40.000Z", "from_ref": "email-addr--d4ef7e1f-086d-5ff4-bce4-312ddc3eae76", "to_refs": [ "email-addr--8b0eb924-208c-5efd-80e5-84e2d610e54b" ], "cc_refs": [ "email-addr--1766f860-5cf3-5697-8789-35f1242663d5" ], "subject": "Check out this picture of a cat!", "additional_header_fields": { "Content-Disposition": "inline", "X-Mailer": "Mutt/1.5.23", "X-Originating-IP": "198.51.100.3" }, "body_multipart": [ { "content_type": "text/plain; charset=utf-8", "content_disposition": "inline", "body": "Cats are funny!" }, { "content_type": "image/png", "content_disposition": "attachment; filename=\\"tabby.png\\"" }, { "content_type": "application/zip", "content_disposition": "attachment; filename=\\"tabby_pics.zip\\"", "body_raw_ref": "file--e63474fc-b386-5630-a003-1b555e22f99b" } ] } """, ], ) def test_parse_email_message_with_at_least_one_error(data): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.parse(data, version='2.1') assert excinfo.value.cls == stix2.v21.EmailMessage assert "At least one of the" in str(excinfo.value) assert "must be populated" in str(excinfo.value) @pytest.mark.parametrize( "data", [ """ { "type": "network-traffic", "src_ref": "ipv4-addr--e535b017-cc1c-566b-a3e2-f69f92ed9c4c", "dst_ref": "ipv4-addr--78327430-9ad9-5632-ae3d-8e2fce8f5483", "protocols": [ "tcp" ] } """, ], ) def test_parse_basic_tcp_traffic(data): odata = stix2.parse( data, version='2.1', ) assert odata.type == "network-traffic" assert odata.src_ref == "ipv4-addr--e535b017-cc1c-566b-a3e2-f69f92ed9c4c" assert odata.dst_ref == "ipv4-addr--78327430-9ad9-5632-ae3d-8e2fce8f5483" assert odata.protocols == ["tcp"] @pytest.mark.parametrize( "data", [ """ { "type": "network-traffic", "src_port": 2487, "dst_port": 1723, "protocols": [ "ipv4", "pptp" ], "src_byte_count": 35779, "dst_byte_count": 935750, "encapsulates_refs": [ "network-traffic--016914c3-b680-5df2-81c4-bb9ccf8dc8b0" ] } """, ], ) def test_parse_basic_tcp_traffic_with_error(data): with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: stix2.parse(data, version='2.1') assert excinfo.value.cls == stix2.v21.NetworkTraffic assert excinfo.value.properties == ["dst_ref", "src_ref"] EXPECTED_PROCESS_OD = """{ "created": "2016-04-06T19:58:16.000Z", "created_by_ref": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c", "first_observed": "2015-12-21T19:00:00Z", "id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", "last_observed": "2015-12-21T19:00:00Z", "modified": "2016-04-06T19:58:16.000Z", "number_observed": 50, "objects": { "0": { "type": "file", "hashes": { "SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100fSHA" }, }, "1": { "type": "process", "pid": 1221, "created": "2016-01-20T14:11:25.55Z", "command_line": "./gedit-bin --new-window", "binary_ref": "0" } }, "spec_version": "2.1", "type": "observed-data" }""" def test_observed_data_with_process_example(): observed_data = stix2.v21.ObservedData( id=OBSERVED_DATA_ID, created_by_ref=IDENTITY_ID, created="2016-04-06T19:58:16.000Z", modified="2016-04-06T19:58:16.000Z", first_observed="2015-12-21T19:00:00Z", last_observed="2015-12-21T19:00:00Z", number_observed=50, objects={ "0": { "type": "file", "id": "file--0d16c8d3-c177-5f5d-a022-b1bdac329bea", "hashes": { "SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f", }, }, "1": { "type": "process", "id": "process--f6c4a02c-23e1-4a6d-a0d7-d862e893817a", "pid": 1221, "created_time": "2016-01-20T14:11:25.55Z", "command_line": "./gedit-bin --new-window", "image_ref": "file--0d16c8d3-c177-5f5d-a022-b1bdac329bea", }, }, ) assert observed_data.objects["0"].type == "file" assert observed_data.objects["0"].hashes["SHA-256"] == "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f" assert observed_data.objects["1"].type == "process" assert observed_data.objects["1"].pid == 1221 assert observed_data.objects["1"].command_line == "./gedit-bin --new-window" # creating cyber observables directly def test_artifact_example(): art = stix2.v21.Artifact( mime_type="image/jpeg", url="https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg", hashes={ "MD5": "6826f9a05da08134006557758bb3afbb", }, ) assert art.mime_type == "image/jpeg" assert art.url == "https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg" assert art.hashes["MD5"] == "6826f9a05da08134006557758bb3afbb" def test_artifact_mutual_exclusion_error(): with pytest.raises(stix2.exceptions.MutuallyExclusivePropertiesError) as excinfo: stix2.v21.Artifact( mime_type="image/jpeg", url="https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg", hashes={ "MD5": "6826f9a05da08134006557758bb3afbb", }, payload_bin="VBORw0KGgoAAAANSUhEUgAAADI==", ) assert excinfo.value.cls == stix2.v21.Artifact assert excinfo.value.properties == ["payload_bin", "url"] assert 'are mutually exclusive' in str(excinfo.value) def test_directory_example(): f = stix2.v21.File( name="penguin.exe", ) dir1 = stix2.v21.Directory( path='/usr/lib', ctime="2015-12-21T19:00:00Z", mtime="2015-12-24T19:00:00Z", atime="2015-12-21T20:00:00Z", contains_refs=[str(f.id)], ) assert dir1.path == '/usr/lib' assert dir1.ctime == dt.datetime(2015, 12, 21, 19, 0, 0, tzinfo=pytz.utc) assert dir1.mtime == dt.datetime(2015, 12, 24, 19, 0, 0, tzinfo=pytz.utc) assert dir1.atime == dt.datetime(2015, 12, 21, 20, 0, 0, tzinfo=pytz.utc) assert dir1.contains_refs == ["file--c6ae2cf8-92d3-56d0-a25f-713efad643a7"] def test_directory_example_ref_error(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Directory( path='/usr/lib', ctime="2015-12-21T19:00:00Z", mtime="2015-12-24T19:00:00Z", atime="2015-12-21T20:00:00Z", contains_refs=["domain-name--02af94ea-7e38-5718-87c3-5cc023e3d49d"], ) assert excinfo.value.cls == stix2.v21.Directory assert excinfo.value.prop_name == "contains_refs" def test_domain_name_example(): dn1 = stix2.v21.DomainName( value="mitre.org", ) dn2 = stix2.v21.DomainName( value="example.com", resolves_to_refs=[str(dn1.id)], ) assert dn2.value == "example.com" assert dn2.resolves_to_refs == ["domain-name--5b5803bf-a7eb-5076-b799-96aa574c44eb"] def test_domain_name_example_invalid_ref_type(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.DomainName( value="example.com", resolves_to_refs=["file--44a431e6-764b-5556-a3f5-bf655930a581"], ) assert excinfo.value.cls == stix2.v21.DomainName assert excinfo.value.prop_name == "resolves_to_refs" def test_file_example(): f = stix2.v21.File( name="qwerty.dll", hashes={ "SHA-256": "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a", }, size=100, magic_number_hex="1C", mime_type="application/msword", ctime="2016-12-21T19:00:00Z", mtime="2016-12-24T19:00:00Z", atime="2016-12-21T20:00:00Z", ) assert f.name == "qwerty.dll" assert f.size == 100 assert f.magic_number_hex == "1C" assert f.hashes["SHA-256"] == "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a" assert f.mime_type == "application/msword" assert f.ctime == dt.datetime(2016, 12, 21, 19, 0, 0, tzinfo=pytz.utc) assert f.mtime == dt.datetime(2016, 12, 24, 19, 0, 0, tzinfo=pytz.utc) assert f.atime == dt.datetime(2016, 12, 21, 20, 0, 0, tzinfo=pytz.utc) def test_file_ssdeep_example(): f = stix2.v21.File( name="example.dll", hashes={ "SHA-256": "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a", "SSDEEP": "96:gS/mFkCpXTWLr/PbKQHbr/S/mFkCpXTWLr/PbKQHbrB:Tu6SXTWGQHbeu6SXTWGQHbV", }, size=1024, ) assert f.name == "example.dll" assert f.size == 1024 assert f.hashes["SHA-256"] == "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a" assert f.hashes["SSDEEP"] == "96:gS/mFkCpXTWLr/PbKQHbr/S/mFkCpXTWLr/PbKQHbrB:Tu6SXTWGQHbeu6SXTWGQHbV" def test_file_example_with_NTFSExt(): f = stix2.v21.File( name="abc.txt", extensions={ "ntfs-ext": { "alternate_data_streams": [ { "name": "second.stream", "size": 25536, }, ], }, }, ) assert f.name == "abc.txt" assert f.extensions["ntfs-ext"].alternate_data_streams[0].size == 25536 def test_file_example_with_empty_NTFSExt(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.File( name="abc.txt", extensions={ "ntfs-ext": {}, }, ) assert excinfo.value.cls == stix2.v21.File def test_file_example_with_PDFExt(): f = stix2.v21.File( name="qwerty.dll", extensions={ "pdf-ext": { "version": "1.7", "document_info_dict": { "Title": "Sample document", "Author": "Adobe Systems Incorporated", "Creator": "Adobe FrameMaker 5.5.3 for Power Macintosh", "Producer": "Acrobat Distiller 3.01 for Power Macintosh", "CreationDate": "20070412090123-02", }, "pdfid0": "DFCE52BD827ECF765649852119D", "pdfid1": "57A1E0F9ED2AE523E313C", }, }, ) assert f.name == "qwerty.dll" assert f.extensions["pdf-ext"].version == "1.7" assert f.extensions["pdf-ext"].document_info_dict["Title"] == "Sample document" def test_file_example_with_PDFExt_Object(): f = stix2.v21.File( name="qwerty.dll", extensions={ "pdf-ext": stix2.v21.PDFExt( version="1.7", document_info_dict={ "Title": "Sample document", "Author": "Adobe Systems Incorporated", "Creator": "Adobe FrameMaker 5.5.3 for Power Macintosh", "Producer": "Acrobat Distiller 3.01 for Power Macintosh", "CreationDate": "20070412090123-02", }, pdfid0="DFCE52BD827ECF765649852119D", pdfid1="57A1E0F9ED2AE523E313C", ), }, ) assert f.name == "qwerty.dll" assert f.extensions["pdf-ext"].version == "1.7" assert f.extensions["pdf-ext"].document_info_dict["Title"] == "Sample document" def test_file_example_with_RasterImageExt_Object(): f = stix2.v21.File( name="qwerty.jpeg", extensions={ "raster-image-ext": { "bits_per_pixel": 123, "exif_tags": { "Make": "Nikon", "Model": "D7000", "XResolution": 4928, "YResolution": 3264, }, }, }, ) assert f.name == "qwerty.jpeg" assert f.extensions["raster-image-ext"].bits_per_pixel == 123 assert f.extensions["raster-image-ext"].exif_tags["XResolution"] == 4928 def test_file_with_archive_ext_object(): ad = stix2.v21.Directory(path="archived/path") f_obj = stix2.v21.File( name="foo", extensions={ "archive-ext": { "contains_refs": [ad, ], }, }, ) f_ref = stix2.v21.File( name="foo", extensions={ "archive-ext": { "contains_refs": [ad.id, ], }, }, ) assert f_obj["id"] == f_ref["id"] assert f_obj["extensions"]["archive-ext"]["contains_refs"][0] == ad["id"] RASTER_IMAGE_EXT = """{ "type": "observed-data", "spec_version": "2.1", "id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", "created": "2016-04-06T19:58:16.000Z", "modified": "2016-04-06T19:58:16.000Z", "first_observed": "2015-12-21T19:00:00Z", "last_observed": "2015-12-21T19:00:00Z", "number_observed": 1, "objects": { "0": { "type": "file", "id": "file--44a431e6-764b-5556-a3f5-bf655930a581", "name": "picture.jpg", "hashes": { "SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f" }, "extensions": { "raster-image-ext": { "image_height": 768, "image_width": 1024, "bits_per_pixel": 72, "exif_tags": { "Make": "Nikon", "Model": "D7000", "XResolution": 4928, "YResolution": 3264 } } } } } } """ def test_raster_image_ext_parse(): obj = stix2.parse(RASTER_IMAGE_EXT, version="2.1") assert obj.objects["0"].extensions['raster-image-ext'].image_width == 1024 def test_raster_images_ext_create(): ext = stix2.v21.RasterImageExt(image_width=1024) assert "image_width" in str(ext) def test_file_example_with_WindowsPEBinaryExt(): f = stix2.v21.File( name="qwerty.dll", extensions={ "windows-pebinary-ext": { "pe_type": "exe", "machine_hex": "014c", "number_of_sections": 4, "time_date_stamp": "2016-01-22T12:31:12Z", "pointer_to_symbol_table_hex": "74726144", "number_of_symbols": 4542568, "size_of_optional_header": 224, "characteristics_hex": "818f", "optional_header": { "magic_hex": "010b", "major_linker_version": 2, "minor_linker_version": 25, "size_of_code": 512, "size_of_initialized_data": 283648, "size_of_uninitialized_data": 0, "address_of_entry_point": 4096, "base_of_code": 4096, "base_of_data": 8192, "image_base": 14548992, "section_alignment": 4096, "file_alignment": 4096, "major_os_version": 1, "minor_os_version": 0, "major_image_version": 0, "minor_image_version": 0, "major_subsystem_version": 4, "minor_subsystem_version": 0, "win32_version_value_hex": "00", "size_of_image": 299008, "size_of_headers": 4096, "checksum_hex": "00", "subsystem_hex": "03", "dll_characteristics_hex": "00", "size_of_stack_reserve": 100000, "size_of_stack_commit": 8192, "size_of_heap_reserve": 100000, "size_of_heap_commit": 4096, "loader_flags_hex": "abdbffde", "number_of_rva_and_sizes": 3758087646, }, "sections": [ { "name": "CODE", "entropy": 0.061089, }, { "name": "DATA", "entropy": 7.980693, }, { "name": "NicolasB", "entropy": 0.607433, }, { "name": ".idata", "entropy": 0.607433, }, ], }, }, ) assert f.name == "qwerty.dll" assert f.extensions["windows-pebinary-ext"].sections[2].entropy == 0.607433 def test_file_example_encryption_error(): with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: stix2.v21.File(magic_number_hex="010b") assert excinfo.value.cls == stix2.v21.File assert "At least one of the (hashes, name)" in str(excinfo.value) def test_ipv4_address_example(): ip4 = stix2.v21.IPv4Address( value="198.51.100.3", resolves_to_refs=["mac-addr--a85820f7-d9b7-567a-a3a6-dedc34139342", "mac-addr--9a59b496-fdeb-510f-97b5-7137210bc699"], ) assert ip4.value == "198.51.100.3" assert ip4.resolves_to_refs == ["mac-addr--a85820f7-d9b7-567a-a3a6-dedc34139342", "mac-addr--9a59b496-fdeb-510f-97b5-7137210bc699"] def test_ipv4_address_valid_refs(): mac1 = stix2.v21.MACAddress( value="a1:b2:c3:d4:e5:f6", ) mac2 = stix2.v21.MACAddress( value="a7:b8:c9:d0:e1:f2", ) ip4 = stix2.v21.IPv4Address( value="177.60.40.7", resolves_to_refs=[str(mac1.id), str(mac2.id)], ) assert ip4.value == "177.60.40.7" assert ip4.resolves_to_refs == ["mac-addr--f72d7d00-86bd-5cd2-8c86-52f7a83bef62", "mac-addr--875ad625-177b-5c2a-9101-d44b0ad55938"] def test_ipv4_address_example_cidr(): ip4 = stix2.v21.IPv4Address(value="198.51.100.0/24") assert ip4.value == "198.51.100.0/24" def test_ipv6_address_example(): ip6 = stix2.v21.IPv6Address(value="2001:0db8:85a3:0000:0000:8a2e:0370:7334") assert ip6.value == "2001:0db8:85a3:0000:0000:8a2e:0370:7334" def test_mac_address_example(): ip6 = stix2.v21.MACAddress(value="d2:fb:49:24:37:18") assert ip6.value == "d2:fb:49:24:37:18" def test_network_traffic_example(): nt = stix2.v21.NetworkTraffic( protocols=["tcp"], src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88", dst_ref="ipv4-addr--6d39dd0b-1f74-5faf-8d76-d8762c2a57cb", ) assert nt.protocols == ["tcp"] assert nt.src_ref == "ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88" assert nt.dst_ref == "ipv4-addr--6d39dd0b-1f74-5faf-8d76-d8762c2a57cb" def test_network_traffic_http_request_example(): h = stix2.v21.HTTPRequestExt( request_method="get", request_value="/download.html", request_version="http/1.1", request_header={ "Accept-Encoding": "gzip,deflate", "User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113", "Host": "www.example.com", }, ) nt = stix2.v21.NetworkTraffic( protocols=["tcp"], src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88", extensions={'http-request-ext': h}, ) assert nt.extensions['http-request-ext'].request_method == "get" assert nt.extensions['http-request-ext'].request_value == "/download.html" assert nt.extensions['http-request-ext'].request_version == "http/1.1" assert nt.extensions['http-request-ext'].request_header['Accept-Encoding'] == "gzip,deflate" assert nt.extensions['http-request-ext'].request_header['User-Agent'] == "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113" assert nt.extensions['http-request-ext'].request_header['Host'] == "www.example.com" def test_network_traffic_icmp_example(): h = stix2.v21.ICMPExt(icmp_type_hex="08", icmp_code_hex="00") nt = stix2.v21.NetworkTraffic( protocols=["tcp"], src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88", extensions={'icmp-ext': h}, ) assert nt.extensions['icmp-ext'].icmp_type_hex == "08" assert nt.extensions['icmp-ext'].icmp_code_hex == "00" def test_network_traffic_socket_example(): h = stix2.v21.SocketExt( is_listening=True, address_family="AF_INET", socket_type="SOCK_STREAM", ) nt = stix2.v21.NetworkTraffic( protocols=["tcp"], src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88", extensions={'socket-ext': h}, ) assert nt.extensions['socket-ext'].is_listening assert nt.extensions['socket-ext'].address_family == "AF_INET" assert nt.extensions['socket-ext'].socket_type == "SOCK_STREAM" def test_correct_socket_options(): se1 = stix2.v21.SocketExt( is_listening=True, address_family="AF_INET", socket_type="SOCK_STREAM", options={"ICMP6_RCVTIMEO": 100}, ) assert se1.address_family == "AF_INET" assert se1.socket_type == "SOCK_STREAM" assert se1.options == {"ICMP6_RCVTIMEO": 100} def test_incorrect_socket_options(): with pytest.raises(ValueError) as excinfo: stix2.v21.SocketExt( is_listening=True, address_family="AF_INET", socket_type="SOCK_STREAM", options={"RCVTIMEO": 100}, ) assert "Incorrect options key" == str(excinfo.value) with pytest.raises(ValueError) as excinfo: stix2.v21.SocketExt( is_listening=True, address_family="AF_INET", socket_type="SOCK_STREAM", options={"SO_RCVTIMEO": '100'}, ) assert "Options value must be an integer" == str(excinfo.value) def test_network_traffic_tcp_example(): h = stix2.v21.TCPExt(src_flags_hex="00000002") nt = stix2.v21.NetworkTraffic( protocols=["tcp"], src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88", extensions={'tcp-ext': h}, ) assert nt.extensions['tcp-ext'].src_flags_hex == "00000002" def test_mutex_example(): m = stix2.v21.Mutex(name="barney") assert m.name == "barney" def test_process_example(): p = stix2.v21.Process( pid=1221, created_time="2016-01-20T14:11:25.55Z", command_line="./gedit-bin --new-window", image_ref="file--ea587d87-5ed2-5625-a9ac-01fd64161fd8", ) assert p.command_line == "./gedit-bin --new-window" def test_process_example_empty_error(): with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: stix2.v21.Process() assert excinfo.value.cls == stix2.v21.Process properties_of_process = list(stix2.v21.Process._properties.keys()) properties_of_process = [prop for prop in properties_of_process if prop not in ["type", "id", "defanged", "spec_version"]] assert excinfo.value.properties == sorted(properties_of_process) msg = "At least one of the ({1}) properties for {0} must be populated." msg = msg.format( stix2.v21.Process.__name__, ", ".join(sorted(properties_of_process)), ) assert str(excinfo.value) == msg def test_process_example_empty_with_extensions(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Process(extensions={ "windows-process-ext": {}, }) assert excinfo.value.cls == stix2.v21.Process def test_process_example_windows_process_ext(): proc = stix2.v21.Process( pid=314, extensions={ "windows-process-ext": { "aslr_enabled": True, "dep_enabled": True, "priority": "HIGH_PRIORITY_CLASS", "owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309", }, }, ) assert proc.extensions["windows-process-ext"].aslr_enabled assert proc.extensions["windows-process-ext"].dep_enabled assert proc.extensions["windows-process-ext"].priority == "HIGH_PRIORITY_CLASS" assert proc.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309" def test_process_example_windows_process_ext_empty(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Process( pid=1221, extensions={ "windows-process-ext": {}, }, ) assert excinfo.value.cls == stix2.v21.Process def test_process_example_extensions_empty(): proc = stix2.v21.Process( pid=314, extensions={}, ) assert '{}' in str(proc) def test_process_example_with_WindowsProcessExt_Object(): p = stix2.v21.Process(extensions={ "windows-process-ext": stix2.v21.WindowsProcessExt( aslr_enabled=True, dep_enabled=True, priority="HIGH_PRIORITY_CLASS", owner_sid="S-1-5-21-186985262-1144665072-74031268-1309", ), # noqa }) assert p.extensions["windows-process-ext"].dep_enabled assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309" def test_process_example_with_WindowsServiceExt(): p = stix2.v21.Process(extensions={ "windows-service-ext": { "service_name": "sirvizio", "display_name": "Sirvizio", "start_type": "SERVICE_AUTO_START", "service_type": "SERVICE_WIN32_OWN_PROCESS", "service_status": "SERVICE_RUNNING", }, }) assert p.extensions["windows-service-ext"].service_name == "sirvizio" assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS" def test_process_example_with_WindowsProcessServiceExt(): p = stix2.v21.Process(extensions={ "windows-service-ext": { "service_name": "sirvizio", "display_name": "Sirvizio", "start_type": "SERVICE_AUTO_START", "service_type": "SERVICE_WIN32_OWN_PROCESS", "service_status": "SERVICE_RUNNING", }, "windows-process-ext": { "aslr_enabled": True, "dep_enabled": True, "priority": "HIGH_PRIORITY_CLASS", "owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309", }, }) assert p.extensions["windows-service-ext"].service_name == "sirvizio" assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS" assert p.extensions["windows-process-ext"].dep_enabled assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309" 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", ) assert s.name == "Word" assert s.cpe == "cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*" assert s.version == "2002" assert s.vendor == "Microsoft" def test_url_example(): s = stix2.v21.URL(value="https://example.com/research/index.html") assert s.type == "url" assert s.value == "https://example.com/research/index.html" def test_user_account_example(): a = stix2.v21.UserAccount( user_id="1001", account_login="jdoe", account_type="unix", display_name="John Doe", is_service_account=False, is_privileged=False, can_escalate_privs=True, account_created="2016-01-20T12:31:12Z", credential_last_changed="2016-01-20T14:27:43Z", account_first_login="2016-01-20T14:26:07Z", account_last_login="2016-07-22T16:08:28Z", ) assert a.user_id == "1001" assert a.account_login == "jdoe" assert a.account_type == "unix" assert a.display_name == "John Doe" assert not a.is_service_account assert not a.is_privileged assert a.can_escalate_privs assert a.account_created == dt.datetime(2016, 1, 20, 12, 31, 12, tzinfo=pytz.utc) assert a.credential_last_changed == dt.datetime(2016, 1, 20, 14, 27, 43, tzinfo=pytz.utc) assert a.account_first_login == dt.datetime(2016, 1, 20, 14, 26, 7, tzinfo=pytz.utc) assert a.account_last_login == dt.datetime(2016, 7, 22, 16, 8, 28, tzinfo=pytz.utc) def test_user_account_unix_account_ext_example(): u = stix2.v21.UNIXAccountExt( gid=1001, groups=["wheel"], home_dir="/home/jdoe", shell="/bin/bash", ) a = stix2.v21.UserAccount( user_id="1001", account_login="jdoe", account_type="unix", extensions={'unix-account-ext': u}, ) assert a.extensions['unix-account-ext'].gid == 1001 assert a.extensions['unix-account-ext'].groups == ["wheel"] assert a.extensions['unix-account-ext'].home_dir == "/home/jdoe" assert a.extensions['unix-account-ext'].shell == "/bin/bash" def test_windows_registry_key_example(): with pytest.raises(stix2.exceptions.InvalidValueError): stix2.v21.WindowsRegistryValueType( name="Foo", data="qwerty", data_type="string", ) v = stix2.v21.WindowsRegistryValueType( name="Foo", data="qwerty", data_type="REG_SZ", ) w = stix2.v21.WindowsRegistryKey( key="hkey_local_machine\\system\\bar\\foo", values=[v], ) assert w.key == "hkey_local_machine\\system\\bar\\foo" assert w["values"][0].name == "Foo" assert w["values"][0].data == "qwerty" assert w["values"][0].data_type == "REG_SZ" # ensure no errors in serialization because of 'values' assert "Foo" in str(w) def test_x509_certificate_example(): x509 = stix2.v21.X509Certificate( issuer="C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com", # noqa validity_not_before="2016-03-12T12:00:00Z", validity_not_after="2016-08-21T12:00:00Z", subject="C=US, ST=Maryland, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org", ) # noqa assert x509.type == "x509-certificate" assert x509.issuer == "C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com" # noqa assert x509.subject == "C=US, ST=Maryland, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org" # noqa def test_x509_certificate_error(): with pytest.raises(stix2.exceptions.PropertyPresenceError) as excinfo: stix2.v21.X509Certificate( defanged=True, ) assert excinfo.value.cls == stix2.v21.X509Certificate assert "At least one of the" in str(excinfo.value) assert "properties for X509Certificate must be populated." in str(excinfo.value) def test_new_version_with_related_objects(): data = stix2.v21.ObservedData( first_observed="2016-03-12T12:00:00Z", last_observed="2016-03-12T12:00:00Z", number_observed=1, objects={ 'src_ip': { 'type': 'ipv4-addr', 'id': 'ipv4-addr--2b94bc65-17d4-54f6-9ffe-7d103551bb9f', 'value': '127.0.0.1/32', }, 'domain': { 'type': 'domain-name', 'id': 'domain-name--220a2699-5ebf-5b57-bf02-424964bb19c0', 'value': 'example.com', 'resolves_to_refs': ['ipv4-addr--2b94bc65-17d4-54f6-9ffe-7d103551bb9f'], }, }, ) new_version = data.new_version(last_observed="2017-12-12T12:00:00Z") assert new_version.last_observed.year == 2017 assert new_version.objects['domain'].resolves_to_refs[0] == 'ipv4-addr--2b94bc65-17d4-54f6-9ffe-7d103551bb9f' def test_objects_deprecation(): with pytest.warns(stix2.exceptions.STIXDeprecationWarning): stix2.v21.ObservedData( first_observed="2016-03-12T12:00:00Z", last_observed="2016-03-12T12:00:00Z", number_observed=1, objects={ "0": { "type": "file", "name": "foo", }, }, )