fix: reject port in host header to prevent SSR SSRF bypass#32981
fix: reject port in host header to prevent SSR SSRF bypass#32981chupaohong wants to merge 1 commit intoangular:mainfrom
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request adds a validation check to disallow ports in host headers within the Angular SSR utility to mitigate potential SSRF risks. A security review identified that the current implementation using new URL().port can be bypassed by default ports (e.g., :80), so it is recommended to also check for the presence of a colon in the header value to ensure complete coverage.
| const { hostname, port } = new URL(url); | ||
| if (port) { | ||
| throw new Error( | ||
| `Header "${headerName}" with value "${value}" contains a port and is not allowed.`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
The current check using new URL().port is insufficient because the port property of a URL object returns an empty string if the port matches the default for the scheme (e.g., port 80 for the hardcoded http:// prefix). This means a host header like localhost:80 would bypass this validation, potentially allowing an SSRF attack if an internal service is running on port 80.
Since VALID_HOST_REGEX already restricts the host format and currently does not support IPv6, checking for the presence of a colon in the value is a more robust way to detect any port specification.
Additionally, please add unit tests in packages/angular/ssr/test/utils/validation_spec.ts to verify that both standard (80) and non-standard ports are correctly rejected, ensuring the fix for CVE-2026-27739 is complete. While out of scope for this specific hunk, consider updating VALID_HOST_REGEX to also disallow ports, which would provide an earlier rejection in validateHeaders.
| const { hostname, port } = new URL(url); | |
| if (port) { | |
| throw new Error( | |
| `Header "${headerName}" with value "${value}" contains a port and is not allowed.`, | |
| ); | |
| } | |
| const { hostname, port } = new URL(url); | |
| if (port || value.includes(':')) { | |
| throw new Error( | |
| "Header \"" + headerName + "\" with value \"" + value + "\" contains a port and is not allowed.", | |
| ); | |
| } |
|
Thanks for this, but this is intentional as We could potentially use the |
Port-based SSRF bypass in SSR host validation
verifyHostAllowedextractshostnamefrom the host header usingnew URL().hostname, which strips the port. SoX-Forwarded-Host: localhost:13337passes validation because hostname islocalhost, but the port 13337 goes unchecked.The SSR request URL then uses
localhost:13337as base. Any relativeHttpClientcalls during rendering go to port 13337 instead of the app's port.This bypasses the host validation fix from CVE-2026-27739.
Attack scenario
Attacker sends
X-Forwarded-Host: localhost:13337to an Angular SSR app behind a reverse proxy. If the app fetches data withHttpClientduring SSR (e.g.this.http.get('/api/data')), the request goes to port 13337 where the attacker runs a service. The response gets rendered into HTML and served to the user.Default Angular SSR config, nothing special needed.
Fix
Reject host headers that contain a port in
verifyHostAllowed. Port should come fromx-forwarded-port(validated separately), not from the host header.Verification
Before:
X-Forwarded-Host: localhost:13337passesverifyHostAllowed.After: throws error.