Skip to content

Commit cde2da8

Browse files
committed
black 79 symbols
1 parent c770cc1 commit cde2da8

10 files changed

Lines changed: 668 additions & 287 deletions

File tree

misp_modules/modules/expansion/_rstcloud/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Shared RST Cloud helpers for the expansion modules (not a registered module)."""
1+
"""Shared RST Cloud helpers for expansion modules (not registered)."""
22

33
from .client import ( # noqa: F401
44
apply_to_source_attribute,

misp_modules/modules/expansion/_rstcloud/client.py

Lines changed: 127 additions & 73 deletions
Large diffs are not rendered by default.

misp_modules/modules/expansion/rst_cs_beacon.py

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""rst_cs_beacon — scan a target for a Cobalt Strike beacon (GET /scan/cs-beacon)."""
1+
"""rst_cs_beacon — scan for Cobalt Strike beacon (GET /scan/cs-beacon)."""
22

33
from __future__ import annotations
44

@@ -20,29 +20,50 @@
2020

2121
misperrors = {"error": "Error"}
2222

23-
_INPUTS = ["ip-dst", "ip-src", "url", "domain", "hostname",
24-
"ip-dst|port", "ip-src|port", "hostname|port", "domain|port"]
23+
_INPUTS = [
24+
"ip-dst",
25+
"ip-src",
26+
"url",
27+
"domain",
28+
"hostname",
29+
"ip-dst|port",
30+
"ip-src|port",
31+
"hostname|port",
32+
"domain|port",
33+
]
2534
# misp_standard: on a hit, return the beacon blob sha256(s) as pivotable
2635
# attributes tagged to the Cobalt Strike galaxy.
2736
mispattributes = {"input": _INPUTS, "format": "misp_standard"}
2837

2938
moduleinfo = {
3039
"version": "0.2",
3140
"author": "RST Cloud",
32-
"description": "Scan a target IP[:port] for a Cobalt Strike beacon configuration via RST Scan API.",
41+
"description": (
42+
"Scan a target IP[:port] for a Cobalt Strike beacon configuration"
43+
" via RST Scan API."
44+
),
3345
"module-type": ["expansion"],
3446
"name": "RST Cloud Cobalt Strike Beacon",
3547
"requirements": ["An RST Cloud API key.", "rstapi>=1.2.0 (PyPI)."],
3648
"features": (
37-
"Probes the target for Cobalt Strike beacon configurations via RST Scan "
38-
"GET /scan/cs-beacon. On a hit, returns file MISP object(s) with pivotable "
39-
"SHA-256 hashes tagged to the Cobalt Strike galaxy."
49+
"Probes the target for Cobalt Strike beacon configurations via RST"
50+
" Scan GET /scan/cs-beacon. On a hit, returns file MISP object(s)"
51+
" with pivotable SHA-256 hashes tagged to the Cobalt Strike"
52+
" galaxy."
53+
),
54+
"references": [
55+
"https://api.rstcloud.net/",
56+
"https://pypi.org/project/rstapi/",
57+
],
58+
"input": (
59+
"IP, URL, domain, or hostname attribute (optional port via config)."
60+
),
61+
"output": (
62+
"file MISP object(s) with beacon hashes and Cobalt Strike galaxy tag."
4063
),
41-
"references": ["https://api.rstcloud.net/", "https://pypi.org/project/rstapi/"],
42-
"input": "IP, URL, domain, or hostname attribute (optional port via config).",
43-
"output": "file MISP object(s) with beacon hashes and Cobalt Strike galaxy tag.",
4464
}
45-
# 'port' (optional): port to probe when the attribute carries none (default 443).
65+
# 'port' (optional): port to probe when the attribute carries none
66+
# (default 443).
4667
moduleconfig = ["api_key", "base_url", "port", "timeout"]
4768

4869
_CS_TAG = 'misp-galaxy:tool="Cobalt Strike"'
@@ -74,7 +95,10 @@ def handler(q=False):
7495
request = json.loads(q)
7596
config = request.get("config")
7697
if not rst_kwargs(config)["APIKEY"]:
77-
return error("An RST Cloud API key is required (set api_key in the module config).")
98+
return error(
99+
"An RST Cloud API key is required (set api_key in the module"
100+
" config)."
101+
)
78102
target = scan_target(request, _INPUTS, config, default_port=443)
79103
if not target:
80104
return error("No target found in the request.")
@@ -83,16 +107,23 @@ def handler(q=False):
83107
if err:
84108
return error(f"RST CS beacon scan failed: {err}")
85109
if not isinstance(data, dict) or not data:
86-
return text_result(f"{target}: no Cobalt Strike beacon found", "RST CS Beacon")
110+
return text_result(
111+
f"{target}: no Cobalt Strike beacon found", "RST CS Beacon"
112+
)
87113

