-
Notifications
You must be signed in to change notification settings - Fork 3
Storm ingest snippets for open source threat feeds #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
/* | ||
Name: cloudflare-domain-rank-top.storm | ||
Author: bartosz.roszewski@smartcontract.com | ||
Author: exc3l_one@protonmail.com | ||
Modified By: [email protected], [email protected] | ||
Last Modified: 2023-11-15 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
/* | ||
Name: cloudflare-domain-rank.storm | ||
Author: bartosz.roszewski@smartcontract.com | ||
Author: exc3l_one@protonmail.com | ||
Modified By: [email protected], [email protected] | ||
Last Modified: 2023-11-15 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
/* | ||
Name: crowdstrike-falcon-actors.storm | ||
Author: bartosz.roszewski@smartcontract.com | ||
Author: exc3l_one@protonmail.com | ||
Modified By: [email protected], [email protected] | ||
Last Modified: 2023-11-15 | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,70 @@ | ||||||||||||||||
/* | ||||||||||||||||
Name: loobins-project-ingest.storm | ||||||||||||||||
Author: [email protected] | ||||||||||||||||
Modified By: TODO | ||||||||||||||||
Last Modified: 2024-06-28 | ||||||||||||||||
|
||||||||||||||||
Description: Ingest the JSON representation of the LOOBins project to model the ou:techniques associated with built-in macOS binaries | ||||||||||||||||
|
||||||||||||||||
References: | ||||||||||||||||
-- LOOBins Project Website: https://loobins.io/ | ||||||||||||||||
*/ | ||||||||||||||||
|
||||||||||||||||
// Generate the LOOBins project ou:org node | ||||||||||||||||
init { | ||||||||||||||||
{[ou:org=$lib.gen.orgByFqdn("loobins.io") | ||||||||||||||||
:name="loobins" | ||||||||||||||||
:url="https://www.loobins.io/" | ||||||||||||||||
:desc="Living Off the Orchard: macOS Binaries (LOOBins) is designed to provide detailed information on various built-in macOS binaries and how they can be used by threat actors for malicious purposes." | ||||||||||||||||
]} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
// Get the LOOBins project data in JSON | ||||||||||||||||
$url = "https://www.loobins.io/loobins.json" | ||||||||||||||||
$resp = $lib.inet.http.get($url) | ||||||||||||||||
|
||||||||||||||||
if ($resp.code = 200) { | ||||||||||||||||
$body = $lib.json.load($body) | ||||||||||||||||
|
||||||||||||||||
for $bin in $body { | ||||||||||||||||
Comment on lines
+27
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
// Create a software node for each LOOBin | ||||||||||||||||
[it:prod:soft=$lib.gen.softByName($bin.name) | ||||||||||||||||
:desc:short=$bin.short_description | ||||||||||||||||
:desc=$bin.full_description | ||||||||||||||||
:type=macos.loobin | ||||||||||||||||
:islib=false | ||||||||||||||||
:isos=false | ||||||||||||||||
] | ||||||||||||||||
|
||||||||||||||||
// Link the software node to the filepaths it uses | ||||||||||||||||
for $path in $bin.paths { | ||||||||||||||||
[+(uses)> {[file:path=$path]}] | ||||||||||||||||
} | ||||||||||||||||
Comment on lines
+40
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This loop will cause the
Suggested change
|
||||||||||||||||
|
||||||||||||||||
// Create technique nodes | ||||||||||||||||
for $ttp in $bin.example_use_cases { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what the intended output of the macro is, but should probably move this loop into a subquery to avoid duplicate |
||||||||||||||||
[+(uses)> {[ou:technique=("loobins", $ttp.code) | ||||||||||||||||
:desc=$ttp.description | ||||||||||||||||
:name=$ttp.name | ||||||||||||||||
:reporter=$lib.gen.orgByFqdn("loobins.io") | ||||||||||||||||
:reporter:name="loobins.io" | ||||||||||||||||
:type=macos.loobins | ||||||||||||||||
] | ||||||||||||||||
|
||||||||||||||||
// Link the technique to the command it uses by creating a it:proc:exec node to represent the command and the file:base | ||||||||||||||||
[+(refs)> { | ||||||||||||||||
[it:exec:proc=($ttp.code, "loobins") | ||||||||||||||||
:cmd=$ttp.code | ||||||||||||||||
:path:base=$bin.name | ||||||||||||||||
:name=$bin.name | ||||||||||||||||
] | ||||||||||||||||
}]}] | ||||||||||||||||
|
||||||||||||||||
//Alternative method: linking the technique to the command via a light edge (choose either this one or the one above, not both at the same time) | ||||||||||||||||
//[+(refs)> {[it:cmd=$ttp.code]}]}] | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
} | ||||||||||||||||
} else { | ||||||||||||||||
$lib.warn("Returned HTTP code: {code}", code=$resp.code) | ||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
Name: c2-tracker-feed-ingest.storm | ||
Author: [email protected] | ||
Modified By: TODO | ||
Last Modified: 2024-06-28 | ||
|
||
Description: Ingest the TXT feeds from the C2-Tracker Github project by montysecurity | ||
|
||
References: | ||
-- Project Repo: https://github.com/montysecurity/C2-Tracker | ||
*/ | ||
|
||
|
||
$url = "https://github.com/montysecurity/C2-Tracker/tree/main/data" | ||
$resp = $lib.inet.http.get($url) | ||
|
||
if ($resp.code = 200 ) { | ||
$data = $resp.body.decode() | ||
// Extract the JSON representation of the Github repo tree from the HTML page | ||
$data = $lib.regex.search('(<script type="application\/json" data-target="react-app\.embeddedData">)(.+)(<\/script>)', $data) | ||
$data = $lib.json.load($data.1) | ||
|
||
// Iterate over every family tracked by the C2-Tracker project | ||
for $family_feed in $data.payload.tree.items { | ||
|
||
if ($family_feed.name = "all.txt") { continue } | ||
|
||
$lib.print(`Fetching data for family: {$family_feed.name}`) | ||
|
||
// Fetch the raw file containing the IPs for the family | ||
$c2_feed = $lib.inet.http.get(`https://raw.githubusercontent.com/montysecurity/C2-Tracker/main/{$family_feed.path}`) | ||
|
||
if ($c2_feed.code = 200) { | ||
// Split the feed by newline | ||
$c2_feed = $c2_feed.body.decode().split("\n") | ||
|
||
// Create new inet:ipv4 nodes for each IP in the feed | ||
for $c2_ip in $c2_feed { | ||
($ok, $ip_val) = $lib.trycast(inet:ipv4, $c2_ip) | ||
if ($ok) { | ||
[inet:ipv4=$c2_ip] | ||
|
||
// Extract the family name + type from the filename and add a timestamp to the rep node | ||
$tag = $family_feed.name.replace(" IPs.txt", "").replace(" C4 ", " C2 ").split(" ") | ||
|
||
// The following if/elif section covers various lengths of the family naming structure, prone to breaking but more granular | ||
if ($lib.len($tag) = 2) { | ||
$tag = $lib.str.concat($tag.1, ".", $tag.0) | ||
[+#rep.c2_tracker.$tag=($lib.time.now())] | ||
} elif ($lib.len($tag) = 1) { | ||
if ($tag ~= "RAT") {$tag = $lib.str.concat("rat", ".", $tag.0)} | ||
if ($tag ~= "C2") {$tag = $lib.str.concat("c2", ".", $tag.0)} | ||
[+#rep.c2_tracker.$tag=($lib.time.now())] | ||
} elif ($lib.len($tag) = 3) { | ||
$tag = $lib.str.concat($tag.2, ".", $tag.0, "_", $tag.1) | ||
[+#rep.c2_tracker.$tag=($lib.time.now())] | ||
} elif ($lib.len($tag) = 4) { | ||
$tag = $lib.str.concat($tag.3, ".", $tag.0, "_", $tag.1, "_", $tag.2) | ||
[+#rep.c2_tracker.$tag=($lib.time.now())] | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
// Do not yield any nodes because it's a lot of data | ||
| spin |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* | ||
Name: drb-feed-ingest.storm | ||
Author: [email protected] | ||
Modified By: TODO | ||
Last Modified: 2024-06-28 | ||
|
||
Description: Ingest the CSV feeds from the C2IntelFeeds Github project by drb-ra. | ||
|
||
References: | ||
-- Project Repo: https://github.com/drb-ra/C2IntelFeeds | ||
*/ | ||
|
||
function ingestC2URLs() { | ||
$url = "https://github.com/drb-ra/C2IntelFeeds/raw/master/feeds/domainC2swithURLwithIP-30day-filter-abused.csv" | ||
$resp = $lib.inet.http.get($url) | ||
|
||
if ($resp.code = 200) { | ||
$body = $resp.body.decode() | ||
$body = $body.split("\n") | ||
for $line in $body { | ||
// Remove comment and empty lines | ||
if (not $line^="#" and $line != "") { | ||
$line = $line.split(",") | ||
|
||
// Create the domain and inet:dns:a nodes | ||
[inet:fqdn=$line.0] | ||
if ($line.3~="\d\.") { | ||
[inet:dns:a=($line.0, $line.3)] | ||
[inet:ipv4=$line.3] | ||
} | ||
//Create the inet:url nodes | ||
$url = $lib.str.concat("https://", $line.0, $line.2) | ||
[inet:url=$url] | ||
|
||
//Since the tags are written in prose, we manually extract them from the CSV with keywords | ||
//The feed persists for 30 days, so we add the timestamp to the tags to reflect when it appears/dissapears from the threat feed | ||
//Remove the timestamp if this is not accurate enough for your use case | ||
if ($line.1 ~= "Cobalt") {[+#rep.c2feeds.mal.cobaltstrike=($lib.time.now())]} | ||
if ($line.1 ~= "PoshC2") {[+#rep.c2feeds.mal.poshc2=($lib.time.now())]} | ||
if ($line.1 ~= "Empire") {[+#rep.c2feeds.mal.empire=($lib.time.now())]} | ||
if ($line.1 ~= "Sliver") {[+#rep.c2feeds.mal.sliver=($lib.time.now())]} | ||
if ($line.1 ~= "Deimos") {[+#rep.c2feeds.mal.deimos=($lib.time.now())]} | ||
if ($line.1 ~= "Havoc") {[+#rep.c2feeds.mal.havoc=($lib.time.now())]} | ||
if ($line.1 ~= "Covenant") {[+#rep.c2feeds.mal.covenant=($lib.time.now())]} | ||
if ($line.1 ~= "Front") {[+#rep.c2feeds.ttp.fronting_domain=($lib.time.now())]} | ||
|
||
} | ||
} | ||
} else { | ||
$lib.warn("Returned HTTP code: {code}", code=$resp.code) | ||
} | ||
} | ||
|
||
function ingestC2IPs() { | ||
$url = "https://raw.githubusercontent.com/drb-ra/C2IntelFeeds/master/feeds/IPC2s.csv" | ||
$resp = $lib.inet.http.get($url) | ||
|
||
if ($resp.code = 200) { | ||
$body = $resp.body.decode() | ||
$body = $body.split("\n") | ||
for $line in $body { | ||
// Remove comment and empty lines | ||
if (not $line^="#" and $line != "") { | ||
$line = $line.split(",") | ||
if ($line.0~="\d\.") { | ||
[inet:ipv4=$line.0] | ||
} | ||
|
||
if ($line.1 ~= "Cobalt") {[+#rep.c2feeds.mal.cobaltstrike.susp=($lib.time.now())]} | ||
if ($line.1 ~= "PoshC2") {[+#rep.c2feeds.mal.poshc2.susp=($lib.time.now())]} | ||
if ($line.1 ~= "Empire") {[+#rep.c2feeds.mal.empire.susp=($lib.time.now())]} | ||
if ($line.1 ~= "Sliver") {[+#rep.c2feeds.mal.sliver.susp=($lib.time.now())]} | ||
if ($line.1 ~= "Deimos") {[+#rep.c2feeds.mal.deimos.susp=($lib.time.now())]} | ||
if ($line.1 ~= "Havoc") {[+#rep.c2feeds.mal.havoc.susp=($lib.time.now())]} | ||
if ($line.1 ~= "Covenant") {[+#rep.c2feeds.mal.covenant.susp=($lib.time.now())]} | ||
|
||
} | ||
} | ||
} else { | ||
$lib.warn("Returned HTTP code: {code}", code=$resp.code) | ||
} | ||
} | ||
|
||
$ingestC2IPs() | ||
$ingestC2URLs() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
Name: phishunt-feed-ingest.storm | ||
Author: [email protected] | ||
Modified By: TODO | ||
Last Modified: 2024-06-28 | ||
|
||
Description: Ingest the daily Phishing feed from Phishunt.io project. Recommend to ingest this as a daily crontab job. | ||
|
||
References: | ||
-- Phishunt Project Website: https://phishunt.io/ | ||
*/ | ||
|
||
$url = "https://phishunt.io/feed.txt" | ||
$resp = $lib.inet.http.get($url) | ||
|
||
if ($resp.code = 200) { | ||
$body = $resp.body.decode() | ||
$body = $body.split("\n") | ||
for $url in $body { | ||
// Create the URL and rep tag | ||
[inet:url=$url] [+#rep.phishunt.phishing=($lib.time.now())] | ||
|
||
//Uncomment the following line to also add a more genering cno.infra tag | ||
//[+#cno.infra.phishing=($lib.time.now())] | ||
} | ||
} else { | ||
$lib.warn("Returned HTTP code: {code}", code=$resp.code) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
Name: ransomwhere-addresses-ingest.storm | ||
Author: [email protected] | ||
Modified By: TODO | ||
Last Modified: 2024-06-28 | ||
|
||
Description: Ingests cryptocurrency addresses related to ransomwhere payments from the RansomWhere project API | ||
|
||
References: | ||
-- RansomWhere Project Website: https://ransomwhe.re/#browse | ||
*/ | ||
|
||
// Fetch the ransomwhe.re data | ||
$resp = $lib.inet.http.get("https://api.ransomwhe.re/export") | ||
|
||
if ($resp.code = 200) { | ||
$data = $lib.json.load($resp.body) | ||
|
||
// Iterate over the addresses and ingest them into the platform - supports | ||
for $address in $data.result { | ||
|
||
// At the moment ransomwhere only supports bitcoin addresses, so other chains are for future proofing only | ||
switch $address.blockchain { | ||
"bitcoin": {[crypto:currency:address=(btc, $address.address)]}, | ||
"ethereum": {[crypto:currency:address=(eth, $address.address)]}, | ||
"monero": {[crypto:currency:address=(xmr, $address.address)]}, | ||
"ripple": {[crypto:currency:address=(xrp, $address.address)]}, | ||
"zcash": {[crypto:currency:address=(zec, $address.address)]}, | ||
"dogecoin": {[crypto:currency:address=(doge, $address.address)]} //because why not | ||
} | ||
|
||
// Cleanup the tags to make them more Synapse friendly, apply to the crypto:currency:address node | ||
$tag = $address.family.replace("(", "").replace(")", "").replace(" / ", "_").replace(" ", "_").replace("/", "_") | ||
[+#rep.ransomwhe_re.$tag] | ||
|
||
} | ||
} else { | ||
$lib.warn("Received non-200 HTTP code: {code}", code=$resp.code) | ||
} | ||
// Don't return the addresses since it's a lot of data | ||
| spin |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
Name: wiz-cloud-threat-landscape.storm | ||
Author: [email protected] | ||
Modified By: TODO | ||
Last Modified: 2024-06-28 | ||
|
||
Description: Ingest the STIX2.1 data feed from Wiz.io's Cloud Threat Landscape feed. This feed contains information about threat actors, campaigns, malware, and attack patterns affecting the cloud. | ||
|
||
References: | ||
-- STIX2.1 Feed: https://www.wiz.io/feed/cloud-threats-landscape/stix.json | ||
*/ | ||
|
||
// Pull the STIX2.1 data | ||
$url = "https://www.wiz.io/feed/cloud-threats-landscape/stix.json" | ||
$resp = $lib.inet.http.get($url) | ||
|
||
if ($resp.code = 200) { | ||
|
||
$data = $lib.json.load($resp.body) | ||
|
||
// Define the STIX2.1 object and relationship mappings | ||
$config = ({ | ||
"objects": { | ||
"tool": { | ||
"storm": "($ok, $name) = $lib.trycast(it:prod:softname, $object.name) if $ok { it:prod:softname=$name -> it:prod:soft return($node) [ it:prod:soft=('wiz.io', $name) :name=$name ] return($node) }" | ||
}, | ||
"threat-actor": { | ||
"storm": "[ risk:threat=$lib.gen.riskThreat($object.name, 'wiz.io') :tag=`rep.wiz_io.{$object.name}` ] $node.data.set(stix:object, $object) return($node)" | ||
}, | ||
"campaign": { | ||
"storm": "[ ou:campaign=(stix, campaign, $object.id) :name?=$object.name :desc?=$object.description :goal={[ou:goal=('wiz.io', $object.objective) :name=$object.objective]} .seen?=$object.last_seen .seen?=$object.first_seen ] [<(refs)+ {[media:news=$lib.gen.newsByUrl($object.external_reference.url, true)]}] $node.data.set(stix:object, $object) return($node)" | ||
}, | ||
"malware": { | ||
"storm": "[ risk:tool:software=$lib.gen.riskToolSoftware($object.name, 'wiz.io')] return($node)" | ||
}, | ||
"attack-pattern": { | ||
"storm": "[ ou:technique=($object.name, 'wiz.io') :name=$object.name ] return($node)" | ||
} | ||
}, | ||
"relationships": [{ | ||
"type": ["campaign", "attributed-to", "threat-actor"], | ||
// Link the ou:campaign to the risk:threat node and add the threat actor's tag to the ou:campaign node to support the Vertex-Triage Power-Up pivoting | ||
"storm": "yield $n1node [<(refs)+ {yield $n2node}] | $tag = `rep.wiz_io.{$node.pack().1.props.'org:name'}` [+#$tag ]" | ||
}, { | ||
"type": ["threat-actor", "uses", "tool"], | ||
"storm": "yield $n1node [ +(uses)> { yield $n2node } ]" | ||
}, { | ||
"type": ["threat-actor", "uses", "malware"], | ||
"storm": "yield $n1node [ +(uses)> { yield $n2node } ]" | ||
}, { | ||
"type": ["campaign", "uses", "malware"], | ||
"storm": "yield $n1node [ +(uses)> { yield $n2node } ]" | ||
}, { | ||
"type": ["campaign", "uses", "tool"], | ||
"storm": "yield $n1node [ +(uses)> { yield $n2node } ]" | ||
}, { | ||
"type": ["campaign", "uses", "attack-pattern"], | ||
"storm": "yield $n1node [ +(uses)> { yield $n2node } ]" | ||
}] | ||
}) | ||
|
||
// Ingest the STIX2.1 data | ||
$lib.stix.import.ingest($data, $config) | ||
|
||
} else { | ||
$lib.warn("Received non-200 HTTP response: {code}", code = $resp.code) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This subquery won't actually execute since there are no nodes in the pipeline here