Every CVE Search/ Get API key

CVE-2026-54770

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

6.1 MediumFix available

WebOb provides objects for HTTP requests and responses. Prior to 1.8.11, Response._make_location_absolute() in src/webob/response.py checks a Location value for a URI scheme or leading double slash before urllib.parse.urljoin() strips leading C0 control characters and spaces. An attacker-controlled value such as a space followed by a protocol-relative or absolute URL can therefore bypass SCHEME_RE and startswith("//") checks and be normalized to an off-host redirect. Request.relative_url() and webob.exc._HTTPMove subclasses, including HTTPFound, are also affected because they use the same unsafe URL joining behavior or bypass the earlier normalization path. An unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft, but exploitation requires the user to follow the redirect. This issue is fixed in version 1.8.11.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
Required

What an attacker can affect

Confidentiality
Low
Integrity
Low
Availability
None

Exploit likelihood

0.42%Up 0.1 points since Aug 21, 2026
90%50%10%1%

What each source reports

CVE List Score used 6.1

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

WebOb provides objects for HTTP requests and responses. Prior to 1.8.11, Response._make_location_absolute() in src/webob/response.py checks a Location value for a URI scheme or leading double slash before urllib.parse.urljoin() strips leading C0 control characters and spaces. An attacker-controlled value such as a space followed by a protocol-relative or absolute URL can therefore bypass SCHEME_RE and startswith("//") checks and be normalized to an off-host redirect. Request.relative_url() and webob.exc._HTTPMove subclasses, including HTTPFound, are also affected because they use the same unsafe URL joining behavior or bypass the earlier normalization path. An unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft, but exploitation requires the user to follow the redirect. This issue is fixed in version 1.8.11.

Record
CVE-2026-54770
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-601
Updated
2026-11-02
EUVDEUVD-2026-63467 6.1

WebOb provides objects for HTTP requests and responses. Prior to 1.8.11, Response._make_location_absolute() in src/webob/response.py checks a Location value for a URI scheme or leading double slash before urllib.parse.urljoin() strips leading C0 control characters and spaces. An attacker-controlled value such as a space followed by a protocol-relative or absolute URL can therefore bypass SCHEME_RE and startswith("//") checks and be normalized to an off-host redirect. Request.relative_url() and webob.exc._HTTPMove subclasses, including HTTPFound, are also affected because they use the same unsafe URL joining behavior or bypass the earlier normalization path. An unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft, but exploitation requires the user to follow the redirect. This issue is fixed in version 1.8.11.

Record
EUVD-2026-63467
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-6hx8-3wjj-gr8g 6.1

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

Summary

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+0000–U+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 = "…
Record
GHSA-6hx8-3wjj-gr8g
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-601
Updated
2026-11-02
NVD 6.1

WebOb provides objects for HTTP requests and responses. Prior to 1.8.11, Response._make_location_absolute() in src/webob/response.py checks a Location value for a URI scheme or leading double slash before urllib.parse.urljoin() strips leading C0 control characters and spaces. An attacker-controlled value such as a space followed by a protocol-relative or absolute URL can therefore bypass SCHEME_RE and startswith("//") checks and be normalized to an off-host redirect. Request.relative_url() and webob.exc._HTTPMove subclasses, including HTTPFound, are also affected because they use the same unsafe URL joining behavior or bypass the earlier normalization path. An unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft, but exploitation requires the user to follow the redirect. This issue is fixed in version 1.8.11.

Record
CVE-2026-54770
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-601
Updated
2026-11-02
OSV 6.1

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

WebOb provides objects for HTTP requests and responses. Prior to 1.8.11, Response._make_location_absolute() in src/webob/response.py checks a Location value for a URI scheme or leading double slash before urllib.parse.urljoin() strips leading C0 control characters and spaces. An attacker-controlled value such as a space followed by a protocol-relative or absolute URL can therefore bypass SCHEME_RE and startswith("//") checks and be normalized to an off-host redirect. Request.relative_url() and webob.exc._HTTPMove subclasses, including HTTPFound, are also affected because they use the same unsafe URL joining behavior or bypass the earlier normalization path. An unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft, but exploitation requires the user to follow the redirect. This issue is fixed in version 1.8.11.

Record
CVE-2026-54770
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-601
Updated
2026-11-02
OSVDEBIAN-CVE-2026-54770 6.1

WebOb provides objects for HTTP requests and responses. Prior to 1.8.11, Response._make_location_absolute() in src/webob/response.py checks a Location value for a URI scheme or leading double slash before urllib.parse.urljoin() strips leading C0 control characters and spaces. An attacker-controlled value such as a space followed by a protocol-relative or absolute URL can therefore bypass SCHEME_RE and startswith("//") checks and be normalized to an off-host redirect. Request.relative_url() and webob.exc._HTTPMove subclasses, including HTTPFound, are also affected because they use the same unsafe URL joining behavior or bypass the earlier normalization path. An unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft, but exploitation requires the user to follow the redirect. This issue is fixed in version 1.8.11.

Record
DEBIAN-CVE-2026-54770
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Updated
2026-11-02
Show 7 moreShow less
OSVGHSA-6hx8-3wjj-gr8g 6.1

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

Summary

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+0000–U+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 = "…
Record
GHSA-6hx8-3wjj-gr8g
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-601
Updated
2026-11-02
OSVPYSEC-2026-3943 6.1

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

Summary

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+0000–U+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 = "…
Record
PYSEC-2026-3943
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Updated
2026-11-02
OSVUBUNTU-CVE-2026-54770 6.1

WebOb provides objects for HTTP requests and responses. Prior to 1.8.11, Response._make_location_absolute() in src/webob/response.py checks a Location value for a URI scheme or leading double slash before urllib.parse.urljoin() strips leading C0 control characters and spaces. An attacker-controlled value such as a space followed by a protocol-relative or absolute URL can therefore bypass SCHEME_RE and startswith("//") checks and be normalized to an off-host redirect. Request.relative_url() and webob.exc._HTTPMove subclasses, including HTTPFound, are also affected because they use the same unsafe URL joining behavior or bypass the earlier normalization path. An unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft, but exploitation requires the user to follow the redirect. This issue is fixed in version 1.8.11.

Record
UBUNTU-CVE-2026-54770
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Updated
2026-11-02
OSVAZL-97575 No score

CVE-2026-54770 affecting package python-webob for versions less than 1.8.11-1

WebOb provides objects for HTTP requests and responses. Prior to 1.8.11, Response._make_location_absolute() in src/webob/response.py checks a Location value for a URI scheme or leading double slash before urllib.parse.urljoin() strips leading C0 control characters and spaces. An attacker-controlled value such as a space followed by a protocol-relative or absolute URL can therefore bypass SCHEME_RE and startswith("//") checks and be normalized to an off-host redirect. Request.relative_url() and webob.exc._HTTPMove subclasses, including HTTPFound, are also affected because they use the same unsafe URL joining behavior or bypass the earlier normalization path. An unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft, but exploitation requires the user to follow the redirect. This issue is fixed in version 1.8.11.

Record
AZL-97575
Updated
2026-11-02
OSVopenSUSE-SU-2026:11864-1 No score

python-WebOb-doc-1.8.11-1.1 on GA media

These are all security issues fixed in the python-WebOb-doc-1.8.11-1.1 package on the GA media of openSUSE Tumbleweed.

Record
openSUSE-SU-2026:11864-1
Updated
2026-11-02
OSVSUSE-SU-2026:4351-1 No score

Security update for python-WebOb

