Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Onyphe analyzers #152

Merged
merged 1 commit into from
Jan 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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