CVE-2026-54770

CVE-2026-54770 is a medium-severity open redirect vulnerability in webob (pip), affecting versions < 1.8.11. It is fixed in 1.8.11.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

WebOb: Open redirect in Location header normalization via leading C0 control / space characters

This is a third follow-up to CVE-2024-42353 / GHSA-mg3v-6m49-jhp3
and CVE-2026-44889 / GHSA-fh3h-vg37-cc95.

WebOb makes the Location header absolute when it serves a redirect. To stop a
relative or protocol-relative target from redirecting users off-host, it checks
the value for a URI scheme and for a leading //, then joins it against the
request URI with urllib.parse.urljoin(). The previous fix additionally stripped
ASCII tab/CR/LF from the value before those checks.

However, on Python 3.10+ urllib.parse.urljoin() (via urlsplit()) does more
than remove tab/CR/LF: it also strips leading and trailing C0 control
characters (U+0000U+001F) and spaces from the URL before parsing it.

Because WebOb's guard checks (SCHEME_RE and startswith("//")) run against the
un-stripped value, a single leading space or control byte slips past them, and
urljoin() then silently removes that byte and parses what remains as a
protocol-relative, or even absolute, URL. The result is an open redirect to an
attacker-controlled host.

Details

Response._make_location_absolute() (in src/webob/response.py) performed,
prior to the fix:

value = value.replace("\t", "").replace("\r", "").replace("\n", "")

if SCHEME_RE.search(value):          # ^[a-z]+:   -> already absolute, return as-is
    return value

if value.startswith("//"):           # neutralize protocol-relative URLs
    value = f"/%2f{value[2:]}"

new_location = urlparse.urljoin(_request_uri(environ), value)

Consider the Location value " //www.example.com/test" (a single leading space):

  1. The explicit strip only removes \t, \r, \n, the leading space
    survives.
  2. SCHEME_RE (^[a-z]+:) does not match, the value starts with a space.
  3. value.startswith("//") is False, the value starts with a space, not
    /. The ///%2f neutralization is skipped.
  4. urllib.parse.urljoin(_request_uri(environ), " //www.example.com/test") then
    strips the leading space before parsing, sees //www.example.com/test,
    treats it as protocol-relative, and returns
    http://www.example.com/test.

The same bypass works with a value such as " https://www.example.com/test"
(leading space + a full scheme): SCHEME_RE does not match the space-prefixed
string, but urljoin() strips the space and returns the fully absolute
attacker URL https://www.example.com/test.

Any C0 control character works equally well in place of the space, e.g.
"\x00//www.example.com/test" or "\x1f//www.example.com/test", because
urlsplit() strips the whole leading C0-control-and-space run.

Affected entry points

  • Response.location, any application that sets a relative/attacker-influenced
    Location and serves the response (the classic redirect path).
  • Request.relative_url(), used urllib.parse.urljoin() directly and was
    subject to the same character stripping.
  • webob.exc._HTTPMove subclasses (HTTPMovedPermanently, HTTPFound,
    HTTPSeeOther, HTTPTemporaryRedirect, HTTPPermanentRedirect, etc.), these
    built their absolute Location with urlparse.urljoin(req.path_url, self.location)
    without going through _make_location_absolute() at all, so they bypassed
    even the tab/CR/LF strip and the ///%2f neutralization. A protocol-relative
    location passed to e.g. HTTPFound(location="//evil.example") redirected off-host.

Proof of Concept

from webob import Response
from webob.request import Request

res = Response()
res.status = "301"
res.location = " //www.example.com/test"   # note the single leading space

req = Request.blank("/")                    # request host is "localhost"
print(req.get_response(res).location)
# Vulnerable (<= 1.8.10):  http://www.example.com/test   <-- open redirect
# Fixed:                   http://localhost/ //www.example.com/test

Absolute-URL variant:

res.location = " https://www.example.com/test"
# Vulnerable: https://www.example.com/test   <-- off-host
# Fixed:      http://localhost/ https://www.example.com/test

