Skip to content
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

Added RST IoC Lookup connector. Fixes for Report Hub and Threat Feed #2864

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c8f63c8
Added RST IoC Lookup connector. Fixes for Report Hub and Threat Feed
k1r10n Oct 29, 2024
fb172f0
fixed regex
k1r10n Oct 29, 2024
1f44101
default=True
k1r10n Nov 22, 2024
6fbc400
turn on/off custom TTP generation
k1r10n Nov 29, 2024
2fa8b27
black formatting
k1r10n Nov 29, 2024
1427c27
black formatting
k1r10n Nov 29, 2024
5245496
Merge branch 'rstcloud-Oct-2024-fixes' of https://github.com/k1r10n/o…
k1r10n Jan 8, 2025
0668fc4
Merge branch 'rstcloud-Oct-2024-fixes' of https://github.com/k1r10n/o…
k1r10n Jan 8, 2025
9b108f5
Merge branch 'rstcloud-Oct-2024-fixes' of https://github.com/k1r10n/o…
k1r10n Jan 8, 2025
07b891b
setting the same timeout as in compose file
k1r10n Jan 8, 2025
cf64424
setting the same timeout as in compose file
k1r10n Jan 8, 2025
5400b02
Merge branch 'rstcloud-Oct-2024-fixes' of https://github.com/k1r10n/o…
k1r10n Jan 9, 2025
facc5de
Merge branch 'rstcloud-Oct-2024-fixes' of https://github.com/k1r10n/o…
k1r10n Jan 9, 2025
ebda7a8
Merge branch 'rstcloud-Oct-2024-fixes' of https://github.com/k1r10n/o…
k1r10n Jan 9, 2025
5e9ecd1
Added RST IoC Lookup connector. Fixes for Report Hub and Threat Feed
k1r10n Oct 29, 2024
acffdc8
fixed regex
k1r10n Oct 29, 2024
cda7899
default=True
k1r10n Nov 22, 2024
475b297
turn on/off custom TTP generation
k1r10n Nov 29, 2024
4f3d5e7
black formatting
k1r10n Nov 29, 2024
12c2c26
black formatting
k1r10n Nov 29, 2024
cf8f6fb
setting the same timeout as in compose file
k1r10n Jan 8, 2025
b5a026a
setting the same timeout as in compose file
k1r10n Jan 8, 2025
da6a024
Merge branch 'rstcloud-Oct-2024-fixes' of https://github.com/k1r10n/o…
k1r10n Jan 10, 2025
78808a2
Added RST IoC Lookup connector. Fixes for Report Hub and Threat Feed
k1r10n Oct 29, 2024
65a72de
fixed regex
k1r10n Oct 29, 2024
c7ad165
default=True
k1r10n Nov 22, 2024
f9c9436
turn on/off custom TTP generation
k1r10n Nov 29, 2024
89734d2
black formatting
k1r10n Nov 29, 2024
b9d8ad9
setting the same timeout as in compose file
k1r10n Jan 8, 2025
e9c3e12
Merge branch 'rstcloud-Oct-2024-fixes' of https://github.com/k1r10n/o…
k1r10n Jan 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion external-import/rst-report-hub/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.12-alpine
FROM python:3.11-alpine
ENV CONNECTOR_TYPE=EXTERNAL_IMPORT

# Copy the connector
Expand Down
19 changes: 9 additions & 10 deletions external-import/rst-report-hub/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The **RST Report Hub Connector** integrates various APT reports from security co
## Key Features

- **Brilliant Time Saver**: Manual import of threat reports is a time consuming activity that does not need to happen anymore.
- **Threat Report Library**: Keep all APT reports and their metadata, extracted objects in one place.
- **Threat Report Library**: Keep all APT reports and their metadata, extracted objects in one place.
- **OpenCTI Integration**: Seamlessly integrates the fetched data into OpenCTI's database.

