Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-51416 — 分析微信 OAuth 处理器中的特定 CVE,识别导致拒绝服务的无界 HTTP 响应读取问题,并提供修复指导。 | Kitploit
工具/GitHubGitHub/renio-wow/cve-2026-51416
静态分析漏洞分析代码分析Web安全
GitHubrenio-wow/cve-2026-51416

CVE-2026-51416

分析微信 OAuth 处理器中的特定 CVE,识别导致拒绝服务的无界 HTTP 响应读取问题,并提供修复指导。

查看仓库
4个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

1. 摘要

  • 漏洞类型:拒绝服务 / 无限制 HTTP 响应读取([CWE-400])
  • 标记位置:backend/internal/handler/auth_wechat_oauth.go:1149
  • 漏洞描述:微信 OAuth 令牌交换过程使用 io.ReadAll(resp.Body) 将完整的上游 HTTP 响应体读入内存,在缓冲之前未强制执行任何最大大小限制。

2. 分析

步骤 1:检查 backend/internal/handler/auth_wechat_oauth.go:1124 处标记的接收点

已审查标记函数及其周围 50 多行上下文。接收点是令牌交换辅助函数 exchangeWeChatOAuthCode()。该函数构造一个对微信的 GET 请求,使用普通 http.Client 发送,然后使用 io.ReadAll(resp.Body) 读取整个响应体。

root@kitploit:~
func exchangeWeChatOAuthCode(ctx context.Context, cfg wechatOAuthConfig, code string) (*wechatOAuthTokenResponse, error) {
    endpoint, err := url.Parse(wechatOAuthAccessTokenURL)
    ...
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
    ...
    client := &http.Client{Timeout: 30 * time.Second}
    resp, err := client.Do(req)
    ...
    defer func() { _ = resp.Body.Close() }()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("read wechat access token response: %w", err)
    }
    if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
        return nil, fmt.Errorf("wechat access token status=%d", resp.StatusCode)
    }
    ...
}

证据:

  • backend/internal/handler/auth_wechat_oauth.go:1142 创建 client := &http.Client{Timeout: 30 * time.Second}。
  • backend/internal/handler/auth_wechat_oauth.go:1149 执行 body, err := io.ReadAll(resp.Body)。
  • 在缓冲响应体之前,没有 io.LimitReader、没有 ContentLength 检查,也没有调用辅助函数。

分析:这里唯一的保护是时间限制(Timeout: 30 * time.Second),而不是内存限制。远程对端仍然可以在到达 EOF 之前触发大量内存分配。随后追踪了该接收点从生产路由的可达性。

步骤 2:从公共 OAuth 回调路由追踪主请求路径

已审查认证路由注册和微信 OAuth 处理器。该路由注册在公共 /auth 组下,而非 JWT 保护的组。回调处理器在调用标记流程之前接收用户提供的 code 和 state 查询参数。

root@kitploit:~
// routes
auth := v1.Group("/auth")
auth.Use(servermiddleware.BackendModeAuthGuard(settingService))
...
auth.GET("/oauth/wechat/start", h.Auth.WeChatOAuthStart)
auth.GET("/oauth/wechat/callback", h.Auth.WeChatOAuthCallback)
root@kitploit:~
// callback handler
func (h *AuthHandler) WeChatOAuthCallback(c *gin.Context) {
    ...
    code := strings.TrimSpace(c.Query("code"))
    state := strings.TrimSpace(c.Query("state"))
    if code == "" || state == "" {
        redirectOAuthError(c, frontendCallback, "missing_params", "missing code/state", "")
        return
    }
    ...
    tokenResp, userInfo, err := fetchWeChatOAuthIdentity(c.Request.Context(), cfg, code)
    if err != nil {
        redirectOAuthError(c, frontendCallback, "provider_error", "wechat_identity_fetch_failed", singleLine(err.Error()))
        return
    }
    ...
}
root@kitploit:~
// start handler
func (h *AuthHandler) WeChatOAuthStart(c *gin.Context) {
    ...
    state, err := oauth.GenerateState()
    ...
    wechatSetCookie(c, wechatOAuthStateCookieName, encodeCookieValue(state), wechatOAuthCookieMaxAgeSec, secureCookie)
    ...
    c.Redirect(http.StatusFound, authURL)
}

证据:

  • backend/internal/server/routes/auth.go:27-28 将这些路由放置在公共 /auth 路由器组中。
  • backend/internal/server/routes/auth.go:73 注册 auth.GET("/oauth/wechat/start", h.Auth.WeChatOAuthStart)。
  • backend/internal/server/routes/auth.go:80 注册 auth.GET("/oauth/wechat/callback", h.Auth.WeChatOAuthCallback)。
  • backend/internal/handler/auth_wechat_oauth.go:160-162 从 HTTP 请求中读取 code 和 state。
  • backend/internal/handler/auth_wechat_oauth.go:206 调用 fetchWeChatOAuthIdentity(c.Request.Context(), cfg, code)。
  • backend/internal/handler/auth_wechat_oauth.go:105-147 显示了正常的预处理步骤,该步骤设置状态 cookie 并将浏览器重定向到 OAuth 流程。

