Skip to content

Commit a1849f6

Browse files
Merge pull request #1333 from jimbobnet/develop
Added support for ONYPHE Ctiscan and ActiveScan analyzers. Updated existing analyzers.
2 parents f2c6dcb + fc6fedf commit a1849f6

26 files changed

Lines changed: 1482 additions & 619 deletions
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"name": "ONYPHE_OnDemandScan",
3+
"version": "1.0",
4+
"author": "James Atack",
5+
"license": "AGPL-V3",
6+
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
7+
"description": "Perform active scan of an asset using a Scanyphe Entreprise On-Demand scanner",
8+
"dataTypeList": ["ip", "domain", "fqdn"],
9+
"command": "ONYPHEActiveScan/onyphe_scanalyzer.py",
10+
"baseConfig": "Onyphe",
11+
"config": {
12+
"service": "scanyphe",
13+
"base_uri": "/api/v3/",
14+
"base_url": "https://www.onyphe.io",
15+
"scanyphe_poll_interval" : 30,
16+
"onyphe_import": false,
17+
"check_tlp": true,
18+
"max_tlp": 2,
19+
"check_pap": true,
20+
"max_pap": 1
21+
},
22+
"configurationItems": [
23+
{
24+
"name": "key",
25+
"description": "Define the API key to use to connect the service",
26+
"type": "string",
27+
"multi": false,
28+
"required": true
29+
},
30+
{
31+
"name": "auto_import",
32+
"description": "Automatically import artifacts as observables (risks, cves, assets, ...)",
33+
"type": "boolean",
34+
"multi": false,
35+
"required": true,
36+
"defaultValue": false
37+
},
38+
{
39+
"name": "maxscantime",
40+
"description": "Max scan time (default 120 seconds)",
41+
"type": "number",
42+
"multi": false,
43+
"required": false,
44+
"defaultValue": 120
45+
},
46+
{
47+
"name": "urlscan",
48+
"description": "Enable/disable urlscan stage",
49+
"type": "boolean",
50+
"multi": false,
51+
"required": false,
52+
"defaultValue": true
53+
},
54+
{
55+
"name": "vulnscan",
56+
"description": "Enable/disable vulnscan stage",
57+
"type": "boolean",
58+
"multi": false,
59+
"required": false,
60+
"defaultValue": true
61+
},
62+
{
63+
"name": "riskscan",
64+
"description": "Enable/disable riskscan stage",
65+
"type": "boolean",
66+
"multi": false,
67+
"required": false,
68+
"defaultValue": false
69+
},
70+
{
71+
"name": "asm",
72+
"description": "Enable/disable asm stage",
73+
"type": "boolean",
74+
"multi": false,
75+
"required": false,
76+
"defaultValue": false
77+
},
78+
{
79+
"name": "ports",
80+
"description": "list of ports to scan, comma-separated list (default to ONYPHE’s scanned ports)",
81+
"type": "string",
82+
"multi": false,
83+
"required": false,
84+
"defaultValue": ""
85+
}
86+
],
87+
"registration_required": true,
88+
"subscription_required": true,
89+
"free_subscription": false,
90+
"service_homepage": "https://www.onyphe.io",
91+
"service_logo": {
92+
"path": "assets/onyphe_logo.png",
93+
"caption": "logo"
94+
}
95+
}
10.3 KB
Loading
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
#!/usr/bin/env python3
2+
3+
from cortexutils.analyzer import Analyzer
4+
from scanyphe_api import Scanyphe,ScanypheError
5+
import time
6+
#from tld import get_fld # TODO FLD/subdomains check
7+
8+
class OnypheScanalyzer(Analyzer):
9+
def __init__(self):
10+
Analyzer.__init__(self)
11+
self.service = self.get_param(
12+
"config.service", None, "Service parameter is missing"
13+
)
14+
self.onyphe_key = self.get_param("config.key", None, "Missing Onyphe API key")
15+
self.onyphe_client = None
16+
self.auto_import = self.get_param("config.auto_import", False)
17+
self.onyphe_maxscantime = self.get_param("config.maxscantime", 120)
18+
self.onyphe_urlscan = self.get_param("config.urlscan", True)
19+
self.onyphe_vulnscan = self.get_param("config.vulnscan", True)
20+
self.onyphe_riskscan = self.get_param("config.riskscan", False)
21+
self.onyphe_asm = self.get_param("config.asm", False)
22+
self.onyphe_ports = self.get_param("config.ports", "")
23+
self.onyphe_import = self.get_param("config.import", False)
24+
self.onyphe_poll_interval = self.get_param("config.onyphe_poll_interval", 30)
25+
self.keep_all_tags = self.get_param("config.keep_all_tags", False)
26+
self.polling_interval = self.get_param("config.polling_interval", 60)
27+
self.base_url = self.get_param("config.base_url","https://www.onyphe.io") + self.get_param("config.base_uri","/api/v3/") #Trailing / is needed for urljoin
28+
29+
def summary(self, raw):
30+
taxonomies = []
31+
namespace = "ONYPHE"
32+
33+
reportlist = []
34+
risklist = []
35+
cvelist = []
36+
37+
for odoc in raw["results"]:
38+
if "forward" in odoc:
39+
assetport = str(odoc["ip"]) + ":" + str(odoc["port"]) + ":" + str(odoc["forward"])
40+
else:
41+
assetport = str(odoc["ip"]) + ":" + str(odoc["port"])
42+
43+
if not assetport in reportlist:
44+
reportlist.append(assetport)
45+
if "cve" in odoc:
46+
for cve in odoc["cve"]:
47+
cveipport = cve + ":" + str(odoc["ip"]) + ":" + str(odoc["port"])
48+
if not cveipport in cvelist:
49+
cvelist.append(cveipport)
50+
elif odoc["@category"] == "riskscan":
51+
risklist.append(assetport)
52+
53+
if len(risklist) > 0:
54+
taxonomies.append(
55+
self.build_taxonomy(
56+
"suspicious",
57+
namespace,
58+
"Risk",
59+
"{} risks found".format(len(risklist)),
60+
)
61+
)
62+
if len(cvelist) > 0:
63+
taxonomies.append(
64+
self.build_taxonomy(
65+
"malicious",
66+
namespace,
67+
"CVE",
68+
"{} CVEs found".format(len(cvelist)),
69+
)
70+
)
71+
if len(reportlist) > 0:
72+
taxonomies.append(
73+
self.build_taxonomy(
74+
"info",
75+
namespace,
76+
"Services",
77+
"{} services found".format(len(reportlist)),
78+
)
79+
)
80+
else:
81+
taxonomies.append(
82+
self.build_taxonomy("info", namespace, "Services", "No services found",)
83+
)
84+
85+
86+
return {"taxonomies": taxonomies}
87+
88+
def artifacts(self, raw):
89+
artifacts = []
90+
build = {}
91+
data = self.get_param("data", None, "Data is missing")
92+
93+
#ONYPHE scanalyzer artifact approach
94+
## If data_type is ip, fqdn, we'd like to update existing observable but it seems importing tags not possible
95+
## ... so currently if fqdn create ip observable, and vice-versa.
96+
## If data_type is domain, create new observable for each IP/hostname
97+
## for each observable data_type:data, consolidate all tags and return as artifacts
98+
99+
try:
100+
#parse ONYPHE documents
101+
for odoc in raw["results"]:
102+
#Define tags
103+
otags = []
104+
if odoc["@category"] == "riskscan":
105+
otags.append("onyphe:risk")
106+
for ta in odoc["tag"]:
107+
if ta.split('::')[0] == 'risk':
108+
otags.append(str(ta))
109+
110+
if "cve" in odoc:
111+
otags.append("onyphe:cve")
112+
for cve in odoc["cve"]:
113+
otags.append(str(cve))
114+
for ta in odoc["tag"]:
115+
otags.append(str(ta))
116+
117+
if self.auto_import:
118+
otags.append("autoImport:true")
119+
120+
if self.keep_all_tags:
121+
for ta in odoc["tag"]:
122+
otags.append(str(ta))
123+
124+
if "cpe" in odoc:
125+
for cpe in odoc["cpe"]:
126+
otags.append(str(cpe))
127+
128+
otags.append(str(odoc["protocol"]))
129+
otags.append(str(odoc["transport"]) + "/" + str(odoc["port"]))
130+
131+
if self.data_type == "ip":
132+
if "forward" in odoc:
133+
thisartifact = "fqdn:" + str(odoc["forward"])
134+
elif "hostname" in odoc:
135+
hostnamelist = odoc["hostname"]
136+
#TODO: currently take first hostname. Possible take all of them, or ask user to configure this choice.
137+
thisartifact = "fqdn:" + str(hostnamelist[0])
138+
elif "reverse" in odoc:
139+
thisartifact = "fqdn:" + str(odoc["reverse"])
140+
else:
141+
thisartifact = "ip:" + str(odoc["ip"])
142+
elif self.data_type == "fqdn":
143+
thisartifact = "ip:" + str(odoc["ip"])
144+
elif self.data_type == "domain":
145+
if "forward" in odoc:
146+
thisartifact = "fqdn:" + str(odoc["forward"])
147+
elif "hostname" in odoc:
148+
hostnamelist = odoc["hostname"]
149+
#TODO: currently take first hostname. Possible take all of them, or ask user to configure this choice.
150+
thisartifact = "fqdn:" + str(hostnamelist[0])
151+
elif "reverse" in odoc:
152+
thisartifact = "fqdn:" + str(odoc["reverse"])
153+
else:
154+
thisartifact = "ip:" + str(odoc["ip"])
155+
156+
if thisartifact in build:
157+
existing_tags = build[thisartifact]
158+
for tag in existing_tags:
159+
if not tag in otags:
160+
otags.append(tag)
161+
162+
build[thisartifact] = otags
163+
164+
except Exception as e:
165+
self.unexpectedError(e)
166+
167+
for key in build:
168+
type = key.split(':')[0]
169+
data = key.split(':')[1]
170+
artifacts.append(self.build_artifact(type, data, tags=otags))
171+
172+
return artifacts
173+
174+
175+
def run(self):
176+
Analyzer.run(self)
177+
178+
self.onyphe_client = Scanyphe(self.onyphe_key, self.base_url)
179+
data = self.get_param("data", None, "Data is missing")
180+
try:
181+
#try and launch a scan
182+
183+
#identify data type
184+
if self.data_type == "ip":
185+
path = "ondemand/scope/ip/single"
186+
scan_params = {'ip': data}
187+
elif self.data_type == "domain":
188+
path = "ondemand/scope/domain/single"
189+
scan_params = {'domain': data}
190+
elif self.data_type == "fqdn":
191+
path = "ondemand/scope/hostname/single"
192+
scan_params = {'hostname': data}
193+
194+
#build params dictionary
195+
scan_params['maxscantime'] = self.onyphe_maxscantime
196+
scan_params['ports'] = self.onyphe_ports
197+
if self.onyphe_urlscan == True:
198+
scan_params['urlscan'] = 'true'
199+
else:
200+
scan_params['urlscan'] = 'false'
201+
if self.onyphe_vulnscan == True:
202+
scan_params['vulnscan'] = 'true'
203+
else:
204+
scan_params['vulnscan'] = 'false'
205+
if self.onyphe_riskscan == True:
206+
scan_params['riskscan'] = 'true'
207+
else:
208+
scan_params['riskscan'] = 'false'
209+
if self.onyphe_asm == True:
210+
scan_params['asm'] = 'true'
211+
else:
212+
scan_params['asm'] = 'false'
213+
if self.onyphe_import == True:
214+
scan_params['import'] = 'true'
215+
else:
216+
scan_params['import'] = 'false'
217+
218+
#call appropriate scan API
219+
scanid_result = self.onyphe_client.scan(path, scan_params)
220+
221+
#handle errors else get scan_id
222+
if 'error' in scanid_result.keys():
223+
if scanid_result['error'] > 0:
224+
error_text = "Scan launch failed. Error code " + str(scanid_result['Error']) + " : " + scanid_result['text']
225+
raise ScanypheError(error_text)
226+
else:
227+
scanid = scanid_result['scan_id']
228+
else:
229+
error_text = "Scan launch failed. API said : {say}".format(say=str(scanid_result))
230+
raise ScanypheError(error_text)
231+
except Exception as e:
232+
self.unexpectedError(e)
233+
234+
235+
try:
236+
#now wait onyphe poll time before polling results API
237+
scan_finished = False
238+
intervals = (self.onyphe_maxscantime // self.onyphe_poll_interval) + 1
239+
interval = self.onyphe_poll_interval
240+
241+
for x in range(intervals):
242+
time.sleep(interval)
243+
waited = (x+1) * interval
244+
results = self.onyphe_client.results(scanid)
245+
246+
if results['error'] == 0:
247+
scan_finished = True
248+
break
249+
elif results['error'] != 2027:
250+
error_text = "Scan results fetch failed after " + str(waited) + " secs. Error code " + str(results['error']) + " : " + results['text']
251+
raise ScanypheError(error_text)
252+
253+
if scan_finished == False:
254+
raise ScanypheError("Scan failed : no results were received from Scanyphe after {say} secs".format(say=str(waited)))
255+
256+
results["total_docs"] = len(results["results"])
257+
258+
self.report(results)
259+
260+
except Exception as e:
261+
self.unexpectedError(e)
262+
263+
264+
if __name__ == "__main__":
265+
OnypheScanalyzer().run()
266+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cortexutils
2+
requests
3+
python-dateutil

0 commit comments

Comments
 (0)