-
Notifications
You must be signed in to change notification settings - Fork 385
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b588172
commit 05b2606
Showing
8 changed files
with
356 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"name": "Onyphe_Forward", | ||
"version": "1.0", | ||
"author": "Pierre Baudry, Adrien Barchapt", | ||
"url": "https://github.com/cybernardo/Cortex-Analyzers", | ||
"license": "AGPL-V3", | ||
"description": "Retrieve forward DNS lookup information we have for the given IPv{4,6} address with history of changes.", | ||
"dataTypeList": ["ip"], | ||
"baseConfig": "Onyphe", | ||
"config": { | ||
"check_tlp": true, | ||
"max_tlp": 1, | ||
"service": "forward" | ||
}, | ||
"command": "Onyphe/onyphe_analyzer.py" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"name": "Onyphe_Geolocate", | ||
"version": "1.0", | ||
"author": "Pierre Baudry, Adrien Barchapt", | ||
"url": "https://github.com/cybernardo/Cortex-Analyzers", | ||
"license": "AGPL-V3", | ||
"description": "Retrieve geolocation information for the given IPv{4,6} address.", | ||
"dataTypeList": ["ip"], | ||
"baseConfig": "Onyphe", | ||
"config": { | ||
"check_tlp": true, | ||
"max_tlp": 1, | ||
"service": "geolocate" | ||
}, | ||
"command": "Onyphe/onyphe_analyzer.py" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"name": "Onyphe_Ports", | ||
"version": "1.0", | ||
"author": "Pierre Baudry, Adrien Barchapt", | ||
"url": "https://github.com/cybernardo/Cortex-Analyzers", | ||
"license": "AGPL-V3", | ||
"description": "Retrieve synscan information we have for the given IPv{4,6} address with history of changes.", | ||
"dataTypeList": ["ip"], | ||
"baseConfig": "Onyphe", | ||
"config": { | ||
"check_tlp": true, | ||
"max_tlp": 1, | ||
"service": "ports" | ||
}, | ||
"command": "Onyphe/onyphe_analyzer.py" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"name": "Onyphe_Reverse", | ||
"version": "1.0", | ||
"author": "Pierre Baudry, Adrien Barchapt", | ||
"url": "https://github.com/cybernardo/Cortex-Analyzers", | ||
"license": "AGPL-V3", | ||
"description": "Retrieve reverse DNS lookup information we have for the given IPv{4,6} address with history of changes.", | ||
"dataTypeList": ["ip"], | ||
"baseConfig": "Onyphe", | ||
"config": { | ||
"check_tlp": true, | ||
"max_tlp": 1, | ||
"service": "reverse" | ||
}, | ||
"command": "Onyphe/onyphe_analyzer.py" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"name": "Onyphe_Threats", | ||
"version": "1.0", | ||
"author": "Pierre Baudry, Adrien Barchapt", | ||
"url": "https://github.com/CERT-BDF/Cortex-Analyzers", | ||
"license": "AGPL-V3", | ||
"description": "Retrieve Onyphe threats information on an IPv{4,6} address with history.", | ||
"dataTypeList": ["ip"], | ||
"baseConfig": "Onyphe", | ||
"config": { | ||
"check_tlp": true, | ||
"max_tlp": 1, | ||
"service": "threats" | ||
}, | ||
"command": "Onyphe/onyphe_analyzer.py" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
#!/usr/bin/env python3 | ||
|
||
from cortexutils.analyzer import Analyzer | ||
from onyphe_api import Onyphe | ||
|
||
|
||
class OnypheAnalyzer(Analyzer): | ||
def __init__(self): | ||
Analyzer.__init__(self) | ||
self.service = self.getParam( | ||
'config.service', None, 'Service parameter is missing') | ||
self.onyphe_key = self.getParam( | ||
'config.key', None, 'Missing Onyphe API key') | ||
self.onyphe_client = None | ||
self.polling_interval = self.getParam('config.polling_interval', 60) | ||
|
||
def summary(self, raw): | ||
taxonomies = [] | ||
namespace = "Onyphe" | ||
if self.service == 'threats': | ||
output_data = {} | ||
for r in raw['threats']['results']: | ||
threatlist = r['threatlist'] | ||
if threatlist not in output_data: | ||
output_data[threatlist] = { | ||
"dates": [], | ||
"subnets": [], | ||
"count": 0 | ||
} | ||
|
||
if r['seen_date'] not in output_data[threatlist]["dates"]: | ||
output_data[threatlist]["dates"].append(r['seen_date']) | ||
output_data[threatlist]["count"] += 1 | ||
if r['subnet'] not in output_data[threatlist]["subnets"]: | ||
output_data[threatlist]["subnets"].append(r['subnet']) | ||
for threatlist, threat_data in output_data.items(): | ||
taxonomies.append(self.build_taxonomy( | ||
'malicious', namespace, "Threat", "threatlist: {}, event count: {}".format( | ||
threatlist, threat_data['count']))) | ||
|
||
if self.service == 'geolocate': | ||
location = raw['location']['results'][0] | ||
taxonomies.append(self.build_taxonomy( | ||
'info', namespace, "Geolocate", "country: {}, city: {}".format( | ||
location["country_name"], location["city"]))) | ||
|
||
if self.service == 'ports': | ||
output_data = {} | ||
for r in raw['ports']['results']: | ||
port = r['port'] | ||
if port not in output_data: | ||
output_data[port] = { | ||
"dates": [] | ||
} | ||
if r['seen_date'] not in output_data[port]['dates']: | ||
output_data[port]['dates'].append(r['seen_date']) | ||
for port_number, port_data in output_data.items(): | ||
taxonomies.append(self.build_taxonomy( | ||
'info', namespace, "Port", "port {} last seen {}".format( | ||
port_number, port_data['dates'][0]))) | ||
|
||
if self.service == 'reverse': | ||
output_data = {} | ||
for r in raw['reverses']['results']: | ||
reverse = r['domain'] | ||
if reverse not in output_data: | ||
output_data[reverse] = { | ||
"dates": [] | ||
} | ||
|
||
if r['seen_date'] not in output_data[reverse]["dates"]: | ||
output_data[reverse]["dates"].append(r['seen_date']) | ||
for reverse, reverse_data in output_data.items(): | ||
taxonomies.append(self.build_taxonomy( | ||
'info', namespace, "DNS Reverse", "name: {}, last_seen: {}".format( | ||
reverse, reverse_data['dates'][0]))) | ||
|
||
if self.service == 'forward': | ||
output_data = {} | ||
for r in raw['forwards']['results']: | ||
forwarder = r['forward'] | ||
if forwarder not in output_data: | ||
output_data[forwarder] = { | ||
"dates": [] | ||
} | ||
|
||
if r['seen_date'] not in output_data[forwarder]["dates"]: | ||
output_data[forwarder]["dates"].append(r['seen_date']) | ||
for forwarder, forward_data in output_data.items(): | ||
taxonomies.append(self.build_taxonomy( | ||
'info', namespace, "DNS Forwarder", "forwarder: {}, last_seen: {}".format( | ||
forwarder, forward_data['dates'][0]))) | ||
|
||
return {'taxonomies': taxonomies} | ||
|
||
def run(self): | ||
Analyzer.run(self) | ||
try: | ||
self.onyphe_client = Onyphe(self.onyphe_key) | ||
if self.service == 'threats': | ||
ip = self.getParam('data', None, 'Data is missing') | ||
results = {'threats': self.onyphe_client.threatlist(ip)} | ||
self.report(results) | ||
if self.service == 'ports': | ||
ip = self.getParam('data', None, 'Data is missing') | ||
results = {'ports': self.onyphe_client.synscan(ip)} | ||
self.report(results) | ||
if self.service == 'geolocate': | ||
ip = self.getParam('data', None, 'Data is missing') | ||
results = {'location': self.onyphe_client.geolocate(ip)} | ||
self.report(results) | ||
if self.service == 'reverse': | ||
ip = self.getParam('data', None, 'Data is missing') | ||
results = {'reverses': self.onyphe_client.reverse(ip)} | ||
self.report(results) | ||
if self.service == 'forward': | ||
ip = self.getParam('data', None, 'Data is missing') | ||
results = {'forwards': self.onyphe_client.forward(ip)} | ||
self.report(results) | ||
except Exception: | ||
pass | ||
|
||
|
||
if __name__ == '__main__': | ||
OnypheAnalyzer().run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import ipaddress | ||
|
||
from requests.compat import urljoin, quote_plus | ||
import requests | ||
|
||
|
||
class Onyphe: | ||
"""Wrapper around the Onyphe REST API | ||
:param key: The Onyphe API key | ||
:type key: str | ||
""" | ||
|
||
def __init__(self, key: str): | ||
"""Intializes the API object | ||
:param key: The Onyphe API key | ||
:type key: str | ||
""" | ||
self.api_key = key | ||
self.base_url = "https://www.onyphe.io" | ||
self._session = requests.Session() | ||
|
||
def _request(self, path: str, query_params: dict={}): | ||
"""Specialized wrapper arround the requests module to request data from Onyphe | ||
:param path: The URL path after the onyphe FQDN | ||
:type path: str | ||
:param query_params: The dictionnary of query parameters that gets appended to the URL | ||
:type query_params: str | ||
""" | ||
query_params["apikey"] = self.api_key | ||
url = urljoin(self.base_url, path) | ||
response = self._session.get(url=url, data=query_params) | ||
|
||
if response.status_code == 429: | ||
raise APIRateLimiting(response.text) | ||
try: | ||
response_data = response.json() | ||
except: | ||
raise APIError("Couldn't parse response JSON") | ||
|
||
if response_data["error"] > 0: | ||
raise APIError("got error {}: {}".format( | ||
response_data["error"], response_data["message"])) | ||
|
||
return response_data | ||
|
||
def _request_without_api(self, path: str, query_params: dict={}): | ||
"""Specialized wrapper arround the requests module to request data from Onyphe without the api_key(geolocate and myip) | ||
:param path: The URL path after the onyphe FQDN | ||
:type path: str | ||
:param query_params: The dictionnary of query parameters that gets appended to the URL | ||
:type query_params: str | ||
""" | ||
url = urljoin(self.base_url, path) | ||
response = self._session.get(url=url, data=query_params) | ||
|
||
if response.status_code == 429: | ||
raise APIRateLimiting(response.text) | ||
try: | ||
response_data = response.json() | ||
except: | ||
raise APIError("Couldn't parse response JSON") | ||
|
||
if response_data["error"] > 0: | ||
raise APIError("got error {}: {}".format( | ||
response_data["error"], response_data["message"])) | ||
|
||
return response_data | ||
|
||
def myip(self, ip: str): | ||
"""This method is open to use. There is need for an API key. | ||
""" | ||
url_path = "/api/myip" | ||
return self._request_without_api(path=url_path) | ||
|
||
def geolocate(self, ip: str): | ||
"""Return geolocate information from ip address (Geolocate doesn't need apikey !!) | ||
""" | ||
url_path = "/api/geoloc/{ip}".format(ip=ip) | ||
return self._request_without_api(path=url_path) | ||
|
||
def ip(self, ip: str): | ||
"""Return a summary of all information we have for the given IPv{4,6} address. History of changes will not be shown, only latest results. | ||
""" | ||
url_path = "/api/ip/{ip}".format(ip=ip) | ||
return self._request(path=url_path) | ||
|
||
def inetnum(self, ip: str): | ||
"""Return inetnum information we have for the given IPv{4,6} address with history of changes. Multiple subnets may match because of delegation mechanisms. We return all of them | ||
""" | ||
url_path = "/api/inetnum/{ip}".format(ip=ip) | ||
return self._request(path=url_path) | ||
|
||
def threatlist(self, ip: str): | ||
"""Return threatlist information we have for the given IPv{4,6} address with history of changes | ||
""" | ||
url_path = "/api/threatlist/{ip}".format(ip=ip) | ||
return self._request(path=url_path) | ||
|
||
def pastries(self, ip: str): | ||
"""Return pastries information we have for the given IPv{4,6} address with history of changes. | ||
""" | ||
url_path = "/api/pastries/{ip}".format(ip=ip) | ||
return self._request(path=url_path) | ||
|
||
def synscan(self, ip: str): | ||
"""Return synscan information we have for the given IPv{4,6} address with history of changes. Multiple synscan entries may match. We return all of them. | ||
""" | ||
url_path = "/api/synscan/{ip}".format(ip=ip) | ||
return self._request(path=url_path) | ||
|
||
def datascan(self, search: str): | ||
"""Return datascan information we have for the given IPv{4,6} address or string with history of changes | ||
""" | ||
url_path = "/api/datascan/{search}".format(search=search) | ||
return self._request(path=url_path) | ||
|
||
def reverse(self, search: str): | ||
"""Return reverse DNS lookup information we have for the given IPv{4,6} address with history of changes. Multiple reverse DNS entries may match. We return all of them. | ||
""" | ||
url_path = "/api/reverse/{search}".format(search=search) | ||
return self._request(path=url_path) | ||
|
||
def forward(self, search: str): | ||
"""Return forward DNS lookup information we have for the given IPv{4,6} address with history of changes. Multiple forward DNS entries may match. We return all of them. | ||
""" | ||
url_path = "/api/forward/{search}".format(search=search) | ||
return self._request(path=url_path) | ||
|
||
|
||
class APIError(Exception): | ||
"""This exception gets raised when the returned error code is non-zero positive""" | ||
|
||
def __init__(self, value): | ||
self.value = value | ||
|
||
def __str__(self): | ||
return self.value | ||
|
||
|
||
class APIRateLimiting(Exception): | ||
"""This exception gets raised when the 429 HTTP code is returned""" | ||
|
||
def __init__(self, value): | ||
self.value = value | ||
|
||
def __str__(self): | ||
return self.value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
cortexutils | ||
requests |