Skip to content

Commit

Permalink
Merge pull request #1188 from nicoctn/falcon_responder_v2
Browse files Browse the repository at this point in the history
FalconCrowstrikeCustomIOC Responder v2
  • Loading branch information
nusantara-self authored Oct 16, 2024
2 parents 760a032 + 7c01494 commit db0361b
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 3 deletions.
4 changes: 2 additions & 2 deletions responders/FalconCustomIOC/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM python:2
FROM python:3

WORKDIR /worker
COPY . FalconCustomIOC
RUN pip install --no-cache-dir -r FalconCustomIOC/requirements.txt
ENTRYPOINT FalconCustomIOC/FalconCustomIOC.py
ENTRYPOINT FalconCustomIOC/FalconCustomIOCv2.py
90 changes: 90 additions & 0 deletions responders/FalconCustomIOC/FalconCustomIOCv2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"name": "Crowdstrike_Falcon_Custom_IOC",
"version": "2.0",
"author": "Nicolas Criton",
"url": "https://www.crowdstrike.com/blog/tech-center/consume-ioc-and-threat-feeds/",
"license": "AGPL-v3",
"description": "Submit observables to the Crowdstrike Falcon Custom IOC API",
"dataTypeList": ["thehive:alert","thehive:case_artifact"],
"command": "FalconCustomIOC/FalconCustomIOCv2.py",
"baseConfig": "FalconCustomIOCv2",
"configurationItems": [
{
"name": "falconapi_endpoint",
"description": "CrowdStrike API endpoints: US-1 | US-2 | US-GOV-1 | EU-1",
"type": "string",
"multi": false,
"required": true
},
{
"name": "falconapi_clientid",
"description": "Crowdstrike Falcon Client ID Oauth2 API client",
"type": "string",
"multi": false,
"required": true
},
{
"name": "falconapi_key",
"description": "Crowdstrike Falcon Oauth2 API Key",
"type": "string",
"multi": false,
"required": true
},
{
"name": "domain_block_expiration_days",
"description": "How many days should we block the domain IOCs sent? Default: 30",
"type": "number",
"multi": false,
"required": false,
"defaultValue": 30
},
{
"name": "ip_block_expiration_days",
"description": "How many days should we block the ip IOCs sent? Default: 30",
"type": "number",
"multi": false,
"required": false,
"defaultValue": 30
},
{
"name": "hash_block_expiration_days",
"description": "How many days should we block the hash IOCs sent? Default: 30",
"type": "number",
"multi": false,
"required": false,
"defaultValue": 30
},
{
"name": "action_to_take",
"description": "How the IOCs should be handled by Falcon ? Choose between 'no_action' or 'detect' -> no_action: Save the indicator for future use, but take no action / detect: Enable detections for the indicator at the selected severity (Default: detect)",
"type": "string",
"multi": false,
"required": false,
"defaultValue": "detect"
},
{
"name": "severity_level",
"description": "Severity level when IOCs are ingested by Falcon CustomIOC: informational / low / medium / high / critical - Default: high",
"type": "string",
"multi": false,
"required": false,
"defaultValue": "high"
},
{
"name": "tag_added_to_cs",
"description": "Tag added to the IOC in Falcon platform - Default: Cortex Incident - FalconCustomIOC",
"type": "string",
"multi": false,
"required": false,
"defaultValue": "Cortex Incident - FalconCustomIOC"
},
{
"name": "tag_added_to_thehive",
"description": "Tag added to the IOC in TheHive platform - Default: Falcon:Custom IOC Uploaded",
"type": "string",
"multi": false,
"required": false,
"defaultValue": "Falcon:Custom IOC Uploaded"
}
]
}
162 changes: 162 additions & 0 deletions responders/FalconCustomIOC/FalconCustomIOCv2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import requests
import re
import json
import ipaddress

from cortexutils.responder import Responder
from cortexutils.extractor import Extractor
from falconpy import OAuth2, IOC
from dateutil.relativedelta import relativedelta
from datetime import datetime