Via the HTTP exceptions:

from webob import exc

environ = {
    "wsgi.url_scheme": "http", "SERVER_NAME": "localhost",
    "SERVER_PORT": "80", "REQUEST_METHOD": "HEAD", "PATH_INFO": "/",
}
m = exc.HTTPFound(location="//www.example.com/test")
m(environ, lambda *a, **k: None)
print(m.location)
# Vulnerable: //www.example.com/test          <-- open redirect
# Fixed:      http://localhost/%2fwww.example.com/test

Workarounds

  • Only ever set the Location header / redirect target to a fully-qualified URI
    whose host you control, or strictly allowlist redirect destinations before
    handing them to WebOb.
  • Reject any redirect target that does not begin with https://yourhost/ (or a
    validated relative path with no leading whitespace/control bytes).

References

To report a vulnerability to the Pylons Project please take a look at:

Credit

Reported via the Pylons Project security mailing list by:

  • tonghuaroot, for the residual open redirect in
    Response._make_location_absolute(): the 1.8.10 fix stripped only ASCII
    tab/CR/LF, but urllib.parse.urljoin() also strips leading C0 control and
    space characters, so values such as " //attacker.example/path" (and
    " https://attacker.example/path") still escaped off-host.
  • Matheus Polkorny, for identifying that the webob.exc._HTTPMove
    redirect exceptions (HTTPFound and friends) performed their own
    urllib.parse.urljoin() normalization and never went through
    _make_location_absolute(), so a protocol-relative location such as
    //evil.example/path/ redirected off-host through that separate code path.

Impact

An unauthenticated remote attacker who controls (in whole or part) the redirect
target of an application built on WebOb can redirect a user from a trusted host to
an attacker-controlled host. This enables phishing and credential-theft campaigns
that abuse the trusted origin, and can be chained with OAuth/SSO redirect_uri
flows to leak tokens. Exploitation requires user interaction (following the
redirect). Confidentiality and integrity impact are limited (L); the scope is
changed (C) because the trust boundary of the originating site is crossed.

Untrusted input controls a URL used for redirection, which can forward users to attacker-controlled sites. Typical impact: phishing and credential harvesting via a trusted domain.

CVE-2026-54770 has a CVSS score of 6.1 (Medium). The vector is network-reachable, no privileges required, and user interaction required. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.8.11); upgrading removes the vulnerable code path.

Affected versions

webob (< 1.8.11)

Security releases

webob → 1.8.11 (pip)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Fixed by replacing the use of urllib.parse.urljoin() with WebOb's own
RFC 3986 reference-resolution implementation, webob.util.urljoin(), which
resolves the reference exactly as given, character for character, with no
whitespace or control-character removal
.

  • Response._make_location_absolute() now uses webob.util.urljoin().
  • Request.relative_url() now uses webob.util.urljoin().
  • webob.exc._HTTPMove now normalizes its Location through the same
    _make_location_absolute() code path as Response, so protocol-relative and
    whitespace-smuggled locations are neutralized there too.

Users should upgrade to the patched release. There are no API changes.

Frequently Asked Questions

  1. What is CVE-2026-54770? CVE-2026-54770 is a medium-severity open redirect vulnerability in webob (pip), affecting versions < 1.8.11. It is fixed in 1.8.11. Untrusted input controls a URL used for redirection, which can forward users to attacker-controlled sites.
  2. How severe is CVE-2026-54770? CVE-2026-54770 has a CVSS score of 6.1 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of webob are affected by CVE-2026-54770? webob (pip) versions < 1.8.11 is affected.
  4. Is there a fix for CVE-2026-54770? Yes. CVE-2026-54770 is fixed in 1.8.11. Upgrade to this version or later.
  5. Is CVE-2026-54770 exploitable, and should I be worried? Whether CVE-2026-54770 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-54770 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-54770? Upgrade webob to 1.8.11 or later.

Other vulnerabilities in webob

Stop the waste.
Protect your environment with Kodem.