Merge branch 'properties'

stix2.1
Greg Back 2017-02-24 12:04:35 -06:00
commit c5bec64143
15 changed files with 668 additions and 5 deletions

View File

@ -2,5 +2,7 @@
from .bundle import Bundle
from .common import ExternalReference
from .sdo import Indicator, Malware
from .sdo import AttackPattern, Campaign, CourseOfAction, Identity, Indicator, \
IntrusionSet, Malware, ObservedData, Report, ThreatActor, Tool, \
Vulnerability
from .sro import Relationship

View File

@ -61,12 +61,13 @@ class _STIXBase(collections.Mapping):
kwargs[prop_name] = prop_metadata['fixed']
if prop_metadata.get('validate'):
if not prop_metadata['validate'](cls, kwargs[prop_name]):
if (prop_name in kwargs and
not prop_metadata['validate'](cls, kwargs[prop_name])):
msg = prop_metadata.get('error_msg', DEFAULT_ERROR).format(
type=class_name,
field=prop_name,
expected=prop_metadata.get('expected',
prop_metadata['default'])(cls),
prop_metadata.get('default', lambda x: ''))(cls),
)
raise ValueError(msg)
elif prop_metadata.get('fixed'):

View File

@ -1,5 +1,6 @@
"""STIX 2 Common Data Types and Properties"""
import re
from .base import _STIXBase
from .utils import NOW
@ -15,6 +16,19 @@ ID_PROPERTY = {
'error_msg': "{type} {field} values must begin with '{expected}'."
}
ref_regex = ("^[a-z][a-z-]+[a-z]--[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}"
"-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
REF_PROPERTY = {
'validate': (lambda x, val: re.match(ref_regex, val)),
'error_msg': "{type} {field} values must consist of a valid STIX type name and a valid UUID, separated by '--'."
}
BOOL_PROPERTY = {
'validate': (lambda x, val: isinstance(val, bool)),
'error_msg': "{type} {field} value must be a boolean."
}
COMMON_PROPERTIES = {
'type': TYPE_PROPERTY,
'id': ID_PROPERTY,
@ -24,6 +38,9 @@ COMMON_PROPERTIES = {
'modified': {
'default': NOW,
},
'external_references': {},
'revoked': BOOL_PROPERTY,
'created_by_ref': REF_PROPERTY
}
@ -36,3 +53,14 @@ class ExternalReference(_STIXBase):
'url': {},
'external_id': {},
}
class KillChainPhase(_STIXBase):
_properties = {
'kill_chain_name': {
'required': True,
},
'phase_name': {
'required': True,
},
}

View File

@ -5,6 +5,116 @@ from .common import COMMON_PROPERTIES
from .utils import NOW
class AttackPattern(_STIXBase):
_type = 'attack-pattern'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'name': {
'required': True,
},
'description': {},
'kill_chain_phases': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - description
# - kill_chain_phases
super(AttackPattern, self).__init__(**kwargs)
class Campaign(_STIXBase):
_type = 'campaign'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'name': {
'required': True,
},
'description': {},
'aliases': {},
'first_seen': {},
'last_seen': {},
'objective': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - description
# - aliases
# - first_seen
# - last_seen
# - objective
super(Campaign, self).__init__(**kwargs)
class CourseOfAction(_STIXBase):
_type = 'course-of-action'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'name': {
'required': True,
},
'description': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - description
super(CourseOfAction, self).__init__(**kwargs)
class Identity(_STIXBase):
_type = 'identity'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'name': {
'required': True,
},
'description': {},
'identity_class': {
'required': True,
},
'sectors': {},
'contact_information': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - description
# - identity_class
# - sectors
# - contact_information
super(Identity, self).__init__(**kwargs)
class Indicator(_STIXBase):
_type = 'indicator'
@ -13,18 +123,21 @@ class Indicator(_STIXBase):
'labels': {
'required': True,
},
'name': {},
'description': {},
'pattern': {
'required': True,
},
'valid_from': {
'default': NOW,
},
'valid_until': {},
'kill_chain_phases': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - revoked
# - external_references
# - object_marking_refs
# - granular_markings
@ -37,6 +150,43 @@ class Indicator(_STIXBase):
super(Indicator, self).__init__(**kwargs)
class IntrusionSet(_STIXBase):
_type = 'intrusion-set'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'name': {
'required': True,
},
'description': {},
'aliases': {},
'first_seen': {},
'last_seen ': {},
'goals': {},
'resource_level': {},
'primary_motivation': {},
'secondary_motivations': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - description
# - aliases
# - first_seen
# - last_seen
# - goals
# - resource_level
# - primary_motivation
# - secondary_motivations
super(IntrusionSet, self).__init__(**kwargs)
class Malware(_STIXBase):
_type = 'malware'
@ -48,12 +198,13 @@ class Malware(_STIXBase):
'name': {
'required': True,
},
'description': {},
'kill_chain_phases': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - revoked
# - external_references
# - object_marking_refs
# - granular_markings
@ -62,3 +213,154 @@ class Malware(_STIXBase):
# - kill_chain_phases
super(Malware, self).__init__(**kwargs)
class ObservedData(_STIXBase):
_type = 'observed-data'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'first_observed': {},
'last_observed': {},
'number_observed': {},
'objects': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - first_observed
# - last_observed
# - number_observed
# - objects
super(ObservedData, self).__init__(**kwargs)
class Report(_STIXBase):
_type = 'report'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'labels': {
'required': True,
},
'name': {
'required': True,
},
'description': {},
'published': {},
'object_refs': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - description
# - published
# - object_refs
super(Report, self).__init__(**kwargs)
class ThreatActor(_STIXBase):
_type = 'threat-actor'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'labels': {
'required': True,
},
'name': {
'required': True,
},
'description': {},
'aliases': {},
'roles': {},
'goals': {},
'sophistication': {},
'resource_level': {},
'primary_motivation': {},
'secondary_motivations': {},
'personal_motivations': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - description
# - aliases
# - roles
# - goals
# - sophistication
# - resource_level
# - primary_motivation
# - secondary_motivations
# - personal_motivations
super(ThreatActor, self).__init__(**kwargs)
class Tool(_STIXBase):
_type = 'tool'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'labels': {
'required': True,
},
'name': {
'required': True,
},
'description': {},
'kill_chain_phases': {},
'tool_version': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - description
# - kill_chain_phases
# - tool_version
super(Tool, self).__init__(**kwargs)
class Vulnerability(_STIXBase):
_type = 'vulnerability'
_properties = COMMON_PROPERTIES.copy()
_properties.update({
'name': {
'required': True,
},
'description': {},
})
def __init__(self, **kwargs):
# TODO:
# - created_by_ref
# - external_references
# - object_marking_refs
# - granular_markings
# - description
super(Vulnerability, self).__init__(**kwargs)

View File

@ -0,0 +1,35 @@
import stix2
EXPECTED = """{
"created": "2016-05-12T08:17:27.000Z",
"description": "...",
"external_references": [
{
"id": "CAPEC-163",
"source_name": "capec"
}
],
"id": "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
"modified": "2016-05-12T08:17:27.000Z",
"name": "Spear Phishing",
"type": "attack-pattern"
}"""
def test_attack_pattern_example():
ap = stix2.AttackPattern(
id="attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
created="2016-05-12T08:17:27.000Z",
modified="2016-05-12T08:17:27.000Z",
name="Spear Phishing",
external_references=[{
"source_name": "capec",
"id": "CAPEC-163"
}],
description="...",
)
assert str(ap) == EXPECTED
# TODO: Add other examples

View File

@ -0,0 +1,26 @@
import stix2
EXPECTED = """{
"created": "2016-04-06T20:03:00.000Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
"modified": "2016-04-06T20:03:00.000Z",
"name": "Green Group Attacks Against Finance",
"type": "campaign"
}"""
def test_campaign_example():
campaign = stix2.Campaign(
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
created="2016-04-06T20:03:00.000Z",
modified="2016-04-06T20:03:00.000Z",
name="Green Group Attacks Against Finance",
description="Campaign by Green Group against a series of targets in the financial services sector."
)
assert str(campaign) == EXPECTED
# TODO: Add other examples

View File

@ -0,0 +1,26 @@
import stix2
EXPECTED = """{
"created": "2016-04-06T20:03:48.000Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
"id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
"modified": "2016-04-06T20:03:48.000Z",
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
"type": "course-of-action"
}"""
def test_course_of_action_example():
coa = stix2.CourseOfAction(
id="course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
created="2016-04-06T20:03:48.000Z",
modified="2016-04-06T20:03:48.000Z",
name="Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
description="This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
)
assert str(coa) == EXPECTED
# TODO: Add other examples

View File

@ -0,0 +1,24 @@
import stix2
EXPECTED = """{
"created": "2015-12-21T19:59:11.000Z",
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
"identity_class": "individual",
"modified": "2015-12-21T19:59:11.000Z",
"name": "John Smith",
"type": "identity"
}"""
def test_identity_example():
report = stix2.Identity(
id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
created="2015-12-21T19:59:11.000Z",
modified="2015-12-21T19:59:11.000Z",
name="John Smith",
identity_class="individual",
)
assert str(report) == EXPECTED
# TODO: Add other examples

View File

@ -0,0 +1,36 @@
import stix2
EXPECTED = """{
"aliases": [
"Zookeeper"
],
"created": "2016-04-06T20:03:48.000Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"description": "Incidents usually feature a shared TTP of a bobcat being released...",
"goals": [
"acquisition-theft",
"harassment",
"damage"
],
"id": "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29",
"modified": "2016-04-06T20:03:48.000Z",
"name": "Bobcat Breakin",
"type": "intrusion-set"
}"""
def test_intrusion_set_example():
intrusion_set = stix2.IntrusionSet(
id="intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29",
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
created="2016-04-06T20:03:48.000Z",
modified="2016-04-06T20:03:48.000Z",
name="Bobcat Breakin",
description="Incidents usually feature a shared TTP of a bobcat being released...",
aliases=["Zookeeper"],
goals=["acquisition-theft", "harassment", "damage"]
)
assert str(intrusion_set) == EXPECTED
# TODO: Add other examples

View File

@ -0,0 +1,38 @@
import stix2
EXPECTED = """{
"created": "2016-04-06T19:58:16.000Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"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"
}
},
"type": "observed-data"
}"""
def test_observed_data_example():
observed_data = stix2.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": {
"type": "file",
},
},
)
assert str(observed_data) == EXPECTED
# TODO: Add other examples

