Skip to content

utils.get_current_url() fails to parse IPv6 IP address #2993

Closed

Description

@andrewpollock

(This has similar smells to it as #2903)

By the looks of it,

def get_current_url(
when called from
def url(self) -> str:
"""The full request URL with the scheme, host, root path, path,
and query string."""
return get_current_url(
self.scheme, self.host, self.root_path, self.path, self.query_string
)
isn't coping with a bare IPv6 address?

ValueError: Port could not be cast to integer value as '1901:0:3dbd::'
    raise ValueError(f"Port could not be cast to integer value as {port!r}")
  File "/usr/local/lib/python3.11/urllib/parse.py", line 182, in port
       ^^^^^^^^^^
    if parts.port:
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/werkzeug/urls.py", line 98, in uri_to_iri
           ^^^^^^^^^^^^^^^^^^^^^^^^
    return uri_to_iri("".join(url))
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/werkzeug/sansio/utils.py", line 137, in get_current_url
           ^^^^^^^^^^^^^^^^
    return get_current_url(
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/werkzeug/sansio/request.py", line 210, in url
            ^^^^^^^^^^^^^^
    value = self.fget(obj)  # type: ignore
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/werkzeug/utils.py", line 107, in __get__
                  ^^^^^^^^^^^^^^^^^
    "requestUrl": flask.request.url,
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/google/cloud/logging_v2/handlers/_helpers.py", line 79, in get_request_data_from_flask
                                                                    ^^^^^^^^^
    http_request, http_trace_id, http_span_id, http_trace_sampled = checker()
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/google/cloud/logging_v2/handlers/_helpers.py", line 273, in get_request_data
        ^^^^^^^^^^^^^^^^^^
    ) = get_request_data()
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/google/cloud/logging_v2/handlers/handlers.py", line 98, in filter
             ^^^^^^^^^^^^^^^^
    result = f.filter(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 830, in filter
         ^^^^^^^^^^^^^^^^^^^
    rv = self.filter(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 974, in handle
    hdlr.handle(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 1706, in callHandlers
    self.callHandlers(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 1644, in handle
    self.handle(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 1634, in _log
    self._log(ERROR, msg, args, **kwargs)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 1518, in error
    self.logger.error(
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 1414, in log_exception
    self.log_exception(exc_info)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 1393, in handle_exception
               ^^^^^^^^^^^^^^^^^^^^^^^^
    response = self.handle_exception(e)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 2193, in wsgi_app
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    return wsgi_app(environ, start_response)
  File "/osv/gcp/appengine/main.py", line 37, in middleware
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    return self.application(environ, start_response)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/whitenoise/base.py", line 81, in __call__
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    return self.wsgi_app(environ, start_response)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 2213, in __call__
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    respiter = self.wsgi(environ, resp.start_response)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/gunicorn/workers/gthread.py", line 334, in handle_request
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Traceback (most recent call last):  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/gunicorn/workers/gthread.py", line 282, in handle    keepalive = self.handle_request(req, conn)
During handling of the above exception, another exception occurred:
ValueError: Port could not be cast to integer value as '1901:0:3dbd::'
    raise ValueError(f"Port could not be cast to integer value as {port!r}")
  File "/usr/local/lib/python3.11/urllib/parse.py", line 182, in port
       ^^^^^^^^^^
    if parts.port:
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/werkzeug/urls.py", line 98, in uri_to_iri
           ^^^^^^^^^^^^^^^^^^^^^^^^
    return uri_to_iri("".join(url))
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/werkzeug/sansio/utils.py", line 137, in get_current_url
           ^^^^^^^^^^^^^^^^
    return get_current_url(
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/werkzeug/sansio/request.py", line 210, in url
            ^^^^^^^^^^^^^^
    value = self.fget(obj)  # type: ignore
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/werkzeug/utils.py", line 107, in __get__
                  ^^^^^^^^^^^^^^^^^
    "requestUrl": flask.request.url,
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/google/cloud/logging_v2/handlers/_helpers.py", line 79, in get_request_data_from_flask
                                                                    ^^^^^^^^^
    http_request, http_trace_id, http_span_id, http_trace_sampled = checker()
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/google/cloud/logging_v2/handlers/_helpers.py", line 273, in get_request_data
        ^^^^^^^^^^^^^^^^^^
    ) = get_request_data()
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/google/cloud/logging_v2/handlers/handlers.py", line 98, in filter
             ^^^^^^^^^^^^^^^^
    result = f.filter(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 830, in filter
         ^^^^^^^^^^^^^^^^^^^
    rv = self.filter(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 974, in handle
    hdlr.handle(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 1706, in callHandlers
    self.callHandlers(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 1644, in handle
    self.handle(record)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 1634, in _log
    self._log(INFO, msg, args, **kwargs)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 1489, in info
    root.info(msg, *args, **kwargs)
  File "/usr/local/lib/python3.11/logging/__init__.py", line 2141, in info
    logging.info('Handled %s - Path attempted: %s', error, request.path)
  File "/osv/gcp/appengine/frontend_handlers.py", line 707, in not_found_error
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    return self.ensure_sync(handler)(e)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 1281, in handle_http_exception
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    return self.handle_http_exception(e)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 1341, in handle_user_exception
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    rv = self.handle_user_exception(e)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 1486, in full_dispatch_request
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Traceback (most recent call last):  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 2190, in wsgi_app    response = self.full_dispatch_request()
During handling of the above exception, another exception occurred:
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
    raise self.mapping[code](*args, **kwargs)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/werkzeug/exceptions.py", line 863, in __call__
    current_app.aborter(code, *args, **kwargs)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/helpers.py", line 277, in abort
    abort(404)
  File "/osv/gcp/appengine/frontend_handlers.py", line 254, in vulnerability_redirector
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 1469, in dispatch_request
         ^^^^^^^^^^^^^^^^^^^^^^^
Traceback (most recent call last):  File "/osv/gcp/appengine/.venv/lib/python3.11/site-packages/flask/app.py", line 1484, in full_dispatch_request    rv = self.dispatch_request()

Environment:

  • Python version: 3.11
  • Werkzeug version: 3.0.6

Activity

  1. davidism commented on Nov 6, 2024

    @davidism
    Member

    When reporting an issue, be sure to include a minimal reproducible example.

  2. davidism commented on Nov 6, 2024

    @davidism
    Member

    I can't reproduce the issue with the information provided. The following example does not raise an error:

    from werkzeug import Request, Response, run_simple
    
    @Request.application
    def app(request: Request) -> Response:
        return Response(request.url)
    
    run_simple("::1", 5000, app)
    $ python example.py
    WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
     * Running on http://[::1]:5000
    Press CTRL+C to quit
    ::1 - - [06/Nov/2024 08:18:57] "GET / HTTP/1.1" 200 -
    
    http://[::1]:5000/
    
  3. andrewpollock commented on Nov 6, 2024

    @andrewpollock
    Author

    I note the IP address from the stack trace had trailing colons (which I think is the problem): 1901:0:3dbd::

  4. davidism commented on Nov 7, 2024

    @davidism
    Member

    What do the following contain in that request?

    • request.server
    • request.headers["host"]
    • request.host

    It sounds like request.host is malformed, like it doesn't have the [] around the IPv6 address. Assuming that's the case, that would need to be reported to something else depending on the three values above: if it's request.server, the WSGI server, if it's the others, your HTTP server or the client sending the header.

    If I call the function manually with the IPv6 in valid form, it works as expected:

    from werkzeug.sansio.utils import get_current_url
    
    print(get_current_url("http", "[1901:0:3dbd::]"))
  5. davidism commented on Nov 7, 2024

    @davidism
    Member

    Aha, I checked the above, and it seems that WSGI server (both the dev server and Gunicorn) set SERVER_NAME to ::1 rather than [::1]. So if the Host header is not sent and request.host falls back to request.server, it produces ::1:5000 instead of [::1]:5000. I couldn't find anything in the spec saying how SERVER_NAME should be formatted for IPv6 addresses, and since Gunicorn does it as well, I think we can try to detect that and wrap it.

    from werkzeug import Request, Response, run_simple
    
    @Request.application
    def app(request: Request) -> Response:
        request.environ.pop("HTTP_HOST", None)
        return Response(request.url)
    
    run_simple("::1", 5000, app)
  6. added this to the 3.1.3 milestone on Nov 7, 2024
  7. davidism commented on Nov 7, 2024

    @davidism
    Member

    As far as I can tell, the Host header must always be formatted with brackets [::1]:5000 according to the spec. WSGI's SERVER_NAME seems to reflect what was passed when binding the socket, where [] isn't used for the host part. So I'll apply a check only when using SERVER_NAME.

  8. locked as resolved and limited conversation to collaborators on Nov 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions