ASP.NET Core Web API中IDOR漏洞修复方案咨询
修复IDOR漏洞(Angular 16 + .NET Core)及ID加密方案
核心认知:IDOR的本质是权限校验缺失
加密ID只是辅助防护手段,不能替代后端的权限验证——哪怕ID藏得再好,只要接口没有校验用户对目标对象的访问权限,攻击者依然可以通过其他手段(比如遍历加密后的ID)获取敏感数据。修复IDOR的核心是确保每个请求都经过严格的权限校验。
具体修复方案
1. 后端(.NET Core):强制权限校验(最关键)
所有涉及对象访问的接口(如获取、修改、删除资源)必须添加权限校验逻辑:
- 对象归属校验:比如用户请求
/api/orders/{id}时,后端需查询该订单的OwnerId,与当前登录用户的ID(从JWT Claims或会话中获取)比对,只有匹配或用户拥有管理员权限时才允许访问。 - 统一校验逻辑:用自定义
AuthorizationHandler或ActionFilter实现全局校验,避免重复代码。示例代码:// 自定义权限处理器 public class ObjectOwnershipHandler : AuthorizationHandler<ObjectOwnershipRequirement> { private readonly AppDbContext _dbContext; public ObjectOwnershipHandler(AppDbContext dbContext) { _dbContext = dbContext; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ObjectOwnershipRequirement requirement) { // 从路由获取资源ID if (!context.Resource is HttpContext httpContext) { context.Fail(); return; } if (!httpContext.Request.RouteValues.TryGetValue("id", out var idValue) || !Guid.TryParse(idValue.ToString(), out var resourceId)) { context.Fail(); return; } // 获取当前用户ID var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) { context.Fail(); return; } // 根据资源类型校验归属(这里以订单为例) var order = await _dbContext.Orders.FindAsync(resourceId); if (order?.OwnerId == userId || context.User.IsInRole("Admin")) { context.Succeed(requirement); } else { context.Fail(); } } } // 注册到DI services.AddAuthorization(options => { options.AddPolicy("ObjectOwnership", policy => policy.Requirements.Add(new ObjectOwnershipRequirement())); }); services.AddScoped<IAuthorizationHandler, ObjectOwnershipHandler>(); - 避免直接暴露数据库ID:后端返回给前端的资源标识,优先使用加密后的ID或映射ID,而非原始数据库自增ID。
2. 前端(Angular 16):减少暴露风险(辅助)
前端的防护只能降低暴露概率,不能依赖它做安全屏障:
- 不在页面源码、控制台日志、localStorage/sessionStorage中存储原始数据库ID。
- 路由跳转或API请求时,仅使用后端返回的加密/映射ID,不手动拼接原始ID。
- 用Angular的路由守卫做基础校验(比如未登录用户无法进入敏感页面),但这只是前端层面的拦截,不能替代后端校验。
ID加密的最佳算法与实现建议
如果选择用加密ID隐藏原始标识,推荐以下方案:
首选:AES-GCM 对称加密
AES-GCM是认证加密算法,同时提供保密性和完整性(防止加密后的ID被篡改),适合ID加密场景:
- .NET Core 实现:使用内置的
AesGcm类,注意每次加密生成随机12字节的nonce(不要重复使用同一nonce和密钥),加密结果包含nonce、密文和标签(用于校验完整性):public static string EncryptId(Guid id, byte[] key) { using var aesGcm = new AesGcm(key); var plaintext = id.ToByteArray(); var nonce = RandomNumberGenerator.GetBytes(12); // AES-GCM标准nonce长度 var ciphertext = new byte[plaintext.Length]; var tag = new byte[16]; aesGcm.Encrypt(nonce, plaintext, ciphertext, tag); // 将nonce、密文、标签拼接后转Base64(URL安全格式) var combined = nonce.Concat(ciphertext).Concat(tag).ToArray(); return Convert.ToBase64String(combined).Replace('+', '-').Replace('/', '_').TrimEnd('='); } public static Guid? DecryptId(string encryptedId, byte[] key) { try { // 还原URL安全的Base64 var base64 = encryptedId.Replace('-', '+').Replace('_', '/'); while (base64.Length % 4 != 0) base64 += '='; var combined = Convert.FromBase64String(base64); var nonce = combined.Take(12).ToArray(); var ciphertext = combined.Skip(12).Take(16).ToArray(); // Guid是16字节 var tag = combined.Skip(28).ToArray(); using var aesGcm = new AesGcm(key); var plaintext = new byte[16]; aesGcm.Decrypt(nonce, ciphertext, tag, plaintext); return new Guid(plaintext); } catch { return null; // 解密失败返回null,拒绝请求 } } - 密钥管理:密钥绝对不能硬编码在代码中,.NET Core可使用Azure Key Vault、本地Secret Manager或环境变量存储;前端不要持有密钥,加密解密全程在后端完成——前端仅传递加密后的ID字符串。
替代方案:UUID映射表
如果不想用加密,可以在后端维护一张ResourceIdMapping表,存储原始数据库ID与UUID的映射关系:
- 后端返回资源时,将原始ID替换为对应的UUID;
- 前端用UUID请求,后端查询映射表得到原始ID,再做权限校验;
- 优点:无需加密逻辑,实现简单;缺点:需要额外存储,且映射表需定期清理无效条目。
关键注意事项
- 加密≠授权:哪怕ID加密了,后端依然要校验用户对目标对象的访问权限,加密只是防止ID被遍历猜测。
- 禁用Base64“伪装”:Base64是编码而非加密,任何人都可以轻松解码,不能作为ID防护手段。
- 密钥轮换:定期轮换加密密钥,避免密钥泄露导致所有加密ID失效。
内容的提问来源于stack exchange,提问作者Rafael
相关产品推荐
相关产品推荐

