Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-51416 — Analyse une CVE spécifique dans le gestionnaire OAuth de WeChat, identifiant des lectures de réponses HTTP non bornées menant à un déni de service, avec des conseils de remédiation. | Kitploit
Outils/GitHubGitHub/renio-wow/cve-2026-51416
Analyse StatiqueAnalyse des VulnérabilitésAnalyse de CodeSécurité Web
GitHubrenio-wow/cve-2026-51416

CVE-2026-51416

Analyse une CVE spécifique dans le gestionnaire OAuth de WeChat, identifiant des lectures de réponses HTTP non bornées menant à un déni de service, avec des conseils de remédiation.

Voir le dépôt
3il y a 4 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

1. Résumé

  • Type de vulnérabilité : Déni de service / Lecture de réponse HTTP sans restriction ([CWE-400])
  • Emplacement signalé : backend/internal/handler/auth_wechat_oauth.go:1149
  • Description de la vulnérabilité : Le processus d'échange de jeton OAuth WeChat utilise io.ReadAll(resp.Body) pour lire l'intégralité du corps de la réponse HTTP en amont en mémoire sans imposer de limite de taille maximale avant la mise en mémoire tampon.

2. Analyse

Étape 1 : Examiner le point sensible signalé à backend/internal/handler/auth_wechat_oauth.go:1124

La fonction signalée et plus de 50 lignes de contexte environnant ont été examinées. Le point sensible est la fonction d'aide à l'échange de jeton exchangeWeChatOAuthCode(). Cette fonction construit une requête GET vers WeChat, l'envoie à l'aide d'un http.Client simple, puis lit l'intégralité du corps de la réponse avec .

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)
    }
    ...
}

Preuves :

  • backend/internal/handler/auth_wechat_oauth.go:1142 crée client := &http.Client{Timeout: 30 * time.Second}.
  • backend/internal/handler/auth_wechat_oauth.go:1149 exécute body, err := io.ReadAll(resp.Body).
  • Aucun io.LimitReader, aucune vérification de ContentLength et aucun appel de fonction d'aide n'est présent avant la mise en mémoire tampon du corps de la réponse.

Analyse : La seule protection ici est une limite de temps (Timeout: 30 * time.Second), pas une limite de mémoire. Un pair distant peut toujours déclencher d'importantes allocations mémoire avant que EOF ne soit atteint. L'accessibilité de ce point sensible depuis les routes de production a ensuite été retracée.

Étape 2 : Retracer le chemin de requête principal depuis la route publique de rappel OAuth

L'enregistrement des routes d'authentification et le gestionnaire OAuth WeChat ont été examinés. La route est enregistrée sous le groupe public /auth, et non sous un groupe protégé par JWT. Le gestionnaire de rappel reçoit les paramètres de requête code et state fournis par l'utilisateur avant d'invoquer le flux signalé.

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)
}

Preuves :

  • backend/internal/server/routes/auth.go:27-28 place ces routes dans le groupe de routeurs public /auth.
  • backend/internal/server/routes/auth.go:73 enregistre auth.GET("/oauth/wechat/start", h.Auth.WeChatOAuthStart).
  • backend/internal/server/routes/auth.go:80 enregistre auth.GET("/oauth/wechat/callback", h.Auth.WeChatOAuthCallback).
  • backend/internal/handler/auth_wechat_oauth.go:160-162 lit code et state depuis la requête HTTP.
  • backend/internal/handler/auth_wechat_oauth.go:206 appelle fetchWeChatOAuthIdentity(c.Request.Context(), cfg, code).
  • backend/internal/handler/auth_wechat_oauth.go:105-147 montre l'étape de pré-traitement normale qui définit un cookie d'état et redirige le navigateur dans le flux OAuth.

Analyse : Il s'agit de code de production actif accessible depuis un rappel GET public. Le cookie d'état fournit une protection OAuth-CSRF, mais il ne limite pas la taille de la réponse HTTP qui arrive en aval après l'échange de jeton. La chaîne d'appels interne et tous les appelants de production du point sensible ont ensuite été suivis.

