-
Notifications
You must be signed in to change notification settings - Fork 385
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
Tor blutmagie #139
Merged
+221
−0
Merged
Tor blutmagie #139
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
7c282d2
Add TorBlutmagie analyzer
srilumpa 3c4f747
Add reports for TheHive usage
srilumpa 6dc740d
Properly escape artifacts in report
srilumpa f307340
Add documentation to class
srilumpa 105fea2
Merge branch 'hotfix/1.7.1'
jeromeleonard f6813d2
Merge branch 'master' into TorBlutmagie
srilumpa 98ef989
Remove dependency with pyfscache to ensure Python3 compatibility
srilumpa f97f984
Fix taxonomies building when no result returned from analyzer
srilumpa 72d1872
Set up cache key to be a private class variable instead of some magic…
srilumpa 0d86579
Fix getting data when cache is deactivated
srilumpa 06330ca
Respect contribution policy
srilumpa c4470c7
Force usage of python3 to execute analyzer
srilumpa 572c996
Fix long TheHive report
srilumpa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,15 @@ | ||
{ | ||
"name": "TorBlutmagie", | ||
"author": "Marc-André DOLL, STARC by EXAPROBE", | ||
"license": "AGPL-V3", | ||
"url": "https://github.com/CERT-BDF/Cortex-Analyzers", | ||
"version": "1.0", | ||
"baseConfig": "TorBlutmagie", | ||
"config": { | ||
"check_tlp": false, | ||
"max_tlp": 3 | ||
}, | ||
"description": "Query http://torstatus.blutmagie.de/query_export.php/Tor_query_EXPORT.csv for TOR exit nodes IP addresses or names.", | ||
"dataTypeList": ["ip", "domain", "fqdn"], | ||
"command": "TorBlutmagie/tor_blutmagie_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,3 @@ | ||
cortexutils | ||
requests | ||
diskcache |
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,110 @@ | ||
import requests | ||
import csv | ||
from diskcache import Cache | ||
|
||
|
||
class TorBlutmagieClient: | ||
"""Simple client to query torstatus.blutmagie.de for exit nodes. | ||
|
||
The client will download http://torstatus.blutmagie.de/query_export.php/Tor_query_EXPORT.csv | ||
and check if a specified IP address, FQDN or domain is present in it. | ||
It will cache the response for `cache_duration` seconds to avoid | ||
too much latency. | ||
|
||
:param cache_duration: Duration before refreshing the cache (in seconds). | ||
Ignored if `cache_duration` is 0. | ||
:param cache_root: Path where to store the cached file | ||
downloaded from torstatus.blutmagie.de | ||
:type cache_duration: int | ||
:type cache_root: str | ||
""" | ||
def __init__(self, cache_duration=3600, cache_root='/tmp/cortex/tor_project'): | ||
self.session = requests.Session() | ||
self.cache_duration = cache_duration | ||
if self.cache_duration > 0: | ||
self.cache = Cache(cache_root) | ||
self.url = 'http://torstatus.blutmagie.de/query_export.php/Tor_query_EXPORT.csv' | ||
|
||
__cache_key = __name__ + ':raw_data' | ||
|
||
def _get_raw_data(self): | ||
try: | ||
return self.cache[self.__cache_key] | ||
except (AttributeError, TypeError): | ||
return self.session.get(self.url).text.encode('utf-8') | ||
except KeyError: | ||
self.cache.set( | ||
self.__cache_key, | ||
self.session.get(self.url).text.encode('utf-8'), | ||
expire=self.cache_duration, read=True) | ||
return self.cache[self.__cache_key] | ||
|
||
def _get_data(self): | ||
return csv.DictReader( | ||
self._get_raw_data().decode('utf-8').splitlines(), | ||
delimiter=',') | ||
|
||
def _extract_fields(self, line): | ||
return { | ||
'hostname': line['Hostname'], | ||
'name': line['Router Name'], | ||
'country_code': line['Country Code'], | ||
'ip': line['IP Address'], | ||
'as_name': line['ASName'], | ||
'as_number': line['ASNumber'] | ||
} | ||
|
||
def _get_node_from_domain(self, domain): | ||
results = [] | ||
for line in self._get_data(): | ||
if domain.lower() in line['Hostname'].lower(): | ||
results.append(self._extract_fields(line)) | ||
return results | ||
|
||
def _get_node_from_fqdn(self, fqdn): | ||
results = [] | ||
for line in self._get_data(): | ||
if fqdn.lower() == line['Hostname'].lower(): | ||
results.append(self._extract_fields(line)) | ||
break | ||
return results | ||
|
||
def _get_node_from_ip(self, ip): | ||
results = [] | ||
for line in self._get_data(): | ||
if ip == line['IP Address']: | ||
results.append(self._extract_fields(line)) | ||
break | ||
return results | ||
|
||
def search_tor_node(self, data_type, data): | ||
"""Lookup an artifact to check if it is a known tor exit node. | ||
|
||
:param data_type: The artifact type. Must be one of 'ip', 'fqdn' | ||
or 'domain' | ||
:param data: The artifact to lookup | ||
:type data_type: str | ||
:type data: str | ||
:return: Data relative to the tor node. If the looked-up artifact is | ||
related to a tor exit node it will contain a `nodes` array. | ||
That array will contains a list of nodes containing the | ||
following keys: | ||
- name: name given to the router | ||
- ip: their IP address | ||
- hostname: Hostname of the router | ||
- country_code: ISO2 code of the country hosting the router | ||
- as_name: ASName registering the router | ||
- as_number: ASNumber registering the router | ||
Otherwise, `nodes` will be empty. | ||
:rtype: list | ||
""" | ||
results = [] | ||
if data_type == 'ip': | ||
results = self._get_node_from_ip(data) | ||
elif data_type == 'fqdn': | ||
results = self._get_node_from_fqdn(data) | ||
elif data_type == 'domain': | ||
results = self._get_node_from_domain(data) | ||
else: | ||
pass | ||
return {"nodes": results} |
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,45 @@ | ||
#!/usr/bin/env python3 | ||
from cortexutils.analyzer import Analyzer | ||
import tor_blutmagie | ||
|
||
|
||
class TorBlutmagieAnalyzer(Analyzer): | ||
"""Cortex analyzer to query TorBlutmagie for exit nodes IP addresses and/or names""" | ||
def __init__(self): | ||
Analyzer.__init__(self) | ||
self.cache_duration = self.getParam('config.cache.duration', 3600) | ||
self.cache_root = self.getParam( | ||
'config.cache.root', '/tmp/cortex/tor_project' | ||
) | ||
|
||
self.client = tor_blutmagie.TorBlutmagieClient( | ||
cache_duration=self.cache_duration, | ||
cache_root=self.cache_root | ||
) | ||
|
||
def summary(self, raw): | ||
taxonomies = [] | ||
if ('nodes' in raw): | ||
r = len(raw['nodes']) | ||
if r == 0 or r == 1: | ||
value = "{} node".format(r) | ||
else: | ||
value = "{} nodes".format(r) | ||
|
||
if r > 0: | ||
level = 'suspicious' | ||
else: | ||
level = 'info' | ||
taxonomies.append( | ||
self.build_taxonomy(level, 'TorBlutmagie', 'Node', value)) | ||
return {"taxonomies": taxonomies} | ||
|
||
def run(self): | ||
if self.data_type not in ['ip', 'domain', 'fqdn']: | ||
return self.error('Not an IP address, FQDN or domain name') | ||
report = self.client.search_tor_node(self.data_type, self.get_data()) | ||
self.report(report) | ||
|
||
|
||
if __name__ == '__main__': | ||
TorBlutmagieAnalyzer().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,45 @@ | ||
<div class="panel panel-warning" ng-if="success && content.nodes.length > 0"> | ||
<div class="panel-heading"> | ||
Tor nodes - {{artifact.data | fang}} | ||
</div> | ||
<div class="panel-body"> | ||
<div ng-repeat="n in content.nodes" class="panel panel-default"> | ||
<div class="panel-heading"> | ||
{{n.name}} | ||
</div> | ||
<div class="panel-body"> | ||
<dl class="dl-horizontal"> | ||
<dt>Address</dt> | ||
<dd>{{n.hostname | fang}} ({{n.ip | fang}})</dd> | ||
<dt>AS</dt> | ||
<dd>{{n.as_name}} ({{n.as_number}})</dd> | ||
<dt>Country</dt> | ||
<dd>{{n.country_code}}</dd> | ||
</dl> | ||
<span> | ||
<i class="fa fa-search"></i> | ||
<a ng-href="http://torstatus.blutmagie.de/cgi-bin/whois.pl?ip={{n.ip}}" target="_blank">WHOIS</a> | ||
</span> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div class="panel panel-info" ng-if="success && content.nodes.length == 0"> | ||
<div class="panel-heading"> | ||
Tor nodes - {{artifact.data | fang}} | ||
</div> | ||
<div class="panel-body"> | ||
<b>No matches.</b> | ||
</div> | ||
</div> | ||
|
||
<!-- General error --> | ||
<div class="panel panel-danger" ng-if="!success"> | ||
<div class="panel-heading"> | ||
<strong>{{(artifact.data || artifact.attachment.name) | fang}}</strong> | ||
</div> | ||
<div class="panel-body"> | ||
{{content.errorMessage}} | ||
</div> | ||
</div> |
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,3 @@ | ||
<span class="label" ng-repeat="t in content.taxonomies" ng-class="{'info': 'label-info', 'safe': 'label-success', 'suspicious': 'label-warning', 'malicious':'label-danger'}[t.level]"> | ||
{{t.namespace}}:{{t.predicate}}={{t.value}} | ||
</span> |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please switch the
<dt>
and<dd>
here: