mcpField
S
security-headers

security-headers

github.com/wshaddix安全8676 次安装35 周前更新
代码仓库

关于 security-headers

面向 Razor Pages 应用的 .NET 安全配置技能,介绍 HTTP 安全标头的内置与自定义中间件、CSP 选项、按环境配置及标头测试。

主要功能

  1. 01内置及自定义标头中间件
  2. 02内容安全策略配置
  3. 03CSP 随机数与违规报告
  4. 04按环境和路由配置标头
  5. 05安全标头测试指南

适用场景

  1. 01为 Razor Pages 添加防护标头
  2. 02为内联脚本配置 CSP
  3. 03在安全审查中检查响应标头

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill security-headers

安装后在 Claude Code 中输入/security-headers确认已加载。

详细介绍

你是一名资深 .NET 安全架构师。在 Razor Pages 应用中实现安全标头时,请采用这些模式,以防范 XSS、点击劫持和中间人攻击等常见 Web 漏洞。目标平台为 .NET 8+,并启用可空引用类型。

理由

安全标头是纵深防御的关键机制,可在不更改应用代码的情况下保护应用免受各种攻击。正确配置可防止 XSS、数据注入、点击劫持、MIME 嗅探和其他常见漏洞。所有现代浏览器都支持这些标头。

安全标头概览

标头用途OWASP 类别
Content-Security-Policy防止 XSS、数据注入A7
Strict-Transport-Security强制使用 HTTPS 连接A2
X-Frame-Options防止点击劫持A6
X-Content-Type-Options防止 MIME 嗅探A6
Referrer-Policy控制来源信息隐私
Permissions-Policy限制浏览器功能隐私
X-XSS-Protection旧版 XSS 防护A7

模式 1:内置安全标头中间件

ASP.NET Core 提供了用于常见安全标头的内置中间件。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// HSTS (only in production)
if (!app.Environment.IsDevelopment())
{
    app.UseHsts(); // Adds Strict-Transport-Security header
}

// HTTPS Redirection
app.UseHttpsRedirection();

// Security headers middleware (built-in .NET 8+)
// AddHeader can be used for custom headers

模式 2:自定义安全标头中间件

如需全面控制,请创建自定义中间件。

public class SecurityHeadersMiddleware(RequestDelegate next)
{
    public async Task Invoke(HttpContext context)
    {
        // Prevent MIME sniffing
        context.Response.Headers["X-Content-Type-Options"] = "nosniff";
        
        // Prevent clickjacking
        context.Response.Headers["X-Frame-Options"] = "DENY";
        
        // Legacy XSS protection (redundant with CSP, but good for older browsers)
        context.Response.Headers["X-XSS-Protection"] = "1; mode=block";
        
        // Control referrer information
        context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
        
        // Permissions Policy (formerly Feature-Policy)
        context.Response.Headers["Permissions-Policy"] = 
            "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
        
        await next(context);
    }
}

// Extension method
public static class SecurityHeadersExtensions
{
    public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app)
    {
        return app.UseMiddleware<SecurityHeadersMiddleware>();
    }
}
注册
// Program.cs
var app = builder.Build();

app.UseSecurityHeaders(); // Add early in pipeline
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

模式 3:内容安全策略(CSP)

CSP 是防止 XSS 和数据注入攻击的最强大安全标头。

基本 CSP 配置
public class CspMiddleware(RequestDelegate next, ILogger<CspMiddleware> logger)
{
    private const string CspHeaderName = "Content-Security-Policy";
    
    public async Task Invoke(HttpContext context)
    {
        var csp = new StringBuilder();
        
        // Default fallback
        csp.Append("default-src 'self'; ");
        
        // Scripts: self + inline (nonce) + specific external sources
        csp.Append("script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://js.stripe.com; ");
        
        // Styles: self + inline + external CDNs
        csp.Append("style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; ");
        
        // Images: self + data URIs + external sources
        csp.Append("img-src 'self' data: https: blob:; ");
        
        // Fonts: self + Google Fonts
        csp.Append("font-src 'self' https://fonts.gstatic.com; ");
        
        // Connections (AJAX/WebSockets)
        csp.Append("connect-src 'self' https://api.example.com wss://ws.example.com; ");
        
        // Frames: only allow specific sources
        csp.Append("frame-src 'self' https://js.stripe.com https://hooks.stripe.com; ");
        
        // Form submissions
        csp.Append("form-action 'self'; ");
        
        // Base URI restrictions
        csp.Append("base-uri 'self'; ");
        
        // Prevent mixed content
        csp.Append("upgrade-insecure-requests; ");
        
        // Report violations (report-uri is deprecated, use report-to)
        csp.Append("report-uri /api/csp-report; ");
        
        context.Response.Headers[CspHeaderName] = csp.ToString();
        
        await next(context);
    }
}
使用 nonce 配置内联脚本的 CSP
public class CspNonceMiddleware(RequestDelegate next)
{
    public static readonly string NonceKey = "CSP-Nonce";
    
    public async Task Invoke(HttpContext context)
    {
        // Generate cryptographically secure nonce
        var nonce = GenerateNonce();
        
        // Store in HttpContext for use in views
        context.Items[NonceKey] = nonce;
        
        // Add nonce to CSP header
        var csp = $"script-src 'nonce-{nonce}' 'self'; " +
                  $"style-src 'nonce-{nonce}' 'self'; " +
                  "default-src 'self';";
        
        context.Response.Headers["Content-Security-Policy"] = csp;
        
        await next(context);
    }
    
    private static string GenerateNonce()
    {
        var bytes = new byte[16];
        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(bytes);
        return Convert.ToBase64String(bytes);
    }
}

// Tag Helper for nonce
[HtmlTargetElement("script", Attributes = "asp-add-nonce")]
public class ScriptNonceTagHelper(IHttpContextAccessor httpContextAccessor) : TagHelper
{
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        var nonce = httpContextAccessor.HttpContext?.Items[CspNonceMiddleware.NonceKey] as string;
        if (!string.IsNullOrEmpty(nonce))
        {
            output.Attributes.SetAttribute("nonce", nonce);
        }
    }
}

// Usage in Razor view
<script asp-add-nonce>
    console.log('This inline script is allowed because it has a nonce');
</script>

模式 4:可配置的安全标头

允许针对不同环境采用不同配置。

public class SecurityHeadersOptions
{
    public bool UseStrictCsp { get; set; } = true;
    public List<string> AllowedScriptSources { get; set; } = new() { "'self'" };
    public List<string> AllowedStyleSources { get; set; } = new() { "'self'" };
    public List<string> AllowedImageSources { get; set; } = new() { "'self'", "data:", "https:" };
    public bool UpgradeInsecureRequests { get; set; } = true;
    public string? ReportUri { get; set; }
}

public class ConfigurableSecurityHeadersMiddleware(RequestDelegate next, IOptions<SecurityHeadersOptions> options, ILogger<ConfigurableSecurityHeadersMiddleware> logger)
{
    private readonly SecurityHeadersOptions _options = options.Value;
    
    public async Task Invoke(HttpContext context)
    {
        // Standard security headers
        context.Response.Headers["X-Content-Type-Options"] = "nosniff";
        context.Response.Headers["X-Frame-Options"] = "DENY";
        context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
        
        // Build CSP
        var csp = new StringBuilder();
        
        csp.Append($"default-src 'self'; ");
        csp.Append($"script-src {string.Join(" ", _options.AllowedScriptSources)}; ");
        csp.Append($"style-src {string.Join(" ", _options.AllowedStyleSources)}; ");
        csp.Append($"img-src {string.Join(" ", _options.AllowedImageSources)}; ");
        csp.Append("font-src 'self'; ");
        csp.Append("connect-src 'self'; ");
        csp.Append("form-action 'self'; ");
        csp.Append("base-uri 'self'; ");
        
        if (_options.UpgradeInsecureRequests)
        {
            csp.Append("upgrade-insecure-requests; ");
        }
        
        if (!string.IsNullOrEmpty(_options.ReportUri))
        {
            csp.Append($"report-uri {_options.ReportUri}; ");
        }
        
        context.Response.Headers["Content-Security-Policy"] = csp.ToString();
        
        await next(context);
    }
}

// Configuration in appsettings.json
{
  "SecurityHeaders": {
    "UseStrictCsp": true,
    "AllowedScriptSources": ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net"],
    "AllowedStyleSources": ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
    "UpgradeInsecureRequests": true,
    "ReportUri": "/api/csp-report"
  }
}

// Registration
builder.Services.Configure<SecurityHeadersOptions>(
    builder.Configuration.GetSection("SecurityHeaders"));

模式 5:CSP 违规报告

public class CspReportRequest
{
    [JsonPropertyName("csp-report")]
    public CspReport? Report { get; set; }
}

public class CspReport
{
    [JsonPropertyName("document-uri")]
    public string? DocumentUri { get; set; }
    
    [JsonPropertyName("referrer")]
    public string? Referrer { get; set; }
    
    [JsonPropertyName("violated-directive")]
    public string? ViolatedDirective { get; set; }
    
    [JsonPropertyName("effective-directive")]
    public string? EffectiveDirective { get; set; }
    
    [JsonPropertyName("blocked-uri")]
    public string? BlockedUri { get; set; }
    
    [JsonPropertyName("source-file")]
    public string? SourceFile { get; set; }
}

public class CspReportHandler(ILogger<CspReportHandler> logger) : IRequestHandler<CspReportRequest>
{
    public Task Handle(CspReportRequest request, CancellationToken cancellationToken)
    {
        var report = request.Report;
        
        if (report != null)
        {
            logger.LogWarning(
                "CSP Violation: {BlockedUri} blocked by {ViolatedDirective} on {DocumentUri}",
                report.BlockedUri,
                report.ViolatedDirective,
                report.DocumentUri);
        }
        
        return Task.CompletedTask;
    }
}

// Minimal API endpoint
app.MapPost("/api/csp-report", (CspReportRequest report, IMediator mediator) =>
{
    // Process asynchronously
    _ = mediator.Send(report);
    return Results.Ok();
});

模式 6:HSTS 配置

// Program.cs
if (!app.Environment.IsDevelopment())
{
    // Add HSTS middleware
    app.UseHsts();
}

// Or configure explicitly
builder.Services.AddHsts(options =>
{
    options.MaxAge = TimeSpan.FromDays(365);
    options.IncludeSubDomains = true;
    options.Preload = true; // Submit to browser preload list
});

// HSTS can also be configured via web.config for IIS
HSTS 预加载注意事项
// Only enable preload after thorough testing
builder.Services.AddHsts(options =>
{
    options.MaxAge = TimeSpan.FromDays(365 * 2); // Minimum 1 year for preload
    options.IncludeSubDomains = true; // Required for preload
    options.Preload = true;
});

// Before enabling preload:
// 1. Ensure HTTPS works correctly
// 2. Ensure all subdomains serve HTTPS
// 3. Test thoroughly
// 4. Submit to https://hstspreload.org/

模式 7:功能策略(Permissions Policy)

控制可使用哪些浏览器功能。

public class PermissionsPolicyMiddleware(RequestDelegate next)
{
    public async Task Invoke(HttpContext context)
    {
        // Modern Permissions-Policy header
        var permissions = new[]
        {
            "accelerometer=()",
            "camera=()",
            "geolocation=()",
            "gyroscope=()",
            "magnetometer=()",
            "microphone=()",
            "payment=()",
            "usb=()",
            "screen-wake-lock=()",
            "xr-spatial-tracking=()",
            "display-capture=()"
        };
        
        context.Response.Headers["Permissions-Policy"] = string.Join(", ", permissions);
        
        await next(context);
    }
}

// Allow specific features
// camera=(self "https://trusted-site.com")
// geolocation=(self)

模式 8:针对特定路由设置条件式标头

public class ConditionalSecurityHeadersMiddleware(RequestDelegate next)
{
    public async Task Invoke(HttpContext context)
    {
        var path = context.Request.Path.Value?.ToLowerInvariant();
        
        // Disable CSP for admin area (may use rich editors)
        if (path?.StartsWith("/admin") == true)
        {
            context.Response.Headers["Content-Security-Policy"] = "default-src 'self' 'unsafe-inline' 'unsafe-eval';";
        }
        else
        {
            // Standard CSP
            context.Response.Headers["Content-Security-Policy"] = 
                "default-src 'self'; script-src 'self'; style-src 'self';";
        }
        
        // Always add these
        context.Response.Headers["X-Content-Type-Options"] = "nosniff";
        context.Response.Headers["X-Frame-Options"] = "DENY";
        
        await next(context);
    }
}

模式 9:静态文件的安全标头

// Custom static file options with security headers
app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        // Add cache control for static assets
        ctx.Context.Response.Headers["Cache-Control"] = "public, max-age=31536000, immutable";
        
        // Ensure static files also get security headers
        ctx.Context.Response.Headers["X-Content-Type-Options"] = "nosniff";
        ctx.Context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
    }
});

反模式

过于宽松的 CSP
// ❌ BAD: Allowing everything defeats the purpose
csp.Append("default-src * 'unsafe-inline' 'unsafe-eval'; ");

// ✅ GOOD: Explicit allowlist
csp.Append("default-src 'self'; ");
csp.Append("script-src 'self' https://trusted-cdn.com; ");
生产环境中缺少 HSTS
// ❌ BAD: No HSTS in production
if (app.Environment.IsProduction())
{
    // Missing HSTS!
}

// ✅ GOOD: HSTS always enabled in production
if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
    app.UseHttpsRedirection();
}
标头值不一致
// ❌ BAD: Conflicting frame options
context.Response.Headers["X-Frame-Options"] = "DENY";
csp.Append("frame-ancestors 'self'; "); // Conflicts with DENY

// ✅ GOOD: Consistent values
context.Response.Headers["X-Frame-Options"] = "SAMEORIGIN";
csp.Append("frame-ancestors 'self'; "); // Aligns with SAMEORIGIN

测试安全标头

# Using curl to check headers
curl -I https://your-site.com

# Using online scanners
# https://securityheaders.com/
# https://csp-evaluator.withgoogle.com/
# https://observatory.mozilla.org/

参考资料

评论

支持 Markdown