88114
# The scanner ALWAYS returns x86/x64 probe blocks; an actual beacon is only
89115
# present when a block carries a parsed `config` (or a non-zero `size`). An
90116
# empty config / size 0 means "probed, nothing found" — NOT a detection.
91117
blocks = {"x86": _arch(data.get("x86")), "x64": _arch(data.get("x64"))}
92-
hits = {arch: b for arch, b in blocks.items()
93-
if b.get("config") or _to_int(b.get("size")) > 0}
118+
hits = {
119+
arch: b
120+
for arch, b in blocks.items()
121+
if b.get("config") or _to_int(b.get("size")) > 0
122+
}
94123
if not hits:
95-
return text_result(f"{target}: no Cobalt Strike beacon detected", "RST CS Beacon")
124+
return text_result(
125+
f"{target}: no Cobalt Strike beacon detected", "RST CS Beacon"
126+
)
96127

97128
from pymisp import MISPObject
98129

@@ -105,8 +136,9 @@ def handler(q=False):
105136
if not sha or sha in seen:
106137
continue
107138
seen.add(sha)
108-
# The beacon payload is a file; group its hash + config as a file object
109-
# so the detection is tied to the scanned host, not a loose sha256.
139+
# The beacon payload is a file; group its hash + config as a file
140+
# object so the detection is tied to the scanned host, not a loose
141+
# sha256.
110142
fobj = MISPObject("file")
111143
sha_attr = fobj.add_attribute("sha256", value=sha)
112144
sha_attr.add_tag(_CS_TAG) # tags attach to attributes, not objects
@@ -115,8 +147,13 @@ def handler(q=False):
115147
if block.get("size"):
116148
fobj.add_attribute("size-in-bytes", value=block["size"])
117149
cfg = block.get("config") or {}
118-
fobj.add_attribute("text", value=f"Cobalt Strike beacon ({arch}) on {target}; "
119-
f"config: {json.dumps(cfg)[:400]}")
150+
fobj.add_attribute(
151+
"text",
152+
value=(
153+
f"Cobalt Strike beacon ({arch}) on {target}; "
154+
f"config: {json.dumps(cfg)[:400]}"
155+
),
156+
)
120157
fobj.comment = "RST CS Beacon"
121158
if anchor:
122159
fobj.add_reference(anchor, "characterizes")

misp_modules/modules/expansion/rst_favicon.py

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""rst_favicon — favicon hashes + image as a file object (GET /scan/favicon)."""
1+
"""rst_favicon — favicon hashes as a file object (GET /scan/favicon)."""
22

33
from __future__ import annotations
44

@@ -23,27 +23,45 @@
2323

2424
misperrors = {"error": "Error"}
2525

26-
_INPUTS = ["url", "domain", "hostname", "ip-src", "ip-dst",
27-
"ip-src|port", "ip-dst|port", "hostname|port", "domain|port"]
28-
# misp_standard: file object (md5/sha1/sha256 pivotable in Netlas/Censys) plus a
29-
# standalone favicon_hash attribute (Murmur3/MMH3, pivotable in Shodan/FOFA).
26+
_INPUTS = [
27+
"url",
28+
"domain",
29+
"hostname",
30+
"ip-src",
31+
"ip-dst",
32+
"ip-src|port",
33+
"ip-dst|port",
34+
"hostname|port",
35+
"domain|port",
36+
]
37+
# misp_standard: file object (md5/sha1/sha256 pivotable in Netlas/Censys)
38+
# plus a standalone favicon_hash attribute (Murmur3/MMH3, Shodan/FOFA).
3039
mispattributes = {"input": _INPUTS, "format": "misp_standard"}
3140

3241
moduleinfo = {
3342
"version": "0.3",
3443
"author": "RST Cloud",
35-
"description": "Fetch a target's favicon (image + all hashes for Shodan/Netlas/Censys pivoting) via RST Scan API.",
44+
"description": (
45+
"Fetch a target's favicon (image + all hashes for"
46+
" Shodan/Netlas/Censys pivoting) via RST Scan API."
47+
),
3648
"module-type": ["expansion"],
3749
"name": "RST Cloud Favicon",
3850
"requirements": ["An RST Cloud API key.", "rstapi>=1.2.0 (PyPI)."],
3951
"features": (
40-
"Retrieves the favicon image and cryptographic hashes via RST Scan GET "
41-
"/scan/favicon. Returns a file MISP object with MD5/SHA-1/SHA-256 for Censys/Netlas pivoting and a "
42-
"standalone Murmur3 favicon-hash attribute for Shodan/FOFA-style pivoting."
52+
"Retrieves the favicon image and cryptographic hashes via RST Scan"
53+
" GET /scan/favicon. Returns a file MISP object with"
54+
" MD5/SHA-1/SHA-256 for Censys/Netlas pivoting and a standalone"
55+
" Murmur3 favicon-hash attribute for Shodan/FOFA-style pivoting."
4356
),
44-
"references": ["https://api.rstcloud.net/", "https://pypi.org/project/rstapi/"],
57+
"references": [
58+
"https://api.rstcloud.net/",
59+
"https://pypi.org/project/rstapi/",
60+
],
4561
"input": "URL, domain, hostname, or IP attribute.",
46-
"output": "file MISP object, favicon-hash attribute, and resolved favicon URL.",
62+
"output": (
63+
"file MISP object, favicon-hash attribute, and resolved favicon URL."
64+
),
4765
}
4866
moduleconfig = ["api_key", "base_url", "timeout"]
4967

@@ -63,15 +81,22 @@ def handler(q=False):
6381
request = json.loads(q)
6482
config = request.get("config")
6583
if not rst_kwargs(config)["APIKEY"]:
66-
return error("An RST Cloud API key is required (set api_key in the module config).")
84+
return error(
85+
"An RST Cloud API key is required (set api_key in the module"
86+
" config)."
87+
)
6788
# Favicon endpoint expects a bare host or a full URL — never host:port.
6889
# The API fetches the page over HTTP/HTTPS itself; adding ":443" breaks it.
6990
raw = value_from_request(request, _INPUTS)
7091
if not raw:
7192
return error("No target found in the request.")
7293
target = raw if raw.startswith(("http://", "https://")) else host_only(raw)
7394

74-
data, err = unwrap(rstapi.scan(**scan_kwargs(config)).GetFavicon(target, include_base64=True))
95+
data, err = unwrap(
96+
rstapi.scan(**scan_kwargs(config)).GetFavicon(
97+
target, include_base64=True
98+
)
99+
)
75100
if err:
76101
return error(f"RST favicon scan failed: {err}")
77102
if not isinstance(data, dict) or not data.get("favicon_hash"):
@@ -84,7 +109,9 @@ def handler(q=False):
84109
content_type = data.get("req_content_type") or "image/x-icon"
85110

86111
# Real filename from the resolved favicon URL (e.g. "drive_2026_32dp.ico")
87-
raw_fname = req_loc.rstrip("/").split("/")[-1].split("?")[0] if req_loc else ""
112+
raw_fname = (
113+
req_loc.rstrip("/").split("/")[-1].split("?")[0] if req_loc else ""
114+
)
88115
fname = raw_fname if (raw_fname and "." in raw_fname) else "favicon.ico"
89116

90117
event, source = misp_event_with_source(request)
@@ -101,7 +128,11 @@ def handler(q=False):
101128

102129
# Attach the raw image when the API returned base64
103130
try:
104-
raw = base64.b64decode(data["base64_image"]) if data.get("base64_image") else None
131+
raw = (
132+
base64.b64decode(data["base64_image"])
133+
if data.get("base64_image")
134+
else None
135+
)
105136
except Exception:
106137
raw = None
107138
if raw:
@@ -114,12 +145,18 @@ def handler(q=False):
114145

115146
# Resolved favicon URL — where the image actually lives after redirects
116147
if req_loc:
117-
event.add_attribute("link", req_loc, comment="RST Favicon resolved URL", to_ids=False)
118-
119-
# favicon_hash (Murmur3/MMH3): standalone attribute so it correlates independently
120-
# across events and is searchable in Shodan/FOFA-style hunting workflows.
148+
event.add_attribute(
149+
"link",
150+
req_loc,
151+
comment="RST Favicon resolved URL",
152+
to_ids=False,
153+
)
154+
155+
# favicon_hash (Murmur3/MMH3): standalone attribute for independent
156+
# correlation across events and Shodan/FOFA-style hunting workflows.
121157
fav_attr = event.add_attribute(
122-
"other", fhash,
158+
"other",
159+
fhash,
123160
comment=f"Murmur3 favicon hash for {target} (Shodan/FOFA pivot)",
124161
to_ids=False,
125162
)

0 commit comments

Comments
 (0)