ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

重保前的 Web 自查渗透(六):API 接口未授权与签名重放漏洞闭环

重保前的 Web 自查渗透(六):API 接口未授权与签名重放漏洞闭环 重保前的 Web 自查渗透六API 接口未授权与签名重放漏洞闭环在重保与大促前夕的外部暴露面排查中传统的 SQL 注入与文件上传漏洞因 WAF 和参数化框架的普及已大幅减少。相反业务逻辑层面的“API 未授权访问BOLA/IDOR”与“API 接口签名机制缺陷导致的重放与参数篡改”成为了红队突破防线、抓取核心业务数据的高发路径。本文基于自查渗透中发现的真实高危缺陷拆解漏洞利用链并给出生产级防重放、强防篡改与水平越权拦截中间件的设计实现。API 安全自查高危风险拓扑在针对 120 余个公网 API 微服务接口的渗透自查中共发现 3 类典型的认证与逻辑鉴权漏洞------------------------------------------------------------------------ | 1. BOLA / 水平越权 (Broken Object Level Authorization) | | 用户 A 携带自身合法 JWT Token直接修改请求 Body 中的 account_id 为用户 B| | 后端 Controller 仅校验了 Token 合法性未校验 Token 身份与资源归属性。| ------------------------------------------------------------------------ | 2. 接口签名可预测与重放 (Signature Weakness Replay Attack) | | 前端 App/小程序使用固定的 MD5(Secret Query) 计算签名缺少时间戳/Nonce | | 或服务端未在 Redis 中建立 Nonce 校验窗口导致请求可被永久重放。 | ------------------------------------------------------------------------ | 3. 敏感字段批量赋值越权 (Mass Assignment) | | 更新个人资料接口未对 DTO 进行字段白名单过滤攻击者传入 is_admin: true | | 或 role: SUPERUSERORM 自动映射直接完成提权。 | ------------------------------------------------------------------------漏洞代码深度复盘典型 BOLA 水平越权漏洞以下是修复前常见的漏洞控制器代码// [VULNERABLE] 存在水平越权的订单查询接口 func GetOrderDetailHandler(c *gin.Context) { // 从 URL 路径获取要查询的订单号 orderID : c.Param(order_id) // 中间件已校验 JWT获取当前登录用户 ID currentUserID, _ : c.Get(user_id) var order Order // 危险直接根据 orderID 查询未将 currentUserID 作为查询约束条件 if err : db.Where(id ?, orderID).First(order).Error; err ! nil { c.JSON(404, gin.H{error: Order not found}) return } // 攻击者只需遍历 order_id即可拉取全平台所有用户的手机号、地址与消费明细 c.JSON(200, order) }签名校验形同虚设部分内部 API 虽然设计了X-Signature头但服务端仅校验了sign md5(body secret)未校验X-Timestamp的时效性且未校验X-Nonce的唯一性。红队抓取到一次带有签名的优惠券兑换请求后利用并发工具发起 5,000 次重放直接套取大额营销资产。生产级加固API 防重放与防篡改网关中间件在 Go (Gin) 框架中实现严密的签名与防重放中间件涵盖时间戳窗口±300 秒、Redis 原子性 Nonce 幂等校验以及 HMAC-SHA256 签名计算package middleware import ( bytes crypto/hmac crypto/sha256 encoding/hex fmt io net/http strconv time github.com/gin-gonic/gin github.com/go-redis/redis/v8 ) // APISecurityMiddleware 签名与防重放校验中间件 func APISecurityMiddleware(rdb *redis.Client, apiSecret string) gin.HandlerFunc { return func(c *gin.Context) { clientSign : c.GetHeader(X-Signature) timestampStr : c.GetHeader(X-Timestamp) nonce : c.GetHeader(X-Nonce) // 1. 必填头缺失检查 if clientSign || timestampStr || nonce { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{error: Missing security headers}) return } // 2. 时间戳时效性校验防长时间过后的重放攻击容忍窗口 5 分钟 ts, err : strconv.ParseInt(timestampStr, 10, 64) if err ! nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{error: Invalid timestamp format}) return } currentTime : time.Now().Unix() if ts currentTime-300 || ts currentTime300 { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{error: Request expired (timestamp out of sync)}) return } // 3. Nonce 唯一性与原子性占用防短时间内的高频重放 nonceKey : fmt.Sprintf(api:nonce:%s, nonce) // 设置 10 分钟 TTL使用 SetNX 实现分布式原子锁 success, err : rdb.SetNX(c.Request.Context(), nonceKey, 1, 10*time.Minute).Result() if err ! nil || !success { c.AbortWithStatusJSON(http.StatusConflict, gin.H{error: Replay attack detected (duplicate nonce)}) return } // 4. 读取 Request Body 并保持可再次读取 var bodyBytes []byte if c.Request.Body ! nil { bodyBytes, _ io.ReadAll(c.Request.Body) // 重置 Request.Body 供后续 Controller 绑定 c.Request.Body io.NopCloser(bytes.NewBuffer(bodyBytes)) } // 5. 计算并比对 HMAC-SHA256 签名 // 签名规范: METHOD PATH TIMESTAMP NONCE RAW_BODY rawSignaturePayload : fmt.Sprintf(%s\n%s\n%s\n%s\n%s, c.Request.Method, c.Request.URL.Path, timestampStr, nonce, string(bodyBytes), ) mac : hmac.New(sha256.New, []byte(apiSecret)) mac.Write([]byte(rawSignaturePayload)) expectedSign : hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(clientSign), []byte(expectedSign)) { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{error: Invalid API signature}) return } c.Next() } }水平越权BOLA的架构级治理治理水平越权绝不能仅仅依靠业务开发在每个 SQL 查询里手动补充WHERE user_id ?必须在数据访问层DAL与 ORM 拦截器中引入强制的租户与属主绑定// [SECURE] 加固后的安全订单查询DAL 强制带入上下文身份 func GetOrderDetailSecure(c *gin.Context) { orderID : c.Param(order_id) currentUserID, exists : c.Get(user_id) if !exists { c.JSON(http.StatusUnauthorized, gin.H{error: Unauthorized context}) return } var order Order // 在 SQL 层面强制绑定 owner_id currentUserID杜绝越权 err : db.Where(id ? AND owner_id ?, orderID, currentUserID).First(order).Error if err ! nil { // 使用模糊错误提示防止攻击者通过 404/403 探测 ID 是否存在 c.JSON(http.StatusNotFound, gin.H{error: Resource not found or access denied}) return } c.JSON(http.StatusOK, order) }通过“网关层签名防篡改与 Nonce 强防重放”加上“数据访问层身份边界强制约束”我们在重保前彻底闭环了 18 处潜在的越权与刷单隐患确保公网业务接口安全可用。
返回列表