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

Tor project analyzer #138

Merged
merged 20 commits into from
Dec 18, 2017
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
15 changes: 15 additions & 0 deletions analyzers/TorProject/TorProject.json
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"
}
6 changes: 6 additions & 0 deletions analyzers/TorProject/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cortexutils
requests
datetime
python-dateutil
pytz
diskcache
80 changes: 80 additions & 0 deletions analyzers/TorProject/tor_project.py
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, {})
41 changes: 41 additions & 0 deletions analyzers/TorProject/tor_project_analyzer.py
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()
29 changes: 29 additions & 0 deletions thehive-templates/TorProject_1_0/long.html
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>
3 changes: 3 additions & 0 deletions thehive-templates/TorProject_1_0/short.html
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>