Skip to content

fix(@angular/build): add host validation to internal SSR middleware - #33674

Closed
deprrous wants to merge 1 commit into
angular:mainfrom
deprrous:fix/ssr-middleware-host-validation
Closed

fix(@angular/build): add host validation to internal SSR middleware#33674
deprrous wants to merge 1 commit into
angular:mainfrom
deprrous:fix/ssr-middleware-host-validation

Conversation

@deprrous

Copy link
Copy Markdown

Problem

createAngularSsrInternalMiddleware calls AngularServerApp.handle() directly without validating the request hostname against the configured allowedHosts.

The external middleware correctly propagates the check:

AngularAppEngine.ɵdisableAllowedHostsCheck = disableAllowedHostsCheck;

But the internal middleware has no equivalent validation. When allowedHosts is configured as an array (not true), the internal SSR path still processes requests with arbitrary Host headers.

Impact

With host: "0.0.0.0" + allowedHosts: ["myapp.com"], a DNS rebinding attack can set Host: attacker.com and the internal SSR middleware will:

  1. Construct the request URL from the attacker-controlled Host header
  2. Resolve all relative HttpClient requests against attacker.com
  3. Send credentials/cookies to the attacker's server

This is the same SSRF class as GHSA-rfh7-fxqc-q52v but in the dev server's internal middleware path.

Reproduction

# angular.json: "serve": { "options": { "host": "0.0.0.0", "allowedHosts": ["myapp.com"] } }
ng serve

# Attacker sends request with spoofed Host:
curl -H 'Host: attacker.com' http://VICTIM:4200/
# SSR engine resolves relative HttpClient URLs against attacker.com

Fix

Validate the request URL hostname against allowedHosts before calling angularServerApp.handle(), consistent with the external middleware's behavior.

The internal SSR middleware (createAngularSsrInternalMiddleware) calls
AngularServerApp.handle() directly without validating the request
hostname against the configured allowedHosts. The external middleware
correctly propagates the allowedHosts check via
AngularAppEngine.ɵdisableAllowedHostsCheck, but the internal path
has no equivalent validation.

When allowedHosts is configured (not set to true), validate the
request URL hostname before passing the request to the SSR engine.
This prevents hostname hijacking via DNS rebinding when the dev
server is exposed on 0.0.0.0.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces host validation in the Angular SSR middleware by checking incoming requests against Vite's allowedHosts configuration. The feedback identifies a flaw in the host matching logic where subdomains and leading dots are not handled correctly, potentially leading to failed matches or over-permissive access. A code suggestion is provided to resolve this issue.

Comment on lines +71 to +73
const isAllowed = allowedHosts.some(
(host) => hostname === host || hostname.endsWith(`.${host}`),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current host matching logic has two issues:

  1. If an allowed host starts with a dot (e.g., .example.com to allow subdomains), hostname.endsWith(.${host}) evaluates to hostname.endsWith('..example.com'), which will fail to match subdomains.
  2. If an allowed host does not start with a dot (e.g., example.com), hostname.endsWith(.${host}) will match subdomains (e.g., sub.example.com), which is over-permissive and violates standard host matching behavior where subdomains are only allowed if a leading dot is explicitly specified.

We should update the matching logic to correctly handle leading dots, consistent with Vite and webpack-dev-server standards.

        const isAllowed = allowedHosts.some((host) => {
          if (host.startsWith('.')) {
            return hostname === host.slice(1) || hostname.endsWith(host);
          }
          return hostname === host;
        });

@alan-agius4

Copy link
Copy Markdown
Collaborator

The steps above cannot reproduce this issue. The request will be blocked.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants