chg: Improve popup, make sanejs a module, cache

pull/79/head
Raphaël Vinot 2020-05-19 17:47:55 +02:00
parent 72afeb5f81
commit c3bf87beca
6 changed files with 139 additions and 19 deletions

View File

@ -2,5 +2,8 @@
"VirusTotal": { "VirusTotal": {
"apikey": "KEY", "apikey": "KEY",
"autosubmit": false "autosubmit": false
},
"SaneJS": {
"enabled": true
} }
} }

View File

@ -19,13 +19,12 @@ from zipfile import ZipFile
from defang import refang # type: ignore from defang import refang # type: ignore
from har2tree import CrawledTree, Har2TreeError, HarFile from har2tree import CrawledTree, Har2TreeError, HarFile
from pysanejs import SaneJS
from redis import Redis from redis import Redis
from scrapysplashwrapper import crawl from scrapysplashwrapper import crawl
from .exceptions import NoValidHarFile from .exceptions import NoValidHarFile
from .helpers import get_homedir, get_socket_path, load_cookies, load_configs, safe_create_dir, get_email_template from .helpers import get_homedir, get_socket_path, load_cookies, load_configs, safe_create_dir, get_email_template
from .modules import VirusTotal from .modules import VirusTotal, SaneJavaScript
class Lookyloo(): class Lookyloo():
@ -50,17 +49,14 @@ class Lookyloo():
self.vt = VirusTotal(self.configs['modules']['VirusTotal']) self.vt = VirusTotal(self.configs['modules']['VirusTotal'])
if not self.vt.available: if not self.vt.available:
self.logger.warning('Unable to setup the VirusTotal module') self.logger.warning('Unable to setup the VirusTotal module')
if 'SaneJS' in self.configs['modules']:
self.sanejs = SaneJavaScript(self.configs['modules']['SaneJS'])
if not self.sanejs.available:
self.logger.warning('Unable to setup the SaneJS module')
if not self.redis.exists('cache_loaded'): if not self.redis.exists('cache_loaded'):
self._init_existing_dumps() self._init_existing_dumps()
# Try to reach sanejs
self.sanejs = SaneJS()
if not self.sanejs.is_up:
self.use_sane_js = False
else:
self.use_sane_js = True
def rebuild_cache(self) -> None: def rebuild_cache(self) -> None:
self.redis.flushdb() self.redis.flushdb()
self._init_existing_dumps() self._init_existing_dumps()
@ -312,11 +308,6 @@ class Lookyloo():
def get_capture(self, capture_dir: Path) -> BytesIO: def get_capture(self, capture_dir: Path) -> BytesIO:
return self._get_raw(capture_dir) return self._get_raw(capture_dir)
def sane_js_query(self, sha512: str) -> Dict[str, Any]:
if self.use_sane_js:
return self.sanejs.sha512(sha512)
return {'response': []}
def scrape(self, url: str, cookies_pseudofile: Optional[BufferedIOBase]=None, def scrape(self, url: str, cookies_pseudofile: Optional[BufferedIOBase]=None,
depth: int=1, listing: bool=True, user_agent: Optional[str]=None, depth: int=1, listing: bool=True, user_agent: Optional[str]=None,
perma_uuid: str=None, os: str=None, browser: str=None) -> Union[bool, str]: perma_uuid: str=None, os: str=None, browser: str=None) -> Union[bool, str]:

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import Dict, Any, Optional from typing import Dict, Any, Optional, List, Union
from datetime import date from datetime import date
import hashlib import hashlib
import json import json
@ -13,6 +13,65 @@ from .helpers import get_homedir
from .exceptions import ConfigError from .exceptions import ConfigError
import vt # type: ignore import vt # type: ignore
from pysanejs import SaneJS
class SaneJavaScript():
def __init__(self, config: Dict[str, Any]):
if not ('enabled' in config or config['enabled']):
self.available = False
return
self.client = SaneJS()
if not self.client.is_up:
self.available = False
return
self.available = True
self.storage_dir = get_homedir() / 'sanejs'
self.storage_dir.mkdir(parents=True, exist_ok=True)
def hashes_lookup(self, sha512: Union[List[str], str], force: bool=False) -> Optional[Dict[str, Any]]:
if isinstance(sha512, str):
hashes = [sha512]
else:
hashes = sha512
today_dir = self.storage_dir / date.today().isoformat()
today_dir.mkdir(parents=True, exist_ok=True)
sanejs_unknowns = today_dir / 'unknown'
unknown_hashes = []
if sanejs_unknowns.exists():
with sanejs_unknowns.open() as f:
unknown_hashes = [line.strip() for line in f.readlines()]
if force:
to_lookup = hashes
else:
to_lookup = [h for h in sha512 if (h not in unknown_hashes
and not (today_dir / h).exists())]
to_return = {}
for h in to_lookup:
response = self.client.sha512(h)
if 'error' in response:
# Server not ready
break
if 'response' in response and response['response']:
cached_path = today_dir / h
with cached_path.open('w') as f:
json.dump(response['response'], f)
to_return[h] = response['response']
else:
unknown_hashes.append(h)
for h in hashes:
cached_path = today_dir / h
if h in unknown_hashes or h in to_return:
continue
elif cached_path.exists():
with cached_path.open() as f:
to_return[h] = json.load(f)
return to_return
class VirusTotal(): class VirusTotal():

