Sitelet https://github.com/angular/angular-cli/issues/31464#top
Skip to content

SSR server side request forgery #31464

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(/sitelet?url=https%3A%2F%2Fgithub.com%2Fangular%2Fangular-cli%2Fissues%2ForiginalUrl%2520%3F%3F%2520url%2C%2520%60%24%7Bprotocol%7D%3A%2F%2F%24%7BhostnameWithPort%7D%60);

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions