mcpField
D
dotnet-security

dotnet-security

github.com/claude-dev-suiteセキュリティ46338 回インストール2 日前に更新
リポジトリ

dotnet-security について

.NET 開発者向けのセキュリティリファレンススキルです。依存関係の監査、ASP.NET Core の設定、入力処理、認証に加え、コード例やアンチパターン、トラブルシューティングも扱います。

主な機能

  1. 01脆弱・古い NuGet パッケージの監査
  2. 02ASP.NET Core のセキュリティヘッダー設定
  3. 03SQL インジェクションと XSS 対策の確認
  4. 04認証・認可・入力検証の解説
  5. 05ファイルアップロードとシークレット管理の助言
  6. 06アンチパターンと問題の切り分け

活用シーン

  1. 01リリース前の .NET 依存関係の脆弱性確認
  2. 02ASP.NET Core アプリのセキュリティ設定
  3. 03データベースクエリとユーザー入力処理のレビュー
  4. 04JWT、CORS、NuGet のセキュリティ問題の調査

インストール

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

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill dotnet-security

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

詳細

.NET セキュリティ - クイックリファレンス

このスキルを使用しない場合

  • 一般的な OWASP の概念 - owasp または owasp-top-10 スキルを使用してください
  • Java のセキュリティ - java-security スキルを使用してください
  • Python のセキュリティ - python-security スキルを使用してください
  • シークレット管理 - secrets-management スキルを使用してください

詳細な知識:ASP.NET Core のセキュリティに関するドキュメントには、technology: dotnet を指定して mcp__documentation__fetch_docs を使用してください。

依存関係の監査

# .NET built-in audit
dotnet list package --vulnerable

# Detailed audit with transitive dependencies
dotnet list package --vulnerable --include-transitive

# Check outdated packages
dotnet list package --outdated

# Snyk for .NET
snyk test
CI/CD との統合
# GitHub Actions
- name: Security audit
  run: |
    dotnet list package --vulnerable --include-transitive
    dotnet tool install -g snyk
    snyk test
NuGet.config のセキュリティ
<configuration>
  <packageSources>
    
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
  <packageSourceCredentials>
    
  </packageSourceCredentials>
</configuration>

ASP.NET Core のセキュリティ設定

Program.cs のセキュリティ設定
var builder = WebApplication.CreateBuilder(args);

// Security headers
builder.Services.AddHsts(options =>
{
    options.MaxAge = TimeSpan.FromDays(365);
    options.IncludeSubDomains = true;
    options.Preload = true;
});

// CORS configuration
builder.Services.AddCors(options =>
{
    options.AddPolicy("Production", policy =>
    {
        policy.WithOrigins("https://myapp.com")
              .AllowCredentials()
              .WithMethods("GET", "POST", "PUT", "DELETE")
              .WithHeaders("Authorization", "Content-Type");
    });
});

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
        };
    });

// Rate limiting (.NET 7+)
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("login", limiter =>
    {
        limiter.Window = TimeSpan.FromMinutes(15);
        limiter.PermitLimit = 5;
        limiter.QueueLimit = 0;
    });
});

var app = builder.Build();

// Security middleware order matters
app.UseHsts();
app.UseHttpsRedirection();
app.UseCors("Production");
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();
セキュリティヘッダーのミドルウェア
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
    context.Response.Headers.Add("X-Frame-Options", "DENY");
    context.Response.Headers.Add("X-XSS-Protection", "0"); // Use CSP instead
    context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
    context.Response.Headers.Add("Content-Security-Policy",
        "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");

    await next();
});

SQL インジェクションの防止

Entity Framework Core - 安全
// SAFE - LINQ queries
var user = await context.Users
    .FirstOrDefaultAsync(u => u.Email == email);

// SAFE - Parameterized raw SQL
var users = await context.Users
    .FromSqlRaw("SELECT * FROM Users WHERE Email = {0}", email)
    .ToListAsync();

// SAFE - Interpolated (converted to parameters)
var users = await context.Users
    .FromSqlInterpolated($"SELECT * FROM Users WHERE Email = {email}")
    .ToListAsync();
Entity Framework Core - 安全ではない
// UNSAFE - String concatenation
var query = $"SELECT * FROM Users WHERE Email = '{email}'";  // NEVER!
var users = await context.Users.FromSqlRaw(query).ToListAsync();

// UNSAFE - FormattableString with raw
var users = await context.Users
    .FromSqlRaw($"SELECT * FROM Users WHERE Email = '{email}'")  // NEVER!
    .ToListAsync();
Dapper - 安全
// SAFE - Anonymous parameters
var user = await connection.QueryFirstOrDefaultAsync<User>(
    "SELECT * FROM Users WHERE Email = @Email",
    new { Email = email }
);

// SAFE - DynamicParameters
var parameters = new DynamicParameters();
parameters.Add("Email", email);
var user = await connection.QueryFirstOrDefaultAsync<User>(
    "SELECT * FROM Users WHERE Email = @Email",
    parameters
);

XSS の防止

Razor Pages(自動エンコード)

<p>@Model.UserInput</p>


<p>@Html.Raw(Model.UserInput)</p>  
手動でのサニタイズ
using Ganss.Xss;

var sanitizer = new HtmlSanitizer();
sanitizer.AllowedTags.Add("p");
sanitizer.AllowedTags.Add("b");
sanitizer.AllowedTags.Add("i");

string safeHtml = sanitizer.Sanitize(userInput);
API レスポンスのエンコード
using System.Text.Encodings.Web;

var encoder = HtmlEncoder.Default;
var safeString = encoder.Encode(userInput);

認証と認可

JWT トークンの生成
public class JwtService
{
    private readonly IConfiguration _config;

    public JwtService(IConfiguration config) => _config = config;

    public string GenerateToken(User user)
    {
        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Email, user.Email),
            new Claim(ClaimTypes.Role, user.Role)
        };

        var token = new JwtSecurityToken(
            issuer: _config["Jwt:Issuer"],
            audience: _config["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddHours(1),
            signingCredentials: credentials
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}
Identity を使用したパスワードのハッシュ化
// Use ASP.NET Core Identity's PasswordHasher
var hasher = new PasswordHasher<User>();

// Hash password
string hashed = hasher.HashPassword(user, password);

// Verify password
var result = hasher.VerifyHashedPassword(user, hashed, password);
if (result == PasswordVerificationResult.Success)
{
    // Password matches
}
認可ポリシー
// Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireRole("Admin"));

    options.AddPolicy("ResourceOwner", policy =>
        policy.Requirements.Add(new ResourceOwnerRequirement()));
});

// Custom requirement handler
public class ResourceOwnerHandler : AuthorizationHandler<ResourceOwnerRequirement, Resource>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        ResourceOwnerRequirement requirement,
        Resource resource)
    {
        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (resource.OwnerId == userId)
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

// Controller usage
[Authorize(Policy = "AdminOnly")]
public IActionResult AdminDashboard() => View();

入力の検証

データアノテーション
public class CreateUserRequest
{
    [Required]
    [EmailAddress]
    [StringLength(255)]
    public string Email { get; set; } = default!;

    [Required]
    [StringLength(128, MinimumLength = 12)]
    [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).+$",
        ErrorMessage = "Password must contain uppercase, lowercase, number and special character")]
    public string Password { get; set; } = default!;

    [Required]
    [StringLength(100, MinimumLength = 2)]
    [RegularExpression(@"^[a-zA-Z\s\-']+$")]
    public string Name { get; set; } = default!;
}

[HttpPost]
public IActionResult CreateUser([FromBody] CreateUserRequest request)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    // request is validated
}
FluentValidation
public class CreateUserValidator : AbstractValidator<CreateUserRequest>
{
    public CreateUserValidator()
    {
        RuleFor(x => x.Email)
            .NotEmpty()
            .EmailAddress()
            .MaximumLength(255);

        RuleFor(x => x.Password)
            .NotEmpty()
            .MinimumLength(12)
            .MaximumLength(128)
            .Matches(@"[A-Z]").WithMessage("Must contain uppercase")
            .Matches(@"[a-z]").WithMessage("Must contain lowercase")
            .Matches(@"\d").WithMessage("Must contain digit")
            .Matches(@"[@$!%*?&]").WithMessage("Must contain special character");

        RuleFor(x => x.Name)
            .NotEmpty()
            .Length(2, 100)
            .Matches(@"^[a-zA-Z\s\-']+$");
    }
}

安全なファイルアップロード