Étape 3 : Retracer tous les appelants de production vers exchangeWeChatOAuthCode()

La chaîne d'appels interne entre le gestionnaire et les fonctions d'aide a été retracée. Le rappel principal atteint le point sensible via une fonction d'aide (fetchWeChatOAuthIdentity), tandis que le rappel de paiement atteint le même point sensible directement.

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
}

Preuves :

  • backend/internal/handler/auth_wechat_oauth.go:1112-1117 montre fetchWeChatOAuthIdentity() appelant exchangeWeChatOAuthCode(), puis fetchWeChatUserInfo().
  • backend/internal/handler/auth_wechat_oauth.go:206 est l'endroit où le rappel OAuth principal appelle fetchWeChatOAuthIdentity().
  • backend/internal/handler/auth_wechat_oauth.go:438 est l'endroit où le rappel OAuth de paiement appelle exchangeWeChatOAuthCode() directement.

Analyse : Le point sensible signalé n'est pas du code mort. Une recherche d'appelants à l'échelle du projet révèle deux chemins d'entrée de production vers la même fonction d'aide : le rappel standard de connexion/liaison WeChat et le rappel de paiement WeChat. Les éventuels sanitiseurs, validateurs ou gardes de taille au niveau du framework présents sur ce chemin de réponse ont ensuite été vérifiés.

Étape 4 : Vérifier les limites de taille et les modèles de sécurité existants dans la base de code

La base de code a été recherchée pour les modèles de limitation de taille du corps de réponse, et la fonction d'aide partagée déjà utilisée ailleurs pour les lectures en amont bornées a été examinée. Cette fonction d'aide utilise io.LimitReader(..., maxBytes+1) et génère une erreur explicite lorsque la limite est dépassée, mais le code OAuth WeChat ne l'utilise pas.

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))

Preuves :

  • backend/internal/service/upstream_response_limit.go:26-40 définit readUpstreamResponseBodyLimited() en utilisant io.LimitReader(reader, maxBytes+1).
  • backend/internal/service/upstream_response_limit.go:49-61 définit ReadUpstreamResponseBody() comme un wrapper de lecture bornée partagé.
  • backend/internal/config/config.go:55-58 définit DefaultUpstreamResponseReadMaxBytes.
  • backend/internal/service/crs_sync_service.go:1166 utilise io.ReadAll(io.LimitReader(resp.Body, 1<<20)) pour lire une réponse en amont.
  • backend/internal/service/crs_sync_service.go:1201 utilise io.ReadAll(io.LimitReader(resp.Body, 5<<20)) pour une autre réponse en amont.
  • En revanche, backend/internal/handler/auth_wechat_oauth.go:1149 utilise toujours un io.ReadAll(resp.Body) brut.

Analyse : Le projet a déjà reconnu la nécessité de limiter les tailles des corps de réponse en amont, et des implémentations de limite ad hoc et partagées sont en usage. Le chemin OAuth WeChat signalé contourne entièrement ces protections. Le point de terminaison distant a ensuite été vérifié pour déterminer s'il est fixe et si cela change la conclusion.

Étape 5 : Vérifier les frontières de confiance et le contexte de code adjacent

Les définitions de constantes et le code adjacent de récupération des informations utilisateur ont été examinés. Le point de terminaison du jeton OAuth est codé en dur vers WeChat et n'est pas fourni par l'utilisateur, mais le contenu du corps de la réponse reste des données réseau distantes et est lu sans aucune limite de taille. Une seconde lecture en amont non bornée existe dans le même fichier pour la requête d'informations utilisateur.

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
    }
}

Preuves :

  • backend/internal/handler/auth_wechat_oauth.go:52-54 code en dur les points de terminaison en amont vers les URL WeChat.
  • backend/internal/handler/auth_wechat_oauth.go:1197 effectue une seconde lecture io.ReadAll(resp.Body) non bornée dans fetchWeChatUserInfo().
  • backend/internal/server/middleware/backend_mode_guard.go:38-55 autorise explicitement /auth/oauth/wechat/callback et /auth/oauth/wechat/payment/callback même lorsque le mode backend est activé.

Analyse : Le nom d'hôte WeChat fixe réduit le contrôle d'un attaquant sur la sélection du point de terminaison, mais il n'ajoute aucune limite de taille de réponse. La faiblesse persiste car l'application met en mémoire tampon des octets de réponse distants arbitraires sans borne supérieure. Il s'agit de code de gestionnaire de production, pas de code de test, de démonstration ou mort.

Chemin de décision

  • Q1 : Le code effectue-t-il une lecture non bornée de données fournies à distance ? → Oui. exchangeWeChatOAuthCode() émet une requête HTTP sortante puis appelle io.ReadAll(resp.Body) à backend/internal/handler/auth_wechat_oauth.go:1142-1149 sans garde de taille.
  • Q2 : Le point sensible est-il accessible depuis un chemin de requête de production réel ? → Oui. backend/internal/server/routes/auth.go:73 et backend/internal/server/routes/auth.go:80 enregistrent les routes publiques de démarrage/rappel WeChat, et backend/internal/handler/auth_wechat_oauth.go:206 achemine le flux de rappel via fetchWeChatOAuthIdentity().
  • Q3 : Des sanitiseurs, validateurs ou fonctions d'aide du framework imposent-ils une taille maximale de corps de réponse avant la lecture ? → Non. La fonction d'aide de limite partagée existe à backend/internal/service/upstream_response_limit.go:26-40 et d'autres services utilisent io.LimitReader à backend/internal/service/crs_sync_service.go:1166 et backend/internal/service/crs_sync_service.go:1201, mais la fonction signalée n'en utilise aucun.
  • Q4 : Le code est-il mort, réservé aux tests ou autrement exclu de la production ? → Non. Le code réside dans le fichier de gestionnaire de production backend/internal/handler/auth_wechat_oauth.go, est enregistré à backend/internal/server/routes/auth.go:73-82, et le middleware de mode backend autorise toujours les chemins de rappel à backend/internal/server/middleware/backend_mode_guard.go:38-55.
  • → Vrai positif

3. Conclusion

Vraie vulnérabilité

Preuves clés :

  • backend/internal/handler/auth_wechat_oauth.go:1149 utilise body, err := io.ReadAll(resp.Body) pour lire l'intégralité de la réponse de jeton en amont après avoir défini uniquement un délai d'attente — aucune limite de taille.
  • backend/internal/server/routes/auth.go:73-82 expose /oauth/wechat/callback et /oauth/wechat/payment/callback, et les deux routes atteignent la fonction d'aide signalée via backend/internal/handler/auth_wechat_oauth.go:206 et backend/internal/handler/auth_wechat_oauth.go:438.
  • backend/internal/service/upstream_response_limit.go:26-40 et backend/internal/service/crs_sync_service.go:1166 montrent que la base de code utilise déjà io.LimitReader pour les lectures en amont bornées — le code OAuth WeChat ne le fait simplement pas.

4. Remédiation

  • Ajoutez une taille maximale explicite du corps de réponse avant la mise en mémoire tampon de la réponse de jeton WeChat — par exemple, enveloppez resp.Body avec io.LimitReader(..., maxBytes+1) et rejetez les corps qui dépassent la limite.
  • Appliquez le même correctif à la lecture adjacente des informations utilisateur à backend/internal/handler/auth_wechat_oauth.go:1197, qui présente le même modèle non borné.
  • Privilégiez la réutilisation du modèle de lecture bornée existant de backend/internal/service/upstream_response_limit.go:26-40 afin que le flux OAuth soit cohérent avec la façon dont le reste de la base de code gère les réponses en amont.
  • Après le correctif, revérifiez les deux chemins d'appel de production : le rappel OAuth principal (backend/internal/handler/auth_wechat_oauth.go:206) et le rappel de paiement (backend/internal/handler/auth_wechat_oauth.go:438).
Télécharger l’outil