TAXII 2.1 Client Core Concepts (using the OASIS TAXII client)

A growing number of cyber security products are introducing native TAXII 2.1 Client functionality to consume cyber threat intelligence from TAXII 2.1 Servers.

In a previous post (TAXII 2.1 Server Core Concepts (using the Medallion TAXII server)) I showed examples of interacting with TAXII 2.1 Server APIs manually using curl requests. It is of course possible to use this knowledge in addition to the TAXII specification to build your own client.

However, before you decide to build out all the logic required for a TAXII 2.1 Client into your own product, here are some existing open-source options you might want to consider, or at least to use for inspiration.

cti-taxii-client

cti-taxii-client is a client developed by Oasis, and it is probably the best template to use if you plan to build your own TAXII 2.1 Client.

cti-taxii-client is a minimal client implementation for the TAXII 2.X server. It supports the following TAXII 2.0 and 2.1 API services:

  • Server Discovery
  • Get API Root Information
  • Get Status
  • Get Collections
  • Get a Collection
  • Get Objects
  • Add Objects
  • Get an Object
  • Delete an Object (2.1 only)
  • Get Object Manifests
  • Get Object Versions (2.1 only)

The easiest way to install the TAXII client is with pip:

git clone https://github.com/oasis-open/cti-taxii-client/
cd cti-taxii-client
python3 -m venv cti-taxii-client_env
source cti-taxii-client_env/bin/activate
pip3 install taxii2-client

To test out the TAXII 2.1 Client, you will need a running TAXII 2.1 Server. For this post I will use the same TAXII 2.1 Server as I demonstrated in the previous post, Medallion.

Unlike in the last post when I was manually creating curl requests that required me to add the full logic in each request to interact with the TAXII 2.1 Server. Using a TAXII Client, in this case cti-taxii-client, is much simpler as these already contain the logic for the TAXII Client / TAXII Server interaction.

cti-taxii-client supports both TAXII 2.0 and TAXII 2.1 in two distinct sub-modules. For this tutorial I will use TAXII 2.1 to keep the tutorial as current has it can be.

To begin with I have written a simple Python script that uses the cti-taxii-client module functions to query the discovery endpoint to find out what is on the server.

## python3 server-discovery.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.Server
from taxii2client.v21 import Server

server = Server('http://localhost:5000/taxii2/', user='admin', password='Password0')

print('server.title : ', server.title)
print('server.description : ', server.description)
print('server.contact : ', server.contact)
print('server.default.url :', server.default.url)
print('server.custom_properties :', server.custom_properties)

roots = []
for api in server.api_roots:
    roots.append(api.url)

print('server.api_roots : ',roots)

Which prints;

python3 server-discovery.py
server.title :  Some TAXII Server
server.description :  This TAXII Server contains a listing of
server.contact :  string containing contact information
server.default.url : http://localhost:5000/trustgroup1/
server.custom_properties : {}
server.api_roots :  ['http://localhost:5000/api1/', 'http://localhost:5000/api2/', 'http://localhost:5000/trustgroup1/']

Three server.api_roots are printed (thus accessible to this user). I can now identify what Collections exist in one of the API Roots (I will use http://localhost:5000/trustgroup1).

## python3 get-collections.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.ApiRoot
from taxii2client.v21 import ApiRoot

default = ApiRoot(url='http://localhost:5000/trustgroup1', user='admin', password='Password0')

collection_no = 1

for collections in default.collections:

    print()
    print('Collection {}'.format(collection_no))
    print()
    print("collection.title: ", collections.title)
    print("collection.description: ", collections.description)
    print("collection.id: ", collections.id)
    print('collection.custom_properties: ',collections.custom_properties)
    print('collection.can_read: ',collections.can_read)
    print('collection.can_write: ',collections.can_write)
    print('collection.media_types: ',collections.media_types)
    print()

    collection_no += 1

Which prints;

python3 get-collections.py
Collection 1

collection.title:  This data collection is for testing querying across collections
collection.description:  None
collection.id:  472c94ae-3113-4e3e-a4dd-a9f4ac7471d4
collection.custom_properties:  {}
collection.can_read:  False
collection.can_write:  True
collection.media_types:  ['application/stix+json;version=2.1']


Collection 2

collection.title:  This data collection is for testing adding objects
collection.description:  None
collection.id:  365fed99-08fa-fdcd-a1b3-fb247eb41d01
collection.custom_properties:  {}
collection.can_read:  True
collection.can_write:  True
collection.media_types:  ['application/stix+json;version=2.1']


Collection 3

collection.title:  High Value Indicator Collection
collection.description:  This data collection is for collecting high value IOCs
collection.id:  91a7b528-80eb-42ed-a74d-c6fbd5a26116
collection.custom_properties:  {}
collection.can_read:  True
collection.can_write:  True
collection.media_types:  ['application/stix+json;version=2.1']


Collection 4

collection.title:  Indicators from the past 24-hours
collection.description:  This data collection is for collecting current IOCs
collection.id:  52892447-4d7e-4f70-b94d-d7f22742ff63
collection.custom_properties:  {}
collection.can_read:  True
collection.can_write:  False
collection.media_types:  ['application/stix+json;version=2.1']


Collection 5

collection.title:  Secret Indicators
collection.description:  Non accessible
collection.id:  64993447-4d7e-4f70-b94d-d7f33742ee63
collection.custom_properties:  {}
collection.can_read:  False
collection.can_write:  False
collection.media_types:  ['application/stix+json;version=2.1']

Now I can start to discover the Objects held by each of these Collections. I will use 91a7b528-80eb-42ed-a74d-c6fbd5a26116 to demonstrate.

## python3 get_objects.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.Server
from taxii2client.v21 import Server
import json

server = Server('http://localhost:5000/taxii2/', user='admin', password='Password0')

col = {}

for api_roots in server.api_roots:
    api_root = api_roots.collections
    try:
        for collections in api_roots.collections:
            col[collections.id] = collections 

    except:
        print('')
        continue

collection3 =  col['91a7b528-80eb-42ed-a74d-c6fbd5a26116']

response = collection3.get_objects()
# Parse the response body as JSON
stix_objects = json.loads(response.text)

# Print the STIX objects
print(json.dumps(stix_objects, indent=4))
python3 get_objects.py
{
    "more": false,
    "objects": [
        {
            "created": "2014-05-08T09:00:00.000Z",
            "id": "relationship--2f9a9aa9-108a-4333-83e2-4fb25add0463",
            "modified": "2014-05-08T09:00:00.000Z",
            "relationship_type": "indicates",
            "source_ref": "indicator--cd981c25-8042-4166-8945-51178443bdac",
            "spec_version": "2.1",
            "target_ref": "malware--c0931cc6-c75e-47e5-9036-78fabc95d4ec",
            "type": "relationship"
        },
        {
            "created": "2014-05-08T09:00:00.000Z",
            "id": "indicator--cd981c25-8042-4166-8945-51178443bdac",
            "indicator_types": [
                "file-hash-watchlist"
            ],
            "modified": "2014-05-08T09:00:00.000Z",
            "name": "File hash for Poison Ivy variant",
            "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",
            "pattern_type": "stix",
            "spec_version": "2.1",
            "type": "indicator",
            "valid_from": "2014-05-08T09:00:00.000000Z"
        },
        {
            "created": "2017-01-20T00:00:00.000Z",
            "definition": {
                "tlp": "green"
            },
            "definition_type": "tlp",
            "id": "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da",
            "name": "TLP:GREEN",
            "spec_version": "2.1",
            "type": "marking-definition"
        },
        {
            "created": "2017-01-27T13:49:53.997Z",
            "description": "Poison Ivy",
            "id": "malware--c0931cc6-c75e-47e5-9036-78fabc95d4ec",
            "is_family": true,
            "malware_types": [
                "remote-access-trojan"
            ],
            "modified": "2017-01-27T13:49:53.997Z",
            "name": "Poison Ivy",
            "spec_version": "2.1",
            "type": "malware"
        },
        {
            "created": "2016-11-03T12:30:59.000Z",
            "description": "Accessing this url will infect your machine with malware. This is the last updated indicator",
            "id": "indicator--6770298f-0fd8-471a-ab8c-1c658a46574e",
            "indicator_types": [
                "url-watchlist"
            ],
            "modified": "2017-01-27T13:49:53.935Z",
            "name": "Malicious site hosting downloader",
            "pattern": "[url:value = 'http://x4z9arb.cn/4712']",
            "pattern_type": "stix",
            "spec_version": "2.1",
            "type": "indicator",
            "valid_from": "2016-11-03T12:30:59.000Z"
        }
    ]
}

However, in many cases, the Collection will return Objects over multiple pages.

If you remember back to the last post, the Object printed per page is set in the medallion server config file. To demonstrate how the library deals with pagination I can change the setting to one Object per page like so;

  "taxii": {
      "max_page_size": 2
  }

With this now set, for the same script as before I would expect 3 pages (5/2) when this Collection is returned

python3 get_objects.py
{
    "more": true,
    "next": "55ae8300-b8a4-4fc9-bbfe-77f63097803b",
    "objects": [
        {
            "created": "2014-05-08T09:00:00.000Z",
            "id": "relationship--2f9a9aa9-108a-4333-83e2-4fb25add0463",
            "modified": "2014-05-08T09:00:00.000Z",
            "relationship_type": "indicates",
            "source_ref": "indicator--cd981c25-8042-4166-8945-51178443bdac",
            "spec_version": "2.1",
            "target_ref": "malware--c0931cc6-c75e-47e5-9036-78fabc95d4ec",
            "type": "relationship"
        },
        {
            "created": "2014-05-08T09:00:00.000Z",
            "id": "indicator--cd981c25-8042-4166-8945-51178443bdac",
            "indicator_types": [
                "file-hash-watchlist"
            ],
            "modified": "2014-05-08T09:00:00.000Z",
            "name": "File hash for Poison Ivy variant",
            "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",
            "pattern_type": "stix",
            "spec_version": "2.1",
            "type": "indicator",
            "valid_from": "2014-05-08T09:00:00.000000Z"
        }
    ]
}

Here, as expected, 2 results are returned, and now the more property is equal to true.

As such, I need to introduce some pagination logic into the script if I want to obtain all the objects. The TAXII Client ships with a Class (as_pages) for TAXII 2.1 endpoints that support pagination.

## python3 get_objects_paginated.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.Server
### https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.as_pages
from taxii2client.v21 import as_pages, Server
import json

server = Server('http://localhost:5000/taxii2/', user='admin', password='Password0')

col = {}

for api_roots in server.api_roots:
    try:
        for collections in api_roots.collections:
            col[collections.id] = collections 
    except:
        print('')
        continue

collection3 = col['91a7b528-80eb-42ed-a74d-c6fbd5a26116']

page_no = 1
for envelope in as_pages(collection3.get_objects, per_request=1000):
    print('\nPage # {}'.format(page_no))
    
    # Parse the envelope as JSON
    stix_objects = json.loads(envelope.text)

    # Pretty-print the STIX objects
    print(json.dumps(stix_objects, indent=4))

    page_no += 1
Page # 1
{
    "more": true,
    "next": "5639bfb9-84db-40df-ad8f-7de05104ebbb",
    "objects": [
        {
            "created": "2014-05-08T09:00:00.000Z",
            "id": "relationship--2f9a9aa9-108a-4333-83e2-4fb25add0463",
            "modified": "2014-05-08T09:00:00.000Z",
            "relationship_type": "indicates",
            "source_ref": "indicator--cd981c25-8042-4166-8945-51178443bdac",
            "spec_version": "2.1",
            "target_ref": "malware--c0931cc6-c75e-47e5-9036-78fabc95d4ec",
            "type": "relationship"
        },
        {
            "created": "2014-05-08T09:00:00.000Z",
            "id": "indicator--cd981c25-8042-4166-8945-51178443bdac",
            "indicator_types": [
                "file-hash-watchlist"
            ],
            "modified": "2014-05-08T09:00:00.000Z",
            "name": "File hash for Poison Ivy variant",
            "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",
            "pattern_type": "stix",
            "spec_version": "2.1",
            "type": "indicator",
            "valid_from": "2014-05-08T09:00:00.000000Z"
        }
    ]
}
[taxii2client.v21] [WARNING ] [2023-12-21 12:12:06,540] TAXII Server Response with different amount of objects! Setting limit=2

Page # 2
{
    "more": true,
    "next": "5639bfb9-84db-40df-ad8f-7de05104ebbb",
    "objects": [
        {
            "created": "2017-01-20T00:00:00.000Z",
            "definition": {
                "tlp": "green"
            },
            "definition_type": "tlp",
            "id": "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da",
            "name": "TLP:GREEN",
            "spec_version": "2.1",
            "type": "marking-definition"
        },
        {
            "created": "2017-01-27T13:49:53.997Z",
            "description": "Poison Ivy",
            "id": "malware--c0931cc6-c75e-47e5-9036-78fabc95d4ec",
            "is_family": true,
            "malware_types": [
                "remote-access-trojan"
            ],
            "modified": "2017-01-27T13:49:53.997Z",
            "name": "Poison Ivy",
            "spec_version": "2.1",
            "type": "malware"
        }
    ]
}

Page # 3
{
    "more": false,
    "objects": [
        {
            "created": "2016-11-03T12:30:59.000Z",
            "description": "Accessing this url will infect your machine with malware. This is the last updated indicator",
            "id": "indicator--6770298f-0fd8-471a-ab8c-1c658a46574e",
            "indicator_types": [
                "url-watchlist"
            ],
            "modified": "2017-01-27T13:49:53.935Z",
            "name": "Malicious site hosting downloader",
            "pattern": "[url:value = 'http://x4z9arb.cn/4712']",
            "pattern_type": "stix",
            "spec_version": "2.1",
            "type": "indicator",
            "valid_from": "2016-11-03T12:30:59.000Z"
        }
    ]
}

This code is very rough, and you can see warnings printed, but is shows the basic logic to get this working.

Of course one of the other key functions is filtering – I don’t always want all objects returned. This is very easy to do…

## python3 get_objects_filtered.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.Server
from taxii2client.v21 import Server
import json

server = Server('http://localhost:5000/taxii2/', user='admin', password='Password0')

col = {}

for api_roots in server.api_roots:
    try:
        for collections in api_roots.collections:
            col[collections.id] = collections 
    except:
        print('')
        continue

collection3 = col['91a7b528-80eb-42ed-a74d-c6fbd5a26116']

# Retrieve a specific object by ID
response = collection3.get_object(obj_id='indicator--cd981c25-8042-4166-8945-51178443bdac')

# Parse the response body as JSON
stix_object = json.loads(response.text)

# Print the STIX object
print(json.dumps(stix_object, indent=4))
python3 get_objects_filtered.py
{
    "more": false,
    "objects": [
        {
            "created": "2014-05-08T09:00:00.000Z",
            "id": "indicator--cd981c25-8042-4166-8945-51178443bdac",
            "indicator_types": [
                "file-hash-watchlist"
            ],
            "modified": "2014-05-08T09:00:00.000Z",
            "name": "File hash for Poison Ivy variant",
            "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",
            "pattern_type": "stix",
            "spec_version": "2.1",
            "type": "indicator",
            "valid_from": "2014-05-08T09:00:00.000000Z"
        }
    ]
}

You can see 'more': False indicating no more pages of objects. However, it is entirely possible there are more than one version of the same Object as I discussed in the last post.

Remember in the last post I created 3 versions of an Attack Pattern object? Let’s use the TAXII Client to do this.

cti-taxii-client also supports the publishing of Objects (add_objects). You can pass the STIX Objects using .add_objects in a JSON escaped STIX 2.1 Object, as follows…

## python3 add_objects.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.Collection.add_objects
from taxii2client.v21 import Server 

server = Server('http://localhost:5000/taxii2/', user='admin', password='Password0')

col = {}

for api_roots in server.api_roots:
    api_root = api_roots.collections
    try:
        for collections in api_roots.collections:
            col[collections.id] = collections 

    except:
        print('')
        continue

collection3 = col['91a7b528-80eb-42ed-a74d-c6fbd5a26116']

x = collection3.add_objects("{\"objects\":[{\"type\":\"attack-pattern\",\"spec_version\":\"2.1\",\"id\":\"attack-pattern--6b948b5a-3c09-5365-b48a-da95c3964cb5\",\"created_by_ref\":\"identity--d2916708-57b9-5636-8689-62f049e9f727\",\"created\":\"2020-01-01T11:21:07.478851Z\",\"modified\":\"2020-01-01T11:21:07.478851Z\",\"name\":\"Spear Phishing\",\"description\":\"Used for tutorial content\",\"object_marking_refs\":[\"marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da\"]},{\"type\":\"attack-pattern\",\"spec_version\":\"2.1\",\"id\":\"attack-pattern--6b948b5a-3c09-5365-b48a-da95c3964cb5\",\"created_by_ref\":\"identity--d2916708-57b9-5636-8689-62f049e9f727\",\"created\":\"2020-01-01T11:21:07.478851Z\",\"modified\":\"2020-01-02T11:21:07.478851Z\",\"name\":\"Spear Phishing Updated ONCE\",\"description\":\"Used for tutorial content\",\"object_marking_refs\":[\"marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da\"]},{\"type\":\"attack-pattern\",\"spec_version\":\"2.1\",\"id\":\"attack-pattern--6b948b5a-3c09-5365-b48a-da95c3964cb5\",\"created_by_ref\":\"identity--d2916708-57b9-5636-8689-62f049e9f727\",\"created\":\"2020-01-01T11:21:07.478851Z\",\"modified\":\"2020-01-03T11:21:07.478851Z\",\"name\":\"Spear Phishing Updated TWICE\",\"description\":\"Used for tutorial content\",\"object_marking_refs\":[\"marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da\"]}]}")

print('status: ', x.status)
print('id: ', x.id)
print('failure_count: ', x.failure_count)
print('pending_count: ', x.pending_count)
print('success_count: ', x.success_count)
python3 add_objects.py
status:  complete
id:  2571f72a-520a-485c-8239-c64ef24cc4c4
failure_count:  0
pending_count:  0
success_count:  3

Now, if I just request the object via the Attack Pattern object we’ll get one result…

## python3 get_objects_filtered_2.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.Server
from taxii2client.v21 import Server
import json

server = Server('http://localhost:5000/taxii2/', user='admin', password='Password0')

col = {}

for api_roots in server.api_roots:
    try:
        for collections in api_roots.collections:
            col[collections.id] = collections 
    except:
        print('')
        continue

collection3 = col['91a7b528-80eb-42ed-a74d-c6fbd5a26116']

# Retrieve a specific object by ID
response = collection3.get_object(obj_id='attack-pattern--6b948b5a-3c09-5365-b48a-da95c3964cb5')

# Parse the response body as JSON
stix_object = json.loads(response.text)

# Print the STIX object
print(json.dumps(stix_object, indent=4))
python3 get_objects_filtered_2.py
{
    "more": false,
    "objects": [
        {
            "created": "2020-01-01T11:21:07.478851Z",
            "created_by_ref": "identity--d2916708-57b9-5636-8689-62f049e9f727",
            "description": "Used for tutorial content",
            "id": "attack-pattern--6b948b5a-3c09-5365-b48a-da95c3964cb5",
            "modified": "2020-01-03T11:21:07.478851Z",
            "name": "Spear Phishing Updated TWICE",
            "object_marking_refs": [
                "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da"
            ],
            "spec_version": "2.1",
            "type": "attack-pattern"
        }
    ]
}

So now I need to find the object versions;

## python3 get_object_versions.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.Server

from taxii2client.v21 import Server, as_pages
import json

server = Server('http://localhost:5000/taxii2/', user='admin', password='Password0')

col = {}

try:
    for api_roots in server.api_roots:
        for collection in api_roots.collections:
            col[collection.id] = collection
except Exception as e:
    print('Error while retrieving collections:', e)
    exit()

collection_id = '91a7b528-80eb-42ed-a74d-c6fbd5a26116'
obj_id = 'attack-pattern--6b948b5a-3c09-5365-b48a-da95c3964cb5'

if collection_id in col:
    collection3 = col[collection_id]

    page_no = 1
    try:
        for envelope in as_pages(collection3.object_versions, obj_id=obj_id, per_request=1000):
            print(f'\nPage # {page_no}')

            # Parse the envelope as JSON
            versions = json.loads(envelope.text)

            # Pretty-print the STIX object versions
            print(json.dumps(versions, indent=4))

            page_no += 1
    except Exception as e:
        print('Error retrieving or processing object versions:', e)
else:
    print(f'Collection with ID {collection_id} not found.')
python3 get_object_versions.py

Page # 1
{
    "more": true,
    "next": "64818bce-04e4-461d-876a-729ad5ab6229",
    "versions": [
        "2020-01-02T11:21:07.478851Z",
        "2020-01-01T11:21:07.478851Z"
    ]
}
[taxii2client.v21] [WARNING ] [2023-12-21 12:12:06,540] TAXII Server Response with different amount of objects! Setting limit=2

Page # 2
{
    "more": false,
    "versions": [
        "2020-01-03T11:21:07.478851Z"
    ]
}

Now I can use multiple filters, to include version, to get a specific version of the object.

## python3 get_specific_object_version.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.Server
from taxii2client.v21 import Server
import json

server = Server('http://localhost:5000/taxii2/', user='admin', password='Password0')

col = {}

for api_roots in server.api_roots:
    api_root = api_roots.collections
    try:
        for collections in api_roots.collections:
            col[collections.id] = collections 

    except:
        print('')
        continue

collection3 =  col['91a7b528-80eb-42ed-a74d-c6fbd5a26116'] 

def filter(object_id, object_version, collection=collection3):
    x =  collection.get_object(obj_id=object_id, modified=object_version)
    return x

get_version = filter('attack-pattern--6b948b5a-3c09-5365-b48a-da95c3964cb5','2020-01-01T11:21:07.478851Z' )

# Parse the response body as JSON
stix_object = json.loads(get_version.text)

# Print the STIX object
print(json.dumps(stix_object, indent=4))
python3 get_specific_object_version.py
{
    "more": false,
    "objects": [
        {
            "created": "2020-01-01T11:21:07.478851Z",
            "created_by_ref": "identity--d2916708-57b9-5636-8689-62f049e9f727",
            "description": "Used for tutorial content",
            "id": "attack-pattern--6b948b5a-3c09-5365-b48a-da95c3964cb5",
            "modified": "2020-01-01T11:21:07.478851Z",
            "name": "Spear Phishing",
            "object_marking_refs": [
                "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da"
            ],
            "spec_version": "2.1",
            "type": "attack-pattern"
        }
    ]
}

Finally, delete operations are also covered by cti-taxii-client.

## python3 delete_object.py
### import requirements https://taxii2client.readthedocs.io/en/latest/api/taxii2client.v21.html#taxii2client.v21.Server
from taxii2client.v21 import Server 

server = Server('http://localhost:5000/taxii2/', user='admin', password='Password0')

col = {}

for api_roots in server.api_roots:
    api_root = api_roots.collections
    try:
        for collections in api_roots.collections:
            col[collections.id] = collections 

    except:
        print('')
        continue

collection3 =  col['91a7b528-80eb-42ed-a74d-c6fbd5a26116']

collection3.delete_object(obj_id='attack-pattern--6b948b5a-3c09-5365-b48a-da95c3964cb5')
print('Successfully deleted')

Which prints;

Successfully deleted

Remember, the delete behaviour when used like this only deletes the latest version of the object. I can see this by rerunning;

python3 get_object_versions.py
Page # 1
{
    "more": false,
    "versions": [
        "2020-01-02T11:21:07.478851Z",
        "2020-01-01T11:21:07.478851Z"
    ]
}

Hopefully some of these demo scripts have given you a brief overview of what is possible using cti-taxii-client. They are not designed to be perfect, and the scripts are far from production. Nor have I covered all its features.

As cti-taxii-client is a minimal implementation, there are some functions missing. That said, it is still a great starting point to build off or to use for testing the responses from a TAXII Server.

Other TAXII 2.1 Clients

There is another other open-source option out there I’ve found, but never used, CyTAXII2.

CYTAXII2 is an Open Source offering from Cyware that provides developers with the support for interacting with the TAXII server using a Python library. It implements all TAXII services according to TAXII 2.X specifications.

If you know of any others, please do share them with me and I can include them in this post.