Skip to content

SSR server side request forgery #31464

@meDavidNS

Description

@meDavidNS

Command

other

Is this a regression?

  • Yes, this behavior used to work in the previous version

The previous version in which this bug was not present was

No response

Description

When using Angular server side rendering you can trick Angular in thinking the request is for a different domain.

Root cause
The room cause seems to be in the createRequestUrl function that glues together the protocol, host, port and quest path (url or originalUrl) using the native URL class constructor.

return new URL(originalUrl ?? url, `${protocol}://${hostnameWithPort}`);

return new URL(originalUrl ?? url, `${protocol}://${hostnameWithPort}`);

When originalUrl or url looks like a schema relative url ('//something') it will ignore the second base argument. Zo in our example it would convert to return new URL('//something', 'http://localhost:4200') which according to the specs resolves to http://something

Attack scenario
As Angular thinks (using the PlatformLocator or DOCUMENT token location.href) it is handling a domain that can be defined in the URL path. An attacker can let the server execute requests to a server of their choosing if relative requests are used, possibly injecting content.

Executing a request to http://localhost:4200//domain-from-attacker.com/some-path will in the above example execute a request to http://domain-from-attacker.com

Minimal Reproduction

  1. Create a new Angular SSR project using ng new --ssr and activate RenderMode.Server. See the example repo at https://github.com/meDavidNS/angular-ssr

  2. For illustration purpose inject the DOCUMENT token and the httpClient to make a relative request.

  protected readonly httpClient = inject(HttpClient)
  protected readonly doc = inject(DOCUMENT)
  protected readonly loc = inject(PlatformLocation)
  ngOnInit() {
    console.log({
      'HOSTNAME': this.loc.hostname,
      'LOCATION': this.doc.location.href,
    })
    this.httpClient.get('assets/some.json').subscribe(console.log)
  }

  1. Request a page that starts the path with a double slash, like http://localhost:4200//something, this will trick Angular in thinking it is handling a request from http://something/ instead of http://localhost:4200. In the above example it will execute the http request to http://something/assets/some.json instead of http://localhost:4200/assets/some.json

Your Environment

Angular CLI: 20.3.5
Node: 22.14.0
Package Manager: npm 10.9.2
OS: darwin arm64
    

Angular: 20.3.4
... common, compiler, compiler-cli, core, forms
... platform-browser, platform-server, router

Package                      Version
------------------------------------
@angular-devkit/architect    0.2003.5
@angular-devkit/core         20.3.5
@angular-devkit/schematics   20.3.5
@angular/build               20.3.5
@angular/cli                 20.3.5
@angular/ssr                 20.3.5
@schematics/angular          20.3.5
rxjs                         7.8.2
typescript                   5.9.3

Anything else relevant?

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions