mcpField
D
dotnet-security

dotnet-security

github.com/claude-dev-suite安全46338 次安装2 天前更新
代码仓库

关于 dotnet-security

面向 .NET 开发者的安全参考 Skill,整理依赖审计、ASP.NET Core 配置、输入处理和身份验证等实践。内容还包括安全代码示例、常见反模式与故障排查。

主要功能

  1. 01审计易受攻击及过时的 NuGet 包
  2. 02配置 ASP.NET Core 安全响应头
  3. 03参考 SQL 注入与 XSS 防护写法
  4. 04涵盖身份验证、授权和输入验证
  5. 05提供文件上传与机密管理建议
  6. 06列出安全反模式及排查方法

适用场景

  1. 01在发布前检查 .NET 依赖漏洞
  2. 02配置 ASP.NET Core 应用的安全措施
  3. 03审查数据库查询和用户输入处理
  4. 04排查 JWT、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 技能

深入了解:使用 mcp__documentation__fetch_docs,并将 technology 设为 dotnet,以获取 ASP.NET Core 安全文档。

依赖项审计

# .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 });
}

密钥管理

用户机密(开发环境)
# 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 中存储机密机密泄露使用用户机密/Key Vault
到处使用 [AllowAnonymous]无身份验证有选择地应用
禁用 HTTPS 重定向中间人攻击保持启用 HTTPS
自定义加密实现加密强度弱使用内置库
捕获所有异常掩盖安全问题记录日志并针对性处理

快速故障排除

问题可能原因解决方案
401 未授权JWT 验证失败检查颁发者、受众和密钥
CORS 错误不允许此来源将来源添加到 CORS 策略
触发速率限制请求过多调整速率限制器设置
密码验证失败策略要求检查 Identity 密码选项
NuGet 漏洞软件包过时更新到已修复的版本
未加载用户机密不在 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 .

相关技能

评论

支持 Markdown