cti-python-stix2/stix2/test/v20/test_observed_data.py

1360 lines
42 KiB
Python

import datetime as dt
import re
import pytest
import pytz
import stix2
from ...exceptions import InvalidValueError
from .constants import IDENTITY_ID, OBSERVED_DATA_ID
OBJECTS_REGEX = re.compile('\"objects\": {(?:.*?)(?:(?:[^{]*?)|(?:{[^{]*?}))*}', re.DOTALL)
EXPECTED = """{
"type": "observed-data",
"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",
"name": "foo.exe"
}
}
}"""
def test_observed_data_example():
observed_data = stix2.v20.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": {
"name": "foo.exe",
"type": "file",
},
},
)
assert str(observed_data) == EXPECTED
EXPECTED_WITH_REF = """{
"type": "observed-data",
"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",
"name": "foo.exe"
},
"1": {
"type": "directory",
"path": "/usr/home",
"contains_refs": [
"0"
]
}
}
}"""
def test_observed_data_example_with_refs():
observed_data = stix2.v20.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": {
"name": "foo.exe",
"type": "file",
},
"1": {
"type": "directory",
"path": "/usr/home",
"contains_refs": ["0"],
},
},
)
assert str(observed_data) == EXPECTED_WITH_REF
def test_observed_data_example_with_bad_refs():
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
stix2.v20.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",
"name": "foo.exe",
},
"1": {
"type": "directory",
"path": "/usr/home",
"contains_refs": ["2"],
},
},
)
assert excinfo.value.cls == stix2.v20.ObservedData
assert excinfo.value.prop_name == "objects"
assert excinfo.value.reason == "Invalid object reference for 'Directory:contains_refs': '2' is not a valid object in local scope"
def test_observed_data_example_with_non_dictionary():
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
stix2.v20.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.v20.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.v20.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.v20.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",
"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",
},
},
},
],
)
def test_parse_observed_data(data):
odata = stix2.parse(data, version="2.0")
assert odata.type == 'observed-data'
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.0")
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(InvalidValueError):
stix2.parse(odata_str, version="2.0")
def test_artifact_example_dependency_error():
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
stix2.v20.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.0")
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": "0"
}""",
],
)
def test_parse_email_address(data):
odata = stix2.parse_observable(data, {"0": "user-account"}, version='2.0')
assert odata.type == "email-addr"
odata_str = re.compile('"belongs_to_ref": "0"', re.DOTALL).sub('"belongs_to_ref": "3"', data)
with pytest.raises(stix2.exceptions.InvalidObjRefError):
stix2.parse_observable(odata_str, {"0": "user-account"}, version='2.0')
@pytest.mark.parametrize(
"data", [
"""
{
"type": "email-message",
"is_multipart": true,
"content_type": "multipart/mixed",
"date": "2016-06-19T14:20:40.000Z",
"from_ref": "1",
"to_refs": [
"2"
],
"cc_refs": [
"3"
],
"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": "4"
},
{
"content_type": "application/zip",
"content_disposition": "attachment; filename=\\"tabby_pics.zip\\"",
"body_raw_ref": "5"
}
]
}
""",
],
)
def test_parse_email_message(data):
valid_refs = {
"0": "email-message",
"1": "email-addr",
"2": "email-addr",
"3": "email-addr",
"4": "artifact",
"5": "file",
}
odata = stix2.parse_observable(data, valid_refs, version='2.0')
assert odata.type == "email-message"
assert odata.body_multipart[0].content_disposition == "inline"
@pytest.mark.parametrize(
"data", [
"""
{
"type": "email-message",
"from_ref": "0",
"to_refs": ["1"],
"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):
valid_refs = {
"0": "email-addr",
"1": "email-addr",
}
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
stix2.parse_observable(data, valid_refs, version='2.0')
assert excinfo.value.cls == stix2.v20.EmailMessage
assert excinfo.value.dependencies == [("is_multipart", "body")]
@pytest.mark.parametrize(
"data", [
""""0": {
"type": "file",
"hashes": {
"SHA-256": "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a"
}
},
"1": {
"type": "file",
"hashes": {
"SHA-256": "19c549ec2628b989382f6b280cbd7bb836a0b461332c0fe53511ce7d584b89d3"
}
},
"2": {
"type": "file",
"hashes": {
"SHA-256": "0969de02ecf8a5f003e3f6d063d848c8a193aada092623f8ce408c15bcb5f038"
}
},
"3": {
"type": "file",
"name": "foo.zip",
"hashes": {
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
},
"mime_type": "application/zip",
"extensions": {
"archive-ext": {
"contains_refs": [
"0",
"1",
"2"
],
"version": "5.0"
}
}
}""",
],
)
def test_parse_file_archive(data):
odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED)
odata = stix2.parse(odata_str, version="2.0")
assert odata.objects["3"].extensions['archive-ext'].version == "5.0"
@pytest.mark.parametrize(
"data", [
"""
{
"type": "email-message",
"is_multipart": true,
"content_type": "multipart/mixed",
"date": "2016-06-19T14:20:40.000Z",
"from_ref": "1",
"to_refs": [
"2"
],
"cc_refs": [
"3"
],
"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": "5"
}
]
}
""",
],
)
def test_parse_email_message_with_at_least_one_error(data):
valid_refs = {
"0": "email-message",
"1": "email-addr",
"2": "email-addr",
"3": "email-addr",
"4": "artifact",
"5": "file",
}
with pytest.raises(InvalidValueError) as excinfo:
stix2.parse_observable(data, valid_refs, version='2.0')
assert excinfo.value.cls == stix2.v20.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": "0",
"dst_ref": "1",
"protocols": [
"tcp"
]
}
""",
],
)
def test_parse_basic_tcp_traffic(data):
odata = stix2.parse_observable(
data, {"0": "ipv4-addr", "1": "ipv4-addr"},
version='2.0',
)
assert odata.type == "network-traffic"
assert odata.src_ref == "0"
assert odata.dst_ref == "1"
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": [
"4"
]
}
""",
],
)
def test_parse_basic_tcp_traffic_with_error(data):
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
stix2.parse_observable(data, {"4": "network-traffic"}, version='2.0')
assert excinfo.value.cls == stix2.v20.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,
"name": "gedit-bin",
"created": "2016-01-20T14:11:25.55Z",
"arguments" :[
"--new-window"
],
"binary_ref": "0"
}
},
"type": "observed-data"
}"""
def test_observed_data_with_process_example():
observed_data = stix2.v20.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",
"hashes": {
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f",
},
},
"1": {
"type": "process",
"pid": 1221,
"name": "gedit-bin",
"created": "2016-01-20T14:11:25.55Z",
"arguments": [
"--new-window",
],
"binary_ref": "0",
},
},
)
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"].name == "gedit-bin"
assert observed_data.objects["1"].arguments[0] == "--new-window"
# creating cyber observables directly
def test_artifact_example():
art = stix2.v20.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.v20.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.v20.Artifact
assert excinfo.value.properties == ["payload_bin", "url"]
assert 'are mutually exclusive' in str(excinfo.value)
def test_directory_example():
dir = stix2.v20.Directory(
_valid_refs={"1": "file"},
path='/usr/lib',
created="2015-12-21T19:00:00Z",
modified="2015-12-24T19:00:00Z",
accessed="2015-12-21T20:00:00Z",
contains_refs=["1"],
)
assert dir.path == '/usr/lib'
assert dir.created == dt.datetime(2015, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
assert dir.modified == dt.datetime(2015, 12, 24, 19, 0, 0, tzinfo=pytz.utc)
assert dir.accessed == dt.datetime(2015, 12, 21, 20, 0, 0, tzinfo=pytz.utc)
assert dir.contains_refs == ["1"]
def test_directory_example_ref_error():
with pytest.raises(stix2.exceptions.InvalidObjRefError) as excinfo:
stix2.v20.Directory(
_valid_refs=[],
path='/usr/lib',
created="2015-12-21T19:00:00Z",
modified="2015-12-24T19:00:00Z",
accessed="2015-12-21T20:00:00Z",
contains_refs=["1"],
)
assert excinfo.value.cls == stix2.v20.Directory
assert excinfo.value.prop_name == "contains_refs"
def test_domain_name_example():
dn = stix2.v20.DomainName(
_valid_refs={"1": 'domain-name'},
value="example.com",
resolves_to_refs=["1"],
)
assert dn.value == "example.com"
assert dn.resolves_to_refs == ["1"]
def test_domain_name_example_invalid_ref_type():
with pytest.raises(stix2.exceptions.InvalidObjRefError) as excinfo:
stix2.v20.DomainName(
_valid_refs={"1": "file"},
value="example.com",
resolves_to_refs=["1"],
)
assert excinfo.value.cls == stix2.v20.DomainName
assert excinfo.value.prop_name == "resolves_to_refs"
def test_file_example():
f = stix2.v20.File(
name="qwerty.dll",
hashes={
"SHA-256": "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a",
},
size=100,
magic_number_hex="1C",
mime_type="application/msword",
created="2016-12-21T19:00:00Z",
modified="2016-12-24T19:00:00Z",
accessed="2016-12-21T20:00:00Z",
is_encrypted=True,
encryption_algorithm="AES128-CBC",
decryption_key="fred",
)
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.created == dt.datetime(2016, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
assert f.modified == dt.datetime(2016, 12, 24, 19, 0, 0, tzinfo=pytz.utc)
assert f.accessed == dt.datetime(2016, 12, 21, 20, 0, 0, tzinfo=pytz.utc)
assert f.is_encrypted
assert f.encryption_algorithm == "AES128-CBC"
assert f.decryption_key == "fred" # does the key have a format we can test for?
def test_file_ssdeep_example():
f = stix2.v20.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.v20.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(InvalidValueError) as excinfo:
stix2.v20.File(
name="abc.txt",
extensions={
"ntfs-ext": {},
},
)
assert excinfo.value.cls == stix2.v20.File
def test_file_example_with_PDFExt():
f = stix2.v20.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.v20.File(
name="qwerty.dll",
extensions={
"pdf-ext": stix2.v20.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.v20.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
RASTER_IMAGE_EXT = """{
"type": "observed-data",
"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",
"name": "picture.jpg",
"hashes": {
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
},
"extensions": {
"raster-image-ext": {
"image_height": 768,
"image_width": 1024,
"bits_per_pixel": 72,
"image_compression_algorithm": "JPEG",
"exif_tags": {
"Make": "Nikon",
"Model": "D7000",
"XResolution": 4928,
"YResolution": 3264
}
}
}
}
}
}
"""
def test_raster_image_ext_parse():
obj = stix2.parse(RASTER_IMAGE_EXT, version="2.0")
assert obj.objects["0"].extensions['raster-image-ext'].image_width == 1024
def test_raster_images_ext_create():
ext = stix2.v20.RasterImageExt(image_width=1024)
assert "image_width" in str(ext)
def test_file_example_with_WindowsPEBinaryExt():
f = stix2.v20.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.DependentPropertiesError) as excinfo:
stix2.v20.File(
name="qwerty.dll",
is_encrypted=False,
encryption_algorithm="AES128-CBC",
)
assert excinfo.value.cls == stix2.v20.File
assert excinfo.value.dependencies == [("is_encrypted", "encryption_algorithm")]
assert "property dependencies" in str(excinfo.value)
assert "are not met" in str(excinfo.value)
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
stix2.v20.File(
name="qwerty.dll",
encryption_algorithm="AES128-CBC",
)
def test_ip4_address_example():
ip4 = stix2.v20.IPv4Address(
_valid_refs={"4": "mac-addr", "5": "mac-addr"},
value="198.51.100.3",
resolves_to_refs=["4", "5"],
)
assert ip4.value == "198.51.100.3"
assert ip4.resolves_to_refs == ["4", "5"]
def test_ip4_address_valid_refs():
mac1 = stix2.v20.MACAddress(
value="a1:b2:c3:d4:e5:f6",
)
mac2 = stix2.v20.MACAddress(
value="a7:b8:c9:d0:e1:f2",
)
ip4 = stix2.v20.IPv4Address(
_valid_refs={"1": mac1, "2": mac2},
value="177.60.40.7",
resolves_to_refs=["1", "2"],
)
assert ip4.value == "177.60.40.7"
assert ip4.resolves_to_refs == ["1", "2"]
def test_ip4_address_example_cidr():
ip4 = stix2.v20.IPv4Address(value="198.51.100.0/24")
assert ip4.value == "198.51.100.0/24"
def test_ip6_address_example():
ip6 = stix2.v20.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.v20.MACAddress(value="d2:fb:49:24:37:18")
assert ip6.value == "d2:fb:49:24:37:18"
def test_network_traffic_example():
nt = stix2.v20.NetworkTraffic(
_valid_refs={"0": "ipv4-addr", "1": "ipv4-addr"},
protocols="tcp",
src_ref="0",
dst_ref="1",
)
assert nt.protocols == ["tcp"]
assert nt.src_ref == "0"
assert nt.dst_ref == "1"
def test_network_traffic_http_request_example():
h = stix2.v20.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.v20.NetworkTraffic(
_valid_refs={"0": "ipv4-addr"},
protocols="tcp",
src_ref="0",
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.v20.ICMPExt(icmp_type_hex="08", icmp_code_hex="00")
nt = stix2.v20.NetworkTraffic(
_valid_refs={"0": "ipv4-addr"},
protocols="tcp",
src_ref="0",
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.v20.SocketExt(
is_listening=True,
address_family="AF_INET",
protocol_family="PF_INET",
socket_type="SOCK_STREAM",
)
nt = stix2.v20.NetworkTraffic(
_valid_refs={"0": "ipv4-addr"},
protocols="tcp",
src_ref="0",
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'].protocol_family == "PF_INET"
assert nt.extensions['socket-ext'].socket_type == "SOCK_STREAM"
def test_network_traffic_tcp_example():
h = stix2.v20.TCPExt(src_flags_hex="00000002")
nt = stix2.v20.NetworkTraffic(
_valid_refs={"0": "ipv4-addr"},
protocols="tcp",
src_ref="0",
extensions={'tcp-ext': h},
)
assert nt.extensions['tcp-ext'].src_flags_hex == "00000002"
def test_mutex_example():
m = stix2.v20.Mutex(name="barney")
assert m.name == "barney"
def test_process_example():
p = stix2.v20.Process(
_valid_refs={"0": "file"},
pid=1221,
name="gedit-bin",
created="2016-01-20T14:11:25.55Z",
arguments=["--new-window"],
binary_ref="0",
)
assert p.name == "gedit-bin"
assert p.arguments == ["--new-window"]
def test_process_example_empty_error():
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
stix2.v20.Process()
assert excinfo.value.cls == stix2.v20.Process
properties_of_process = list(stix2.v20.Process._properties.keys())
properties_of_process.remove("type")
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.v20.Process.__name__,
", ".join(sorted(properties_of_process)),
)
assert str(excinfo.value) == msg
def test_process_example_empty_with_extensions():
with pytest.raises(InvalidValueError) as excinfo:
stix2.v20.Process(
extensions={
"windows-process-ext": {},
},
)
assert excinfo.value.cls == stix2.v20.Process
def test_process_example_windows_process_ext():
proc = stix2.v20.Process(
pid=314,
name="foobar.exe",
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(InvalidValueError) as excinfo:
stix2.v20.Process(
pid=1221,
name="gedit-bin",
extensions={
"windows-process-ext": {},
},
)
assert excinfo.value.cls == stix2.v20.Process
def test_process_example_extensions_empty():
proc = stix2.v20.Process(
pid=314,
name="foobar.exe",
extensions={},
)
assert '{}' in str(proc)
def test_process_example_with_WindowsProcessExt_Object():
p = stix2.v20.Process(extensions={
"windows-process-ext": stix2.v20.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.v20.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.v20.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.v20.Software(
name="Word",
cpe="cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*",
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.v20.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.v20.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",
password_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.password_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.v20.UNIXAccountExt(
gid=1001,
groups=["wheel"],
home_dir="/home/jdoe",
shell="/bin/bash",
)
a = stix2.v20.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(InvalidValueError):
stix2.v20.WindowsRegistryValueType(
name="Foo",
data="qwerty",
data_type="string",
)
v = stix2.v20.WindowsRegistryValueType(
name="Foo",
data="qwerty",
data_type="REG_SZ",
)
w = stix2.v20.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.v20.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_new_version_with_related_objects():
data = stix2.v20.ObservedData(
first_observed="2016-03-12T12:00:00Z",
last_observed="2016-03-12T12:00:00Z",
number_observed=1,
objects={
'src_ip': {
'type': 'ipv4-addr',
'value': '127.0.0.1/32',
},
'domain': {
'type': 'domain-name',
'value': 'example.com',
'resolves_to_refs': ['src_ip'],
},
},
)
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] == 'src_ip'