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
33from __future__ import annotations
44
2020
2121misperrors = {"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.
2736mispattributes = {"input" : _INPUTS , "format" : "misp_standard" }
2837
2938moduleinfo = {
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).
4667moduleconfig = ["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" )
0 commit comments