mcpField
D
dotnet-security-owasp

dotnet-security-owasp

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

关于 dotnet-security-owasp

dotnet-security-owasp 面向 .NET 开发者,按 OWASP Top 10(2021)组织安全指导,并结合 .NET 特定风险、缓解示例和常见陷阱。

主要功能

  1. 01覆盖 OWASP Top 10(2021)
  2. 02说明 .NET 特定漏洞风险
  3. 03提供缓解措施代码示例
  4. 04提示常见安全陷阱
  5. 05警示已弃用的安全模式

适用场景

  1. 01审查 .NET 代码中的 OWASP 相关风险
  2. 02为常见应用漏洞选择缓解措施
  3. 03识别不安全或已弃用的 .NET 安全模式

安装

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

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

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

详细介绍

dotnet-security-owasp

适用于 .NET 应用程序的 OWASP Top 10(2021)安全指南。每个类别都包含漏洞说明、.NET 特定风险、缓解措施代码示例和常见陷阱。此技能是已弃用安全模式警告(CAS、APTCA、.NET Remoting、DCOM、BinaryFormatter)的权威来源。

**不在范围内:**身份验证/授权实现——API 级身份验证模式(Identity、OAuth/OIDC、JWT、passkeys、CORS)——请参阅 [skill:dotnet-api-security]。Blazor 身份验证 UI(AuthorizeView、CascadingAuthenticationState)——请参阅 [skill:dotnet-blazor-auth]。特定于云的安全服务(Azure Key Vault、AWS Secrets Manager)——请参阅云相关专题。加密算法选择和密钥管理——请参阅 [skill:dotnet-cryptography]。配置绑定和 Options 模式——请参阅 [skill:dotnet-csharp-configuration]。

交叉引用:[skill:dotnet-secrets-management] 了解机密处理,[skill:dotnet-cryptography] 了解加密最佳实践,[skill:dotnet-csharp-coding-standards] 了解安全编码规范。


A01:访问控制失效

**漏洞:**用户执行超出其预期权限的操作——访问其他用户的数据、提升权限或绕过访问检查。

**.NET 风险:**控制器/端点缺少 [Authorize] 属性;存在不安全的直接对象引用(IDOR),即从路由参数获取用户 ID,却不验证所有权;以及 CORS 配置错误,允许非预期的来源。

缓解措施
// 1. Apply authorization globally, then opt out explicitly
builder.Services.AddAuthorizationBuilder()
    .SetFallbackPolicy(new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build());

var app = builder.Build();
app.MapControllers(); // All endpoints require auth by default

// 2. Resource-based authorization to prevent IDOR
public sealed class DocumentAuthorizationHandler
    : AuthorizationHandler<EditRequirement, Document>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        EditRequirement requirement,
        Document resource)
    {
        if (resource.OwnerId == context.User.FindFirstValue(ClaimTypes.NameIdentifier))
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

// In the endpoint:
app.MapPut("/documents/{id}", async (
    int id,
    DocumentDto dto,
    IAuthorizationService authService,
    ClaimsPrincipal user,
    AppDbContext db) =>
{
    var document = await db.Documents.FindAsync(id);
    if (document is null) return Results.NotFound();

    var authResult = await authService.AuthorizeAsync(user, document, "Edit");
    if (!authResult.Succeeded) return Results.Forbid();

    document.Title = dto.Title;
    await db.SaveChangesAsync();
    return Results.NoContent();
});
// 3. Restrict CORS to known origins
builder.Services.AddCors(options =>
{
    options.AddPolicy("Strict", policy =>
    {
        policy.WithOrigins("https://app.example.com")
              .WithMethods("GET", "POST")
              .WithHeaders("Content-Type", "Authorization");
    });
});

注意:AllowAnyOrigin() 与 AllowCredentials() 组合使用时,ASP.NET Core 会在运行时拒绝该配置;但 SetIsOriginAllowed(_ => true) 与 AllowCredentials() 组合使用时,会静默地允许所有来源——切勿使用此模式。


A02:加密失效

**漏洞:**由于加密薄弱或缺失而导致敏感数据暴露——以明文存储、使用已弃用的算法或密钥管理不当。

**.NET 风险:**使用 MD5/SHA1 哈希密码、在 appsettings.json 中以明文密码存储连接字符串、通过 HTTP 传输敏感数据,或使用 DES/RC2 加密。

缓解措施
// Enforce HTTPS and HSTS
builder.Services.AddHttpsRedirection(options =>
{
    options.HttpsPort = 443;
});

var app = builder.Build();
app.UseHsts(); // Strict-Transport-Security header
app.UseHttpsRedirection();

// Never store secrets in appsettings.json -- use user secrets or env vars
// See [skill:dotnet-secrets-management] for proper secrets handling
// Use Data Protection API for symmetric encryption of application data
public sealed class TokenProtector(IDataProtectionProvider provider)
{
    private readonly IDataProtector _protector =
        provider.CreateProtector("Tokens.V1");

    public string Protect(string plaintext) => _protector.Protect(plaintext);

    public string Unprotect(string ciphertext) => _protector.Unprotect(ciphertext);
}

有关算法选择(AES-GCM、RSA、ECDSA)和密钥派生,请参阅 [skill:dotnet-cryptography]。


A03:注入

**漏洞:**将不可信数据作为命令或查询的一部分发送给解释器——SQL 注入、命令注入、LDAP 注入和跨站脚本(XSS)。

**.NET 风险:**在 SQL 查询中拼接字符串、使用 Process.Start 处理未经清理的输入,以及在 Razor 页面中将用户输入作为原始 HTML 呈现。

缓解措施
// SQL injection prevention: always use parameterized queries
// EF Core is parameterized by default via LINQ
var orders = await db.Orders
    .Where(o => o.CustomerId == customerId)
    .ToListAsync();

// When raw SQL is needed, use parameterized interpolation
var results = await db.Orders
    .FromSqlInterpolated($"SELECT * FROM Orders WHERE Status = {status}")
    .ToListAsync();

// NEVER concatenate user input into SQL:
// var bad = db.Orders.FromSqlRaw("SELECT * FROM Orders WHERE Status = '" + status + "'");
// XSS prevention: Razor encodes output by default.
// Use @Html.Raw() ONLY for trusted, pre-sanitized HTML.
// In Minimal APIs, return typed results -- not raw strings:
app.MapGet("/greeting", (string name) =>
    Results.Content($"<p>Hello, {HtmlEncoder.Default.Encode(name)}</p>",
        "text/html"));

// Command injection prevention: avoid Process.Start with user input.
// If unavoidable, validate against an allowlist:
public static bool IsAllowedTool(string toolName) =>
    toolName is "dotnet" or "git" or "nuget";

注意:FromSqlRaw 与字符串拼接结合使用会绕过参数化。请始终使用 FromSqlInterpolated,或将 SqlParameter 对象传递给 FromSqlRaw。


A04:不安全设计

**漏洞:**设计模式中的缺陷无法仅通过实现来修复——缺少速率限制、缺乏纵深防御、资源消耗不受限制。

**.NET 风险:**未实施速率限制的 API、不受限制的文件上传,以及状态变更操作缺少防伪令牌。

缓解措施
// Rate limiting with built-in middleware (.NET 7+)
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", limiterOptions =>
    {
        limiterOptions.PermitLimit = 100;
        limiterOptions.Window = TimeSpan.FromMinutes(1);
        limiterOptions.QueueLimit = 0;
    });
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});

var app = builder.Build();
app.UseRateLimiter();

app.MapGet("/api/data", () => Results.Ok("data"))
    .RequireRateLimiting("api");
// Anti-forgery for Minimal APIs (.NET 8+)
builder.Services.AddAntiforgery();

var app = builder.Build();
app.UseAntiforgery();

// Form-bound endpoint: antiforgery validated automatically
app.MapPost("/orders", async ([FromForm] string productId, AppDbContext db) =>
{
    var order = new Order { ProductId = productId };
    db.Orders.Add(order);
    await db.SaveChangesAsync();
    return Results.Created($"/orders/{order.Id}", order);
});

// JSON endpoint: opt in explicitly with RequireAntiforgery()
app.MapPost("/api/orders", async (CreateOrderDto dto, AppDbContext db) =>
{
    var order = new Order { ProductId = dto.ProductId };
    db.Orders.Add(order);
    await db.SaveChangesAsync();
    return Results.Created($"/api/orders/{order.Id}", order);
}).RequireAntiforgery();

**注意:**必须在 UseRouting() 之后、MapControllers()/MapGet() 之前调用 UseRateLimiter(),才能正确生效。


A05:安全配置错误

**漏洞:**默认配置不安全、配置不完整、云存储开放、不必要的功能处于启用状态,以及错误消息过于详细。

**.NET 风险:**在生产环境中显示详细异常页面(UseDeveloperExceptionPage)、Kestrel 默认设置暴露服务器标头、调试端点仍处于启用状态,或缺少安全标头。

缓解措施
// Remove server identity headers (configure BEFORE Build)
builder.WebHost.ConfigureKestrel(options =>
{
    options.AddServerHeader = false;
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    // Generic error handler in production -- no stack traces
    app.UseExceptionHandler("/error");
    app.UseHsts();
}

// Add security headers via middleware
app.Use(async (context, next) =>
{
    context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
    context.Response.Headers.Append("X-Frame-Options", "DENY");
    context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
    context.Response.Headers.Append(
        "Content-Security-Policy",
        "default-src 'self'; script-src 'self'; style-src 'self'");
    await next();
});
// Constrain request body size to prevent resource exhaustion (configure BEFORE Build)
builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10 MB
    options.Limits.MaxRequestHeadersTotalSize = 32 * 1024; // 32 KB
    options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
});

// Then: var app = builder.Build();

注意:UseDeveloperExceptionPage() 会泄露源代码路径和堆栈跟踪。请确保仅在 IsDevelopment() 条件下启用,绝不要在生产或预发布环境中启用。


A06:易受攻击和过时的组件

漏洞: 使用存在已知漏洞的组件、不受支持的框架或未修补的依赖项。

.NET 中的风险: 在已停止支持的 .NET 版本上运行,使用存在已知 CVE 的 NuGet 包,未审计传递依赖项漏洞。

缓解措施

<PropertyGroup>
  <NuGetAudit>true</NuGetAudit>
  <NuGetAuditLevel>low</NuGetAuditLevel>
  <NuGetAuditMode>all</NuGetAuditMode> 
</PropertyGroup>
# Audit NuGet packages for known vulnerabilities
dotnet list package --vulnerable --include-transitive

# Keep packages up to date
dotnet outdated  # requires dotnet-outdated-tool

# Check .NET SDK/runtime support status
dotnet --info

注意事项: NuGetAuditMode 默认值为 direct——除非设置为 all,否则会隐藏传递依赖项漏洞。在 CI 中始终使用 all,以发现深层依赖项问题。


A07:身份识别和身份验证失败

漏洞: 身份验证机制薄弱、凭据填充攻击、会话固定、缺少多重身份验证。

.NET 中的风险: 默认 Identity 密码策略过于宽松,会话 Cookie 缺少 Secure/SameSite 属性,未配置账户锁定。

缓解措施
// Configure strong Identity password and lockout policies
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
    // Password requirements
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 12;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireLowercase = true;

    // Account lockout
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;

    // User settings
    options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
// Secure cookie configuration
builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Strict;
    options.ExpireTimeSpan = TimeSpan.FromHours(2);
    options.SlidingExpiration = true;
});

注意事项: CookieSecurePolicy.SameAsRequest 允许在开发环境中通过 HTTP 传输 Cookie,这没有问题。但在生产环境中,如果应用位于终止 TLS 的反向代理之后,应用看到的就是 HTTP——因此 Cookie 会以不安全的方式发送。生产环境中始终使用 CookieSecurePolicy.Always,并配置转发标头。


A08:软件和数据完整性失败

漏洞: 代码和基础设施未能防范完整性破坏——未签名的包、不安全的 CI/CD 管道、反序列化不可信数据。

.NET 中的风险: 使用 BinaryFormatter 进行反序列化(可导致任意代码执行),接受来自不可信源的未签名 NuGet 包,未配置包源映射。

缓解措施


<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="internal" value="https://pkgs.example.com/nuget/v3/index.json" />
  </packageSources>
  <packageSourceMapping>
    <packageSource key="nuget.org">
      <package pattern="*" />
    </packageSource>
    <packageSource key="internal">
      <package pattern="MyCompany.*" />
    </packageSource>
  </packageSourceMapping>
</configuration>
// NEVER use BinaryFormatter -- it is a critical deserialization vulnerability.
// BinaryFormatter is obsolete as error (SYSLIB0011) in .NET 8+ and removed in .NET 9+.
// Use System.Text.Json instead:
var data = JsonSerializer.Deserialize<OrderDto>(jsonString);

// For binary serialization needs, use MessagePack or Protobuf:
// <PackageReference Include="MessagePack" Version="3.*" />
var bytes = MessagePackSerializer.Serialize(order);
var restored = MessagePackSerializer.Deserialize<Order>(bytes);

注意事项: 包源映射采用最具体模式优先规则:MyCompany.* 优先于 * 通配符。始终为内部包定义具体模式,以防范依赖项混淆攻击。


A09:安全日志记录和监控失败

漏洞: 未能充分记录与安全相关的事件,缺乏对安全漏洞的监控,无法检测和响应正在进行的攻击。

.NET 中的风险: 未记录身份验证失败,缺少敏感操作的审计跟踪,以明文形式记录敏感数据(密码、令牌)。

缓解措施
// Log security events with structured logging
public sealed class AuditMiddleware(RequestDelegate next, ILogger<AuditMiddleware> logger)
{
    public async Task InvokeAsync(HttpContext context)
    {
        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous";
        var path = context.Request.Path.Value;

        using (logger.BeginScope(new Dictionary<string, object?>
        {
            ["UserId"] = userId,
            ["RequestPath"] = path,
            ["RemoteIp"] = context.Connection.RemoteIpAddress?.ToString()
        }))
        {
            await next(context);

            // Log failed authentication attempts
            if (context.Response.StatusCode == StatusCodes.Status401Unauthorized)
            {
                logger.LogWarning("Authentication failed for {Path}", path);
            }

            // Log authorization failures
            if (context.Response.StatusCode == StatusCodes.Status403Forbidden)
            {
                logger.LogWarning("Authorization denied for {Path}", path);
            }
        }
    }
}
// NEVER log sensitive data -- redact credentials and PII
// Configure log filtering to exclude sensitive paths
builder.Logging.AddFilter("Microsoft.AspNetCore.Authentication", LogLevel.Warning);

// Use IHttpLoggingInterceptor (.NET 8+) to redact request/response headers
builder.Services.AddHttpLogging(options =>
{
    options.LoggingFields = HttpLoggingFields.RequestPath
        | HttpLoggingFields.RequestMethod
        | HttpLoggingFields.ResponseStatusCode
        | HttpLoggingFields.Duration;
    // Explicitly exclude request/response bodies and auth headers
});

注意事项: 使用 {Placeholder} 语法的结构化日志记录是安全的,但在日志调用中使用字符串插值($"User {userId}")会绕过结构化日志记录,并可能将个人身份信息泄露到不支持数据脱敏的日志接收端。


A10:服务器端请求伪造(SSRF)

漏洞: 应用根据用户提供的 URL 获取远程资源,却未进行验证,因此攻击者可以访问内部服务或元数据端点。

.NET 中的风险: 使用用户提供的 URL 调用 HttpClient,跟随 URL 重定向访问内部网络,访问云元数据端点(169.254.169.254)。

缓解措施
// Validate and restrict outbound URLs
public static class UrlValidator
{
    private static readonly HashSet<string> AllowedHosts = new(StringComparer.OrdinalIgnoreCase)
    {
        "api.example.com",
        "cdn.example.com"
    };

    public static bool IsAllowed(string url)
    {
        if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
            return false;

        // Block non-HTTPS
        if (uri.Scheme != Uri.UriSchemeHttps)
            return false;

        // Block private/internal IPs
        if (IPAddress.TryParse(uri.Host, out var ip))
        {
            if (IsPrivateOrReserved(ip))
                return false;
        }

        // Allowlist hosts
        return AllowedHosts.Contains(uri.Host);
    }

    private static bool IsPrivateOrReserved(IPAddress ip)
    {
        byte[] bytes = ip.GetAddressBytes();
        return bytes[0] switch
        {
            10 => true,                                         // 10.0.0.0/8
            127 => true,                                        // 127.0.0.0/8
            169 when bytes[1] == 254 => true,                   // 169.254.0.0/16 (link-local / cloud metadata)
            172 when bytes[1] >= 16 && bytes[1] <= 31 => true,  // 172.16.0.0/12
            192 when bytes[1] == 168 => true,                   // 192.168.0.0/16
            _ => false
        };
    }
}

// Usage in an endpoint
app.MapPost("/fetch", async (FetchRequest request, IHttpClientFactory factory) =>
{
    if (!UrlValidator.IsAllowed(request.Url))
        return Results.BadRequest("URL not allowed");

    var client = factory.CreateClient();
    var response = await client.GetStringAsync(request.Url);
    return Results.Ok(response);
});
// Configure HttpClient to disable automatic redirect following
builder.Services.AddHttpClient("external", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
    AllowAutoRedirect = false // Prevent redirect-based SSRF
});

注意事项: DNS 重绑定可以绕过 IP 允许列表——攻击者的域名在验证期间解析为公共 IP,但在实际请求期间解析为内部 IP。固定 DNS 解析结果,或在建立连接后重新验证。


已弃用的安全模式

本技能是已弃用安全模式警告的权威来源。其他技能应交叉引用此处,而不是重复这些警告。

代码访问安全(CAS)

.NET Core/.NET 5+ 不支持 CAS。出于 CAS 目的而引用 System.Security.Permissions、SecurityPermission 或 [SecurityCritical]/[SecuritySafeCritical] 属性的代码必须删除,或替换为操作系统级安全边界(容器、进程隔离)。

AllowPartiallyTrustedCallers(APTCA)

[AllowPartiallyTrustedCallers] 属性在 .NET Core/.NET 5+ 中不起作用。部分信任模型已被移除。迁移时删除 APTCA 属性,改用标准授权和输入验证。

.NET Remoting

.NET Remoting 在 .NET Core/.NET 5+ 中不可用。由于远程对象的反序列化不受限制,它本质上并不安全。请替换为:

  • 使用 gRPC 进行跨进程/跨机器 RPC(传输安全请参阅 [skill:dotnet-cryptography])
  • 使用命名管道进行同机 IPC
  • 使用 HTTP API 进行服务间通信
DCOM

分布式 COM(DCOM)仅适用于 Windows,且 .NET Core/.NET 5+ 不支持。请替换为 gRPC、REST API 或消息队列,以实现分布式通信。

BinaryFormatter

在 .NET 8 中,BinaryFormatter 已过时并会报错(SYSLIB0011);在 .NET 9+ 中已移除。它会通过反序列化攻击导致任意代码执行。请替换为:

  • 使用 System.Text.Json 进行 JSON 序列化
  • 使用 MessagePack 或 Protocol Buffers 处理二进制格式
  • 在 XML 场景中使用 XmlSerializer,并严格限制允许的类型

不要将 System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization 设为 true 来绕过此问题。


Agent 注意事项

  1. 不要在没有明确理由的情况下使用 [AllowAnonymous] —— 它会覆盖全局回退策略。请为每个匿名端点添加注释,说明使用原因。
  2. 不要为了方便而禁用 HTTPS 重定向 —— 本地开发请改用 dotnet dev-certs https --trust。
  3. 不要记录原始请求正文 —— 其中可能包含凭据、令牌或个人身份信息(PII)。请使用 HttpLoggingFields 选择安全字段。
  4. 不要只依赖客户端验证 —— 始终在服务器端进行验证。Razor 表单验证用于提升用户体验,而非保障安全。
  5. 不要使用字符串插值搭配 FromSqlRaw —— 请使用会自动参数化的 FromSqlInterpolated。
  6. 不要在 appsettings.json 中存储机密 —— 开发时使用用户机密;生产环境使用环境变量或托管标识。请参阅 [skill:dotnet-secrets-management]。
  7. 不要使用已弃用的模式生成安全敏感代码 —— CAS、APTCA、.NET Remoting、DCOM 和 BinaryFormatter 在现代 .NET 中均不受支持。请参阅上文“已弃用的安全模式”一节。

先决条件

  • .NET 8.0+(LTS 基线)
  • ASP.NET Core 8.0+,用于安全中间件、防伪和速率限制
  • Microsoft.AspNetCore.Identity,用于身份验证/身份管理(如果使用 A07 模式)

参考资料

评论

支持 Markdown