[HttpPost("upload")]
[RequestSizeLimit(10 * 1024 * 1024)] // 10 MB
public async Task<IActionResult> Upload(IFormFile file)
{
    // Validate content type
    var allowedTypes = new[] { "image/jpeg", "image/png", "application/pdf" };
    if (!allowedTypes.Contains(file.ContentType))
        return BadRequest("File type not allowed");

    // Validate file extension
    var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".pdf" };
    var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
    if (!allowedExtensions.Contains(extension))
        return BadRequest("File extension not allowed");

    // Generate safe filename
    var safeName = $"{Guid.NewGuid()}{extension}";
    var uploadPath = Path.Combine(_uploadDirectory, safeName);

    // Save file
    await using var stream = new FileStream(uploadPath, FileMode.Create);
    await file.CopyToAsync(stream);

    return Ok(new { filename = safeName });
}

シークレット管理

User Secrets(開発環境)
# Initialize user secrets
dotnet user-secrets init

# Set secrets
dotnet user-secrets set "Jwt:Key" "your-secret-key"
dotnet user-secrets set "ConnectionStrings:Default" "your-connection-string"
appsettings.json(シークレットを保存しないでください)
{
  "Jwt": {
    "Issuer": "https://myapp.com",
    "Audience": "https://myapp.com"
    // Key should come from environment or secrets manager
  }
}
環境変数
// Program.cs - Load from environment
builder.Configuration.AddEnvironmentVariables();

// Access
var jwtKey = builder.Configuration["Jwt:Key"]
    ?? throw new InvalidOperationException("JWT Key not configured");
Azure Key Vault との統合
builder.Configuration.AddAzureKeyVault(
    new Uri($"https://{vaultName}.vault.azure.net/"),
    new DefaultAzureCredential()
);

セキュリティイベントのログ記録

public class SecurityLogger
{
    private readonly ILogger<SecurityLogger> _logger;

    public SecurityLogger(ILogger<SecurityLogger> logger) => _logger = logger;

    public void LogLoginAttempt(string username, bool success, string ipAddress)
    {
        _logger.LogInformation(
            "Login attempt: User={Username}, Success={Success}, IP={IpAddress}",
            username, success, ipAddress
        );
    }

    public void LogAccessDenied(string userId, string resource, string ipAddress)
    {
        _logger.LogWarning(
            "Access denied: User={UserId}, Resource={Resource}, IP={IpAddress}",
            userId, resource, ipAddress
        );
    }

    // NEVER log sensitive data
    // _logger.LogInformation("Password: {Password}", password);  // NEVER!
}

アンチパターン

アンチパターン問題点正しいアプローチ
SQL での文字列補間SQL インジェクションパラメーター化されたクエリを使用する
Html.Raw(userInput)XSS の脆弱性デフォルトのエンコードを使用する
appsettings にシークレットを保存するシークレットの漏えいUser Secrets/Key Vault を使用する
あらゆる場所で [AllowAnonymous] を指定する認証されない選択的に適用する
HTTPS リダイレクトを無効にする中間者攻撃HTTPS を有効に保つ
独自の暗号化を実装する暗号化が弱い組み込みライブラリを使用する
すべての例外を捕捉するセキュリティ上の問題が隠れるログに記録し、個別に処理する

クイックトラブルシューティング

問題考えられる原因解決策
401 UnauthorizedJWT の検証に失敗した発行者、対象者、キーを確認する
CORS エラーオリジンが許可されていないCORS ポリシーにオリジンを追加する
レート制限に達したリクエストが多すぎるレートリミッターの設定を調整する
パスワードの検証に失敗するポリシーの要件Identity のパスワードオプションを確認する
NuGet の脆弱性パッケージが古い修正済みバージョンに更新する
User Secrets が読み込まれないDevelopment 環境ではないASPNETCORE_ENVIRONMENT を確認する

セキュリティスキャンのコマンド

# Dependency audit
dotnet list package --vulnerable --include-transitive

# Security analyzers (add NuGet packages)
# Microsoft.CodeAnalysis.NetAnalyzers
# SecurityCodeScan.VS2019

# Snyk
snyk test

# Check for secrets
gitleaks detect
trufflehog git file://.

# SAST with Semgrep
semgrep --config=p/csharp .

関連スキル

コメント