stix

Threat intelligence in STIX 2.1: read bundles, pull out indicators and their IOCs (IPs, domains, URLs, hashes, emails) from STIX patterns, turn them into detect rules you can run on logs, write your own IOCs as a bundle, and download from TAXII 2.1 servers.

Reading and writing bundles works everywhere, including the playground. The TAXII client (stix.taxii) talks to a server, so it needs the TB app on your computer.

import stix

Example

import stix

# Write IOCs as a STIX 2.1 bundle, then read the indicators back.
let bundle = stix.bundle(["203.0.113.42", "login-verify.example.net"])
for ind in stix.indicators(bundle) {
  say(f"{ind.pattern} -> {ind.iocs}")
}

# Indicators become detect rules you can run on logs.
let rules = stix.to_detect_rules(bundle)
say(
  f"{len(rules)} rules, the first looks at: {rules[0].detection.ioc.map(fn(m) { return m.keys()[0] }).slice(0, 3)}",
)
say(stix.pattern("[ipv4-addr:value IN ('192.0.2.1', '192.0.2.2')]"))

Functions

parse(text)

returns map

Read STIX JSON text into a bundle map {"type": "bundle", "id", "objects"}. A bare list of objects or a single object is wrapped in a bundle; so is a TAXII envelope ({"objects": [...]}).

Visualizehow stix.parse works

