Adrian Maraj 2024-05-02 11:33:41 +00:00 committed by GitHub
commit 9e66c82efb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 183 additions and 8 deletions

View File

@ -116,7 +116,7 @@ class AsyncCapture(AbstractManager):
if send_report:
self.lookyloo.send_mail(uuid, email=settings.get('email', ''),
comment=settings.get('comment'))
comment=settings.get('comment'), recipient_mail= settings.get("recipient_mail"))
lazy_cleanup = self.lookyloo.redis.pipeline()
if queue and self.lookyloo.redis.zscore('queues', queue):

View File

@ -0,0 +1,3 @@
{
"email" : "analyst@test.de"
}

View File

@ -57,6 +57,10 @@ def load_configs(path_to_config_files: str | Path | None=None) -> None:
for path in config_path.glob('*.json'):
with path.open() as _c:
configs[path.stem] = json.load(_c)
user_path = config_path / 'users'
for path in user_path.glob('*.json'):
with path.open() as _c:
configs[path.stem] = json.load(_c)
@lru_cache(64)

View File

@ -864,7 +864,7 @@ class Lookyloo():
return f"Malicious capture according to {len(modules)} module(s): {', '.join(modules)}"
def send_mail(self, capture_uuid: str, /, email: str='', comment: str | None=None) -> bool | dict[str, Any]:
def send_mail(self, capture_uuid: str, /, email: str='', comment: str | None=None, recipient_mail: str | None = None) -> bool | dict[str, Any]:
'''Send an email notification regarding a specific capture'''
if not get_config('generic', 'enable_mail_notification'):
return {"error": "Unable to send mail: mail notification disabled"}
@ -913,7 +913,7 @@ class Lookyloo():
msg['From'] = email_config['from']
if email:
msg['Reply-To'] = email
msg['To'] = email_config['to']
msg['To'] = email_config['to'] if not recipient_mail else recipient_mail
msg['Subject'] = email_config['subject']
body = get_email_template()
body = body.format(

6
poetry.lock generated
View File

@ -737,13 +737,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth
[[package]]
name = "filelock"
version = "3.13.4"
version = "3.14.0"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
{file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"},
{file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"},
{file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"},
{file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"},
]
[package.extras]

View File

@ -12,6 +12,7 @@ import json
import logging
import logging.config
import os
import re
import sys
import time
@ -41,7 +42,7 @@ from werkzeug.wrappers.response import Response as WerkzeugResponse
from lookyloo import Lookyloo, CaptureSettings, Indexing
from lookyloo.capturecache import CaptureCache
from lookyloo.default import get_config
from lookyloo.default import get_config, get_homedir
from lookyloo.exceptions import MissingUUID, NoValidHarFile
from lookyloo.helpers import get_taxonomies, UserAgents, load_cookies
@ -52,7 +53,7 @@ else:
all_timezones_set = available_timezones()
from .genericapi import api as generic_api
from .helpers import (User, build_users_table, get_secret_key,
from .helpers import (User, is_valid_username, build_users_table, get_secret_key,
load_user_from_request, src_request_ip, sri_load,
get_lookyloo_instance)
from .proxied import ReverseProxied
@ -106,6 +107,9 @@ def login() -> WerkzeugResponse | str | Response:
'''
username = request.form['username']
if not is_valid_username(username):
flash('User is not permitted.', 'error')
return redirect(url_for('login'))
users_table = build_users_table()
if username in users_table and check_password_hash(users_table[username]['password'], request.form['password']):
user = User()
@ -1636,6 +1640,50 @@ def capture_web() -> str | Response | WerkzeugResponse:
# render template
return _prepare_capture_template(user_ua=request.headers.get('User-Agent'))
@app.route('/simple_capture', methods=['GET','POST'])
@flask_login.login_required # type: ignore[misc]
def simple_capture() -> str | Response | WerkzeugResponse:
user = flask_login.current_user.get_id()
if not is_valid_username(user):
# Username has been manipulated
flash('User is not permitted.', 'error')
return redirect(url_for('submit_capture'))
if request.method == 'POST':
if not (request.form.get('url') or request.form.get('urls')):
flash('Invalid submission: please submit at least a URL.', 'error')
return render_template('simple_capture.html')
capture_query: CaptureSettings = {}
capture_query['listing'] = False
if request.form.get('auto_report'):
path = get_homedir() /'config'/ 'users' / (user + ".json")
if os.path.isfile(path):
email = get_config(user, 'email')
capture_query['auto_report'] = {"recipient_mail": email}
else:
capture_query['auto_report'] = True
if request.form.get('url'):
capture_query['url'] = request.form['url']
perma_uuid = lookyloo.enqueue_capture(capture_query, source='web', user=user,
authenticated=flask_login.current_user.is_authenticated)
time.sleep(2)
if perma_uuid:
flash('Recording is in progress and is reported automatically.', 'success')
return redirect(url_for('simple_capture'))
elif request.form.get('urls'):
for url in request.form['urls'].strip().split('\n'):
if not url:
continue
query = capture_query.copy()
query['url'] = url
new_capture_uuid = lookyloo.enqueue_capture(query, source='web', user=user,
authenticated=flask_login.current_user.is_authenticated)
if new_capture_uuid:
flash('Recording is in progress and is reported automatically.', 'success')
return redirect(url_for('simple_capture'))
# render template
return render_template('simple_capture.html')
@app.route('/cookies/<string:cookie_name>', methods=['GET'])
def cookies_name_detail(cookie_name: str) -> str:

View File

@ -5,6 +5,7 @@ from __future__ import annotations
import hashlib
import json
import os
import re
from functools import lru_cache
from pathlib import Path
@ -49,6 +50,8 @@ def load_user_from_request(request: Request) -> User | None:
return user
return None
def is_valid_username(username: str) -> bool:
return re.match("^[A-Za-z0-9]+$", username)
@lru_cache(64)
def build_keys_table() -> dict[str, str]:

View File

@ -83,6 +83,11 @@ $(document).ready(function () {
<a href="{{ url_for('submit_capture') }}">
<button class="new-capture-button btn btn-primary">Submit capture</button>
</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('simple_capture') }}">
<button class="new-capture-button btn btn-primary">Takedown process</button>
</a>
{% endif %}
<br><br>
{{ render_messages(container=True, dismissible=True) }}
</center>

View File

@ -0,0 +1,112 @@
{% extends "main.html" %}
{% from 'bootstrap5/utils.html' import render_messages %}
{% block title %}Capture{% endblock %}
{% block card %}
<meta property="og:title" content="Lookyloo" />
<meta property="og:type" content="website"/>
<meta
property="og:description"
content="Lookyloo captures websites and let you investigate them."
/>
<meta
property="og:image"
content="https://{{public_domain}}{{ url_for('static', filename='lookyloo.jpeg') }}"
/>
<meta
property="og:url"
content="https://{{public_domain}}"
/>
<meta name="twitter:card" content="summary_large_image">
{% endblock %}
{% block content %}
<div class="container">
<center>
<a href="{{ url_for('index') }}" title="Go back to index">
<img src="{{ url_for('static', filename='lookyloo.jpeg') }}"
alt="Lookyloo" width="25%">
</a>
</center>
{{ render_messages(container=True, dismissible=True) }}
<form role="form" action="{{ url_for('simple_capture') }}" method=post enctype=multipart/form-data>
<div class="row mb-3">
<div class="col-sm-10">
<div class="form-check">
<input class="form-check-input" id="auto_report" type="checkbox" name="auto_report" checked=checked>
<label for="auto_report" class="form-check-label">Auto report</label>
</div>
</div>
</div>
<!-- Submission type -->
<div class="tab-content" id="nav-tabContent">
</br>
<div class="tab-pane fade show active" id="nav-url" role="tabpanel" aria-labelledby="nav-url-tab">
<div class="row input-group mb-3">
<label for="singleCaptureField" class="col-sm-1 col-form-label">URL(s):</label>
<input type="text" class="form-control col-auto" name="url" id=singleCaptureField
placeholder="URL to capture" value="{{predefined_url_to_capture}}" required>
<textarea class="form-control col-auto d-none" placeholder="URLs to capture, one per line"
name="urls" id=multipleCapturesField></textarea>
<span class="col-sm-2 input-group-text">
<div class="form-check">
<input class="form-check-input" name="multipleCaptures" id="multipleCaptures" type="checkbox"
value="" aria-label="tick to enable multiple captures">
<label for="multipleCaptures" class="form-check-label">Multiple captures</label>
</div>
</span>
</div>
</div>
</div>
<hr>
<center>
</br>
<button type="submit" class="new-capture-button btn btn-primary" id="btn-looking">Submit!</button>
</center>
</form>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script src='{{ url_for('static', filename='capture.js') }}'
integrity="{{get_sri('static', 'capture.js')}}"
crossorigin="anonymous"></script>
<script>
$('#nav-url-tab').on('click', function(e) {
document.getElementById("singleCaptureField").required = true;
document.getElementById("document").required = false;
$("#singleCaptureField").removeClass("d-none");
document.getElementById('multipleCaptures').checked = false;
$("#multipleCapturesField").addClass("d-none");
});
$('#nav-doc-tab').on('click', function(e) {
document.getElementById("document").required = true;
document.getElementById("multipleCapturesField").required = false;
document.getElementById("singleCaptureField").required = false;
});
</script>
<script>
$('#multipleCaptures').on('click', function(e) {
if (document.getElementById('multipleCaptures').checked == true) {
document.getElementById('singleCaptureField').value = '';
$("#singleCaptureField").addClass("d-none");
document.getElementById("singleCaptureField").required = false;
$("#multipleCapturesField").removeClass("d-none");
document.getElementById("multipleCapturesField").required = true;
}
else {
document.getElementById('multipleCapturesField').value = '';
$("#singleCaptureField").removeClass("d-none");
document.getElementById("singleCaptureField").required = true;
$("#multipleCapturesField").addClass("d-none");
document.getElementById("multipleCapturesField").required = false;
}
})
</script>
{% endblock %}