View File

@ -136,7 +136,40 @@ def hostnode_details_text(node_uuid: str):
@app.route('/tree/hostname_popup/<string:node_uuid>', methods=['GET']) @app.route('/tree/hostname_popup/<string:node_uuid>', methods=['GET'])
def hostnode_popup(node_uuid: str): def hostnode_popup(node_uuid: str):
return render_template('hostname_popup.html', hostname_uuid=node_uuid) with open(session["tree"], 'rb') as f:
ct = pickle.load(f)
hostnode = ct.root_hartree.get_host_node_by_uuid(node_uuid)
table_keys = {
'js': "/static/javascript.png",
'exe': "/static/exe.png",
'css': "/static/css.png",
'font': "/static/font.png",
'html': "/static/html.png",
'json': "/static/json.png",
'iframe': "/static/ifr.png",
'image': "/static/img.png",
'unknown_mimetype': "/static/wtf.png",
'video': "/static/video.png",
'request_cookie': "/static/cookie_read.png",
'response_cookie': "/static/cookie_received.png",
'redirect': "/static/redirect.png",
'redirect_to_nothing': "/static/cookie_in_url.png"
}
urls = []
if lookyloo.sanejs.available:
to_lookup = [url.body_hash for url in hostnode.urls if hasattr(url, 'body_hash')]
lookups = lookyloo.sanejs.hashes_lookup(to_lookup)
for url in hostnode.urls:
if lookyloo.sanejs.available and hasattr(url, 'body_hash') and url.body_hash in lookups:
url.add_feature('sane_js_details', lookups[url.body_hash])
# TODO: Do something with it.
urls.append(url)
return render_template('hostname_popup.html',
hostname_uuid=node_uuid,
hostname=hostnode.name,
urls=urls,
keys=table_keys)
@app.route('/tree/hostname/<string:node_uuid>', methods=['GET']) @app.route('/tree/hostname/<string:node_uuid>', methods=['GET'])

View File

@ -1,6 +1,6 @@
{% extends "main.html" %} {% extends "main.html" %}
{% block title %}Details for hostname {% endblock %} {% block title %}Details for {{ hostname }} {% endblock %}
{% block scripts %} {% block scripts %}
<script> <script>
@ -11,5 +11,40 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<button onclick="whereAmI()">Where am I</button> <center>
<h3>{{ hostname }}</h3>
<button type="button" class="btn btn-secondary" onclick="whereAmI()">Locate node on tree</button>
<a href="{{ url_for('hostnode_details_text', node_uuid=hostname_uuid) }}" class="btn btn-info" role="button">Get URLs as text</a>
</center>
<p>Click on the URL to get the content of the response</p>
<div class="table-responsive">
<table id="table" class="table">
<thead>
<tr>
<th>URL</th>
{% for alt, path in keys.items() %}
<th><img src="{{ path }}" alt="{{ alt }}" width="21" height="21"/></th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for url in urls %}
<tr>
<td style="white-space:nowrap">
<a href="{{ url_for('urlnode_details', node_uuid=url.uuid) }}">{{ url.name }}</a>
</td>
{% for key in keys.keys() %}
<td><br/>
{% if url[key] %}
X
{% else%}
-
{%endif%}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %} {% endblock %}

View File

@ -4,7 +4,6 @@
<dl class="row"> <dl class="row">
<dt class="col-sm-3">{{ key }}</dt> <dt class="col-sm-3">{{ key }}</dt>
<dd class="col-sm-3">{{ value }}</dd> <dd class="col-sm-3">{{ value }}</dd>
</center>
</dl> </dl>
{% endfor %} {% endfor %}
{% endif%} {% endif%}