Custom STIX Content¶
Custom Properties¶
Attempting to create a STIX object with properties not defined by the
specification will result in an error. Try creating an Identity
object with a custom x_foo
property:
In [3]:
from stix2 import Identity
Identity(name="John Smith",
identity_class="individual",
x_foo="bar")
ExtraPropertiesError: Unexpected properties for Identity: (x_foo).
To create a STIX object with one or more custom properties, pass them in
as a dictionary parameter called custom_properties
:
In [4]:
from stix2 import Identity
identity = Identity(name="John Smith",
identity_class="individual",
custom_properties={
"x_foo": "bar"
})
print(identity)
Out[4]:
{
"type": "identity",
"id": "identity--87aac643-341b-413a-b702-ea5820416155",
"created": "2018-04-05T18:38:10.269Z",
"modified": "2018-04-05T18:38:10.269Z",
"name": "John Smith",
"identity_class": "individual",
"x_foo": "bar"
}
Alternatively, setting allow_custom
to True
will allow custom
properties without requiring a custom_properties
dictionary.
In [5]:
identity2 = Identity(name="John Smith",
identity_class="individual",
x_foo="bar",
allow_custom=True)
print(identity2)
Out[5]:
{
"type": "identity",
"id": "identity--a1ad0a6f-39ab-4642-9a72-aaa198b1eee2",
"created": "2018-04-05T18:38:12.270Z",
"modified": "2018-04-05T18:38:12.270Z",
"name": "John Smith",
"identity_class": "individual",
"x_foo": "bar"
}
Likewise, when parsing STIX content with custom properties, pass
allow_custom=True
to
parse():
In [6]:
from stix2 import parse
input_string = """{
"type": "identity",
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
"created": "2015-12-21T19:59:11Z",
"modified": "2015-12-21T19:59:11Z",
"name": "John Smith",
"identity_class": "individual",
"x_foo": "bar"
}"""
identity3 = parse(input_string, allow_custom=True)
print(identity3.x_foo)
Out[6]:
bar
Custom STIX Object Types¶
To create a custom STIX object type, define a class with the
@CustomObject
decorator. It takes the type name and a list of property tuples, each
tuple consisting of the property name and a property instance. Any
special validation of the properties can be added by supplying an
__init__
function.
Let’s say zoo animals have become a serious cyber threat and we want to
model them in STIX using a custom object type. Let’s use a species
property to store the kind of animal, and make that property required.
We also want a property to store the class of animal, such as “mammal”
or “bird” but only want to allow specific values in it. We can add some
logic to validate this property in __init__
.
In [7]:
from stix2 import CustomObject, properties
@CustomObject('x-animal', [
('species', properties.StringProperty(required=True)),
('animal_class', properties.StringProperty()),
])
class Animal(object):
def __init__(self, animal_class=None, **kwargs):
if animal_class and animal_class not in ['mammal', 'bird', 'fish', 'reptile']:
raise ValueError("'%s' is not a recognized class of animal." % animal_class)
Now we can create an instance of our custom Animal
type.
In [8]:
animal = Animal(species="lion",
animal_class="mammal")
print(animal)
Out[8]:
{
"type": "x-animal",
"id": "x-animal--b1e4fe7f-7985-451d-855c-6ba5c265b22a",
"created": "2018-04-05T18:38:19.790Z",
"modified": "2018-04-05T18:38:19.790Z",
"species": "lion",
"animal_class": "mammal"
}
Trying to create an Animal
instance with an animal_class
that’s
not in the list will result in an error:
In [9]:
Animal(species="xenomorph",
animal_class="alien")
ValueError: 'alien' is not a recognized class of animal.
Parsing custom object types that you have already defined is simple and no different from parsing any other STIX object.
In [10]:
input_string2 = """{
"type": "x-animal",
"id": "x-animal--941f1471-6815-456b-89b8-7051ddf13e4b",
"created": "2015-12-21T19:59:11Z",
"modified": "2015-12-21T19:59:11Z",
"species": "shark",
"animal_class": "fish"
}"""
animal2 = parse(input_string2)
print(animal2.species)
Out[10]:
shark
However, parsing custom object types which you have not defined will result in an error:
In [11]:
input_string3 = """{
"type": "x-foobar",
"id": "x-foobar--d362beb5-a04e-4e6b-a030-b6935122c3f9",
"created": "2015-12-21T19:59:11Z",
"modified": "2015-12-21T19:59:11Z",
"bar": 1,
"baz": "frob"
}"""
parse(input_string3)
ParseError: Can't parse unknown object type 'x-foobar'! For custom types, use the CustomObject decorator.
Custom Cyber Observable Types¶
Similar to custom STIX object types, use a decorator to create custom
Cyber
Observable
types. Just as before, __init__()
can hold additional validation,
but it is not necessary.
In [12]:
from stix2 import CustomObservable
@CustomObservable('x-new-observable', [
('a_property', properties.StringProperty(required=True)),
('property_2', properties.IntegerProperty()),
])
class NewObservable():
pass
new_observable = NewObservable(a_property="something",
property_2=10)
print(new_observable)
Out[12]:
{
"type": "x-new-observable",
"a_property": "something",
"property_2": 10
}
Likewise, after the custom Cyber Observable type has been defined, it can be parsed.
In [13]:
from stix2 import ObservedData
input_string4 = """{
"type": "observed-data",
"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": "x-new-observable",
"a_property": "foobaz",
"property_2": 5
}
}
}"""
obs_data = parse(input_string4)
print(obs_data.objects["0"].a_property)
print(obs_data.objects["0"].property_2)
Out[13]:
foobaz
Out[13]:
5
Custom Cyber Observable Extensions¶
Finally, custom extensions to existing Cyber Observable types can also
be created. Just use the
@CustomExtension
decorator. Note that you must provide the Cyber Observable class to
which the extension applies. Again, any extra validation of the
properties can be implemented by providing an __init__()
but it is
not required. Let’s say we want to make an extension to the File
Cyber Observable Object:
In [16]:
from stix2 import File, CustomExtension
@CustomExtension(File, 'x-new-ext', [
('property1', properties.StringProperty(required=True)),
('property2', properties.IntegerProperty()),
])
class NewExtension():
pass
new_ext = NewExtension(property1="something",
property2=10)
print(new_ext)
Out[16]:
{
"property1": "something",
"property2": 10
}
Once the custom Cyber Observable extension has been defined, it can be parsed.
In [17]:
input_string5 = """{
"type": "observed-data",
"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",
"name": "foo.bar",
"hashes": {
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
},
"extensions": {
"x-new-ext": {
"property1": "bla",
"property2": 50
}
}
}
}
}"""
obs_data2 = parse(input_string5)
print(obs_data2.objects["0"].extensions["x-new-ext"].property1)
print(obs_data2.objects["0"].extensions["x-new-ext"].property2)
Out[17]:
bla
Out[17]:
50