分析:这是可从公共 GET 回调访问的活跃生产代码。状态 cookie 提供 OAuth-CSRF 保护,但它不限制令牌交换后到达的下游 HTTP 响应的大小。随后追踪了内部调用链和接收点的所有生产调用者。

步骤 3:追踪进入 exchangeWeChatOAuthCode() 的所有生产调用者

已追踪处理器与辅助函数之间的内部调用链。主回调通过辅助函数(fetchWeChatOAuthIdentity)到达接收点,而支付回调直接到达同一接收点。

root@kitploit:~
func fetchWeChatOAuthIdentity(ctx context.Context, cfg wechatOAuthConfig, code string) (*wechatOAuthTokenResponse, *wechatOAuthUserInfoResponse, error) {
    tokenResp, err := exchangeWeChatOAuthCode(ctx, cfg, code)
    if err != nil {
        return nil, nil, err
    }
    userInfo, err := fetchWeChatUserInfo(ctx, tokenResp)
    ...
}
root@kitploit:~
cfg, err := h.getWeChatOAuthConfig(c.Request.Context(), "mp", c)
...
cfg.redirectURI = h.resolveWeChatPaymentOAuthCallbackURL(c.Request.Context(), c)
tokenResp, err := exchangeWeChatOAuthCode(c.Request.Context(), cfg, code)
if err != nil {
    redirectOAuthError(c, frontendCallback, "token_exchange_failed", "failed to exchange oauth code", err.Error())
    return
}

证据:

  • backend/internal/handler/auth_wechat_oauth.go:1112-1117 显示 fetchWeChatOAuthIdentity() 调用 exchangeWeChatOAuthCode(),然后调用 fetchWeChatUserInfo()。
  • backend/internal/handler/auth_wechat_oauth.go:206 是主 OAuth 回调调用 fetchWeChatOAuthIdentity() 的位置。
  • backend/internal/handler/auth_wechat_oauth.go:438 是支付 OAuth 回调直接调用 exchangeWeChatOAuthCode() 的位置。

分析:标记的接收点不是死代码。项目范围的调用者搜索解析出两条进入同一辅助函数的生产入口路径:标准微信登录/绑定回调和微信支付回调。随后检查了此响应路径上存在的任何清理器、验证器或框架级大小保护。

步骤 4:检查代码库中的大小限制和现有安全模式

搜索了代码库中的响应体大小限制模式,并审查了代码库其他位置已用于有界上游读取的共享辅助函数。该辅助函数使用 io.LimitReader(..., maxBytes+1),并在超过限制时显式报错,但微信 OAuth 代码未使用它。

root@kitploit:~
func readUpstreamResponseBodyLimited(reader io.Reader, maxBytes int64) ([]byte, error) {
    ...
    body, err := io.ReadAll(io.LimitReader(reader, maxBytes+1))
    if err != nil {
        return nil, err
    }
    if int64(len(body)) > maxBytes {
        return nil, fmt.Errorf("%w: limit=%d", ErrUpstreamResponseBodyTooLarge, maxBytes)
    }
    return body, nil
}
root@kitploit:~
// DefaultUpstreamResponseReadMaxBytes is the default read cap for upstream non-streaming response bodies.
const DefaultUpstreamResponseReadMaxBytes int64 = 128 * 1024 * 1024
root@kitploit:~
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
...
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 5<<20))

证据:

  • backend/internal/service/upstream_response_limit.go:26-40 使用 io.LimitReader(reader, maxBytes+1) 定义 readUpstreamResponseBodyLimited()。
  • backend/internal/service/upstream_response_limit.go:49-61 将 ReadUpstreamResponseBody() 定义为共享的有界读取包装器。
  • backend/internal/config/config.go:55-58 定义 DefaultUpstreamResponseReadMaxBytes。
  • backend/internal/service/crs_sync_service.go:1166 使用 io.ReadAll(io.LimitReader(resp.Body, 1<<20)) 读取上游响应。
  • backend/internal/service/crs_sync_service.go:1201 使用 io.ReadAll(io.LimitReader(resp.Body, 5<<20)) 读取另一个上游响应。
  • 相比之下, 仍使用原始的 。

分析:项目已经认识到限制上游响应体大小的必要性,并且临时和共享的限制实现都在使用中。标记的微信 OAuth 路径完全绕过了这些保护。随后检查了远程端点以确定它是否固定以及这是否改变结论。

步骤 5:验证信任边界和相邻代码上下文

已审查常量定义和相邻的用户信息获取代码。OAuth 令牌端点硬编码为微信且不由用户提供,但响应体内容仍然是远程网络数据,并且在没有任何大小限制的情况下被读取。同一文件中存在第二个无界上游读取,用于用户信息请求。

root@kitploit:~
var (
    wechatOAuthAccessTokenURL = "https://api.weixin.qq.com/sns/oauth2/access_token"
    wechatOAuthUserInfoURL    = "https://api.weixin.qq.com/sns/userinfo"
)
root@kitploit:~
body, err := io.ReadAll(resp.Body)
if err != nil {
    return nil, fmt.Errorf("read wechat userinfo response: %w", err)
}
root@kitploit:~
for _, suffix := range []string{
    "/auth/oauth/linuxdo/callback",
    "/auth/oauth/wechat/callback",
    "/auth/oauth/wechat/payment/callback",
    ...
} {
    if strings.HasSuffix(path, suffix) {
        return true
    }
}

证据:

  • backend/internal/handler/auth_wechat_oauth.go:52-54 将上游端点硬编码为微信 URL。
  • backend/internal/handler/auth_wechat_oauth.go:1197 在 fetchWeChatUserInfo() 内执行第二次无界 io.ReadAll(resp.Body)。
  • backend/internal/server/middleware/backend_mode_guard.go:38-55 明确允许 /auth/oauth/wechat/callback 和 /auth/oauth/wechat/payment/callback,即使在启用后端模式时也是如此。

分析:固定的微信主机名减少了攻击者对端点选择的控制,但并未添加任何响应大小限制。该弱点仍然存在,因为应用程序在没有上限的情况下将任意远程响应字节缓冲到内存中。这是生产处理器代码,而非测试、演示或死代码。

决策路径

  • 问题 1:代码是否对远程提供的数据执行无界读取? → 是。 exchangeWeChatOAuthCode() 发出出站 HTTP 请求,然后在 backend/internal/handler/auth_wechat_oauth.go:1142-1149 处调用 io.ReadAll(resp.Body),且没有大小保护。
  • 问题 2:接收点是否可从真实的生产请求路径访问? → 是。 backend/internal/server/routes/auth.go:73 和 backend/internal/server/routes/auth.go:80 注册了公共微信开始/回调路由,backend/internal/handler/auth_wechat_oauth.go:206 通过 fetchWeChatOAuthIdentity() 路由回调流程。
  • 问题 3:在读取之前是否有任何清理器、验证器或框架/辅助函数强制执行最大响应体大小? → 否。 共享限制辅助函数存在于 backend/internal/service/upstream_response_limit.go:26-40,其他服务在 backend/internal/service/crs_sync_service.go:1166 和 backend/internal/service/crs_sync_service.go:1201 处使用 io.LimitReader,但标记的函数未使用其中任何一个。

3. 结论

真实漏洞

关键证据:

  • backend/internal/handler/auth_wechat_oauth.go:1149 使用 body, err := io.ReadAll(resp.Body) 在仅设置超时后读取完整的上游令牌响应——没有大小上限。
  • backend/internal/server/routes/auth.go:73-82 暴露 /oauth/wechat/callback 和 /oauth/wechat/payment/callback,两条路由通过 backend/internal/handler/auth_wechat_oauth.go:206 和 backend/internal/handler/auth_wechat_oauth.go:438 到达标记的辅助函数。
  • backend/internal/service/upstream_response_limit.go:26-40 和 backend/internal/service/crs_sync_service.go:1166 表明代码库已使用 io.LimitReader 进行有界上游读取——微信 OAuth 代码只是没有使用。

4. 修复建议

  • 在缓冲微信令牌响应之前添加显式的最大响应体大小——例如,使用 io.LimitReader(..., maxBytes+1) 包装 resp.Body,并拒绝超过限制的响应体。
  • 将相同的修复应用于 backend/internal/handler/auth_wechat_oauth.go:1197 处相邻的用户信息读取,该处具有相同的无界模式。
  • 优先复用 backend/internal/service/upstream_response_limit.go:26-40 中现有的有界读取模式,使 OAuth 流程与代码库其余部分处理上游响应的方式保持一致。
  • 修复后,重新验证两条生产调用路径:主 OAuth 回调(backend/internal/handler/auth_wechat_oauth.go:206)和支付回调(backend/internal/handler/auth_wechat_oauth.go:438)。
下载工具
backend/internal/handler/auth_wechat_oauth.go:1149
io.ReadAll(resp.Body)
  • 问题 4:代码是死代码、仅测试代码或以其他方式排除在生产之外? → 否。 代码位于生产处理器文件 backend/internal/handler/auth_wechat_oauth.go 中,注册于 backend/internal/server/routes/auth.go:73-82,后端模式中间件仍允许 backend/internal/server/middleware/backend_mode_guard.go:38-55 中的回调路径。
  • → 真阳性