class FalconCustomIOC(Responder):
def __init__(self):
Responder.__init__(self)
self.falconapi_endpoint = self.get_param(
"config.falconapi_endpoint", None, "Falcon API Endpoint: US-1 | US-2 | US-GOV-1 | EU-1",
)
self.falconapi_clientid = self.get_param(
"config.falconapi_clientid", None, "Falcon clientid missing"
)
self.falconapi_key = self.get_param(
"config.falconapi_key", None, "Falcon api key missing"
)
self.domain_block_expiration_days = self.get_param(
"config.domain_block_expiration_days", 30
)
self.ip_block_expiration_days = self.get_param(
"config.ip_block_expiration_days", 30
)
self.hash_block_expiration_days = self.get_param(
"config.hash_block_expiration_days", 30
)
self.action_to_take = self.get_param(
"config.action_to_take", "detect"
)
self.severity_level = self.get_param(
"config.severity_level", "high"
)
self.tag_added_to_cs = self.get_param(
"config.tag_added_to_cs", "Cortex Incident - FalconCustomIOC"
)
self.tag_added_to_thehive = self.get_param(
"config.tag_added_to_thehive", "CrowdStrike:Custom IOC Uploaded"
)

def run(self):
try:
Responder.run(self)
ioctypes = {
"hash": "sha256",
"sha256": "sha256",
"md5": "md5",
"ip": "ipv4",
"ipv4": "ipv4",
"ip6": "ipv6",
"ipv6": "ipv6",
"domain": "domain",
"url": "domain",
}

data_type = self.get_param("data.dataType")
if not data_type in ioctypes:
self.error("Unsupported IOC type")
return False
ioc = self.get_param("data.data", None, "No IOC provided")

if data_type == "url":
match = re.match(r"(http:\/\/|https:\/\/)?([\w\-\.]{0,256}).*", ioc)
if match is None or match.group(2) is None:
self.error("Could not parse iocs from URL")
return False
else:
ioc = match.group(2)
data_type = Extractor().check_string(ioc)

if data_type == "ip":
try:
ip_check = ipaddress.ip_address(ioc)
except Exception as e:
self.error(f"Could not check IP type from IOC : {e}")
return False
if isinstance(ip_check, ipaddress.IPv6Address):
data_type = "ipv6"
elif isinstance(ip_check, ipaddress.IPv4Address):
data_type = "ipv4"
else:
self.error("Could not determine IP type from IOC")
return False

if data_type == "hash":
if len(ioc) == 32:
data_type = "md5"
elif len(ioc) == 40:
self.error("Unsupported IOC type")
return False
elif len(ioc) == 64:
data_type = "sha256"

if data_type in ("fqdn", "domain"):
expiration_date = datetime.today() + relativedelta(days=self.domain_block_expiration_days)
elif data_type in ("ip", "ipv4", "ipv6", "ip6"):
expiration_date = datetime.today() + relativedelta(days=self.ip_block_expiration_days)
elif data_type in ("hash", "sha256", "md5"):
expiration_date = datetime.today() + relativedelta(days=self.hash_block_expiration_days)
expiration = expiration_date.strftime("%Y-%m-%dT%H:%M:%SZ")

incident_title = self.get_param("data.case.title", None, "Can't get case title").encode("utf-8")[:128]

auth = OAuth2(
client_id=self.falconapi_clientid,
client_secret=self.falconapi_key,
base_url=self.falconapi_endpoint
)

falcon_api = IOC(auth_object=auth)
response = falcon_api.indicator_create(action=self.action_to_take,
applied_globally=True,
comment="TheHive IOC incident",
description=incident_title.decode("utf-8"),
expiration=expiration,
filename="",
ignore_warnings=False,
platforms='mac,windows,linux',
severity=self.severity_level,
source="Cortex - FalconCustomIOC [" + incident_title.decode("utf-8") + "]",
tags=self.tag_added_to_cs,
type=ioctypes[data_type],
value=ioc.strip()
)

response_error = str(response['body']['errors'])
response_ressources = str(response['body']['resources'])

if response['body']['errors'] is None:
self.report(
{"message": f"{ioc} successuflly submitted to Crowdstrike Falcon custom IOC api - status code: {response['status_code']}"}
)
elif 'Duplicate type' in response_ressources:
self.error(f"Not submitted because of duplicated entry - {ioc} already found on your Falcon CustomIOC database")
return False
else:
self.error(f"Error: unable to complete action - received {response['status_code']} status code from FalconIOC API with the following message: {response_error}")
return False

except Exception as ex:
self.error(f"Unable to send IOC to FalconCustomIOC API: {ex}")
return False
return True

def operations(self, raw):
return [
self.build_operation(
"AddTagToArtifact", tag=self.tag_added_to_thehive
)
]

if __name__ == "__main__":
FalconCustomIOC().run()
4 changes: 3 additions & 1 deletion responders/FalconCustomIOC/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
cortexutils
requests
crowdstrike-falconpy
datetime
python-dateutil

0 comments on commit db0361b

Please sign in to comment.