Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

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

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 响应读取问题,并提供修复指导。

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

最受欢迎

查看全部 →

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

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

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) 读取整个响应体。

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 查询参数。

// 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)
// 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
    }
    ...
}
// 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)到达接收点,而支付回调直接到达同一接收点。

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)
    ...
}
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 代码未使用它。

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
}
// DefaultUpstreamResponseReadMaxBytes is the default read cap for upstream non-streaming response bodies.
const DefaultUpstreamResponseReadMaxBytes int64 = 128 * 1024 * 1024
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)) 读取另一个上游响应。
  • 相比之下,backend/internal/handler/auth_wechat_oauth.go:1149 仍使用原始的 io.ReadAll(resp.Body)。

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

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

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

var (
    wechatOAuthAccessTokenURL = "https://api.weixin.qq.com/sns/oauth2/access_token"
    wechatOAuthUserInfoURL    = "https://api.weixin.qq.com/sns/userinfo"
)
body, err := io.ReadAll(resp.Body)
if err != nil {
    return nil, fmt.Errorf("read wechat userinfo response: %w", err)
}
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,即使在启用后端模式时也是如此。

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

下载工具