Posted by:

David Greenwood

David Greenwood, Chief of Signal

If you are reading this blog post via a 3rd party source it is very likely that many parts of it will not render correctly. Please view the post on signalscorps.com for the full interactive viewing experience.

In this post I will introduce you to a few tools that will help you create and manage STIX 2.1 content.

Over the last two months I have shown many examples of STIX 2.1 content. Today I will lift the curtain and show you exactly how I created it.

cti-python-stix2

cti-python-stix2 from OASIS is a set of Python APIs that allow you to quickly start creating STIX 2.1 content. It is likely to be the tool you use most as a STIX 2.1 producer.

There are a wide range of functions it can be used for. This post aims to cover some of the most common that you will likely want to perform.

To follow along with this tutorial, first clone our tutorial repository and install the cti-python-stix2 library like so;

git clone https://github.com/signalscorps/blog-tutorials
cd blog-tutorials/cti-python-stix2-tutorial
python3 -m venv tutorial_env
source tutorial_env/bin/activate
pip3 install -r requirements.txt
cd examples

1. Creating some core Objects

Code: 01-print-statement-marking-identity.py

First I will start by creating an Identity SDO (SCIdentitySDO) and Marking Definition (SCMarkingDefinitionStatement) for the content I will create during this post. For this I can use the appropriate classes Identity and MarkingDefinition respectively like so

