VYPR
Critical severityNVD Advisory· Published Jan 23, 2024· Updated Nov 13, 2024

Whoogle Search Server Side Request Forgery vulnerability

CVE-2024-22203

Description

Whoogle Search is a self-hosted metasearch engine. In versions prior to 0.8.4, 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 request.py, which leads to a server-side request forgery. This issue allows for crafting GET requests to internal and external resources on behalf of the server. For example, this issue would allow for accessing resources on the internal network that the server has access to, even though these resources may not be accessible on the internet. This issue is fixed in version 0.8.4.

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

11

News mentions

0

No linked articles in our index yet.