- Location
- scripts/ari.py:306
- Finding
- Bearer API Key May Be Forwarded Across Origins During HTTP Redirects<![CDATA[
## Vulnerability Details
**File Location**: `scripts/ari.py:306-320`, `scripts/ari.py:338-350`, and `scripts/ari.py:1457-1460`
**Vulnerability Type**: Cross-origin credential disclosure through automatic redirect handling
**Risk Level**: Medium
### Vulnerable Code
```python
def request_json(method, path, payload=None, params=None):
query = {
"method": method,
"path": path,
"params": {k: v for k, v in (params or {}).items() if v not in (None, "")},
"payload": payload,
}
url = base_url() + path
if query["params"]:
url += "?" + urllib.parse.urlencode(query["params"], doseq=True)
data = None if payload is None else json.dumps(payload).encode("utf-8")
headers = {
"Authorization": "Bearer " + require_key(),
"Accept": "application/json",
"User-Agent": user_agent(),
}
if data is not None:
headers["Content-Type"] = "application/json"
try:
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=TIMEOUT_SEC) as resp:
```
The same pattern is used for authenticated Server-Sent Events:
```python
def request_sse(path, payload, recovery_hint=None):
query = {"method": "POST", "path": path, "params": {}, "payload": payload}
url = base_url() + path
headers = {
"Authorization": "Bearer " + require_key(),
"Accept": "text/event-stream",
"Content-Type": "application/json",
"User-Agent": user_agent(),
}
result = {"meta": None, "content": "", "result": None, "reportId": 0, "creditsUsed": 0}
try:
req = urllib.request.Request(
url, data=json.dumps(payload).encode("utf-8"), headers=headers, method="POST")
with urllib.request.urlopen(req, timeout=SSE_TIMEOUT_SEC) as resp:
```
Authenticated downloads are also affected:
```python
headers = {"Authorization": "Bearer " + require_key(), "User-Agent": user_agent()}
...[truncated 3155 chars]
- Remediation
- <![CDATA[
## Remediation Suggestions
1. Disable automatic redirects for every authenticated request and handle redirect responses explicitly.
2. If redirects are required, permit them only when all of the following match the original trusted origin:
- Scheme is `https`.
- Normalized hostname is identical.
- Effective port is identical.
- Destination contains no user-information component.
3. Remove `Authorization`, cookies, and other sensitive headers whenever the destination origin changes.
4. Prefer rejecting cross-origin redirects rather than attempting to resend an authenticated request.
5. Apply the same protected opener to JSON, SSE, and download requests so the policy cannot diverge between request paths.
6. Consider limiting the maximum number of redirects and rejecting HTTPS-to-HTTP downgrade redirects unconditionally.
7. Add automated tests for:
- Same-origin relative redirects.
- Cross-origin redirects.
- HTTPS-to-HTTP redirects.
- Redirect loops.
- Alternate ports and hostname canonicalization.
- Verification that an attacker-controlled test server never receives the bearer header.
8. Encourage key rotation after suspected redirect or proxy compromise, and ensure server-side API keys can be promptly revoked.
A restrictive handler can follow this design:
```python
class SameOriginRedirectHandler(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
old = urllib.parse.urlsplit(req.full_url)
new = urllib.parse.urlsplit(
urllib.parse.urljoin(req.full_url, newurl)
)
old_port = old.port or (443 if old.scheme == "https" else 80)
new_port = new.port or (443 if new.scheme == "https" else 80)
if (
new.scheme != "https"
or old.scheme != new.scheme
or old.hostname != new.hostname
or old_port != new_port
or new.username is not None
or new.password is n
...[truncated 495 chars]