How to create custom STIX objects

The STIX 2.1 Specification covers many of the most common cyber threat intelligence concepts.

However, sometimes there are times where the STIX 2.1 will not be broad enough for your needs.

For these cases, you can use STIX Extensions which allow you to define new STIX Objects and Properties by creating an Extension Definition that defines a schema for them.

There are three ways to extend STIX using STIX Extensions.

  1. Define one or more new STIX Object types.
  2. Define additional properties for an existing STIX Object type as a nested property extension. This is typically done to represent a sub-component or module of one or more STIX Object types.

1. Custom Objects defined using an Extension Definition

Whilst STIX 2.1 has a broad range of Objects, there are times when an existing one does not quite meet need existing needs.

You can define the new Object using an Extension Definition Object, using the extension_types property (in this case new-sdo), like so;

{
    "type": "extension-definition",
    "spec_version": "2.1",
    "id": "extension-definition--51650285-49b2-50ee-916c-20836485532d",
    "created_by_ref": "identity--aae8eb2d-ea6c-56d6-a606-cc9f755e2dd3",
    "created": "2020-01-01T00:00:00.000Z",
    "modified": "2020-01-01T00:00:00.000Z",
    "name": "Weakness",
    "description": "This extension creates a new SDO that can be used to represent weaknesses (for CWEs).",
    "schema": "https://raw.githubusercontent.com/signalscorps/stix4signalscorps/main/objects/extension-definition/schemas/extension-definition--51650285-49b2-50ee-916c-20836485532d.schema",
    "version": "1.0",
    "extension_types": [
        "new-sdo"
    ],
    "object_marking_refs": [
        "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
        "marking-definition--3f588e96-e413-57b5-b735-f0ec6c3a8771"
    ]
}

The schema property should link to a schema that defines the properties for the Object.

The OASIS core STIX 2.1 schemas will help provide some guidance.

The generation of standard STIX 2.1 objects will also help. Here’s how SDOs are structured.

The STIX 2 Python library can help with defining the generation of the Objects schema.

# python3 create_Weakness_schema.py
import uuid
import stix2
import os
import shutil

## import required things
### https://stix2.readthedocs.io/en/latest/guide/extensions.html
### https://stix2.readthedocs.io/en/latest/guide/custom.html
### https://stix2.readthedocs.io/en/latest/api/stix2.properties.html

from stix2 import CustomObject
from stix2.properties import (
    BooleanProperty, ExtensionsProperty, ReferenceProperty,
    IDProperty, IntegerProperty, ListProperty, StringProperty,
    TimestampProperty, TypeProperty,
)
from stix2.v21.common import (
    ExternalReference,
)
from stix2.utils import NOW

_type = 'weakness'
@CustomObject('weakness', [
    ('type', TypeProperty(_type, spec_version='2.1')),
    ('spec_version', StringProperty(fixed='2.1')),
    ('id', IDProperty(_type, spec_version='2.1')),
    ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
    ('created', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
    ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
    ('name', StringProperty(required=True)),
    ('description', StringProperty()),
    ('labels', ListProperty(StringProperty)),
    ('external_references', ListProperty(ExternalReference)),
    ('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
    ('extensions', ExtensionsProperty(spec_version='2.1'))
])
class Weakness(object):
    def __init__(self, **kwargs):
        pass

Here you can see I am reusing existing property types (e.g. TimestampProperty).

However, with this defined, I can now create my Weakness object

# python3 create_Weakness_object.py
from cwe2stix.schema import Weakness

example_WeaknessSDO = Weakness(
    name="CWE Demo",
    description="A demo weakness",
    labels=[
        "example-label"
    ],
    external_references=[
        {
            "source_name": "cwe",
            "url": "http://cwe.mitre.org/data/definitions/117.html",
            "external_id": "CWE-117"    
        }
    ],
    object_marking_refs=[
        "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
    ],
    extensions={
        "extension-definition--51650285-49b2-50ee-916c-20836485532d": {
            "extension_type" : "new-sdo"
        }
    }
)

print(example_WeaknessSDO.serialize(pretty=True))
python3 create_Weakness_object.py
{
    "type": "weakness",
    "spec_version": "2.1",
    "id": "weakness--cbc0b79a-ecbd-59f1-b45b-ea4730df1c2e",
    "created_by_ref": "identity--762246cb-c8a1-53a7-94b3-eafe3ed511c9",
    "created": "2020-01-02T00:00:00.000Z",
    "modified": "2020-01-02T00:00:00.000Z",
    "name": "CWE Demo",
    "description": "A demo weakness",
    "labels": [
        "example-label"
    ],
    "external_references": [
        {
            "source_name": "cwe",
            "url": "http://cwe.mitre.org/data/definitions/117.html",
            "external_id": "CWE-117"    
        }
    ],
    "object_marking_refs": [
        "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
    ],
    "extensions": {
        "extension-definition--51650285-49b2-50ee-916c-20836485532d": {
            "extension_type": "new-sdo"
        }
    }
}

In the case of new Objects, the extension_types can be either new-sdo for SDOs (as above), new-sco for SCOs, or new-sro for SROs.

Note, when working with custom objects with the STIX 2 Python library, you need to pass the allow_custom argument.

For example, if you want to create a Bundle with a custom object (or property), you need to set allow_custom=True, like so;

bundle = Bundle(id=id, objects=object_list, allow_custom=True)

A side note on schemas

In the extension definition objects you might have seen the schema property that typically links to a public site where the schema can be viewed, for example;

    "schema": "https://raw.githubusercontent.com/signalscorps/stix4signalscorps/main/objects/extension-definition/schemas/extension-definition--51650285-49b2-50ee-916c-20836485532d.schema",

A well defined schema is vital for creators of STIX objects wanting to use your schema to understand the properties and data types available for them to use. It’s equally important for consumers to understand the type of values that can be returned.

When getting started with defining a schema is to take a look at some existing examples – the schemas for native STIX objects created by OASIS are perfect for this. For example, the Vulnerability SDO schema.

This guide, Understanding JSON Schema, is also a helpful resource for newbies too.

Here’s the schema for my custom Weakness SDO;

{
    "$id": "https://raw.githubusercontent.com/signalscorps/stix4signalscorps/main/objects/extension-definition/schemas/extension-definition--51650285-49b2-50ee-916c-20836485532d.schema",
    "$schema": "http://json-schema.org/draft/2020-12/schema#",
    "title": "weakness",
    "description": "This extension creates a new SDO that can be used to represent weaknesses (for CWEs).",
    "type": "object",
    "allOf": [
        {
            "$ref": "../common/core.json"
        },
        {
            "properties": {
                "type": {
                    "type": "string",
                    "description": "The type of this object, which MUST be the literal `weakness`.",
                    "enum": [
                        "weakness"
                    ]
                },
                "id": {
                    "title": "id",
                "pattern": "^weakness--"
                },
                "name": {
                    "type": "string",
                    "description": "The name used to identify the Weakness."
                },
                "description": {
                    "type": "string",
                    "description": "A description that provides more details and context about the Weakness."
                }
            }
        }
    ],
    "required": [
        "name"
    ]
}

You’ll see I don’t actually define all the properties, as I can import these from the core STIX schema "$ref": "../common/core.json".

2. Custom Properties defined using an Extension Definition

Custom properties using Extension Definitions are defined in a very similar to creating new Objects, the only difference being the extension_type will equal either property-extension or toplevel-property-extension.

Generally I strongly recommend using property-extensions as these are better understood by downstream tools.

To compare the two, here is an example property-extension;

{
    "id": "extension-definition--d83fce45-ef58-4c6c-a3f4-1fbc32e98c6e",
    "type": "extension-definition",
    "spec_version": "2.1",
    "name": "Extension Foo 1",
    "description": "This schema adds two properties to a STIX object",
    "created": "2014-02-20T09:16:08.989000Z",
    "modified": "2014-02-20T09:16:08.989000Z",
    "created_by_ref": "identity--11b76a96-5d2b-45e0-8a5a-f6994f370731",
    "schema": "https://www.example.com/schema-foo-1/v1/",
    "version": "1.2.1",
    "extension_types": [
        "property-extension"
    ]
},
{
    "type": "indicator",
    "spec_version": "2.1",
    "id": "indicator--e97bfccf-8970-4a3c-9cd1-5b5b97ed5d0c",
    "created": "2014-02-20T09:16:08.989000Z",
    "modified": "2014-02-20T09:16:08.989000Z",
    "name": "File hash for Poison Ivy variant",
    "description": "This file hash indicates that a sample of Poison Ivy is present.",
    "labels": [
       "malicious-activity"
    ],
    "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",
    "pattern_type": "stix",
    "valid_from": "2014-02-20T09:00:00.000000Z",
    "extensions": {
        "extension-definition--d83fce45-ef58-4c6c-a3f4-1fbc32e98c6e" : {
            "extension_type": "property-extension",
            "rank": 5,
            "toxicity": 8
        }
    }
}

As you can see the custom properties rank and toxicity are nested inside the extension definition (extension-definition--d83fce45-ef58-4c6c-a3f4-1fbc32e98c6e)property.

Compared to a toplevel-property-extension;

{
    "id": "extension-definition--71736db5-10db-43d3-b0e3-65cf81601fe1",
    "type": "extension-definition",
    "spec_version": "2.1",
    "name": "Extension Foo 1a",
    "description": "This schema adds two properties to a STIX object at the toplevel",
    "created": "2014-02-20T09:16:08.989000Z",
    "modified": "2014-02-20T09:16:08.989000Z",
    "created_by_ref": "identity--11b76a96-5d2b-45e0-8a5a-f6994f370731",
    "schema": "https://www.example.com/schema-foo-1a/v1/",
    "version": "1.2.1",
    "extension_types": [
        "toplevel-property-extension"
    ],
    "extension_properties" : [
        "toxicity",
        "rank"
    ]
},
{
    "type": "indicator",
    "spec_version": "2.1",
    "id": "indicator--66a63e16-92d7-4b2f-bd3d-21540d6b3fc7",
    "created": "2014-02-20T09:16:08.989000Z",
    "modified": "2014-02-20T09:16:08.989000Z",
    "name": "File hash for Poison Ivy variant",
    "description": "This file hash indicates that a sample of Poison Ivy is present.",
    "labels": [
        "malicious-activity"
    ],
    "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",
    "pattern_type": "stix",
    "valid_from": "2014-02-20T09:00:00.000000Z",
    "rank": 1,
    "toxicity": 2,
    "extensions": {
        "extension-definition--71736db5-10db-43d3-b0e3-65cf81601fe1" : {
            "extension_type": "toplevel-property-extension"
        }
    }
}

As you can see, the key difference being the custom properties in the toplevel-property-extension (rank and toxicity) exist at the top level of the object.

Beware though, many consuming products will throw errors in such a scenario as they won’t consider the Extension Definition and only consider the Indicator SDOs pure STIX 2.1 properties defined in the STIX specification.

Having them nested inside an extension definition in the property-extension is much more widely supported in downstream tools as they don’t conflict with the pure STIX 2.1 Indicator properties.

A note on legacy objects and properties

In older version of the STIX specification, another way to define custom objects and properties was defined.

You will still see this a lot today, however, you should not follow this approach!

The STIX 2.1 MITRE ATT&CK dataset is a good example of the implementation of custom objects and properties.

2.1 Legacy Custom Objects

Legacy custom Objects used to be created by specifying a type Property value prefixed with x- (e.g. "type": "x-my-custom-object").

MITRE ATT&CK is a good example of STIX 2.1 Custom Objects. To represent MITRE ATT&CK Tactics, MITRE use a custom Tactic Object (x-mitre-tactic--).

Here’s TA0006: Credential Access…

{
    "x_mitre_domains": [
        "enterprise-attack"
    ],
    "object_marking_refs": [
        "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
    ],
    "id": "x-mitre-tactic--2558fd61-8c75-4730-94c4-11926db2a263",
    "type": "x-mitre-tactic",
    "created": "2018-10-17T00:14:20.652Z",
    "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5",
    "external_references": [
        {
            "external_id": "TA0006",
            "url": "https://attack.mitre.org/tactics/TA0006",
            "source_name": "mitre-attack"
        }
    ],
    "modified": "2019-07-19T17:43:41.967Z",
    "name": "Credential Access",
    "description": "The adversary is trying to steal account names and passwords.\n\nCredential Access consists of techniques for stealing credentials like account names and passwords. Techniques used to get credentials include keylogging or credential dumping. Using legitimate credentials can give adversaries access to systems, make them harder to detect, and provide the opportunity to create more accounts to help achieve their goals.",
    "x_mitre_version": "1.0",
    "x_mitre_attack_spec_version": "2.1.0",
    "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5",
    "x_mitre_shortname": "credential-access"
}

Custom Objects can contain both Common Properties, Unique Object Properties defined in the STIX 2.1 specification, or Custom Properties (ultimately the producer defines the specification of their Custom Object).

Unlike when using Extension Definitions, Custom Objects defined in this way make no distinction between SDO, SCO, or SRO. The producer defines the type value of the Custom Object and it can be used for any of these three cases (or something else entirely, if need be).

2.2 Legacy Custom Properties

Custom Properties have historically been the most common way producers extend STIX 2.1 Objects because they are very useful for specific information relating to their service or processes, for example, internal references.

Custom Properties in a STIX 2.1 predefined Object or a Custom Object can be declared using the prefix x_ (e.g "x_custom_property": "value").

You can see them in the previous example of TA0006 where the following custom properties being used;

  • x_mitre_version
  • x_mitre_attack_spec_version
  • x_mitre_modified_by_ref
  • x_mitre_shortname

Again, because these properties don’t have to have a defined schema in the way they do for Extension Definitions it can be hard for consumers (namely software products) to work with them.