mcpField
S
security-headers

security-headers

github.com/wshaddixセキュリティ8676 回インストール35 週間前に更新
リポジトリ

security-headers について

Razor Pages アプリで HTTP セキュリティヘッダーを設定するための .NET セキュリティスキルです。組み込みおよびカスタムのミドルウェア、CSP の設定、環境別の構成、ヘッダーのテストを扱います。

主な機能

  1. 01組み込みおよびカスタムのヘッダーミドルウェア
  2. 02コンテンツセキュリティポリシーの設定
  3. 03CSP nonce と違反レポート
  4. 04環境別・ルート別のヘッダー設定
  5. 05セキュリティヘッダーのテスト手順

活用シーン

  1. 01Razor Pages への防御用ヘッダーの追加
  2. 02インラインスクリプト向け CSP の設定
  3. 03セキュリティレビューでのレスポンスヘッダー確認

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/security-headersを入力して読み込まれたか確認します。

詳細

Razor Pages アプリケーションでセキュリティヘッダーを実装する際は、シニア .NET セキュリティアーキテクトとして、XSS、クリックジャッキング、中間者攻撃などの一般的な Web 脆弱性から保護するため、以下のパターンを適用してください。対象は nullable reference types が有効な .NET 8 以降です。

理由

セキュリティヘッダーは、アプリケーションコードを変更せずにさまざまな攻撃からアプリケーションを保護する、多層防御における重要な仕組みです。適切に設定することで、XSS、クリックジャッキング、MIME スニッフィングなどの一般的な脆弱性を防げます。これらのヘッダーは、すべての最新ブラウザーでサポートされています。

セキュリティヘッダーの概要

ヘッダー目的OWASP カテゴリー
Content-Security-PolicyXSS、データインジェクションを防止A7
Strict-Transport-SecurityHTTPS 接続を強制A2
X-Frame-Optionsクリックジャッキングを防止A6
X-Content-Type-OptionsMIME スニッフィングを防止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 の preload に関する考慮事項
// 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:Feature Policy(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/

参考資料

コメント