mirror of https://github.com/CIRCL/lookyloo
chg: Complete rework of the login system, add UI for MISP Push
parent
13e1614f5b
commit
39dd2021dd
|
@ -953,7 +953,8 @@ class Lookyloo():
|
||||||
obj.comment = f'Redirect {nb}'
|
obj.comment = f'Redirect {nb}'
|
||||||
self.__misp_add_ips_to_URLObject(obj, ct.root_hartree.hostname_tree)
|
self.__misp_add_ips_to_URLObject(obj, ct.root_hartree.hostname_tree)
|
||||||
redirects.append(obj)
|
redirects.append(obj)
|
||||||
obj.comment = f'Last redirect ({nb})'
|
if redirects:
|
||||||
|
redirects[-1].comment = f'Last redirect ({nb})'
|
||||||
|
|
||||||
if redirects:
|
if redirects:
|
||||||
prec_object = initial_url
|
prec_object = initial_url
|
||||||
|
@ -977,7 +978,7 @@ class Lookyloo():
|
||||||
|
|
||||||
screenshot: MISPAttribute = event.add_attribute('attachment', 'screenshot_landing_page.png', data=self.get_screenshot(capture_uuid), disable_correlation=True) # type: ignore
|
screenshot: MISPAttribute = event.add_attribute('attachment', 'screenshot_landing_page.png', data=self.get_screenshot(capture_uuid), disable_correlation=True) # type: ignore
|
||||||
try:
|
try:
|
||||||
fo = FileObject(pseudofile=ct.root_hartree.rendered_node.body, filename='body_response.html')
|
fo = FileObject(pseudofile=ct.root_hartree.rendered_node.body, filename=ct.root_hartree.rendered_node.filename)
|
||||||
fo.comment = 'Content received for the final redirect (before rendering)'
|
fo.comment = 'Content received for the final redirect (before rendering)'
|
||||||
fo.add_reference(event.objects[-1], 'loaded-by', 'URL loading that content')
|
fo.add_reference(event.objects[-1], 'loaded-by', 'URL loading that content')
|
||||||
fo.add_reference(screenshot, 'rendered-as', 'Screenshot of the page')
|
fo.add_reference(screenshot, 'rendered-as', 'Screenshot of the page')
|
||||||
|
|
|
@ -44,11 +44,14 @@ class MISP():
|
||||||
self.enable_lookup = True
|
self.enable_lookup = True
|
||||||
if config.get('enable_push'):
|
if config.get('enable_push'):
|
||||||
self.enable_push = True
|
self.enable_push = True
|
||||||
self.default_tags: List[str] = config.get('default_tags') # type: ignore
|
self.default_tags: List[str] = config.get('default_tags') # type: ignore
|
||||||
self.auto_publish = config.get('auto_publish')
|
self.auto_publish = config.get('auto_publish')
|
||||||
self.storage_dir_misp = get_homedir() / 'misp'
|
self.storage_dir_misp = get_homedir() / 'misp'
|
||||||
self.storage_dir_misp.mkdir(parents=True, exist_ok=True)
|
self.storage_dir_misp.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def get_fav_tags(self):
|
||||||
|
return self.client.tags(pythonify=True, favouritesOnly=1)
|
||||||
|
|
||||||
def push(self, event: MISPEvent) -> Union[MISPEvent, Dict]:
|
def push(self, event: MISPEvent) -> Union[MISPEvent, Dict]:
|
||||||
if self.available and self.enable_push:
|
if self.available and self.enable_push:
|
||||||
for tag in self.default_tags:
|
for tag in self.default_tags:
|
||||||
|
|
|
@ -280,9 +280,9 @@ docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-
|
||||||
dotenv = ["python-dotenv"]
|
dotenv = ["python-dotenv"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flask-httpauth"
|
name = "flask-login"
|
||||||
version = "4.2.0"
|
version = "0.5.0"
|
||||||
description = "Basic and Digest HTTP authentication for Flask routes"
|
description = "User session management for Flask"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
@ -744,7 +744,7 @@ requests = ">=2.22.0,<3.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pymisp"
|
name = "pymisp"
|
||||||
version = "2.4.137.3"
|
version = "2.4.137.4"
|
||||||
description = "Python API for MISP."
|
description = "Python API for MISP."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -1147,7 +1147,7 @@ misp = ["python-magic", "pydeep"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "b3e7ae942355125f62268270e7888093f64b33035d04d574d49ad39189c48f40"
|
content-hash = "51d0bb7529658d3b5323d7c87d7676c1126d88c2b927af77a91c426f842a9249"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
aiohttp = [
|
aiohttp = [
|
||||||
|
@ -1354,9 +1354,9 @@ flask = [
|
||||||
{file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"},
|
{file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"},
|
||||||
{file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"},
|
{file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"},
|
||||||
]
|
]
|
||||||
flask-httpauth = [
|
flask-login = [
|
||||||
{file = "Flask-HTTPAuth-4.2.0.tar.gz", hash = "sha256:8c7e49e53ce7dc14e66fe39b9334e4b7ceb8d0b99a6ba1c3562bb528ef9da84a"},
|
{file = "Flask-Login-0.5.0.tar.gz", hash = "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b"},
|
||||||
{file = "Flask_HTTPAuth-4.2.0-py2.py3-none-any.whl", hash = "sha256:3fcedb99a03985915335a38c35bfee6765cbd66d7f46440fa3b42ae94a90fac7"},
|
{file = "Flask_Login-0.5.0-py2.py3-none-any.whl", hash = "sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0"},
|
||||||
]
|
]
|
||||||
gunicorn = [
|
gunicorn = [
|
||||||
{file = "gunicorn-20.0.4-py2.py3-none-any.whl", hash = "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"},
|
{file = "gunicorn-20.0.4-py2.py3-none-any.whl", hash = "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"},
|
||||||
|
@ -1724,8 +1724,8 @@ pylookyloo = [
|
||||||
{file = "pylookyloo-1.3.tar.gz", hash = "sha256:3996798b68203c1ebfdb4744d029a2c741e3a7d4e5d98541afd0c9b4a20eee86"},
|
{file = "pylookyloo-1.3.tar.gz", hash = "sha256:3996798b68203c1ebfdb4744d029a2c741e3a7d4e5d98541afd0c9b4a20eee86"},
|
||||||
]
|
]
|
||||||
pymisp = [
|
pymisp = [
|
||||||
{file = "pymisp-2.4.137.3-py3-none-any.whl", hash = "sha256:433a58cd5d6ec8bee9394c43bb679dc0422bec68450f7642f7fd51b870e97118"},
|
{file = "pymisp-2.4.137.4-py3-none-any.whl", hash = "sha256:814c3e5cd3218ba885edab7b8808f45dbe16bbfccb3cd9d19bf062b1ced70fc0"},
|
||||||
{file = "pymisp-2.4.137.3.tar.gz", hash = "sha256:d3a8074e3276a698d25bcd56f4ce3913d375b05a04fb36f4c8cfe732f4aaefd9"},
|
{file = "pymisp-2.4.137.4.tar.gz", hash = "sha256:ea029360d7e76646403571b479c8208a678c8b51b9e683d51b0ce95d6ebe3274"},
|
||||||
]
|
]
|
||||||
pyopenssl = [
|
pyopenssl = [
|
||||||
{file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"},
|
{file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"},
|
||||||
|
|
|
@ -42,7 +42,6 @@ bootstrap-flask = "^1.5.1"
|
||||||
cloudscraper = "^1.2.56"
|
cloudscraper = "^1.2.56"
|
||||||
defang = "^0.5.3"
|
defang = "^0.5.3"
|
||||||
vt-py = "^0.6.1"
|
vt-py = "^0.6.1"
|
||||||
Flask-HTTPAuth = "^4.2.0"
|
|
||||||
pyeupi = "^1.0"
|
pyeupi = "^1.0"
|
||||||
scrapysplashwrapper = "^1.3"
|
scrapysplashwrapper = "^1.3"
|
||||||
pysanejs = "^1.3"
|
pysanejs = "^1.3"
|
||||||
|
@ -56,6 +55,7 @@ python-magic = {version = "^0.4.18", optional = true}
|
||||||
pydeep = {version = "^0.4", optional = true}
|
pydeep = {version = "^0.4", optional = true}
|
||||||
Pillow = "^8.1.0"
|
Pillow = "^8.1.0"
|
||||||
lief = "^0.11.0"
|
lief = "^0.11.0"
|
||||||
|
Flask-Login = "^0.5.0"
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
misp = ['python-magic', 'pydeep']
|
misp = ['python-magic', 'pydeep']
|
||||||
|
|
|
@ -16,7 +16,7 @@ import hashlib
|
||||||
|
|
||||||
from flask import Flask, render_template, request, send_file, redirect, url_for, Response, flash, jsonify
|
from flask import Flask, render_template, request, send_file, redirect, url_for, Response, flash, jsonify
|
||||||
from flask_bootstrap import Bootstrap # type: ignore
|
from flask_bootstrap import Bootstrap # type: ignore
|
||||||
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth # type: ignore
|
import flask_login # type: ignore
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
from pymisp import MISPEvent
|
from pymisp import MISPEvent
|
||||||
|
@ -44,9 +44,8 @@ app.config['SESSION_COOKIE_NAME'] = 'lookyloo'
|
||||||
app.debug = False
|
app.debug = False
|
||||||
|
|
||||||
# Auth stuff
|
# Auth stuff
|
||||||
basic_auth = HTTPBasicAuth()
|
login_manager = flask_login.LoginManager()
|
||||||
token_auth = HTTPTokenAuth('LookylooToken')
|
login_manager.init_app(app)
|
||||||
auth = MultiAuth(basic_auth, token_auth)
|
|
||||||
try:
|
try:
|
||||||
# Use legacy user mgmt
|
# Use legacy user mgmt
|
||||||
users = get_config('generic', 'cache_clean_user')
|
users = get_config('generic', 'cache_clean_user')
|
||||||
|
@ -79,17 +78,61 @@ for username, authstuff in users_table.items():
|
||||||
keys_table[authstuff['authkey']] = username
|
keys_table[authstuff['authkey']] = username
|
||||||
|
|
||||||
|
|
||||||
@basic_auth.verify_password
|
class User(flask_login.UserMixin):
|
||||||
def verify_password(username, password):
|
pass
|
||||||
if users_table.get(username):
|
|
||||||
if check_password_hash(users_table['username']['password'], password):
|
|
||||||
return username
|
|
||||||
|
|
||||||
|
|
||||||
@token_auth.verify_token
|
@login_manager.user_loader
|
||||||
def verify_token(token):
|
def user_loader(username):
|
||||||
if token in keys_table:
|
if username not in users_table:
|
||||||
return keys_table[token]
|
return None
|
||||||
|
user = User()
|
||||||
|
user.id = username
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.request_loader
|
||||||
|
def load_user_from_request(request):
|
||||||
|
api_key = request.headers.get('Authorization')
|
||||||
|
if not api_key:
|
||||||
|
return None
|
||||||
|
user = User()
|
||||||
|
api_key = api_key.strip()
|
||||||
|
if api_key in keys_table:
|
||||||
|
user.id = keys_table[api_key]
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
if request.method == 'GET':
|
||||||
|
return '''
|
||||||
|
<form action='login' method='POST'>
|
||||||
|
<input type='text' name='username' id='username' placeholder='username'/>
|
||||||
|
<input type='password' name='password' id='password' placeholder='password'/>
|
||||||
|
<input type='submit' name='submit'/>
|
||||||
|
</form>
|
||||||
|
'''
|
||||||
|
|
||||||
|
username = request.form['username']
|
||||||
|
if username in users_table and check_password_hash(users_table[username]['password'], request.form['password']):
|
||||||
|
user = User()
|
||||||
|
user.id = username
|
||||||
|
flask_login.login_user(user)
|
||||||
|
flash(f'Logged in as: {flask_login.current_user.id}', 'success')
|
||||||
|
else:
|
||||||
|
flash(f'Unable to login as: {username}', 'error')
|
||||||
|
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
@flask_login.login_required
|
||||||
|
def logout():
|
||||||
|
flask_login.logout_user()
|
||||||
|
flash('Successfully logged out.', 'success')
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
|
@ -212,7 +255,7 @@ def hostnode_popup(tree_uuid: str, node_uuid: str):
|
||||||
# ##### Tree level Methods #####
|
# ##### Tree level Methods #####
|
||||||
|
|
||||||
@app.route('/tree/<string:tree_uuid>/rebuild')
|
@app.route('/tree/<string:tree_uuid>/rebuild')
|
||||||
@auth.login_required
|
@flask_login.login_required
|
||||||
def rebuild_tree(tree_uuid: str):
|
def rebuild_tree(tree_uuid: str):
|
||||||
try:
|
try:
|
||||||
lookyloo.remove_pickle(tree_uuid)
|
lookyloo.remove_pickle(tree_uuid)
|
||||||
|
@ -361,7 +404,7 @@ def export(tree_uuid: str):
|
||||||
|
|
||||||
|
|
||||||
@app.route('/tree/<string:tree_uuid>/hide', methods=['GET'])
|
@app.route('/tree/<string:tree_uuid>/hide', methods=['GET'])
|
||||||
@auth.login_required
|
@flask_login.login_required
|
||||||
def hide_capture(tree_uuid: str):
|
def hide_capture(tree_uuid: str):
|
||||||
lookyloo.hide_capture(tree_uuid)
|
lookyloo.hide_capture(tree_uuid)
|
||||||
return redirect(url_for('tree', tree_uuid=tree_uuid))
|
return redirect(url_for('tree', tree_uuid=tree_uuid))
|
||||||
|
@ -383,6 +426,7 @@ def send_mail(tree_uuid: str):
|
||||||
email = ''
|
email = ''
|
||||||
comment: str = request.form.get('comment') if request.form.get('comment') else '' # type: ignore
|
comment: str = request.form.get('comment') if request.form.get('comment') else '' # type: ignore
|
||||||
lookyloo.send_mail(tree_uuid, email, comment)
|
lookyloo.send_mail(tree_uuid, email, comment)
|
||||||
|
flash("Email notification sent", 'success')
|
||||||
return redirect(url_for('tree', tree_uuid=tree_uuid))
|
return redirect(url_for('tree', tree_uuid=tree_uuid))
|
||||||
|
|
||||||
|
|
||||||
|
@ -426,7 +470,6 @@ def tree(tree_uuid: str, node_uuid: Optional[str]=None):
|
||||||
except IndexError as e:
|
except IndexError as e:
|
||||||
print(e)
|
print(e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return render_template('tree.html', tree_json=ct.to_json(),
|
return render_template('tree.html', tree_json=ct.to_json(),
|
||||||
start_time=ct.start_time.isoformat(),
|
start_time=ct.start_time.isoformat(),
|
||||||
user_agent=ct.user_agent, root_url=ct.root_url,
|
user_agent=ct.user_agent, root_url=ct.root_url,
|
||||||
|
@ -437,6 +480,7 @@ def tree(tree_uuid: str, node_uuid: Optional[str]=None):
|
||||||
enable_context_by_users=enable_context_by_users,
|
enable_context_by_users=enable_context_by_users,
|
||||||
enable_categorization=enable_categorization,
|
enable_categorization=enable_categorization,
|
||||||
enable_bookmark=enable_bookmark,
|
enable_bookmark=enable_bookmark,
|
||||||
|
misp_push=lookyloo.misp.enable_push,
|
||||||
blur_screenshot=blur_screenshot, urlnode_uuid=hostnode_to_highlight,
|
blur_screenshot=blur_screenshot, urlnode_uuid=hostnode_to_highlight,
|
||||||
auto_trigger_modules=auto_trigger_modules,
|
auto_trigger_modules=auto_trigger_modules,
|
||||||
has_redirects=True if cache.redirects else False)
|
has_redirects=True if cache.redirects else False)
|
||||||
|
@ -446,7 +490,7 @@ def tree(tree_uuid: str, node_uuid: Optional[str]=None):
|
||||||
|
|
||||||
|
|
||||||
@app.route('/tree/<string:tree_uuid>/mark_as_legitimate', methods=['POST'])
|
@app.route('/tree/<string:tree_uuid>/mark_as_legitimate', methods=['POST'])
|
||||||
@auth.login_required
|
@flask_login.login_required
|
||||||
def mark_as_legitimate(tree_uuid: str):
|
def mark_as_legitimate(tree_uuid: str):
|
||||||
if request.data:
|
if request.data:
|
||||||
legitimate_entries = request.get_json(force=True)
|
legitimate_entries = request.get_json(force=True)
|
||||||
|
@ -502,7 +546,7 @@ def index():
|
||||||
|
|
||||||
|
|
||||||
@app.route('/hidden', methods=['GET'])
|
@app.route('/hidden', methods=['GET'])
|
||||||
@auth.login_required
|
@flask_login.login_required
|
||||||
def index_hidden():
|
def index_hidden():
|
||||||
return index_generic(show_hidden=True)
|
return index_generic(show_hidden=True)
|
||||||
|
|
||||||
|
@ -538,14 +582,14 @@ def categories():
|
||||||
|
|
||||||
|
|
||||||
@app.route('/rebuild_all')
|
@app.route('/rebuild_all')
|
||||||
@auth.login_required
|
@flask_login.login_required
|
||||||
def rebuild_all():
|
def rebuild_all():
|
||||||
lookyloo.rebuild_all()
|
lookyloo.rebuild_all()
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/rebuild_cache')
|
@app.route('/rebuild_cache')
|
||||||
@auth.login_required
|
@flask_login.login_required
|
||||||
def rebuild_cache():
|
def rebuild_cache():
|
||||||
lookyloo.rebuild_cache()
|
lookyloo.rebuild_cache()
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
@ -733,7 +777,7 @@ def hashes_urlnode(tree_uuid: str, node_uuid: str):
|
||||||
|
|
||||||
|
|
||||||
@app.route('/tree/<string:tree_uuid>/url/<string:node_uuid>/add_context', methods=['POST'])
|
@app.route('/tree/<string:tree_uuid>/url/<string:node_uuid>/add_context', methods=['POST'])
|
||||||
@auth.login_required
|
@flask_login.login_required
|
||||||
def add_context(tree_uuid: str, node_uuid: str):
|
def add_context(tree_uuid: str, node_uuid: str):
|
||||||
if not enable_context_by_users:
|
if not enable_context_by_users:
|
||||||
return redirect(url_for('ressources'))
|
return redirect(url_for('ressources'))
|
||||||
|
@ -766,25 +810,43 @@ def add_context(tree_uuid: str, node_uuid: str):
|
||||||
return redirect(url_for('ressources'))
|
return redirect(url_for('ressources'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/tree/<string:tree_uuid>/misp_push', methods=['GET'])
|
@app.route('/tree/<string:tree_uuid>/misp_push', methods=['GET', 'POST'])
|
||||||
@auth.login_required
|
@flask_login.login_required
|
||||||
def web_misp_push(tree_uuid: str):
|
def web_misp_push_view(tree_uuid: str):
|
||||||
|
error = False
|
||||||
if not lookyloo.misp.available:
|
if not lookyloo.misp.available:
|
||||||
flash('MISP module not available.', 'error')
|
flash('MISP module not available.', 'error')
|
||||||
|
error = True
|
||||||
elif not lookyloo.misp.enable_push:
|
elif not lookyloo.misp.enable_push:
|
||||||
flash('Push not enabled in MISP module.', 'error')
|
flash('Push not enabled in MISP module.', 'error')
|
||||||
|
error = True
|
||||||
else:
|
else:
|
||||||
event = lookyloo.misp_export(tree_uuid)
|
event = lookyloo.misp_export(tree_uuid)
|
||||||
if isinstance(event, dict):
|
if isinstance(event, dict):
|
||||||
flash(f'Unable to generate the MISP export: {event}', 'error')
|
flash(f'Unable to generate the MISP export: {event}', 'error')
|
||||||
else:
|
error = True
|
||||||
event = lookyloo.misp.push(event)
|
if error:
|
||||||
if isinstance(event, MISPEvent):
|
return redirect(url_for('tree', tree_uuid=tree_uuid))
|
||||||
flash(f'MISP event {event.id} created on {lookyloo.misp.client.root_url}', 'success')
|
|
||||||
else:
|
|
||||||
flash(f'Unable to create event: {event}', 'error')
|
|
||||||
|
|
||||||
return redirect(url_for('tree', tree_uuid=tree_uuid))
|
if request.method == 'POST':
|
||||||
|
# event is a MISPEvent at this point
|
||||||
|
# Submit the event
|
||||||
|
tags = request.form.getlist('tags')
|
||||||
|
for tag in tags:
|
||||||
|
event.add_tag(tag) # type: ignore
|
||||||
|
event = lookyloo.misp.push(event) # type: ignore
|
||||||
|
if isinstance(event, MISPEvent):
|
||||||
|
flash(f'MISP event {event.id} created on {lookyloo.misp.client.root_url}', 'success')
|
||||||
|
else:
|
||||||
|
flash(f'Unable to create event: {event}', 'error')
|
||||||
|
return redirect(url_for('tree', tree_uuid=tree_uuid))
|
||||||
|
|
||||||
|
fav_tags = lookyloo.misp.get_fav_tags()
|
||||||
|
|
||||||
|
return render_template('misp_push_view.html', tree_uuid=tree_uuid,
|
||||||
|
event=event, fav_tags=fav_tags,
|
||||||
|
auto_publish=lookyloo.misp.auto_publish,
|
||||||
|
default_tags=lookyloo.misp.default_tags)
|
||||||
|
|
||||||
|
|
||||||
# Query API
|
# Query API
|
||||||
|
@ -793,9 +855,9 @@ def web_misp_push(tree_uuid: str):
|
||||||
def json_get_token():
|
def json_get_token():
|
||||||
auth = request.get_json(force=True)
|
auth = request.get_json(force=True)
|
||||||
if 'username' in auth and 'password' in auth: # Expected keys in json
|
if 'username' in auth and 'password' in auth: # Expected keys in json
|
||||||
username = verify_password(auth['username'], auth['password'])
|
if (auth['username'] in users_table
|
||||||
if username == auth['username']:
|
and check_password_hash(users_table[auth['username']]['password'], auth['password'])):
|
||||||
return jsonify({'authkey': users_table[username]['authkey']})
|
return jsonify({'authkey': users_table[auth['username']]['authkey']})
|
||||||
return jsonify({'error': 'User/Password invalid.'})
|
return jsonify({'error': 'User/Password invalid.'})
|
||||||
|
|
||||||
|
|
||||||
|
@ -830,7 +892,7 @@ def misp_export(tree_uuid: str):
|
||||||
|
|
||||||
|
|
||||||
@app.route('/json/<string:tree_uuid>/misp_push', methods=['GET'])
|
@app.route('/json/<string:tree_uuid>/misp_push', methods=['GET'])
|
||||||
@auth.login_required
|
@flask_login.login_required
|
||||||
def misp_push(tree_uuid: str):
|
def misp_push(tree_uuid: str):
|
||||||
to_return: Dict = {}
|
to_return: Dict = {}
|
||||||
if not lookyloo.misp.available:
|
if not lookyloo.misp.available:
|
||||||
|
|
|
@ -14,6 +14,12 @@
|
||||||
stroke-width: 2px;
|
stroke-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flashed-messages {
|
||||||
|
position: fixed;
|
||||||
|
top: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
#menu_container {
|
#menu_container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<div>
|
||||||
|
<p>Event to push: {{event.info}}</p>
|
||||||
|
<p>Auto Publish: {{auto_publish}}</p>
|
||||||
|
<p>Default tags: {{', '.join(default_tags)}}</p>
|
||||||
|
<form role="form" action="{{ url_for('web_misp_push_view', tree_uuid=tree_uuid) }}" method=post enctype=multipart/form-data>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="tags" class="col-sm-2 col-form-label">Available tags:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select class="form-control" name="tags" id="tags" multiple>
|
||||||
|
{% for tag in fav_tags %}
|
||||||
|
<option value="{{ tag.name }}">{{ tag.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-info" id="btn-misp-push">Push to MISP</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -65,6 +65,13 @@
|
||||||
modal.find('.modal-body').load(button.data("remote"));
|
modal.find('.modal-body').load(button.data("remote"));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
$('#mispPushModal').on('show.bs.modal', function(e) {
|
||||||
|
var button = $(e.relatedTarget);
|
||||||
|
var modal = $(this);
|
||||||
|
modal.find('.modal-body').load(button.data("remote"));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
{% if urlnode_uuid %}
|
{% if urlnode_uuid %}
|
||||||
|
@ -146,6 +153,12 @@
|
||||||
data-toggle="modal" data-target="#categoriesModal" role="button">Manage categories</a>
|
data-toggle="modal" data-target="#categoriesModal" role="button">Manage categories</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if current_user.is_authenticated and misp_push%}
|
||||||
|
<li>
|
||||||
|
<a href="#mispPushModal" data-remote="{{ url_for('web_misp_push_view', tree_uuid=tree_uuid) }}"
|
||||||
|
data-toggle="modal" data-target="#mispPushModal" role="button">Prepare push to MISP</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a href="#statsModal" data-remote="{{ url_for('stats', tree_uuid=tree_uuid) }}"
|
<a href="#statsModal" data-remote="{{ url_for('stats', tree_uuid=tree_uuid) }}"
|
||||||
data-toggle="modal" data-target="#statsModal" role="button">Show Statistics</a>
|
data-toggle="modal" data-target="#statsModal" role="button">Show Statistics</a>
|
||||||
|
@ -312,6 +325,25 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="mispPushModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-xl" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="mispPushModalLabel">MISP Push</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
... loading MISP Push view ...
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="screenshotModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="screenshotModal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog modal-xl" role="document">
|
<div class="modal-dialog modal-xl" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|
Loading…
Reference in New Issue