stix.parse("[{\"type\": \"ipv4-addr\", \"id\": \"ipv4-addr--1\", \"value\": \"203.0.113.9\"}]") gives {"type": "bundle", "objects": [{"type": "ipv4-addr", "id": "ipv4-addr--1", "value": "203.…

stix.parse: inputs flowing into the function and the resulttext[{"type": "ipv4-addr", "id":"ipv4-addr--1", "value":"203.0.113.9"}]stix.parseresult · always a bundle maptype"bundle"objects[list of 1]0{map}type"ipv4-addr"id"ipv4-addr--1"value"203.0.113.9"

Real result: worked out by running this example with TB while the site was built.

read(path)

returns map

Read a STIX JSON file into a bundle map (see stix.parse).

Visualizehow stix.read works

stix.read("apps/data/intel/sample-feed.stix.json").objects.map(o => o.type) gives ["identity", "indicator", "indicator", "indicator", "indicator", "indicator", "indicator"…

stix.read: inputs flowing into the function and the resultpathapps/data/intel/sample-feed.stix.jsonstix.readresult · a bundle; its objects' types shown"identity""indicator""indicator""indicator""indicator""indicator""indicator""ipv4-addr"

Real result: worked out by running this example with TB while the site was built.

write(path, bundle)

returns nil

Write a bundle (from stix.bundle() or stix.read()) as pretty JSON.

Visualizehow stix.write works

stix.write("feed-copy.json", feed) gives nil

stix.write: inputs flowing into the function and the resultbundlethe bundle map8 objectsstix.writepathfeed-copy.jsonresult · nil: pretty JSON is writtennil

Real result: worked out by running this example with TB while the site was built.

stringify(bundle)

returns text

The bundle as pretty-printed STIX JSON text.

Visualizehow stix.stringify works

stix.stringify(stix.bundle(["203.0.113.9"])) gives "{\n \"type\": \"bundle\",\n \"id\": \"bundle--b4378729-22bb-5bd3-ad6c-22d6b3f86f6e\",\…

stix.stringify: inputs flowing into the function and the resultbundlestix.bundle(["203.0.113.9"])stix.stringifyresult · pretty STIX JSON text{ "type": "bundle", "id": "bundle--b4378729-22bb-5bd3-ad6c-22d6b3f86f6e", "objects": [ { "type": "indicator", "spec_version": "2.1",… 18 more lines

Real result: worked out by running this example with TB while the site was built.

indicators(bundle, options?)

returns list

The indicator objects in a bundle, each as a map.

Fields of each indicator

  • "id", "name", "description" from the STIX object.
  • "pattern", "pattern_type", "indicator_types" from the STIX object.
  • "valid_from", "valid_until", "expired", "confidence" from the STIX object.
  • "tags" ATT&CK tags from the kill chain phases.
  • "iocs" a list of {"type", "value"} maps.
  • "skipped" comparisons that aren't exact (LIKE, MATCHES, !=).

IOCs come from pattern comparisons with = or IN. For example, [ipv4-addr:value = '198.51.100.7'] gives {"type": "ip", ...}.

IOC types

  • ip, domain, url, email, filename, cidr.
  • md5, sha1, sha256 (from file hashes).

Revoked indicators are left out. So are expired ones (valid_until in the past).

Options

  • expired true keeps the expired indicators.
Visualizehow stix.indicators works

stix.indicators(feed, {"active": true}) gives [{"id": "indicator--a1d3a1e2-0c1e-4f7d-8c2b-1b7b4b0e2f01", "name": "SSH password guessing…

stix.indicators: inputs flowing into the function and the resultbundlesample-feed.stix.json6 indicatorsoptions?activetruestix.indicatorsresult · indicators with their IOCsnamepatternconfidenceSSH password…[ipv4-addr:v…80Web vulnerab…[ipv4-addr:v…50Telnet scann…[ipv4-addr:v…40Phishing inf…[domain-name…nilSuspicious u…[network-tra…nil

Real result: worked out by running this example with TB while the site was built.

iocs(bundle)

returns map

Every IOC in a bundle (from indicator patterns and from cyber observables such as ipv4-addr objects), grouped like ioc.extract(): {"ips", "domains", "urls", "emails", "md5", "sha1", "sha256", "sha512", "cidrs", "filenames"}, each a list without repeats.

Visualizehow stix.iocs works

stix.iocs(feed) gives {"ips": ["203.0.113.42", "198.51.100.77", "198.51.100.23"], "domains": ["login-verify.exa…

stix.iocs: inputs flowing into the function and the resultbundlesample-feed.stix.jsonpatterns + observablesstix.iocsresult · IOCs grouped like ioc.extract()ips["203.0.113.42", "198.51.100.77", "198.…domains["login-verify.example.net"]urls["http://login-verify.example.net/rese…emails[]md5[]sha1[]sha256["9f86d081884c7d659a2feaa0c55ad015a3…sha512[]

Real result: worked out by running this example with TB while the site was built.

pattern(text)

returns list

The IOCs in one STIX pattern: stix.pattern("[ipv4-addr:value = '203.0.113.9' OR domain-name:value = 'bad.example']") gives [{"type": "ip", "value": "203.0.113.9", "object", "property"}, ...].

Visualizehow stix.pattern works

stix.pattern("[ipv4-addr:value = '203.0.113.9' OR domain-name:value = 'bad.example']") gives [{"type": "ip", "value": "203.0.113.9", "object": "ipv4-addr", "property": "value"}, {"ty…

stix.pattern: inputs flowing into the function and the resulttext[ipv4-addr:value = '203.0.113.9' ORdomain-name:value = 'bad.example']stix.patternresult · one map per IOC compared with = or INtypevalueobjectip203.0.113.9ipv4-addrdomainbad.exampledomain-name

Real result: worked out by running this example with TB while the site was built.

to_detect_rules(bundle, options?)

returns list

Detect rules (for detect.run()) from a bundle's indicators. By default there is one rule per indicator.

A rule alerts when a record field holds one of its IOCs:

  • ip, src_ip or dst_ip for IPs (CIDR ranges use |cidr).
  • domain, host or query for domains.
  • url, hash, email or file name fields for those types.

The level comes from the indicator's confidence (high when there is none). ATT&CK kill chain phases become tags, so attack.map() works on the alerts.

Options

  • level a fixed level for every rule, like "critical".
  • group "type" makes one rule per IOC type instead of per indicator. This is better for big feeds.
  • fields which record fields to check for a type, like {"ip": ["client"]}.
  • expired true keeps expired indicators.
Visualizehow stix.to_detect_rules works

stix.to_detect_rules(feed, {"level": "high"}) gives [{"name": "Threat intel: SSH password guessing source", "level": "high", "detection": {"i…

stix.to_detect_rules: inputs flowing into the function and the resultbundlesample-feed.stix.json6 indicatorsoptions?level"high"stix.to_detect_rulesresult · rules ready for detect.run()namelevelThreat intel: SSH pa…highThreat intel: Web vu…highThreat intel: Telnet…highThreat intel: Phishi…high

Real result: worked out by running this example with TB while the site was built.

bundle(iocs, options?)

returns map

Write IOCs as a STIX 2.1 bundle of indicators. Ids are stable: the same IOC always gets the same indicator id.

iocs can be:

  • a list of text. The kind is worked out: IP, domain, URL, email, hash, CIDR.
  • a list of {"type", "value", "name"?, "description"?} maps.
  • the map from ioc.extract().

Options

  • indicator_types the STIX indicator types (default: ["malicious-activity"]).
  • description text for every indicator.
  • confidence 0-100.
  • valid_until when the indicators expire.
  • time the created / valid_from timestamp (default: now).
  • tags attack.<tactic> tags become kill chain phases.

Example

let b = stix.bundle(["198.51.100.7", "bad.example"])
say(len(b.objects))
Visualizehow stix.bundle works

stix.bundle(["203.0.113.9", "bad.example", "http://bad.example/x"], {"confidence": 80}).objects.map(o => o.pattern) gives ["[ipv4-addr:value = '203.0.113.9']", "[domain-name:value = 'bad.example']", "[url:value …

stix.bundle: inputs flowing into the function and the resultiocs"203.0.113.9""bad.example""http://bad.example/x"options?confidence80stix.bundleresult · a STIX 2.1 bundle (each indicator's pattern shown)"[ipv4-addr:value = '…"[domain-name:value =…"[url:value = 'http:/…

Real result: worked out by running this example with TB while the site was built.

taxii(url, auth?, options?)

returns client

A TAXII 2.1 client. url is the server's discovery address (like https://intel.example/taxii2/) or an API root.

auth can be:

  • nil no login.
  • text a bearer token.
  • {"user", "password"}, {"token"} or {"header", "key"}.

The auth is never printed.

Methods

  • discover() gives back a map about the server.
  • collections() a list of {"id", "title", "description", "can_read", "can_write"}.
  • objects(collection_id, added_after?) a bundle. It follows the server's paging, up to max_pages pages.

Options

  • timeout seconds to wait.
  • max_pages the most pages objects() reads (default: 20).
Visualizehow stix.taxii works

stix.taxii("https://intel.example/taxii2/", {"token": "…"}).collections() gives [{"id": "91a7b528", "title": "Phishing feed", "can_read": true}]

stix.taxii: your computer talking to another oneyour computerTAXII serverhttps://intel.exampl…sends: GET collectionsgets back: STIX objectsauth?token"…"options?timeout20client.collections() gives backidtitlecan_read91a7b528Phishing feedtrue

Illustration: example values, drawn from what the function does.