import datetime as dt import json import pytest import pytz import stix2 from ...exceptions import InvalidValueError, PropertyPresenceError from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS EXPECTED_MALWARE = """{ "type": "malware", "spec_version": "2.1", "id": "malware--9c4638ec-f1de-4ddb-abf4-1b760417654e", "created": "2016-05-12T08:17:27.000Z", "modified": "2016-05-12T08:17:27.000Z", "name": "Cryptolocker", "is_family": false }""" def test_malware_with_all_required_properties(): now = dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc) malware = stix2.v21.Malware( type="malware", id=MALWARE_ID, created=now, modified=now, name="Cryptolocker", is_family=False, ) assert malware.serialize(pretty=True) == EXPECTED_MALWARE def test_malware_autogenerated_properties(malware): assert malware.type == 'malware' assert malware.id == 'malware--00000000-0000-4000-8000-000000000001' assert malware.created == FAKE_TIME assert malware.modified == FAKE_TIME assert malware.malware_types == ['ransomware'] assert malware.name == "Cryptolocker" assert malware['type'] == 'malware' assert malware['id'] == 'malware--00000000-0000-4000-8000-000000000001' assert malware['created'] == FAKE_TIME assert malware['modified'] == FAKE_TIME assert malware['malware_types'] == ['ransomware'] assert malware['name'] == "Cryptolocker" def test_malware_type_must_be_malware(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Malware(type='xxx', **MALWARE_KWARGS) assert excinfo.value.cls == stix2.v21.Malware assert excinfo.value.prop_name == "type" assert excinfo.value.reason == "must equal 'malware'." assert str(excinfo.value) == "Invalid value for Malware 'type': must equal 'malware'." def test_malware_id_must_start_with_malware(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Malware(id='my-prefix--', **MALWARE_KWARGS) assert excinfo.value.cls == stix2.v21.Malware assert excinfo.value.prop_name == "id" assert excinfo.value.reason == "must start with 'malware--'." assert str(excinfo.value) == "Invalid value for Malware 'id': must start with 'malware--'." def test_malware_required_properties(): with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo: stix2.v21.Malware() assert excinfo.value.cls == stix2.v21.Malware assert excinfo.value.properties == ["is_family"] def test_malware_required_property_name(): with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo: stix2.v21.Malware(malware_types=['ransomware']) assert excinfo.value.cls == stix2.v21.Malware assert excinfo.value.properties == ["is_family"] def test_cannot_assign_to_malware_attributes(malware): with pytest.raises(stix2.exceptions.ImmutableError) as excinfo: malware.name = "Cryptolocker II" assert str(excinfo.value) == "Cannot modify 'name' property in 'Malware' after creation." def test_invalid_kwarg_to_malware(): with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo: stix2.v21.Malware(my_custom_property="foo", **MALWARE_KWARGS) assert excinfo.value.cls == stix2.v21.Malware assert excinfo.value.properties == ['my_custom_property'] assert str(excinfo.value) == "Unexpected properties for Malware: (my_custom_property)." @pytest.mark.parametrize( "data", [ EXPECTED_MALWARE, { "type": "malware", "spec_version": "2.1", "id": MALWARE_ID, "created": "2016-05-12T08:17:27.000Z", "modified": "2016-05-12T08:17:27.000Z", "name": "Cryptolocker", "is_family": False, }, ], ) def test_parse_malware(data): mal = stix2.parse(data) assert mal.type == 'malware' assert mal.spec_version == '2.1' assert mal.id == MALWARE_ID assert mal.created == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc) assert mal.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc) assert mal.name == 'Cryptolocker' assert not mal.is_family def test_parse_malware_invalid_types(): data = json.loads(EXPECTED_MALWARE) data["malware_types"] = 1 # Oops, not a list data = json.dumps(data) with pytest.raises(InvalidValueError) as excinfo: stix2.parse(data) assert "Invalid value for Malware 'malware_types'" in str(excinfo.value) def test_parse_malware_kill_chain_phases(): kill_chain = """ "kill_chain_phases": [ { "kill_chain_name": "lockheed-martin-cyber-kill-chain", "phase_name": "reconnaissance" } ]""" data = EXPECTED_MALWARE.replace('malware"', 'malware",%s' % kill_chain) mal = stix2.parse(data, version="2.1") assert mal.kill_chain_phases[0].kill_chain_name == "lockheed-martin-cyber-kill-chain" assert mal.kill_chain_phases[0].phase_name == "reconnaissance" assert mal['kill_chain_phases'][0]['kill_chain_name'] == "lockheed-martin-cyber-kill-chain" assert mal['kill_chain_phases'][0]['phase_name'] == "reconnaissance" def test_parse_malware_clean_kill_chain_phases(): kill_chain = """ "kill_chain_phases": [ { "kill_chain_name": "lockheed-martin-cyber-kill-chain", "phase_name": 1 } ]""" data = EXPECTED_MALWARE.replace('2.1"', '2.1",%s' % kill_chain) mal = stix2.parse(data, version="2.1") assert mal['kill_chain_phases'][0]['phase_name'] == "1" def test_malware_invalid_last_before_first(): with pytest.raises(ValueError) as excinfo: stix2.v21.Malware(first_seen="2017-01-01T12:34:56.000Z", last_seen="2017-01-01T12:33:56.000Z", **MALWARE_KWARGS) assert "'last_seen' must be greater than or equal to 'first_seen'" in str(excinfo.value) def test_malware_family_no_name(): with pytest.raises(PropertyPresenceError): stix2.parse({ "type": "malware", "id": MALWARE_ID, "spec_version": "2.1", "is_family": True, "malware_types": ["spyware"], }) def test_malware_non_family_no_name(): stix2.parse({ "type": "malware", "id": MALWARE_ID, "spec_version": "2.1", "is_family": False, "malware_types": ["spyware"], }) 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": ["spyware"], "operating_system_refs": [software], }) assert malware["operating_system_refs"][0] == software["id"]