VYPR
High severityOSV Advisory· Published Oct 16, 2025· Updated Apr 15, 2026

CVE-2025-62427

CVE-2025-62427

Description

The Angular SSR is a server-rise rendering tool for Angular applications. The vulnerability is a Server-Side Request Forgery (SSRF) flaw within the URL resolution mechanism of Angular's Server-Side Rendering package (@angular/ssr) before 19.2.18, 20.3.6, and 21.0.0-next.8. The function createRequestUrl uses the native URL constructor. When an incoming request path (e.g., originalUrl or url) begins with a double forward slash (//) or backslash (\\), the URL constructor treats it as a schema-relative URL. This behavior overrides the security-intended base URL (protocol, host, and port) supplied as the second argument, instead resolving the URL against the scheme of the base URL but adopting the attacker-controlled hostname. This allows an attacker to specify an external domain in the URL path, tricking the Angular SSR environment into setting the page's virtual location (accessible via DOCUMENT or PlatformLocation tokens) to this attacker-controlled domain. Any subsequent relative HTTP requests made during the SSR process (e.g., using HttpClient.get('assets/data.json')) will be incorrectly resolved against the attacker's domain, forcing the server to communicate with an arbitrary external endpoint. This vulnerability is fixed in 19.2.18, 20.3.6, and 21.0.0-next.8.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@angular/ssrnpm
>= 19.0.0-next.0, < 19.2.1819.2.18
@angular/ssrnpm
>= 20.0.0-next.0, < 20.3.620.3.6
@angular/ssrnpm
>= 21.0.0-next.0, < 21.0.0-next.821.0.0-next.8

Affected products

1

Patches

1
5271547c8066

fix(@angular/ssr): prevent malicious URL from overriding host

https://github.com/angular/angular-cliAlan AgiusOct 15, 2025via ghsa
2 files changed · +160 2
  • packages/angular/ssr/node/src/request.ts+2 2 modified
    @@ -76,7 +76,7 @@ function createRequestHeaders(nodeHeaders: IncomingHttpHeaders): Headers {
      * @param nodeRequest - The Node.js `IncomingMessage` or `Http2ServerRequest` object to extract URL information from.
      * @returns A `URL` object representing the request URL.
      */
    -function createRequestUrl(nodeRequest: IncomingMessage | Http2ServerRequest): URL {
    +export function createRequestUrl(nodeRequest: IncomingMessage | Http2ServerRequest): URL {
       const {
         headers,
         socket,
    @@ -101,7 +101,7 @@ function createRequestUrl(nodeRequest: IncomingMessage | Http2ServerRequest): UR
         }
       }
     
    -  return new URL(originalUrl ?? url, `${protocol}://${hostnameWithPort}`);
    +  return new URL(`${protocol}://${hostnameWithPort}${originalUrl ?? url}`);
     }
     
     /**
    
  • packages/angular/ssr/node/test/request_spec.ts+158 0 added
    @@ -0,0 +1,158 @@
    +/**
    + * @license
    + * Copyright Google LLC All Rights Reserved.
    + *
    + * Use of this source code is governed by an MIT-style license that can be
    + * found in the LICENSE file at https://angular.dev/license
    + */
    +
    +import { IncomingMessage } from 'node:http';
    +import { Http2ServerRequest } from 'node:http2';
    +import { Socket } from 'node:net';
    +import { createRequestUrl } from '../src/request';
    +
    +// Helper to create a mock request object for testing.
    +function createRequest(details: {
    +  headers: Record<string, string | string[] | undefined>;
    +  encryptedSocket?: boolean;
    +  url?: string;
    +  originalUrl?: string;
    +}): IncomingMessage {
    +  return {
    +    headers: details.headers,
    +    socket: details.encryptedSocket ? ({ encrypted: true } as unknown as Socket) : new Socket(),
    +    url: details.url,
    +    originalUrl: details.originalUrl,
    +  } as unknown as IncomingMessage;
    +}
    +
    +// Helper to create a mock Http2ServerRequest object for testing.
    +function createHttp2Request(details: {
    +  headers: Record<string, string | string[] | undefined>;
    +  url?: string;
    +}): Http2ServerRequest {
    +  return {
    +    headers: details.headers,
    +    socket: new Socket(),
    +    url: details.url,
    +  } as Http2ServerRequest;
    +}
    +
    +describe('createRequestUrl', () => {
    +  it('should create a http URL with hostname and port from the host header', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: { host: 'localhost:8080' },
    +        url: '/test',
    +      }),
    +    );
    +    expect(url.href).toBe('http://localhost:8080/test');
    +  });
    +
    +  it('should create a https URL when the socket is encrypted', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: { host: 'example.com' },
    +        encryptedSocket: true,
    +        url: '/test',
    +      }),
    +    );
    +    expect(url.href).toBe('https://example.com/test');
    +  });
    +
    +  it('should use "/" as the path when the URL path is empty', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: { host: 'example.com' },
    +        encryptedSocket: true,
    +        url: '',
    +      }),
    +    );
    +    expect(url.href).toBe('https://example.com/');
    +  });
    +
    +  it('should preserve query parameters in the URL path', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: { host: 'example.com' },
    +        encryptedSocket: true,
    +        url: '/test?a=1',
    +      }),
    +    );
    +    expect(url.href).toBe('https://example.com/test?a=1');
    +  });
    +
    +  it('should prioritize "originalUrl" over "url" for the path', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: { host: 'example.com' },
    +        encryptedSocket: true,
    +        url: '/test',
    +        originalUrl: '/original',
    +      }),
    +    );
    +    expect(url.href).toBe('https://example.com/original');
    +  });
    +
    +  it('should use "/" as the path when both "url" and "originalUrl" are not provided', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: { host: 'example.com' },
    +        encryptedSocket: true,
    +        url: undefined,
    +        originalUrl: undefined,
    +      }),
    +    );
    +    expect(url.href).toBe('https://example.com/');
    +  });
    +
    +  it('should treat a protocol-relative value in "url" as part of the path', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: { host: 'localhost:8080' },
    +        url: '//example.com/test',
    +      }),
    +    );
    +    expect(url.href).toBe('http://localhost:8080//example.com/test');
    +  });
    +
    +  it('should treat a protocol-relative value in "originalUrl" as part of the path', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: { host: 'localhost:8080' },
    +        url: '/test',
    +        originalUrl: '//example.com/original',
    +      }),
    +    );
    +    expect(url.href).toBe('http://localhost:8080//example.com/original');
    +  });
    +
    +  it('should prioritize "x-forwarded-host" and "x-forwarded-proto" headers', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: {
    +          host: 'localhost:8080',
    +          'x-forwarded-host': 'example.com',
    +          'x-forwarded-proto': 'https',
    +        },
    +        url: '/test',
    +      }),
    +    );
    +    expect(url.href).toBe('https://example.com/test');
    +  });
    +
    +  it('should use "x-forwarded-port" header for the port', () => {
    +    const url = createRequestUrl(
    +      createRequest({
    +        headers: {
    +          host: 'localhost:8080',
    +          'x-forwarded-host': 'example.com',
    +          'x-forwarded-proto': 'https',
    +          'x-forwarded-port': '8443',
    +        },
    +        url: '/test',
    +      }),
    +    );
    +    expect(url.href).toBe('https://example.com:8443/test');
    +  });
    +});
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.