Skip to content

Commit

Permalink
Add Onyphe analyzers
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Baudry committed Dec 8, 2017
1 parent b588172 commit 05b2606
Show file tree
Hide file tree
Showing 8 changed files with 356 additions and 0 deletions.
16 changes: 16 additions & 0 deletions analyzers/Onyphe/Onyphe_Forward.json
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"
}
16 changes: 16 additions & 0 deletions analyzers/Onyphe/Onyphe_Geolocate.json
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"
}
16 changes: 16 additions & 0 deletions analyzers/Onyphe/Onyphe_Ports.json
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"
}
16 changes: 16 additions & 0 deletions analyzers/Onyphe/Onyphe_Reverse.json
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"
}
16 changes: 16 additions & 0 deletions analyzers/Onyphe/Onyphe_Threats.json
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"
}
125 changes: 125 additions & 0 deletions analyzers/Onyphe/onyphe_analyzer.py
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()
149 changes: 149 additions & 0 deletions analyzers/Onyphe/onyphe_api.py
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
2 changes: 2 additions & 0 deletions analyzers/Onyphe/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cortexutils
requests

0 comments on commit 05b2606

Please sign in to comment.