Skip to content

Add TrustForwardedHeaders setting to gate X-Forwarded-Proto and X-Forwarded-For #1447

@michaellwest

Description

@michaellwest

M6 + L6: Forwarded Headers Trusted Without Validation

Severity: MEDIUM
Category: TLS Bypass / Audit Trail Integrity / Configuration Weakness
Files:

  • src/Spe/Core/Settings/Authorization/WebServiceSettings.cs (line 224) — X-Forwarded-Proto
  • src/Spe/sitecore modules/PowerShell/Services/RemoteScriptCall.ashx.cs (line ~447) — X-Forwarded-For

Risk Explanation

Two forwarded headers are trusted unconditionally without a configuration gate:

X-Forwarded-Proto (originally M6)

CheckSecureConnectionRequirement trusts X-Forwarded-Proto to determine if the connection is secure:

return string.Equals(HttpContext.Current.Request.Headers["X-Forwarded-Proto"],
    Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);

When requireSecureConnection is enabled, an attacker on plain HTTP can bypass the TLS requirement by sending X-Forwarded-Proto: https.

Impact: Credential interception on unencrypted connections. JWT tokens, shared secrets, and plaintext passwords traverse the network in cleartext while the application believes the connection is secure.

X-Forwarded-For (originally L6)

GetIp trusts X-Forwarded-For unconditionally:

private static string GetIp(HttpRequest request)
{
    var ip = request.ServerVariables["HTTP_X_FORWARDED_FOR"];
    if (string.IsNullOrEmpty(ip))
        ip = request.ServerVariables["REMOTE_ADDR"];
    return ip;
}

An attacker can set X-Forwarded-For to any value, including fake IPs, log injection patterns, or multiple comma-separated IPs.

Impact: Audit log entries contain attacker-controlled IP addresses, undermining forensic analysis. Any IP-based rate limiting or blocking would also be bypassable.

Deployment context

The Docker setup uses Traefik as a reverse proxy, which sets both headers correctly. However, bare-metal IIS deployments or misconfigured load balancers may expose SPE directly to the network.


Implementation Plan

Fix — single TrustForwardedHeaders setting governs both headers

  1. Add a configurable trusted proxy setting in Spe.config:

    <!-- Set to true only when behind a reverse proxy that sets X-Forwarded-Proto and X-Forwarded-For -->
    <setting name="Spe.TrustForwardedHeaders" value="false" />
  2. Expose the setting via WebServiceSettings:

    public static bool TrustForwardedHeaders =>
        Settings.GetBoolSetting("Spe.TrustForwardedHeaders", false);
  3. Gate X-Forwarded-Proto on the setting in WebServiceSettings.cs:

    public static bool IsSecureConnection()
    {
        if (HttpContext.Current.Request.IsSecureConnection)
            return true;
        if (TrustForwardedHeaders)
        {
            return string.Equals(
                HttpContext.Current.Request.Headers["X-Forwarded-Proto"],
                Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
        }
        return false;
    }
  4. Gate X-Forwarded-For on the same setting in RemoteScriptCall.ashx.cs:

    private static string GetIp(HttpRequest request)
    {
        if (!WebServiceSettings.TrustForwardedHeaders)
            return request.ServerVariables["REMOTE_ADDR"];
        var forwarded = request.ServerVariables["HTTP_X_FORWARDED_FOR"];
        if (!string.IsNullOrEmpty(forwarded))
        {
            var clientIp = forwarded.Split(',')[0].Trim();
            if (System.Net.IPAddress.TryParse(clientIp, out _))
                return clientIp;
        }
        return request.ServerVariables["REMOTE_ADDR"];
    }
  5. Log a warning at startup when requireSecureConnection is enabled but TrustForwardedHeaders is false and not HTTPS.

  6. Update Docker config to set Spe.TrustForwardedHeaders = true.

Files to modify

File Change
src/Spe/Core/Settings/Authorization/WebServiceSettings.cs Add TrustForwardedHeaders property; gate X-Forwarded-Proto check
src/Spe/sitecore modules/PowerShell/Services/RemoteScriptCall.ashx.cs Gate GetIp on TrustForwardedHeaders; validate and extract first IP
src/Spe/App_Config/Include/Spe/Spe.config Add Spe.TrustForwardedHeaders setting (default false)
docker/deploy/Spe/App_Config/Include/Spe/Spe.config Override to true for Docker deployments

Test Plan

X-Forwarded-Proto tests

  1. Spoofed header without trust setting: TrustForwardedHeaders=false, send X-Forwarded-Proto: https over HTTP → rejected.
  2. Legitimate proxy header with trust setting: Through Traefik → succeeds.
  3. Direct HTTPS works regardless of setting.

X-Forwarded-For tests

  1. Spoofed header ignored when trust disabled.
  2. Valid header used when trust enabled.
  3. Comma-separated chain extracts first IP.
  4. Invalid IP value falls back to REMOTE_ADDR.

Verify

  1. Existing integration tests pass (Docker with Traefik).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions