Detection people write rules. Intel people write reports.
STIX Patterns were created as a way for intel teams to include detection logic in their work (or put another way; make their work actionable). In theory it was the perfect solution, but in practice it didn’t really take off. The reason being; most SIEMs/EDRs don’t understand STIX Patterns natively.
STIX Shifter addresses this problem and more offering the ability to:
- Translate STIX Patterns → native query languages (Splunk, Elastic, QRadar, etc.)
- Transmit/Execute queries on the target (optional)
- Results → STIX: convert matches into Observed Data (plus SCOs) for graph-friendly evidence
Here’s a nice presentation describing STIX Shifter;
This post will show you:
- how install STIX Shifter
- translate STIX patterns to vendor queries
- convert detections back into STIX Observed Data—while avoiding common mapping pitfalls.
STIX Shifter Connectors
STIX Shifter is based around the concept of Connectors.
A STIX Shifter connector is a module inside the STIX Shifter library that implements an interface for:
- data source query and result set translation
- data source communication
Each Connector supports a set of STIX objects and properties as defined in the connector’s mapping files.
There are about 30 Connectors that currently exist, detailed here.
Let me demonstrate this concept using some examples.
Installing STIX Shifter
STIX Shifter can be used as a command line utility or as a Python library.
To install STIX Shifter in both ways;
mkdir stix-shifter
python3 -m venv stix-shifter
source stix-shifter/bin/activate
pip3 install stix-shifter
pip3 install stix-shifter-utils
stix-shifter -h
To use a connector (to translate a STIX pattern), you must first install it. You can do this using pip as follows;
pip3 install stix-shifter-modules-<CONNECTOR NAME>
For example, to install the Splunk Connector to translate STIX queries into Splunk SPL queries;
pip3 install stix-shifter-modules-splunk
STIX Shifter core functions
STIX Shifter provides three core functions;
translate: The translate command converts STIX patterns into SIEM/EDR queries or translates data source results (in JSON format) into bundled STIX observation objects.transmit: The transmit command allows stix-shifter to connect with products that house repositories of cybersecurity data. Connection and authentication credentials are passed to the data source APIs where stix-shifter can make calls to ping the data source, make queries, delete queries, check query status, and fetch query results.execute: The translation and transmission functions can work in sequence by using the execute command from the CLI.
In this post we will only cover conversion using translate.
The translate works in two ways;
| Mode | Purpose |
|---|---|
query |
STIX Pattern → SIEM query |
results |
SIEM match → STIX Observed Data |
The translate command has a fixed positional argument structure:
stix-shifter translate <CONNECTOR> <MODE> <STIX IDENTITY> <PATTERN or RESULTS> <OPTIONS>
Translating STIX Patterns into SIEM and EDR queries
To convert the STIX Pattern [url:value = 'http://www.testaddress.com'] OR [ipv4-addr:value = '192.168.122.84'] using the Splunk Connector I can run:
stix-shifter translate splunk query "{}" "[url:value = 'http://www.testaddress.com'] OR [ipv4-addr:value = '192.168.122.84']"
Note, I pass an empty "{}" in the command above. You’re required to pass something in the STIX Identity parameter position, even if it isn’t used for translation. For simple query translation commands, STIX Shifter doesn’t use the identity—but the CLI still expects it as part of the API structure. So we pass {} as a dummy identity object just to satisfy the required argument.
The command prints the converted Splunk query in a JSON response;
{
"queries": [
"search (url = \"http://www.testaddress.com\") OR ((src_ip = \"192.168.122.84\") OR (dest_ip = \"192.168.122.84\")) earliest=\"-5minutes\" | head 10000 | fields src_ip, src_port, src_mac, src_ipv6, dest_ip, dest_port, dest_mac, dest_ipv6, file_hash, user, url, protocol, host, source, DeviceType, Direction, severity, EventID, EventName, ss_name, TacticId, Tactic, TechniqueId, Technique, process, process_id, process_name, process_exec, process_path, process_hash, parent_process, parent_process_id, parent_process_name, parent_process_exec, description, result, signature, signature_id, query, answer"
]
}
You will notice the Splunk search (nested in the queries field). The key part of the search is;
(url = \"http://www.testaddress.com\") OR ((src_ip = \"192.168.122.84\") OR (dest_ip = \"192.168.122.84\"))
STIX Shifter has converted the STIX fields url:value into url and ipv4-addr:value into both src_ip and dest_ip fields (as the STIX pattern could refer to either).
Splunk data (assumed to be in the Common Information Model (CIM) standard) to STIX mapping is defined in the Splunk modules to_stix_map.json file.
Lets try another conversion, this time using the Elastic ECS Connector on the same STIX Pattern;
pip3 install stix-shifter-modules-elastic_ecs
The Elastic Common Schema (ECS) is Elastics own standard, similar to the Splunk CIM.
stix-shifter translate elastic_ecs query "{}" "[url:value = 'http://www.testaddress.com'] OR [ipv4-addr:value = '192.168.122.84']"
{
"queries": [
"(url.original : \"http://www.testaddress.com\") OR ((source.ip : \"192.168.122.84\" OR destination.ip : \"192.168.122.84\" OR client.ip : \"192.168.122.84\" OR server.ip : \"192.168.122.84\" OR host.ip : \"192.168.122.84\" OR dns.resolved_ip : \"192.168.122.84\")) AND (@timestamp:[\"2022-08-23T06:28:44.754Z\" TO \"2022-08-23T06:33:44.754Z\"])"
]
}
You can see the STIX Pattern to Elastic ECS conversion logic here.
STIX Shifter has converted the STIX fields url:value into url.original and ipv4-addr:value into source.ip, server.ip, host.ip, and dns.resolved_ip.
Translating Detections into STIX Observed Data Graphs
As noted earlier, I won’t cover transmit in this write up, however, imagine my converted rules were sent down to Splunk to look for matching log lines.
I will show a simulated example of a match being detected and written into a STIX 2.1 Observed Data Object, similar to the flow I showed in this post.
Lets assume the downstream tool (Splunk) detects a match between a converted STIX Pattern ([ipv4-addr:value = '1.1.1.1'] -> src_ip=1.1.1.1 OR dest_ip=1.1.1.1) and a log line that contains src_ip=1.1.1.1. In addition to the matching field, the log line has the following fields (this is where the fields command in the the Splunk STIX-Shifter output is important) modelled in json;
[
{
"src_ip": "1.1.1.1",
"dest_ip": "2.2.2.2",
"url": "www.testaddress.com"
}
]
It is vital that the log fields match those defined in the STIX-Shifter Connector so that the can be mapped to the correct STIX Cyber Observable Object (e.g. IPv4 STIX Cyber Observable Object) during the translation. The STIX-Shifter Splunk Connector expects CIM compliant fields (src_ip, dest_ip and url are all CIM compliant).
This time the translate query takes a slightly different form to create STIX 2.1 Observed Data and Cyber Observable Objects from the detection (using result instead of query);
stix-shifter translate <MODULE NAME> results '<STIX IDENTITY OBJECT>' '<LIST OF JSON RESULTS>'
Unlike before using query, a STIX Identity Object is required to be used in the results command to attribute the Observed Data Objects to someone.
Which written out into an entire translate query gives;
python main.py translate splunk results \
'{"type":"identity","spec_version":"2.1","id":"identity--d2916708-57b9-5636-8689-62f049e9f727","created_by_ref":"identity--aae8eb2d-ea6c-56d6-a606-cc9f755e2dd3","created":"2020-01-01T00:00:00.000Z","modified":"2020-01-01T00:00:00.000Z","name":"dogesec-demo","description":"https://github.com/dogesec/","identity_class":"organization","sectors":["technology"],"contact_information":"https://www.dogesec.com/contact/","object_marking_refs":["marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9","marking-definition--3f588e96-e413-57b5-b735-f0ec6c3a8771"]}' \
'[{"src_ip":"1.1.1.1","dest_ip":"2.2.2.2","url":"www.testaddress.com"}]' \
'{"stix_2.1": true}'
By default, JSON results are translated into STIX 2.0. To return STIX 2.1 results include {"stix_2.1": true} in the options part (last part) of the CLI command.
This command prints a JSON bundle with a STIX 2.1 Observed Data Object (covering the entire log line representing the match), and four STIX 2.1 Cyber Observable Objects representing each field type in the log line, src_ip, dest_ip, and url.
Note, there are four results as the single url in my log (www.testaddress.com) is converted by the Splunk STIX-Shifter Connector into STIX 2.1 SCO types url and domain-name as a catchall approach to mapping.
Now let me highlight why the fields printed in the log data, must match those expected by the Connector.
This time I will use the Elastic ECS Connector on the same log line. Elastic ECS does not use the CIM field name standard used by Splunk. For example, as shown in the example translate conversion from STIX Pattern to Elastic ECS, IPs are captured in the field name source.ip (in Splunk the CIM compliant field is src_ip).
Demonstrating using the same command as I did for Splunk without changing the log properties but instead using the elastic_ecs connector:
python main.py translate elastic_ecs results \
'{"type":"identity","spec_version":"2.1","id":"identity--d2916708-57b9-5636-8689-62f049e9f727","created_by_ref":"identity--aae8eb2d-ea6c-56d6-a606-cc9f755e2dd3","created":"2020-01-01T00:00:00.000Z","modified":"2020-01-01T00:00:00.000Z","name":"dogesec-demo","description":"https://github.com/dogesec/","identity_class":"organization","sectors":["technology"],"contact_information":"https://www.dogesec.com/contact/","object_marking_refs":["marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9","marking-definition--3f588e96-e413-57b5-b735-f0ec6c3a8771"]}' \
'[{"src_ip":"1.1.1.1","dest_ip":"2.2.2.2","url":"www.testaddress.com"}]' \
'{"stix_2.1": true}'
See how an Observed Data Object is created, but STIX Shifter cannot convert any Cyber Observable Data Objects from the input because the field names in the log are not mapped in the Elastic ECS Connector configuration.
It is important to understand that when field mappings are incorrect, STIX Shifter can product inconsistent results. That is, if the log properties don’t match those expected by the connector it won’t always produce no SCOs.
Let me demonstrate using the QRadar Connector;
pip3 install stix-shifter-modules-qradar
python main.py translate qradar results \
'{"type":"identity","spec_version":"2.1","id":"identity--d2916708-57b9-5636-8689-62f049e9f727","created_by_ref":"identity--aae8eb2d-ea6c-56d6-a606-cc9f755e2dd3","created":"2020-01-01T00:00:00.000Z","modified":"2020-01-01T00:00:00.000Z","name":"dogesec-demo","description":"https://github.com/dogesec/","identity_class":"organization","sectors":["technology"],"contact_information":"https://www.dogesec.com/contact/","object_marking_refs":["marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9","marking-definition--3f588e96-e413-57b5-b735-f0ec6c3a8771"]}' \
'[{"src_ip":"1.1.1.1","dest_ip":"2.2.2.2","url":"www.testaddress.com"}]' \
'{"stix_2.1": true}'
QRadar does use the url field name, so this is mapped correctly to a STIX URL Cyber Observable Object (note, this is different behaviour to the Splunk Connector which creates a URL and Domain Observable for this record).
However, for the fields the connector does not recognise (src_ip and dest_ip) it creates a custom STIX 2.1 Cyber Observable Object ("type": "dogesec-demo"), which contains the properties "src_ip": "1.1.1.1" and "dest_ip": "2.2.2.2" for these unrecognised fields.
The point being here; be careful with field mappings, because if the Connector does not support the fields in the log, the results from STIX-Shifter can be unexpected.
Wrapping Up
STIX Shifter is the bridge between threat intelligence and detection engineering. It takes STIX patterns you can share and reason about, and turns them into queries that actually run in your SIEM or EDR. Just remember:
- Use
translate queryto convert STIX Patterns into vendor syntax - Use
translate resultsto bring detections back into STIX for graph analysis - Mappings matter — connect the right fields or you’ll get empty or broken SCOs
- Each connector behaves differently — test your mappings early
This isn’t just about conversion. It’s how you operationalise STIX across multiple platforms consistently. With STIX Shifter, you can build vendor-agnostic detection pipelines, reuse detection logic across ecosystems, and send detections right back into your graph/TIP for correlation and context.
SIEM Rules
Your detection engineering AI assistant. Turn cyber threat intelligence research into highly-tuned detection rules.
Discuss this post
Head on over to the dogesec community to discuss this post.
Never miss an update
Sign up to receive new articles in your inbox as they published.