This connector provides users with an enhanced and comprehensive understanding of the cybersecurity threat landscape by leveraging the detailed threat intelligence provided by RST Cloud.
Expand All @@ -22,7 +22,7 @@ This connector is aligned with data populated by common OpenCTI connectors. We r
- CISA Known Exploited Vulnerabilities (https://github.com/OpenCTI-Platform/connectors/tree/master/external-import/cisa-known-exploited-vulnerabilities)


## Configuration:
## Configuration

Configuration of the connector is straightforward. The minimal configuration requires you just enter the RST Cloud API key to be provided and OpenCTI connection settings specified. Below is the full list of parameters you can set:

Expand All @@ -33,17 +33,16 @@ Configuration of the connector is straightforward. The minimal configuration req
| Connector ID | `CONNECTOR_ID` | Yes | A unique `UUIDv4` identifier for this connector instance. |
| Connector Name | `CONNECTOR_NAME` | Yes | Name of the connector. For example: `RST Report Hub`. |
| Connector Scope | `CONNECTOR_SCOPE` | Yes | The scope or type of data the connector is importing, either a MIME type or Stix Object. E.g. application/json |
| Confidence Level | `CONNECTOR_CONFIDENCE_LEVEL` | Yes | The default confidence level for created sightings. It's a number between 1 and 100, with 100 being the most confident. |
| Log Level | `CONNECTOR_LOG_LEVEL` | Yes | Determines the verbosity of the logs. Options are `debug`, `info`, `warn`, or `error`. |
| Run and Terminate | `CONNECTOR_RUN_AND_TERMINATE` | Yes | If set to true, the connector will terminate after a successful run. Useful for debugging or one-time runs. |
| Update Existing Data | `CONFIG_UPDATE_EXISTING_DATA` | Yes | Decide whether the connector should update already existing data in the database. |
| Interval | `CONFIG_INTERVAL` | Yes | Determines how often the connector will run, set in hours. |
| RST Report Hub API Key | `RST_REPORT_HUB_API_KEY` | Yes | Your API Key for accessing RST Cloud. |
| RST Report Hub Base URL | `RST_REPORT_HUB_BASE_URL` | No | By default, use https://api.rstcloud.net/v1/. In some cases, you may want to use a local API endpoint |
| RST Report Hub API Key | `RST_REPORT_HUB_API_KEY` | Yes | Your API Key for accessing RST Cloud. |
| RST Report Hub Connection Timeout | `RST_REPORT_HUB_CONNECTION_TIMEOUT` | No | Connection timeout to the API. Default (sec): `30` |
| RST Report Hub Read Timeout | `RST_REPORT_HUB_READ_TIMEOUT` | No | Read timeout for each feed. If the connector is unable to fetch a report in time, increase the read timeout. Default (sec): `60` |
| RST Report Hub Read Timeout | `RST_REPORT_HUB_RETRY_DELAY` | No | Specifies how long to wait in seconds before next attempt to connect to the API. Default (sec): `30` |
| RST Report Hub Retry Delay | `RST_REPORT_HUB_RETRY_DELAY` | No | Specifies how long to wait in seconds before next attempt to connect to the API. Default (sec): `30` |
| RST Report Hub Download Retry Count | `RST_REPORT_HUB_RETRY_ATTEMPTS` | No | Default (attempts): `5` |
| RST Report Hub Fetch Interval | `RST_REPORT_HUB_FETCH_INTERVAL` | No | Default (sec): `300` |
| RST Report Hub Minimal Score to Import | `RST_REPORT_HUB_IMPORT_START_DAY` | No | Specify the date from which you want to retrieve the reports. Data import for each day will occur with a delay equal to the RST_REPORT_HUB_FETCH_INTERVAL. By default, this start date is calculated as 7 days ago. |
| RST Report Hub Minimal Score for IP to be marked for Detection | `RST_REPORT_HUB_LANGUAGE` | No | Reach out to support@rstcloud.net if you want to update thids parameter. Default: `eng` |
| RST Report Hub Date to start pulling data from | `RST_REPORT_HUB_IMPORT_START_DATE` | No | Specify the date from which you want to retrieve the reports in the format "%Y%m%d" (for example, 20240527). Data import for each day will occur with a delay equal to the RST_REPORT_HUB_FETCH_INTERVAL. By default, this start date is calculated as 7 days ago. |
| RST Report Hub Language | `RST_REPORT_HUB_LANGUAGE` | No | Reach out to support@rstcloud.net if you want to update this parameter. Default: `eng` |
| RST Report Hub Connector is to create observables | `RST_REPORT_HUB_CREATE_OBSERVABLES` | No | A user can select if observables are to be created in addition to indicators. Options are `true`, `false`. Default: `false` |
| RST Report Hub Connector is to create related-to relationships | `RST_REPORT_HUB_CREATE_RELATED_TO` | No | A user can select if `related-to` relationships are to be created or not. Options are `true`, `false`. Default: `true` |
| RST Report Hub Connector is to create custom attack-pattern | `RST_REPORT_HUB_CREATE_CUSTOM_TTPS` | No | A user can select if `attack-pattern` objects with custom names that are still not present in the MITRE ATT&CK framework are to be created or not. Options are `true`, `false`. Default: `true` |
3 changes: 3 additions & 0 deletions external-import/rst-report-hub/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ services:
- RST_REPORT_HUB_IMPORT_START_DATE=${RST_REPORT_HUB_STARTDATE}
- RST_REPORT_HUB_FETCH_INTERVAL=300
- RST_REPORT_HUB_LANGUAGE=eng
- RST_REPORT_HUB_CREATE_OBSERVABLES=false
- RST_REPORT_HUB_CREATE_RELATED_TO=true
- RST_REPORT_HUB_CREATE_CUSTOM_TTPS=true
restart: always
depends_on:
- opencti
12 changes: 6 additions & 6 deletions external-import/rst-report-hub/src/config.yml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ opencti:

connector:
id: 'ChangeMe' # Valid UUIDv4
type: 'EXTERNAL_IMPORT'
name: 'RST Report Hub'
scope: 'application/json' # MIME type or SCO
confidence_level: 80 # From 0 (Unknown) to 100 (Fully trusted)
update_existing_data: true
run_and_terminate: true
log_level: 'info'

rst-report-hub:
base_url: 'https://api.rstcloud.net/v1'
api_key: 'ChangeMe'
connection_timeout: 10
connection_timeout: 30
read_timeout: 30
retry_delay: 30
retry_attempts: 5
import_start_date: '20230904'
import_start_date: '20240101'
fetch_interval: 300
language: 'eng'
language: 'eng'
create_observables: false
create_related_to: true
create_custom_ttps: true
172 changes: 154 additions & 18 deletions external-import/rst-report-hub/src/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base64
import json
import os
import re
import sys
import time
import traceback
Expand All @@ -17,7 +18,7 @@ class ReportHub:
def __init__(self):
config_file_path = os.path.dirname(os.path.abspath(__file__)) + "/config.yml"
config = (
yaml.safe_load(open(config_file_path))
yaml.safe_load(open(config_file_path, encoding="UTF-8"))
if os.path.isfile(config_file_path)
else {}
)
Expand All @@ -43,37 +44,172 @@ def __init__(self):
),
"fetch_interval": int(self.get_config("fetch_interval", config, 300)),
"language": str(self.get_config("language", config, "eng")),
"create_observables": bool(
self.get_config("create_observables", config, False)
),
"create_related_to": bool(
self.get_config("create_related_to", config, True)
),
"create_custom_ttps": bool(
self.get_config("create_custom_ttps", config, True)
),
}
self.update_existing_data = get_config_variable(
"CONNECTOR_UPDATE_EXISTING_DATA",
["connector", "update_existing_data"],
config,
self.update_existing_data = bool(
get_config_variable(
"CONNECTOR_UPDATE_EXISTING_DATA",
["connector", "update_existing_data"],
config,
default=True,
)
)

@staticmethod
def get_config(name: str, config, default=None):
env_name = "RST_REPORT_HUB_{}".format(name.upper())
# usually this connector gets its config from variables
# but if these are not defined, then it
# reads 'rst-report-hub' property in the file config.yml
env_name = f"RST_REPORT_HUB_{name.upper()}"
result = get_config_variable(env_name, ["rst-report-hub", name], config)
return result or default
if result is not None:
return result
else:
return default

def extract_file_hashes(self, pattern: str):
hashes = {}
# Regular expression to match hash types and values
hash_pattern = re.compile(
r"file:hashes\.'?(MD5|SHA-1|SHA-256)'? ?= ?'([a-fA-F0-9]{32,64})'"
)
# Find all hash occurrences in the pattern
matches = hash_pattern.findall(pattern)
for hash_type, hash_value in matches:
hashes[hash_type] = hash_value
return hashes

def create_observable(self, stix_indicator):
ioc_type = stix_indicator.get("x_opencti_main_observable_type", "")
shared = {
"object_marking_refs": stix_indicator.get("object_marking_refs", []),
"custom_properties": {
"x_opencti_score": stix_indicator.get("x_opencti_score", []),
"x_opencti_labels": stix_indicator.get("labels", []),
"x_opencti_created_by_ref": stix_indicator.get("created_by_ref", ""),
"x_opencti_external_references": stix_indicator.get(
"external_references", []
),
},
}
if ioc_type == "IPv4-Addr":
stix_observ = stix2.v21.IPv4Address(
value=stix_indicator["pattern"].split("'")[1], **shared
)
elif ioc_type == "Domain-Name":
stix_observ = stix2.v21.DomainName(
value=stix_indicator["pattern"].split("'")[1], **shared
)
elif ioc_type == "Url":
stix_observ = stix2.v21.URL(
value=stix_indicator["pattern"].split("'")[1], **shared
)
elif ioc_type == "StixFile":
stix_observ = stix2.v21.File(
hashes=self.extract_file_hashes(stix_indicator["pattern"]), **shared
)
else:
stix_observ = None
if stix_observ:
based_on = stix2.v21.Relationship(
source_ref=stix_indicator["id"],
relationship_type="based-on",
target_ref=stix_observ["id"],
**shared,
)
else:
based_on = None
return stix_observ, based_on

def _combine_report_and_send(self, stix_bundle, x_opencti_file, report_id):
# Parse the STIX bundle
parsed_bundle = json.loads(stix_bundle)
stix_bundle_main = []
message = f"Importing {report_id}"
observ_ids = []
observ_rel_ids = []
rel_to_ids = []
removed_ids = []
for entry in parsed_bundle.get("objects", []):
# create an observable for every indicator
# if user selects that option
if (
entry.get("type", "") == "indicator"
and self._downloader_config["create_observables"]
):
observ_obj, based_on = self.create_observable(entry)
if observ_obj and based_on:
stix_bundle_main.append(observ_obj)
observ_ids.append(observ_obj.id)
stix_bundle_main.append(based_on)
observ_rel_ids.append(based_on.id)
# remove related-to relationships from the bundle
# if user selects that option
elif (
entry.get("type", "") == "relationship"
and entry.get("relationship_type", "") == "related-to"
and not self._downloader_config["create_related_to"]
):
rel_to_ids.append(entry["id"])
continue
# remove custom TTPs that are not yet present in MITRE ATT&CK®
# if user selects that option
elif (
entry.get("type", "") == "attack-pattern"
and "x_mitre_id" not in entry
and not self._downloader_config["create_custom_ttps"]
):
rel_to_ids.append(entry["id"])
removed_ids.append(entry["id"])
continue
# attach a pdf
elif entry.get("type", "") == "report":
if x_opencti_file:
entry["x_opencti_files"] = [x_opencti_file]
else:
message = f"{message}. No PDF found."
stix_bundle_main.append(entry)
# attach PDFs only to the Report object
if x_opencti_file and entry.get("type", "") == "report":
entry["x_opencti_files"] = [x_opencti_file]

message = "Importing " + report_id.replace("_", " ")
# add observables and based_on rels to the report
# fix report references
if (
self._downloader_config["create_observables"]
or not self._downloader_config["create_related_to"]
or not self._downloader_config["create_custom_ttps"]
):
new_object_refs = []
for entry in stix_bundle_main:
if entry.get("type", "") == "report":
for obj_id in observ_ids:
new_object_refs.append(obj_id)
for obj_id in observ_rel_ids:
new_object_refs.append(obj_id)
for obj_id in entry["object_refs"]:
if obj_id not in rel_to_ids:
new_object_refs.append(obj_id)
entry["object_refs"] = new_object_refs
# cleanup after deletion of objects
if len(removed_ids) > 0:
stix_bundle_main = [
entry
for entry in stix_bundle_main
if not (
entry.get("type") == "relationship"
and (
entry.get("source_ref") in removed_ids
or entry.get("target_ref") in removed_ids
)
)
and not (entry.get("id") in removed_ids)
]
work_id = self.helper.api.work.initiate_work(self.helper.connect_id, message)
self._send_stix_data(work_id, stix_bundle_main)
message = f"Processed {len(stix_bundle_main)} objects from RST Report Hub for {report_id}"
self.helper.api.work.to_processed(work_id, message)
message = f"Processed {len(stix_bundle_main)} objects from RST Report Hub for {report_id}"
self.helper.log_info(message)
return True

Expand Down Expand Up @@ -126,7 +262,7 @@ def _convert_and_attach_pdfs(self, headers, reports, lang):
self.helper.log_error(
f"Failed to download and save entry {report_id} as PDF. {ex}"
)
self._combine_report_and_send(stix_report, {}, "")
self._combine_report_and_send(stix_report, {}, report_id)
return True

def _fetch_stix_reports(self, current_state):
Expand Down Expand Up @@ -169,7 +305,7 @@ def _fetch_stix_reports(self, current_state):
"report_count"
] >= len(reports):
self.helper.log_info(
f"Skipping as all reports for the current day {today} have been downloaded"
f"Skipping as all reports for the current day {today} are downloaded"
)
return True
else:
Expand Down
Binary file modified external-import/rst-report-hub/src/requirements.txt
Binary file not shown.
2 changes: 1 addition & 1 deletion external-import/rst-threat-feed/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.12-alpine
FROM python:3.11-alpine
ENV CONNECTOR_TYPE=EXTERNAL_IMPORT

# Copy the connector
Expand Down
Loading