在Asp.net MVC与Knockout.js应用中解决IDOR漏洞的轻量方案咨询
针对Asp.net MVC + Knockout.js应用中CustomerKey IDOR漏洞的低侵入修复方案
核心问题分析
当前漏洞的根源在于:
- 明文
CustomerKey(客户ID)直接暴露在前端隐藏字段,可被攻击者获取并暴力枚举 - 匿名接口
UpLoadLog直接接受任意CustomerKey,无验证逻辑 - 授权接口
Customer允许传入CustomerKey覆盖会话中的值,存在会话劫持风险
以下是无需大规模改动业务代码的修复方案,按侵入性从低到高排序:
方案一:直接修复授权接口的参数覆盖问题(最小改动)
针对Customer接口,禁止外部传入CustomerKey覆盖会话值,强制从OwinContext读取已验证的用户身份:
[AuthorizeUser] // 移除CustomerKey参数,避免外部传入覆盖会话值 public ActionResult Customer(string FeedData, string FeedInfo) { var owinContext = Request.GetOwinContext(); // 强制从会话获取已验证的CustomerKey var customerKey = owinContext.Get<string>("CustomerKey"); // 仅当会话中无值时,才使用传入的FeedData/FeedInfo(可选) FeedData = owinContext.Get<string>("FeedData") ?? FeedData; FeedInfo = owinContext.Get<string>("FeedInfo") ?? FeedInfo; ViewBag.CustomerKey = customerKey; ViewBag.FeedData = FeedData; ViewBag.FeedInfo = FeedInfo; ViewBag.UseSignalR = true; ViewBag.isOffline = true; return View("Offline", "CustomerLayout"); }
优势:改动极小,直接切断攻击者通过授权接口劫持会话的路径。
方案二:给匿名接口添加会话验证(针对登录用户场景)
如果UpLoadLog仅用于登录用户上传自身日志,直接移除AllowAnonymous特性,并验证传入的CustomerKey与会话中一致:
[HttpPost] // 移除AllowAnonymous,仅允许已登录用户访问 public JsonResult UpLoadLog(string CustomerKey, string message) { try { var owinContext = Request.GetOwinContext(); var sessionCustomerKey = owinContext.Get<string>("CustomerKey"); // 验证传入的CustomerKey是否与会话一致 if (!string.Equals(CustomerKey, sessionCustomerKey, StringComparison.Ordinal)) { HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden; return Json(new { success = false, message = "无效的CustomerKey" }); } var log = message.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (string s in log) { Logger.Write(LogType.Info, this.GetType(), "UpLoadLog", CustomerKey + ": " + s.TrimStart('\r', '\n'), null); } } catch (Exception ex) { Logger.Write(LogType.Fatal, this.GetType(), "UpLoadLog", ex.Message, ex); HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; return Json(new { success = false, message = ex.Message }); } HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; return Json(new { success = true }); }
优势:仅修改接口权限和验证逻辑,不影响现有业务流程。
方案三:引入会话绑定的随机令牌(最彻底,适配多用户SignalR)
不加密CustomerKey本身,而是生成短期有效、与会话绑定的随机令牌,前端仅存储令牌,后端通过映射表获取真实CustomerKey:
1. 登录逻辑修改(生成令牌)
// 登录成功后,获取真实CustomerKey var realCustomerKey = "从数据库获取的客户ID"; // 生成随机令牌 var customerToken = Guid.NewGuid().ToString(); // 将令牌与真实CustomerKey存入缓存(Redis/内存缓存,设置1小时过期) RedisCache.Set(customerToken, realCustomerKey, TimeSpan.FromHours(1)); // 令牌存入会话,供业务逻辑使用 var owinContext = Request.GetOwinContext(); owinContext.Set("CustomerToken", customerToken); owinContext.Set("CustomerKey", realCustomerKey); // 前端隐藏字段改为存储令牌 ViewBag.CustomerToken = customerToken;
2. 前端代码修改
<!-- 替换原隐藏字段 --> <input type="hidden" id="customerToken" value="@ViewBag.CustomerToken"/>
var UpLoadLog = function (log, isAsync) { if (isAsync == null || isAsync == undefined) isAsync = true; jQuery.ajax({ type: "POST", async: isAsync, contentType: "application/json;charset=utf-8", url: rooturl + "Authentication/UpLoadLog", // 传入令牌而非真实CustomerKey data: JSON.stringify({ CustomerToken: jQuery("#customerToken").val(), message: "\r\n\r\n" + log + "\r\n\r\n" }), dataType: "json", success: function (response, status, jqXHR) { }, error: function (jqXHR, status, error) { } }); LogMessages = "\r\n\r\n"; };
3. 后端接口修改(令牌转真实CustomerKey)
[HttpPost] public JsonResult UpLoadLog(string CustomerToken, string message) { try { // 从缓存获取真实CustomerKey var customerKey = RedisCache.Get<string>(CustomerToken); if (string.IsNullOrEmpty(customerKey)) { HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized; return Json(new { success = false, message = "令牌无效或已过期" }); } var log = message.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (string s in log) { Logger.Write(LogType.Info, this.GetType(), "UpLoadLog", customerKey + ": " + s.TrimStart('\r', '\n'), null); } } catch (Exception ex) { Logger.Write(LogType.Fatal, this.GetType(), "UpLoadLog", ex.Message, ex); HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; return Json(new { success = false, message = ex.Message }); } HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; return Json(new { success = true }); }
4. SignalR适配(令牌验证)
public class CustomerHub : Hub { public override Task OnConnected() { var customerToken = Context.QueryString["customerToken"]; var customerKey = RedisCache.Get<string>(customerToken); if (string.IsNullOrEmpty(customerKey)) { Context.Abort(); // 拒绝无效令牌的连接 return base.OnConnected(); } // 将连接与客户分组,不影响多用户同时登录 Groups.Add(Context.ConnectionId, customerKey); return base.OnConnected(); } }
优势:完全隐藏真实CustomerKey,暴力破解难度极大,适配多用户SignalR场景,业务代码无需大规模修改。
方案四:添加限流(辅助防暴力枚举)
配合上述方案,给UpLoadLog接口添加请求限流,防止攻击者暴力枚举令牌或CustomerKey:
// 在Program.cs/Startup.cs中配置限流 builder.Services.AddRateLimiter(options => { options.AddFixedWindowLimiter("UploadLogLimit", opt => { opt.Window = TimeSpan.FromMinutes(1); opt.PermitLimit = 5; // 每分钟最多5次请求 opt.QueueLimit = 0; }); }); // 接口添加限流特性 [HttpPost] [EnableRateLimiting("UploadLogLimit")] public JsonResult UpLoadLog(...) { // 接口逻辑 }
内容的提问来源于stack exchange,提问作者IntelligentCancer
相关产品推荐
相关产品推荐