SCIdentitySDO = Identity(
SCMarkingDefinitionStatement = MarkingDefinition(

When creating any Object, certain required Properties will be added automatically if not provided as keyword arguments using the class logic. For other Properties you need to explicitly pass the required Properties (as well as any optional properties you want to use).

For the Identity SDO, after reviewing the STIX 2.1 specification, the name Property is required. I have also added a description Property.

SCIdentitySDO = Identity(
                        name="Signal Corps Tutorial",
                        description="Used for tutorial content")

It is exactly the same approach, albeit from a different specification, for the Statement Marking Definition.

Do not forget to import the required classes at the top of the code;

from stix2 import Identity
from stix2 import MarkingDefinition, StatementMarking

2. Creating an Indicator SDO setting Patter Manually

Code: 02-print-indicator0-sdo.py

Now I create a more verbose Indicator SDO (in addition to the Identity SDO and Marking Definition).

Indicator0SDOFileHash = Indicator(
                        name="My first SDO",
                        description="Getting started with cti-python-stix2",
                        type='indicator',
                        pattern="[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']",
                        pattern_type="stix",
                        created_by_ref=SCIdentitySDO,
                        object_marking_refs=[TLP_GREEN,SCMarkingDefinitionStatement]
                    )

You will see I have created the pattern manually in this example. If the Pattern does not conform to the STIX 2.1 Pattern specification, the creation of the Indicator SDO will fail with an error similar to;

Invalid value for Indicator 'pattern': FAIL: Error found at line 1:6. no viable alternative at input 'bad:pattern'

In addition to setting some fields as a string, you can see I can call the previously created SCIdentitySDO Object (at the top of the code) in the created_by_ref Property to set the Data Marking.

I also import the generic STIX 2.1 TLP Objects Marking Definition by importing them (in this case I use TLP_GREEN), in addition to my custom Statement Marking Definition.

from stix2 import MarkingDefinition, StatementMarking, TLP_GREEN

3. Saving Objects to the FileSystemStore

Input

Output

Description

In the last two examples I printed the Objects created. Though it typically makes more sense to save them for reuse later than create them all in one go. For this I can use the FileSystemStore API.

To do this I first create the FileSystemStore in the directory tmp/stix2_store like so;

fs = FileSystemStore("tmp/stix2_store")

Now, instead of just printing the Objects (like in example 2), you will also see them now saved to the FileSystemStore specified (tmp/stix2_store).

from stix2 import FileSystemStore
fs.add([SCIdentitySDO,SCMarkingDefinitionStatement,Indicator0SDOFileHash])

4. Creating an Indicator SDO with Helpers

Input

Output

Description

In the second example, I declared the Indicator SDO pattern Properties manually. cti-python-stix2 has an API for generating STIX Patterns too.

In this example you can see the Pattern being constructed where I define it as an ObservationExpression with a EqualityComparisonExpression;

setSCOandContributingProperty = ObjectPath("file", ["parent_directory_ref","path"])
fileParentDirectoryPattern = ObservationExpression(EqualityComparisonExpression(setSCOandContributingProperty, "C:\\Windows\\System32"))

And then reference the created pattern in the Indicator SDOs pattern Property (pattern=fileParentDirectoryPattern).

Also, do not forget to call the other Objects from the FileSystemStore that are needed to create the Indicator Object, in this case the Identity SDO created in step 3, referenced in the Indicator SDOs created_by_ref Property;

fs = FileSystemStore("tmp/stix2_store")
SCIdentitySDO = fs.get("identity--c73bd6f8-6cd0-4b39-a5ec-81c4461f97fb")

5. Create a Malware SDO with Granular Marking and Custom Properties

Input

Output

Description

When creating this Malware SDO I have added a Granular Marking Property, applying the marking to the name field.

granular_markings=[
    {
        "selectors": ["name"],
        "marking_ref": TLP_AMBER
    }

You will see I have also declared a custom_properties Property, with a JSON Object nested within it where the custom a Custom Property (prefixed with x_) and the value is declared.

custom_properties={
    "x_foo": "bar"
},

6. Linking SDOs using the generic SRO

Input

Output

Description

In this example I link the Indicator SDO (created at step 3) to the Malware SDO (created at step 5), using the relationship_type='indicates'.


Indicator0ToMalware0SRO = Relationship(
                        relationship_type='indicates',
                        source_ref=Indicator0SDOFileHash.id,
                        target_ref=Malware0SDOWithGranularMarkings.id,
                        created_by_ref=SCIdentitySDO,
                        object_marking_refs=TLP_AMBER
                    )

You can see I also call the following Objects from the FileSystemStore; Indicator0SDOFileHash and Indicator0SDOFileHash, because I need to declare the ID’s of these SDOs in the source_ref and target_ref Properties of the Relationship SRO.

7. Creating an Observed Data SDO with linked SCOs

Input

Output

Description

Let us assume I am tracking some indicators of compromise (an IPv4 address that resolves to two MAC Addresses). By using SCOs linked to an Observed Data SDO I can model this relationship.

I start by creating the two MACAddress SCOs;

MACAddr0SCO = MACAddress(
                value="a1:b2:c3:d4:e5:f6"
            )
MACAddr1SCO = MACAddress(
                value="a7:b8:c9:d0:e1:f2"
            )

Then I create the IPv4Address SCO, declaring the two MACAddress SCOs under the Property resolves_to_refs;

IPv40SCO = IPv4Address(
            value="177.60.40.7",
            resolves_to_refs=[MACAddr0SCO.id, MACAddr1SCO.id]
        )

Finally, I create the ObservedData SDO, with the object_refs Property declaring the IPv4Address SCO.

ObservedDataSROofIPv40SCO = ObservedData(
                        object_refs=IPv40SCO,
                        first_observed="2021-06-25T12:01:23.868289Z",
                        last_observed="2021-06-25T12:01:23.868289Z",
                        number_observed="1",
                        created_by_ref=SCIdentitySDO
                        )

8. Linking SDOs using the Sighting SRO

Input

Output

Description

Now I can create a Sighting of the Malware SDO that I know is linked to the Observed Data SDO created in step 7.

To do this in the Sighting SRO, I need to set the

  1. observed_data_ref: the Observed Data SDO created at step 7
  2. sighting_of_ref: the Malware SDO created at step 5
  3. where_sighted_refs: the Identity SDO (I assume the same Identity recorded the Sighting) created at step 3
  4. count: of known Sightings
SightingSRO = Sighting(
                sighting_of_ref = Malware0SDOWithGranularMarkings,
                observed_data_refs = [ObservedDataSROofIPv40SCO],
                where_sighted_refs = [SCIdentitySDO],
                count = 1,
                created_by_ref = SCIdentitySDO
            )

9. Creating a Custom Object

Input

Output

Description

In addition to generating the Custom Object, you can also declare (and restrict) the Properties and corresponding Property values for this Object.

To create the Custom Object and define the predefined and required Properties (aka the specification);

@CustomObject('x-dummy-object', [
    ('property0', properties.StringProperty(required=True)),
    ('property1', properties.StringProperty()),
])

To restrict the values allowed for a Property, I then create a new class (in this case Dummy class), and declare the values allowed for the property1 Property.

class Dummy(object):
    def __init__(self, property1=None, **kwargs):
        if property1 and property1 not in ['value0', 'value1', 'value2']:
            raise ValueError("'%s' is not a recognized value for property." % property1)

Finally I create the Custom Object using the defined Dummy class in the same way as other Objects, by supplying all the arguments for the class;

Custom0SDO = Dummy(
                property0="something",
                property1="value0",
                created_by_ref=SCIdentitySDO
            )

If I try and pass a Property that is not predefined in the specification (and not as a Custom Property), omit a value that is required, or pass an invalid Property value (e.g. property1=value3), the creation of the Object will fail.

10. Versioning the Indicator SDO

Input

Output

Description

Over time it is very likely the existing Objects will need to be updated and possibly even revoked.

In this case I want to create a new version of the Indicator SDO created at step 3.

To do this, I pass the original version of the Indicator SDO, declaring a new_version adding a new Property not used in the original indicator_types = ["anomalous-activity"].

It is also possible to remove data, passing the Property as None (e.g. description=None will remove a description).

Update0Indicator0SDOFileHash = Indicator0SDOFileHash.new_version(
                                indicator_types = ["anomalous-activity"]
                            )

To revoke an Object, instead of using new_version, I can pass revoke() with no arguments. For example;

Update0Indicator0SDOFileHash = Indicator0SDOFileHash.revoked()

In my update I added a new value to the indicator_types Property. Note that the Indicator retains its original id Property (indicator--c7162dea-dbbb-42cf-be6d-fc82daeea352), but you will see two things have happened;

  1. A new .json file for the most recent Indicator SDO is created in the FileSystemStore, see here.
  2. In the new copy of the Indicator SDO, the modified Property time is later than the created Property time (whereas these times are equal in the original – the first version)

11. Bundling all our Objects

Input

Output

Description

Now all that is left to do is package all the Objects in a STIX Bundle.

In the case of the versioned Indicator SDO, the Bundle class will only take the latest version of the Object so I do not need to worry about specifying that.

In the case of the Custom Object, you will notice the final argument to the Bundle class is allow_custom=True.

Without this the bundling will fail due to the inclusion of the Custom Object.

It is important downstream tools are aware your Bundles and/or Objects contain custom content to ensure they are also parsed correctly using allow_custom=True.

CONGRATULATIONS!

You have made it to the end of this short course.

Hopefully the last three months have given you a deeper understanding of modelling cyber threat intelligence using STIX 2.1 and a few tools that will help you turn this theory into reality.

Here are some useful links to bookmark following this course, some I have covered, some I have not, that you I find useful when working with STIX 2.1:

  • The STIX 2.1 Specification will be your number one resource for looking information up
    • URL: https://docs.oasis-open.org/cti/stix/v2.1/os/stix-v2.1-os.html
  • Alongside the specification, the Python APIs for STIX 2.1 will prove very handy
    • URL: https://github.com/oasis-open/cti-python-stix2
  • Not using cti-python-stix2? To make sure any STIX Objects you create or modify conform to the STIX 2.1 specification, you can use OASIS’s STIX Object Validator.
    • URL: https://github.com/oasis-open/cti-stix-validator
  • The STIX Pattern Validator is also useful when creating Patterns outside of cti-python-stix2
    • URL: https://github.com/oasis-open/cti-pattern-validator
  • And the STIX Pattern Matcher compares STIX Observed Data content against patterns used in STIX Indicators for detections
    • URL: https://github.com/oasis-open/cti-pattern-matcher
  • For visualising STIX Objects I prefer to use (including in this post), STIX View
    • URL: https://github.com/traut/stixview
  • I should also note OASIS also offer their own STIX Viewer
    • URL: https://github.com/oasis-open/cti-stix-validator

If you have any questions about the content in this tutorial, please do not hesitate to drop us a message on Discord.


STIX 2.1 Certification (Virtual and In Person)

The content used in this post is a small subset of our full training material used in our STIX 2.1 training.

If you want to join a select group of certified STIX 2.1 professionals, subscribe to our newsletter below to be notified of new course dates.




Our brand new Discord!

Like this blog?

Sign up to receive new posts in your inbox.


Stixify

Stixify. Extract machine readable intelligence from unstructured data.

Extract machine readable intelligence from unstructured data.

Obstracts

Obstracts

Turn any blog into structured threat intelligence.


Vulmatch

Vulmatch

Know when software you use is vulnerable, how it is being exploited, and how to detect an attack.

SIEM Rules

SIEM Rules. Your detection engineering database.

View, modify, and deploy SIEM rules for threat hunting.