From 2c00ce4f30e592be48acac5df927019ae9e356ae Mon Sep 17 00:00:00 2001 From: Daniel Browne Date: Wed, 18 Apr 2018 18:48:28 +0200 Subject: [PATCH 1/3] GreyNoise analyzer --- analyzers/GreyNoise/GreyNoise.json | 25 ++++++++ analyzers/GreyNoise/greynoise.py | 87 ++++++++++++++++++++++++++++ analyzers/GreyNoise/requirements.txt | 2 + 3 files changed, 114 insertions(+) create mode 100755 analyzers/GreyNoise/GreyNoise.json create mode 100755 analyzers/GreyNoise/greynoise.py create mode 100755 analyzers/GreyNoise/requirements.txt diff --git a/analyzers/GreyNoise/GreyNoise.json b/analyzers/GreyNoise/GreyNoise.json new file mode 100755 index 0000000..411c4d4 --- /dev/null +++ b/analyzers/GreyNoise/GreyNoise.json @@ -0,0 +1,25 @@ +{ + "name": "GreyNoise", + "version": "2.2", + "author": "Nclose", + "url": "https://github.com/TheHive-Project/Cortex-Analyzers", + "license": "APLv2", + "description": "Determine whether IP has known scanning activity", + "dataTypeList": ["ip"], + "baseConfig": "GreyNoise", + "command": "GreyNoise/greynoise.py", + "configurationItems": [ + { + "name": "key", + "description": "API key for GreyNoise", + "type": "string", + "multi": false, + "required": false + } + ], + "config": { + "check_tlp": true, + "max_tlp": 2, + "auto_extract": false + } +} diff --git a/analyzers/GreyNoise/greynoise.py b/analyzers/GreyNoise/greynoise.py new file mode 100755 index 0000000..5860e11 --- /dev/null +++ b/analyzers/GreyNoise/greynoise.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from collections import defaultdict, OrderedDict +from functools import partial + +from cortexutils.analyzer import Analyzer +import requests + + +class GreyNoiseAnalyzer(Analyzer): + """ + GreyNoise API docs: https://github.com/GreyNoise-Intelligence/api.greynoise.io + """ + + @staticmethod + def _get_level(current_level, new_intention): + """ + Map GreyNoise intentions to Cortex maliciousness levels. + Accept a Cortex level and a GreyNoise intention, the return the more malicious of the two. + + :param current_level: A Cortex maliciousness level + https://github.com/TheHive-Project/CortexDocs/blob/master/api/how-to-create-an-analyzer.md#output + :param new_intention: An intention field value from a GreyNoise record + https://github.com/GreyNoise-Intelligence/api.greynoise.io#v1queryip + :return: The more malicious of the 2 submitted values as a Cortex maliciousness level + """ + + intention_level_map = OrderedDict([ + ('info', 'info'), + ('benign', 'safe'), + ('suspicious', 'suspicious'), + ('malicious', 'malicious') + ]) + levels = intention_level_map.values() + + new_level = intention_level_map.get(new_intention, 'info') + new_index = levels.index(new_level) + + try: + current_index = levels.index(current_level) + except ValueError: # There is no existing level + current_index = -1 + + return new_level if new_index > current_index else current_level + + def run(self): + + if self.data_type == "ip": + api_key = self.get_param('config.key', None) + url = 'http://api.greynoise.io:8888/v1/query/ip' + headers = {'Content-Type': 'application/x-www-form-urlencoded'} + data = {'ip': self.getData()} + if api_key: + data['key'] = api_key + response = requests.post(url, data=data, headers=headers) + if not (200 <= response.status_code < 300): + self.error('Unable to query GreyNoise API\n{}'.format(response.text)) + self.report(response.json()) + else: + self.notSupported() + + def summary(self, raw): + """ + Sum the number of times a specific name appears for the given IP and choose the highest level from the list + """ + + try: + taxonomy_data = defaultdict(partial(defaultdict, int)) + for record in raw.get('records', []): + name = record.get('name', 'unknown') + intention = record.get('intention', 'unknown') + taxonomy_data[name]['count'] += 1 + taxonomy_data[name]['level'] = self._get_level(taxonomy_data[name]['level'], intention) + + taxonomies = [] + for name, details in taxonomy_data.iteritems(): + taxonomies.append(self.build_taxonomy(details['level'], 'GreyNoise', name, details['count'])) + + return {"taxonomies": taxonomies} + + except Exception, e: + self.error('Summary failed\n{}'.format(e.message)) + + +if __name__ == '__main__': + GreyNoiseAnalyzer().run() diff --git a/analyzers/GreyNoise/requirements.txt b/analyzers/GreyNoise/requirements.txt new file mode 100755 index 0000000..6aabc3c --- /dev/null +++ b/analyzers/GreyNoise/requirements.txt @@ -0,0 +1,2 @@ +cortexutils +requests From 90ca156318dba12e7337ba80bc591b13e0a41232 Mon Sep 17 00:00:00 2001 From: Daniel Browne Date: Sat, 21 Apr 2018 10:17:03 +0200 Subject: [PATCH 2/3] Each run of the analysis will now provide only one entry in the summary as requested --- analyzers/GreyNoise/GreyNoise.json | 2 +- analyzers/GreyNoise/greynoise.py | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/analyzers/GreyNoise/GreyNoise.json b/analyzers/GreyNoise/GreyNoise.json index 411c4d4..b30e12d 100755 --- a/analyzers/GreyNoise/GreyNoise.json +++ b/analyzers/GreyNoise/GreyNoise.json @@ -1,6 +1,6 @@ { "name": "GreyNoise", - "version": "2.2", + "version": "2.3", "author": "Nclose", "url": "https://github.com/TheHive-Project/Cortex-Analyzers", "license": "APLv2", diff --git a/analyzers/GreyNoise/greynoise.py b/analyzers/GreyNoise/greynoise.py index 5860e11..7d83bd1 100755 --- a/analyzers/GreyNoise/greynoise.py +++ b/analyzers/GreyNoise/greynoise.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- from collections import defaultdict, OrderedDict -from functools import partial from cortexutils.analyzer import Analyzer import requests @@ -62,20 +61,28 @@ class GreyNoiseAnalyzer(Analyzer): def summary(self, raw): """ - Sum the number of times a specific name appears for the given IP and choose the highest level from the list + Return one taxonomy summarizing the reported tags + If there is only one tag, use it as the predicate + If there are multiple tags, use "entries" as the predicate + Use the total count as the value + Use the most malicious level found """ try: - taxonomy_data = defaultdict(partial(defaultdict, int)) + final_level = None + taxonomy_data = defaultdict(int) for record in raw.get('records', []): name = record.get('name', 'unknown') intention = record.get('intention', 'unknown') - taxonomy_data[name]['count'] += 1 - taxonomy_data[name]['level'] = self._get_level(taxonomy_data[name]['level'], intention) + taxonomy_data[name] += 1 + final_level = self._get_level(final_level, intention) taxonomies = [] - for name, details in taxonomy_data.iteritems(): - taxonomies.append(self.build_taxonomy(details['level'], 'GreyNoise', name, details['count'])) + if len(taxonomy_data) > 1: # Multiple tags have been found + taxonomies.append(self.build_taxonomy(final_level, 'GreyNoise', 'entries', len(taxonomy_data))) + else: # There is only one tag found, possibly multiple times + for name, count in taxonomy_data.iteritems(): + taxonomies.append(self.build_taxonomy(final_level, 'GreyNoise', name, count)) return {"taxonomies": taxonomies} From 9d7047423944825e41e6b2484fbf7fbd76a2a6c1 Mon Sep 17 00:00:00 2001 From: Daniel Browne Date: Sat, 21 Apr 2018 10:27:36 +0200 Subject: [PATCH 3/3] Adding examples to summary method docstring for clarity --- analyzers/GreyNoise/greynoise.py | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/analyzers/GreyNoise/greynoise.py b/analyzers/GreyNoise/greynoise.py index 7d83bd1..41a3644 100755 --- a/analyzers/GreyNoise/greynoise.py +++ b/analyzers/GreyNoise/greynoise.py @@ -66,6 +66,48 @@ class GreyNoiseAnalyzer(Analyzer): If there are multiple tags, use "entries" as the predicate Use the total count as the value Use the most malicious level found + + + Examples: + + + Input + { + "name": SCANNER1, + "intention": "" + } + Output + GreyNoise:SCANNER1 = 1 (info) + + + Input + { + "name": SCANNER1, + "intention": "malicious" + }, + { + "name": SCANNER1, + "intention": "benign" + } + Output + GreyNoise:SCANNER1 = 2 (malicious) + + + Input + { + "name": SCANNER1, + "intention": "" + }, + { + "name": SCANNER1, + "intention": "safe" + }, + { + "name": SCANNER2, + "intention": "" + } + Output + GreyNoise:entries = 3 (safe) """ try: