Validate urls in `element` and `window` endpoints

Domains were previously not validated before being handled, leading to a
potential scenario where someone could pass something like
"element_url=127.0.0.1:<port>/<resource>" to access other resources on a
machine running Whoogle. This change ensures that the resource used in
both endpoints is a valid domain.

This also includes validation of config names to prevent names from
including path values such as "../../(etc)".
main
Ben Busby 2023-09-13 15:45:30 -06:00
parent 8830615abc
commit 3a2e0b262e
No known key found for this signature in database
GPG Key ID: B9B7231E01D924A1
1 changed files with 26 additions and 7 deletions

View File

@ -4,8 +4,10 @@ import io
import json import json
import os import os
import pickle import pickle
import re
import urllib.parse as urlparse import urllib.parse as urlparse
import uuid import uuid
import validators
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import wraps from functools import wraps
@ -420,13 +422,18 @@ def config():
config_disabled = ( config_disabled = (
app.config['CONFIG_DISABLE'] or app.config['CONFIG_DISABLE'] or
not valid_user_session(session)) not valid_user_session(session))
name = ''
if 'name' in request.args:
name = os.path.normpath(request.args.get('name'))
if not re.match(r'^[A-Za-z0-9_.+-]+$', name):
return make_response('Invalid config name', 400)
if request.method == 'GET': if request.method == 'GET':
return json.dumps(g.user_config.__dict__) return json.dumps(g.user_config.__dict__)
elif request.method == 'PUT' and not config_disabled: elif request.method == 'PUT' and not config_disabled:
if 'name' in request.args: if name:
config_pkl = os.path.join( config_pkl = os.path.join(app.config['CONFIG_PATH'], name)
app.config['CONFIG_PATH'],
request.args.get('name'))
session['config'] = (pickle.load(open(config_pkl, 'rb')) session['config'] = (pickle.load(open(config_pkl, 'rb'))
if os.path.exists(config_pkl) if os.path.exists(config_pkl)
else session['config']) else session['config'])
@ -444,7 +451,7 @@ def config():
config_data, config_data,
open(os.path.join( open(os.path.join(
app.config['CONFIG_PATH'], app.config['CONFIG_PATH'],
request.args.get('name')), 'wb')) name), 'wb'))
session['config'] = config_data session['config'] = config_data
return redirect(config_data['url']) return redirect(config_data['url'])
@ -463,6 +470,8 @@ def imgres():
@session_required @session_required
@auth_required @auth_required
def element(): def element():
empty_gif = base64.b64decode(
'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')
element_url = src_url = request.args.get('url') element_url = src_url = request.args.get('url')
if element_url.startswith('gAAAAA'): if element_url.startswith('gAAAAA'):
try: try:
@ -475,6 +484,11 @@ def element():
src_type = request.args.get('type') src_type = request.args.get('type')
# Ensure requested element is from a valid domain
domain = urlparse.urlparse(src_url).netloc
if not validators.domain(domain):
return send_file(io.BytesIO(empty_gif), mimetype='image/gif')
try: try:
file_data = g.user_request.send(base_url=src_url).content file_data = g.user_request.send(base_url=src_url).content
tmp_mem = io.BytesIO() tmp_mem = io.BytesIO()
@ -485,8 +499,6 @@ def element():
except exceptions.RequestException: except exceptions.RequestException:
pass pass
empty_gif = base64.b64decode(
'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')
return send_file(io.BytesIO(empty_gif), mimetype='image/gif') return send_file(io.BytesIO(empty_gif), mimetype='image/gif')
@ -504,6 +516,13 @@ def window():
root_url=request.url_root, root_url=request.url_root,
config=g.user_config) config=g.user_config)
target = urlparse.urlparse(target_url) target = urlparse.urlparse(target_url)
# Ensure requested URL has a valid domain
if not validators.domain(target.netloc):
return render_template(
'error.html',
error_message='Invalid location'), 400
host_url = f'{target.scheme}://{target.netloc}' host_url = f'{target.scheme}://{target.netloc}'
get_body = g.user_request.send(base_url=target_url).text get_body = g.user_request.send(base_url=target_url).text