This update for python-WebOb fixes the following issues:

  • CVE-2026-54770: an unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft (bsc#1275917).
Record
SUSE-SU-2026:4351-1
Updated
2026-11-02
OSVSUSE-SU-2026:4765-1 No score

Security update for python-WebOb

This update for python-WebOb fixes the following issues:

  • CVE-2026-54770: an unauthenticated attacker who can influence an application's redirect target can send users to an attacker-controlled host for phishing or OAuth and SSO token theft (bsc#1275917).
Record
SUSE-SU-2026:4765-1
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
webob
PyPI
before 1.8.11
1.8.11
python-webob
Azure Linux:3
before 1.8.11-1
1.8.11-1
python-webob
Debian:12
All versions, plus listed versions
1:1.8.10-11:1.8.11-11:1.8.6-31:1.8.7-11:1.8.7-21:1.8.7-31:1.8.9-11:1.8.9-2
None known
python-webob
Debian:13
All versions, plus listed versions
1:1.8.10-0+deb13u11:1.8.10-11:1.8.11-11:1.8.9-11:1.8.9-2
None known
python-webob
Debian:14
before 1:1.8.11-1, plus listed versions
1:1.8.10-11:1.8.9-11:1.8.9-2
1:1.8.11-1
webob
PyPI
before 1.8.11, plus listed versions
0.80.8.10.8.20.8.30.8.4
Show 45 moreShow less
0.8.50.90.9.10.9.20.9.30.9.40.9.50.9.60.9.6.10.9.70.9.7.10.9.81.01.0.11.0.21.0.31.0.41.0.51.0.61.0.71.0.81.11.1.11.1b21.1beta11.1rc11.21.2.11.2.21.2.31.2b11.2b21.2b31.2rc11.31.3.11.41.4.11.4.21.5.01.5.0a01.5.0a11.5.0b01.5.11.6.0
1.8.11
python-WebOb
SUSE:Linux Enterprise Module for Public Cloud 12
before 1.2.3-3.6.1
1.2.3-3.6.1
python-WebOb
SUSE:Linux Enterprise Module for Basesystem 15 SP7
before 1.7.4-150000.3.9.1
1.7.4-150000.3.9.1
python-WebOb
SUSE:Linux Enterprise Module for Package Hub 15 SP7
before 1.7.4-150000.3.9.1
1.7.4-150000.3.9.1
python-webob
Ubuntu:Pro:16.04:LTS
All versions, plus listed versions
1.4.1-11.5.1-11.5.1-1ubuntu0.1~esm2
None known
Show 7 moreShow less
Affected packages
PackageAffectedFixed
python-webob
Ubuntu:Pro:18.04:LTS
All versions, plus listed versions
1:1.7.2-0ubuntu11:1.7.3-0ubuntu11:1.7.3-2fakesync11:1.7.3-2ubuntu0.1~esm2
None known
python-webob
Ubuntu:Pro:20.04:LTS
All versions, plus listed versions
1:1.8.5-11:1.8.5-1build11:1.8.5-21:1.8.5-2ubuntu0.11:1.8.5-2ubuntu0.1+esm1
None known
python-webob
Ubuntu:22.04:LTS
All versions, plus listed versions
1:1.8.6-1.11:1.8.6-1.1ubuntu0.11:1.8.6-1.1ubuntu0.2
None known
python-webob
Ubuntu:24.04:LTS
All versions, plus listed versions
1:1.8.6-31:1.8.7-11:1.8.7-1ubuntu0.1.24.04.11:1.8.7-1ubuntu0.1.24.04.2
None known
python-webob
Ubuntu:26.10
All versions, plus listed versions
1:1.8.9-11:1.8.10-1
None known
python-webob
Ubuntu:26.04:LTS
All versions, plus listed versions
1:1.8.9-11:1.8.9-1ubuntu0.1
None known
python-WebOb
openSUSE:Tumbleweed
before 1.8.11-1.1
1.8.11-1.1

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-54770 is a medium vulnerability (CVSS 6.1) in Pylons Webob, published August 20, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.42%, the 36th percentile. Fixed in webob 1.8.11.

References

Show 9 moreShow less
This page doesn't list everything

Left out here: 24 versions and the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open