-
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.
* First draft for the tor analyzer * Moving HTTP request to private method * Add a TTL parameter to ignore to old nodes * Add cache mechhanism to avoid always downloading the same file * Add documentation and fix default value for client. If ttl is set to 0 then the last update status of the node won't be used. If cache.duration is set to 0 then no cache will be kept. * Add report templates * Bumping version to 1.0 * Add author to TorProject config file * Add license to analyzer * Set correct rights on .py files * use correct version number * Change report structure * Slight changes on reports * Fix boolean display in short report * Replace pyfscache by diskcache to ensure Python3 compatibility * Replace getParam() fucntion by get_param() * Respect contribution policy * Force usage of python3 when executing analyzer
- Loading branch information
Showing
6 changed files
with
174 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,15 @@ | ||
{ | ||
"name": "TorProject", | ||
"author": "Marc-André DOLL, STARC by EXAPROBE", | ||
"license": "AGPL-V3", | ||
"url": "https://github.com/CERT-BDF/Cortex-Analyzers", | ||
"version": "1.0", | ||
"baseConfig": "TorProject", | ||
"config": { | ||
"check_tlp": false, | ||
"max_tlp": 3 | ||
}, | ||
"description": "Query https://check.torproject.org/exit-addresses for TOR exit nodes IP addresses.", | ||
"dataTypeList": ["ip"], | ||
"command": "TorProject/tor_project_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,6 @@ | ||
cortexutils | ||
requests | ||
datetime | ||
python-dateutil | ||
pytz | ||
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,80 @@ | ||
import requests | ||
from datetime import datetime, timedelta | ||
from dateutil.parser import parse | ||
import pytz | ||
from diskcache import Cache | ||
|
||
|
||
class TorProjectClient: | ||
"""Simple client to query torproject.org for exit nodes. | ||
The client will download https://check.torproject.org/exit-addresses | ||
and check if a specified IP address is present in it. If that IP address | ||
is found it will check for its last update time and return a description | ||
of the node if its last update time is less than `ttl` seconds ago. | ||
:param ttl: Tor node will be kept only if its last update was | ||
less than `ttl` seconds ago. Ignored if `ttl` is 0 | ||
: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 torproject.org | ||
:type ttl: int | ||
:type cache_duration: int | ||
:type cache_root: str | ||
""" | ||
def __init__(self, ttl=86400, cache_duration=3600, | ||
cache_root='/tmp/cortex/tor_project'): | ||
self.session = requests.Session() | ||
self.delta = None | ||
self.cache = None | ||
if ttl > 0: | ||
self.delta = timedelta(seconds=ttl) | ||
if cache_duration > 0: | ||
self.cache = Cache(cache_root) | ||
self.cache_duration = cache_duration | ||
self.url = 'https://check.torproject.org/exit-addresses' | ||
|
||
__cache_key = __name__ + ':raw_data' | ||
|
||
def _get_raw_data(self): | ||
try: | ||
return self.cache['raw_data'] | ||
except(AttributeError, TypeError): | ||
return self.session.get(self.url).text | ||
except KeyError: | ||
self.cache.set( | ||
'raw_data', | ||
self.session.get(self.url).text, | ||
expire=self.cache_duration) | ||
return self.cache['raw_data'] | ||
|
||
def search_tor_node(self, ip): | ||
"""Lookup an IP address to check if it is a known tor exit node. | ||
:param ip: The IP address to lookup | ||
:type ip: str | ||
:return: Data relative to the tor node. If `ip`is a tor exit node | ||
it will contain a `node` key with the hash of the node and | ||
a `last_status` key with the last update time of the node. | ||
If `ip` is not a tor exit node, the function will return an | ||
empty dictionary. | ||
:rtype: dict | ||
""" | ||
data = {} | ||
tmp = {} | ||
present = datetime.utcnow().replace(tzinfo=pytz.utc) | ||
for line in self._get_raw_data().splitlines(): | ||
params = line.split(' ') | ||
if params[0] == 'ExitNode': | ||
tmp['node'] = params[1] | ||
elif params[0] == 'ExitAddress': | ||
tmp['last_status'] = params[2] + 'T' + params[3] + '+0000' | ||
last_status = parse(tmp['last_status']) | ||
if (self.delta is None or | ||
(present - last_status) < self.delta): | ||
data[params[1]] = tmp | ||
tmp = {} | ||
else: | ||
pass | ||
return data.get(ip, {}) |
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,41 @@ | ||
#!/usr/bin/env python3 | ||
from cortexutils.analyzer import Analyzer | ||
import tor_project | ||
|
||
|
||
class TorProjectAnalyzer(Analyzer): | ||
"""Cortex analyzer to query TorProject for exit nodes IP addresses""" | ||
def __init__(self): | ||
Analyzer.__init__(self) | ||
self.ttl = self.get_param('config.ttl', 86400) | ||
self.cache_duration = self.get_param('config.cache.duration', 3600) | ||
self.cache_root = self.get_param( | ||
'config.cache.root', '/tmp/cortex/tor_project' | ||
) | ||
|
||
self.client = tor_project.TorProjectClient( | ||
ttl=self.ttl, | ||
cache_duration=self.cache_duration, | ||
cache_root=self.cache_root | ||
) | ||
|
||
def summary(self, raw): | ||
taxonomies = [] | ||
level = 'info' | ||
value = False | ||
if ("node" in raw): | ||
level = 'suspicious' | ||
value = True | ||
taxonomies.append( | ||
self.build_taxonomy(level, 'TorProject', 'Node', value)) | ||
return {"taxonomies": taxonomies} | ||
|
||
def run(self): | ||
if self.data_type != 'ip': | ||
return self.error('Not an IP address') | ||
report = self.client.search_tor_node(self.get_data()) | ||
self.report(report) | ||
|
||
|
||
if __name__ == '__main__': | ||
TorProjectAnalyzer().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,29 @@ | ||
<!-- Node found --> | ||
<div class="panel panel-warning" ng-if="success && content.node"> | ||
<div class="panel-heading"> | ||
Tor Project - <strong>{{(artifact.data || artifact.attachment.name) | fang}}</strong> | ||
</div> | ||
<div class="panel-body"> | ||
Exit node found: {{content.node}} updated at {{content.last_status}} | ||
</div> | ||
</div> | ||
|
||
<!-- Empty result --> | ||
<div class="panel panel-success" ng-if="success && !content.node"> | ||
<div class="panel-heading"> | ||
Tor Project - <strong>{{(artifact.data || artifact.attachment.name) | fang}}</strong> | ||
</div> | ||
<div class="panel-body"> | ||
<b>Not a known exit node.</b> | ||
</div> | ||
</div> | ||
|
||
<!-- General error --> | ||
<div class="panel panel-danger" ng-if="!success"> | ||
<div class="panel-heading"> | ||
Tor Project - <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?'yes':'no'}} | ||
</span> |