42
stix2/test/test_report.py Normal file
View File

@ -0,0 +1,42 @@
import stix2
EXPECTED = """{
"created": "2015-12-21T19:59:11.000Z",
"created_by_ref": "identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
"description": "A simple report with an indicator and campaign",
"id": "report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
"labels": [
"campaign"
],
"modified": "2015-12-21T19:59:11.000Z",
"name": "The Black Vine Cyberespionage Group",
"object_refs": [
"indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2",
"campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c",
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"
],
"published": "2016-01-201T17:00:00Z",
"type": "report"
}"""
def test_report_example():
report = stix2.Report(
id="report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
created_by_ref="identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
created="2015-12-21T19:59:11.000Z",
modified="2015-12-21T19:59:11.000Z",
name="The Black Vine Cyberespionage Group",
description="A simple report with an indicator and campaign",
published="2016-01-201T17:00:00Z",
labels=["campaign"],
object_refs=[
"indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2",
"campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c",
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"
],
)
assert str(report) == EXPECTED
# TODO: Add other examples

View File

@ -54,6 +54,7 @@ def test_my_uuid4_fixture(uuid4):
INDICATOR_ID = "indicator--01234567-89ab-cdef-0123-456789abcdef"
MALWARE_ID = "malware--fedcba98-7654-3210-fedc-ba9876543210"
RELATIONSHIP_ID = "relationship--00000000-1111-2222-3333-444444444444"
IDENTITY_ID = "identity--d4d765ce-cff7-40e8-b7a6-e205d005ac2c"
# Minimum required args for an Indicator instance
INDICATOR_KWARGS = dict(
@ -175,6 +176,18 @@ def test_indicator_required_field_pattern():
assert str(excinfo.value) == "Missing required field(s) for Indicator: (pattern)."
def test_indicator_created_ref_invalid_format():
with pytest.raises(ValueError) as excinfo:
indicator = stix2.Indicator(created_by_ref='myprefix--12345678', **INDICATOR_KWARGS)
assert str(excinfo.value) == "Indicator created_by_ref values must consist of a valid STIX type name and a valid UUID, separated by '--'."
def test_indicator_revoked_invalid():
with pytest.raises(ValueError) as excinfo:
indicator = stix2.Indicator(revoked='false', **INDICATOR_KWARGS)
assert str(excinfo.value) == "Indicator revoked value must be a boolean."
def test_cannot_assign_to_indicator_attributes(indicator):
with pytest.raises(ValueError) as excinfo:
indicator.valid_from = dt.datetime.now()

View File

@ -0,0 +1,30 @@
import stix2
EXPECTED = """{
"created": "2016-04-06T20:03:48.000Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"description": "The Evil Org threat actor group",
"id": "threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
"labels": [
"crime-syndicate"
],
"modified": "2016-04-06T20:03:48.000Z",
"name": "Evil Org",
"type": "threat-actor"
}"""
def test_threat_actor_example():
threat_actor = stix2.ThreatActor(
id="threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
created="2016-04-06T20:03:48.000Z",
modified="2016-04-06T20:03:48.000Z",
name="Evil Org",
description="The Evil Org threat actor group",
labels=["crime-syndicate"],
)
assert str(threat_actor) == EXPECTED
# TODO: Add other examples

28
stix2/test/test_tool.py Normal file
View File

@ -0,0 +1,28 @@
import stix2
EXPECTED = """{
"created": "2016-04-06T20:03:48.000Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"id": "tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
"labels": [
"remote-access"
],
"modified": "2016-04-06T20:03:48.000Z",
"name": "VNC",
"type": "tool"
}"""
def test_tool_example():
tool = stix2.Tool(
id="tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
created="2016-04-06T20:03:48.000Z",
modified="2016-04-06T20:03:48.000Z",
name="VNC",
labels=["remote-access"],
)
assert str(tool) == EXPECTED
# TODO: Add other examples

View File

@ -0,0 +1,32 @@
import stix2
EXPECTED = """{
"created": "2016-05-12T08:17:27.000Z",
"external_references": [
{
"external_id": "CVE-2016-1234",
"source_name": "cve"
}
],
"id": "vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
"modified": "2016-05-12T08:17:27.000Z",
"name": "CVE-2016-1234",
"type": "vulnerability"
}"""
def test_vulnerability_example():
vulnerability = stix2.Vulnerability(
id="vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
created="2016-05-12T08:17:27.000Z",
modified="2016-05-12T08:17:27.000Z",
name="CVE-2016-1234",
external_references=[
stix2.ExternalReference(source_name='cve',
external_id="CVE-2016-1234"),
],
)
assert str(vulnerability) == EXPECTED
# TODO: Add other examples