CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27738
6.1

Angular SSR: The One-Slash Wonder (CVE-2026-27738)

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 26, 2026·7 min read·11 visits

PoC Available

Executive Summary (TL;DR)

Angular SSR's URL normalization logic only stripped a single leading slash from path segments. Attackers sending a `X-Forwarded-Prefix: ///evil.com` header can trick the server into generating a `Location: //evil.com` redirect. This protocol-relative URL forces the browser to navigate to the attacker's domain, enabling high-credibility phishing attacks.

A deceptively simple logic error in Angular's Server-Side Rendering (SSR) engine allows attackers to turn internal redirects into open redirects. By exploiting how the framework normalizes URLs from the `X-Forwarded-Prefix` header, a malicious actor can bypass validation with extra slashes, leading to protocol-relative URL redirection. This flaw affects major versions 19, 20, and 21, turning trusted applications into phishing launchpads.

The Hook: Trusting the Proxy

Server-Side Rendering (SSR) is the magic glue that makes heavy JavaScript frameworks feel snappy and SEO-friendly. But SSR is also a dangerous bridge between the raw, untrusted internet and your internal application logic. In the modern cloud stack, your Angular app rarely sits naked on the internet. It lives behind layers of Nginx, AWS ALBs, or Cloudflare, all of which helpfully annotate requests with headers like X-Forwarded-For and X-Forwarded-Prefix so the app knows where it supposedly lives.

Here is the problem: Angular decided to trust these headers a little too much. Specifically, the @angular/ssr package needs to construct full URLs when performing redirects (like when you hit a protected route and get bounced to /login). To do this, it stitches together the base URL and the target path. It sounds simple. It sounds boring. But in the world of exploit development, "boring" utility functions are where the bodies are buried.

CVE-2026-27738 isn't a complex buffer overflow or a heap grooming masterpiece. It is a testament to the fact that developers still treat input validation like a chore rather than a survival skill. The vulnerability lies in a helper function designed to clean up URL paths. It tries to be helpful by removing leading slashes to avoid double-slashing. Spoiler alert: it didn't remove enough of them.

The Flaw: The 'If' That Should Have Been a 'While'

Let's talk about joinUrlParts. This utility function in packages/angular/ssr/src/utils/url.ts has one job: take a bunch of string segments and glue them together into a valid URL path. To prevent the classic //path//subpath mess, it attempts to normalize the segments by stripping leading slashes before joining them.

Here is where the developer's optimism collided with reality. The code checked if a segment started with a slash. If it did, it removed it. Singular. Once. It assumed that a path segment would only ever have one accidental leading slash. It looked something like this:

// The logic of a optimist
if (part[0] === '/') {
  normalizedPart = normalizedPart.slice(1);
}

See the issue? If I hand you /home, you give me home. Perfect. But what if I hand you ///evil.com? The code sees the first slash, says "Gotcha!", removes it, and leaves //evil.com. In the world of browsers, // isn't just two slashes; it's a protocol-relative URL. It tells the browser, "Keep using HTTPS, but switch the host to evil.com."

This is the digital equivalent of a bouncer checking your ID, seeing you have a fake mustache, peeling it off, but failing to notice the second, smaller fake mustache underneath.

The Code: Anatomy of a Fix

The fix provided by the Angular team is a textbook example of "defense in depth" replacing "naive trust." They didn't just fix the loop; they scorched the earth. Let's look at the critical diff in the joinUrlParts function.

The Vulnerable Code:

// Old logic: strictly checks index 0
if (part.startsWith('/')) {
  part = part.substring(1);
}

The Fix (Commit 877f017):

The new implementation treats slashes like an infestation. It uses a while loop to aggressively strip every leading and trailing slash it finds. It doesn't matter if you send ///, ////, or //////////////////—they are all getting nuked.

export function joinUrlParts(...parts: string[]): string {
  const normalizedParts: string[] = [];
  for (const part of parts) {
    let start = 0;
    let end = part.length;
    // Eat all the slashes at the start
    while (start < end && part[start] === '/') { start++; }
    // Eat all the slashes at the end
    while (end > start && part[end - 1] === '/') { end--; }
    
    if (start < end) {
      normalizedParts.push(part.slice(start, end));
    }
  }
  return addLeadingSlash(normalizedParts.join('/'));
}

But they didn't stop there. They realized that X-Forwarded-Prefix shouldn't contain suspicious characters at all. They added a regex validator INVALID_PREFIX_REGEX that instantly throws an error if the header contains double slashes or directory traversal sequences (..).

const INVALID_PREFIX_REGEX = /^[\/\\]{2}|(?:^|[\/\\])\.\.?(?:[\/\\]|$)/;

This is the difference between "patching a bug" and "killing a bug class."

The Exploit: Hijacking the Redirect

So, how do we weaponize this? We need an Angular SSR app sitting behind a proxy (like Nginx) that blindly passes the X-Forwarded-Prefix header. This is a very common configuration for apps hosted on sub-paths (e.g., example.com/app/).

The Setup:

  1. Target: https://vulnerable-bank.com
  2. Goal: Redirect users to https://evil-phishing.com
  3. Vector: The /logout route, which redirects users back to /home after clearing the session.

The Attack: We craft a link or use a CSRF chain to force a request with a manipulated header. If we can control the upstream proxy or if the application is misconfigured to trust client headers directly (common in dev/staging), we send this:

GET /logout HTTP/1.1
Host: vulnerable-bank.com
X-Forwarded-Prefix: ///evil-phishing.com

The Execution:

  1. Angular SSR receives the request.
  2. It needs to build the redirect URL for the home page.
  3. It calls joinUrlParts('///evil-phishing.com', 'home').
  4. The function strips one slash: //evil-phishing.com.
  5. It appends the path: //evil-phishing.com/home.
  6. The server responds:
HTTP/1.1 302 Found
Location: //evil-phishing.com/home

The Result: The victim's browser sees //evil-phishing.com/home. Since the original page was loaded over HTTPS, the browser expands this to https://evil-phishing.com/home. The user is silently whisked away to the attacker's domain, which likely looks exactly like the bank's login page.

The Impact: Why You Should Care

Security engineers often roll their eyes at Open Redirects. "So what? The user goes to the wrong page." But in the context of a trusted application, an Open Redirect is a silver bullet for social engineering.

First, there is the Phishing Implication. Users are trained to check the URL bar. If they click a link that starts with vulnerable-bank.com, they let their guard down. If that link immediately 302s them to a clone site, they rarely notice the switch. The initial trust is established by the valid domain.

Second, consider Cache Poisoning. If the X-Forwarded-Prefix header is not part of the cache key (the Vary header) on your CDN, an attacker could send a single malicious request that gets cached. Now, every legitimate user who visits that page gets redirected to the malware site. This turns a targeted attack into a Denial of Service or a mass-compromise event.

Finally, this bypasses many SSRF filters. While this specific CVE results in a client-side redirect, the underlying logic flaw—insufficient sanitization of path components—often mirrors how internal requests are constructed. If this logic was used to fetch internal resources, we'd be looking at a much nastier RCE scenario.

The Fix: Sanitization and Upgrades

If you are running Angular SSR on versions 19, 20, or 21, you are exposed. The immediate fix is to upgrade to the patched versions:

  • v21.x: Upgrade to 21.1.5
  • v20.x: Upgrade to 20.3.17
  • v19.x: Upgrade to 19.2.21

If you cannot upgrade immediately (corporate bureaucracy is the real vulnerability, after all), you must sanitize the input before it reaches Angular. In your server.ts or Nginx configuration, you need to flatten that header.

Nginx Mitigation: Ensure you are hardcoding the prefix if you know it, rather than trusting the client. If you must pass it dynamically, validate it.

# Don't do this if you can avoid it
# proxy_set_header X-Forwarded-Prefix $http_x_forwarded_prefix;
 
# Do this: explicitly define the prefix
proxy_set_header X-Forwarded-Prefix /my-app;

Node.js Middleware Mitigation:

// Add this early in your server stack
app.use((req, res, next) => {
  const prefix = req.headers['x-forwarded-prefix'];
  if (typeof prefix === 'string' && prefix.startsWith('//')) {
    // Flatten multiple slashes to one
    req.headers['x-forwarded-prefix'] = prefix.replace(/^\/+/, '/');
  }
  next();
});

This isn't just about fixing a bug; it's about acknowledging that "normalization" is hard and that headers are user input. Treat them accordingly.

Official Patches

AngularPatch Commit on GitHub
AngularGitHub Security Advisory

Fix Analysis (1)

Technical Appendix

CVSS Score
6.1/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N

Affected Systems

Angular SSR 19.x < 19.2.21Angular SSR 20.x < 20.3.17Angular SSR 21.x < 21.1.5

Affected Versions Detail

Product
Affected Versions
Fixed Version
@angular/ssr
Angular
>= 19.0.0-next.0, < 19.2.2119.2.21
@angular/ssr
Angular
>= 20.0.0-next.0, < 20.3.1720.3.17
@angular/ssr
Angular
>= 21.0.0-next.0, < 21.1.521.1.5
AttributeDetail
CWECWE-601 (Open Redirect)
CVSS v4.06.9 (Medium)
Attack VectorNetwork (Header Manipulation)
Exploit MaturityProof of Concept
PrivilegesNone
User InteractionRequired (Victim must click link)

MITRE ATT&CK Mapping

T1566.002Spearphishing Link
Initial Access
T1608.002Stage Capabilities: Upload Malware
Resource Development
CWE-601
Open Redirect

URL Redirection to Untrusted Site ('Open Redirect')

Known Exploits & Detection

GitHubOriginal issue report containing PoC steps

Vulnerability Timeline

Vulnerability reported to Google Bug Hunters
2026-02-15
Patch merged and released
2026-02-23
CVE and GHSA published
2026-02-25

References & Sources

  • [1]GHSA-xh43-g2fq-wjrj
  • [2]NVD CVE-2026-27738

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.