VYPR
Moderate severityNVD Advisory· Published Jan 23, 2024· Updated Jun 17, 2025

Whoogle Search Cross-site Scripting vulnerability

CVE-2024-22417

Description

Whoogle Search is a self-hosted metasearch engine. In versions 0.8.3 and prior, the element method in app/routes.py does not validate the user-controlled src_type and element_url variables and passes them to the send method which sends a GET request on lines 339-343 in requests.py. The returned contents of the URL are then passed to and reflected back to the user in the send_file function on line 484, together with the user-controlled src_type, which allows the attacker to control the HTTP response content type leading to a cross-site scripting vulnerability. An attacker could craft a special URL to point to a malicious website and send the link to a victim. The fact that the link would contain a trusted domain (e.g. from one of public Whoogle instances) could be used to trick the user into clicking the link. The malicious website could, for example, be a copy of a real website, meant to steal a person’s credentials to the website, or trick that person in another way. Version 0.8.4 contains a patch for this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
whoogle-searchPyPI
< 0.8.40.8.4

Affected products

1

Patches

1
3a2e0b262e4a

Validate urls in `element` and `window` endpoints

https://github.com/benbusby/whoogle-searchBen BusbySep 13, 2023via ghsa
1 file changed · +26 7
  • app/routes.py+26 7 modified
    @@ -4,8 +4,10 @@
     import json
     import os
     import pickle
    +import re
     import urllib.parse as urlparse
     import uuid
    +import validators
     from datetime import datetime, timedelta
     from functools import wraps
     
    @@ -420,13 +422,18 @@ def config():
         config_disabled = (
                 app.config['CONFIG_DISABLE'] or
                 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':
             return json.dumps(g.user_config.__dict__)
         elif request.method == 'PUT' and not config_disabled:
    -        if 'name' in request.args:
    -            config_pkl = os.path.join(
    -                app.config['CONFIG_PATH'],
    -                request.args.get('name'))
    +        if name:
    +            config_pkl = os.path.join(app.config['CONFIG_PATH'], name)
                 session['config'] = (pickle.load(open(config_pkl, 'rb'))
                                      if os.path.exists(config_pkl)
                                      else session['config'])
    @@ -444,7 +451,7 @@ def config():
                     config_data,
                     open(os.path.join(
                         app.config['CONFIG_PATH'],
    -                    request.args.get('name')), 'wb'))
    +                    name), 'wb'))
     
             session['config'] = config_data
             return redirect(config_data['url'])
    @@ -463,6 +470,8 @@ def imgres():
     @session_required
     @auth_required
     def element():
    +    empty_gif = base64.b64decode(
    +        'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')
         element_url = src_url = request.args.get('url')
         if element_url.startswith('gAAAAA'):
             try:
    @@ -475,6 +484,11 @@ def element():
     
         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:
             file_data = g.user_request.send(base_url=src_url).content
             tmp_mem = io.BytesIO()
    @@ -485,8 +499,6 @@ def element():
         except exceptions.RequestException:
             pass
     
    -    empty_gif = base64.b64decode(
    -        'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')
         return send_file(io.BytesIO(empty_gif), mimetype='image/gif')
     
     
    @@ -504,6 +516,13 @@ def window():
             root_url=request.url_root,
             config=g.user_config)
         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}'
     
         get_body = g.user_request.send(base_url=target_url).text
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

12

News mentions

0

No linked articles in our index yet.