Skip to content

[REVIEW] api-security: add .NET client-certificate forwarding evidence #271

Description

@mlodygbelmondo

Skill Being Reviewed

Skill name: api-security C#/.NET supplement
Skill path: skills/appsec/api-security/csharp-dotnet.md

False Positive Analysis

Benign-looking mTLS evidence that can be over-scored as complete in proxied deployments:

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(https =>
    {
        https.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
        https.ClientCertificateValidation = (cert, chain, errors) =>
            errors == SslPolicyErrors.None && AllowedThumbprints.Contains(cert.Thumbprint);
    });
});

Why this can be misleading:

The C#/.NET supplement currently covers direct Kestrel client-certificate validation for gRPC/service-to-service mTLS. That is useful, but it can mislead reviewers in production deployments where TLS terminates at Azure App Service, IIS, Nginx, Envoy, an ingress controller, or another proxy/load balancer before traffic reaches ASP.NET Core.

Microsoft's ASP.NET Core certificate-authentication guidance explicitly separates direct certificate authentication from proxy/load-balancer scenarios. If a proxy handles client-certificate authentication, the app must receive trusted authentication information from the proxy, commonly through Certificate Forwarding Middleware. The review should therefore require evidence of where TLS terminates, whether the client certificate reaches Kestrel directly, whether UseCertificateForwarding() runs before authentication/authorization, and whether forwarded certificate headers are trusted only from known proxy infrastructure.

Without that evidence, a reviewer can create both false positives and false negatives:

  • false positive: flagging an app behind Azure App Service/custom proxy as missing Kestrel ClientCertificateMode.RequireCertificate even though certificate forwarding is the intended architecture;
  • false negative: accepting a forwarded certificate header without proving it is stripped/overwritten by the trusted proxy and not spoofable by clients.

Coverage Gaps

Missed variant 1: forwarded client certificate header trusted without proxy boundary evidence

builder.Services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "X-SSL-CERT";
    options.HeaderConverter = header => X509Certificate2.CreateFromPem(WebUtility.UrlDecode(header));
});

app.UseCertificateForwarding();
app.UseAuthentication();
app.UseAuthorization();

Why it should be caught:

This can be valid only if the proxy terminates mTLS, strips any inbound client-supplied certificate header, injects its own header, and the app accepts traffic only from that trusted proxy path. The skill should require reviewers to capture the proxy identity, header name, strip/overwrite behavior, allowed source networks, and deployment evidence.

Missed variant 2: middleware ordering makes forwarded certificates invisible to authentication

app.UseAuthentication();
app.UseAuthorization();
app.UseCertificateForwarding();

Why it should be caught:

Microsoft's guidance calls UseCertificateForwarding() before authentication and authorization. If the middleware runs after authentication, certificate claims may never be established for policy evaluation.

Missed variant 3: forwarded headers/proxy trust not configured for auth-sensitive decisions

app.UseForwardedHeaders();
app.UseCertificateForwarding();

Why it should be caught:

Proxy-derived request metadata affects redirects, authentication, policy evaluation, and link generation. ASP.NET Core's forwarded-header guidance emphasizes trusted proxy configuration because forwarded headers can otherwise be spoofed. Review output should record KnownProxies/KnownNetworks or equivalent ingress restrictions when proxy headers are used for security decisions.

Missed variant 4: direct Kestrel mTLS example copied into a proxy-terminated app

Observed architecture:
  client -> cloud load balancer / ingress -> ASP.NET Core over HTTP

Review accepted:
  Kestrel ConfigureHttpsDefaults sample present in code

Missing evidence:
  whether Kestrel actually sees TLS
  whether the load balancer requires client certificates
  whether the certificate identity is forwarded and validated

Why it should be caught:

Direct Kestrel configuration is not proof of mTLS if TLS terminates upstream. The review should require a deployment-path diagram or config evidence showing the actual certificate-authentication boundary.

Edge Cases

  • Azure App Service client certificates have platform-specific forwarding behavior; review should capture platform settings rather than assuming Kestrel direct TLS.
  • Certificate forwarding can be valid in custom proxies, but only when the header converter, header stripping, and trusted proxy path are documented.
  • Header size limits and PEM URL decoding can affect forwarded certificate reliability.
  • Certificate authentication returns 403 after TLS-level failures; reviewers should distinguish handshake rejection from application-level authorization failure.
  • Direct mTLS and forwarded-certificate architectures can coexist in different environments; the report should say which path was reviewed.

Remediation Quality

  • Fix resolves the vulnerability
  • Fix doesn't introduce new security issues
  • Fix doesn't break functionality
  • Issues found: Add C#/.NET client-certificate forwarding evidence gates for proxy/load-balancer deployments. Require reviewers to record TLS termination point, direct-versus-forwarded certificate path, certificate header name/converter, middleware order, proxy header stripping/overwrite behavior, trusted proxy/network restrictions, and Not Applicable criteria.

Comparison to Other Tools

Tool / Framework Catches this? Notes
Kestrel ClientCertificateMode review Partial Valid for direct TLS, but not enough when TLS terminates upstream.
ASP.NET Core Certificate Forwarding Middleware Partial Supplies the client certificate to the app, but must be ordered and trusted correctly.
Forwarded Headers Middleware Partial Helps with proxy metadata, but requires KnownProxies/KnownNetworks or equivalent trust controls.
Manual secure code review Yes Can verify deployment topology, proxy header trust, and middleware ordering together.

Overall Assessment

Strengths:

  • The supplement includes a practical mTLS example for direct Kestrel/gRPC deployments.
  • It requires certificate issuer/thumbprint allowlist validation.
  • It reminds reviewers to apply authorization at the service/method level.

Needs improvement:

  • Add a proxied-deployment branch for Azure App Service, IIS, Nginx, Envoy, ingress controllers, and load balancers.
  • Require reviewers to prove whether Kestrel sees the client certificate directly or receives it from a trusted proxy.
  • Treat forwarded certificate headers as security-sensitive and spoofable unless the proxy boundary is evidenced.
  • Add middleware-order checks for UseCertificateForwarding() before UseAuthentication() and UseAuthorization().

Priority recommendations:

  1. Add a Client Certificate Forwarding Behind Proxies subsection under the .NET mTLS/gRPC section.
  2. Add evidence fields for TLS termination point, proxy product, certificate-forwarding header, header converter, UseCertificateForwarding order, trusted proxy/network configuration, and ingress header strip/overwrite proof.
  3. Add severity guidance: spoofable client-certificate forwarding header on an authenticated service path should be High/Critical depending on exposed authorization impact.
  4. Add Not Evaluable reasons for unknown deployment topology, unknown proxy header behavior, missing ingress config, or unverified middleware ordering.

Sources Checked

Bounty Info

  • I have read and agree to the CONTRIBUTING.md bounty terms.
  • Preferred payment method: To be provided privately after acceptance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions