Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-51416 — Analiza un CVE específico en el manejador OAuth de WeChat, identificando lecturas de respuestas HTTP sin límite que provocan denegación de servicio, con orientación de remediación. | Kitploit
Herramientas/GitHubGitHub/renio-wow/cve-2026-51416
Análisis EstáticoAnálisis de VulnerabilidadesAnálisis de CódigoSeguridad Web
GitHubrenio-wow/cve-2026-51416

CVE-2026-51416

Analiza un CVE específico en el manejador OAuth de WeChat, identificando lecturas de respuestas HTTP sin límite que provocan denegación de servicio, con orientación de remediación.

Ver Repositorio
hace 4 mesesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

1. Resumen

  • Tipo de vulnerabilidad: Denegación de servicio / Lectura no restringida de respuestas HTTP ([CWE-400])
  • Ubicación marcada: backend/internal/handler/auth_wechat_oauth.go:1149
  • Descripción de la vulnerabilidad: El proceso de intercambio de tokens OAuth de WeChat utiliza io.ReadAll(resp.Body) para leer el cuerpo completo de la respuesta HTTP del proveedor en memoria sin imponer ningún límite máximo de tamaño antes del almacenamiento en búfer.

2. Análisis

Paso 1: Examinar el punto de origen marcado en backend/internal/handler/auth_wechat_oauth.go:1124

Se revisaron la función marcada y más de 50 líneas de contexto circundante. El punto de origen es la función auxiliar de intercambio de tokens exchangeWeChatOAuthCode(). Esta función construye una solicitud GET a WeChat, la envía usando un http.Client simple y luego lee el cuerpo completo de la respuesta con .

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

Evidencia:

  • backend/internal/handler/auth_wechat_oauth.go:1142 crea client := &http.Client{Timeout: 30 * time.Second}.
  • backend/internal/handler/auth_wechat_oauth.go:1149 ejecuta body, err := io.ReadAll(resp.Body).
  • No hay io.LimitReader, ni verificación de ContentLength, ni llamada a una función auxiliar antes de almacenar en búfer el cuerpo de la respuesta.

Análisis: La única protección aquí es un límite de tiempo (Timeout: 30 * time.Second), no un límite de memoria. Un par remoto aún puede provocar grandes asignaciones de memoria antes de que se alcance EOF. Luego se rastreó la accesibilidad de este punto de origen desde las rutas de producción.

Paso 2: Rastrear la ruta principal de solicitud desde la ruta pública de devolución de llamada OAuth

Se revisaron el registro de rutas de autenticación y el manejador OAuth de WeChat. La ruta está registrada bajo el grupo público /auth, no bajo un grupo protegido por JWT. El manejador de devolución de llamada recibe los parámetros de consulta code y state proporcionados por el usuario antes de invocar el flujo marcado.

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

Evidencia:

  • backend/internal/server/routes/auth.go:27-28 coloca estas rutas en el grupo público de enrutadores /auth.
  • backend/internal/server/routes/auth.go:73 registra auth.GET("/oauth/wechat/start", h.Auth.WeChatOAuthStart).
  • backend/internal/server/routes/auth.go:80 registra auth.GET("/oauth/wechat/callback", h.Auth.WeChatOAuthCallback).
  • backend/internal/handler/auth_wechat_oauth.go:160-162 lee code y state de la solicitud HTTP.
  • backend/internal/handler/auth_wechat_oauth.go:206 llama a fetchWeChatOAuthIdentity(c.Request.Context(), cfg, code).
  • backend/internal/handler/auth_wechat_oauth.go:105-147 muestra el paso de preprocesamiento normal que establece una cookie de estado y redirige el navegador al flujo OAuth.

Análisis: Este es código de producción activo accesible desde una devolución de llamada GET pública. La cookie de estado proporciona protección OAuth-CSRF, pero no limita el tamaño de la respuesta HTTP que llega posteriormente después del intercambio de tokens. Luego se siguieron la cadena de llamadas interna y todos los llamadores de producción del punto de origen.

Paso 3: Rastrear todos los llamadores de producción hacia exchangeWeChatOAuthCode()

Se rastreó la cadena de llamadas interna entre el manejador y las funciones auxiliares. La devolución de llamada principal llega al punto de origen a través de una función auxiliar (fetchWeChatOAuthIdentity), mientras que la devolución de llamada de pago llega al mismo punto de origen directamente.

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
}

Evidencia:

  • backend/internal/handler/auth_wechat_oauth.go:1112-1117 muestra fetchWeChatOAuthIdentity() llamando a exchangeWeChatOAuthCode() y luego a fetchWeChatUserInfo().
  • backend/internal/handler/auth_wechat_oauth.go:206 es donde la devolución de llamada OAuth principal llama a fetchWeChatOAuthIdentity().
  • backend/internal/handler/auth_wechat_oauth.go:438 es donde la devolución de llamada OAuth de pago llama a exchangeWeChatOAuthCode() directamente.

Análisis: El punto de origen marcado no es código muerto. Una búsqueda de llamadores en todo el proyecto revela dos rutas de entrada de producción hacia la misma función auxiliar: la devolución de llamada estándar de inicio de sesión/vinculación de WeChat y la devolución de llamada de pago de WeChat. Luego se verificaron los sanitizadores, validadores o protecciones de tamaño a nivel de framework presentes en esta ruta de respuesta.

Paso 4: Verificar límites de tamaño y patrones de seguridad existentes en el código base

Se buscaron en el código base patrones de limitación de tamaño del cuerpo de respuesta y se revisó la función auxiliar compartida ya utilizada en otros lugares para lecturas limitadas de proveedores. Esa función auxiliar utiliza io.LimitReader(..., maxBytes+1) y genera un error explícito cuando se excede el límite, pero el código OAuth de WeChat no la utiliza.

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

Evidencia:

  • backend/internal/service/upstream_response_limit.go:26-40 define readUpstreamResponseBodyLimited() usando io.LimitReader(reader, maxBytes+1).
  • backend/internal/service/upstream_response_limit.go:49-61 define ReadUpstreamResponseBody() como un envoltorio compartido de lectura limitada.
  • backend/internal/config/config.go:55-58 define DefaultUpstreamResponseReadMaxBytes.
  • backend/internal/service/crs_sync_service.go:1166 usa io.ReadAll(io.LimitReader(resp.Body, 1<<20)) para leer una respuesta de un proveedor.
  • backend/internal/service/crs_sync_service.go:1201 usa io.ReadAll(io.LimitReader(resp.Body, 5<<20)) para otra respuesta de un proveedor.
  • En contraste, backend/internal/handler/auth_wechat_oauth.go:1149 todavía usa un io.ReadAll(resp.Body) sin procesar.

Análisis: El proyecto ya ha reconocido la necesidad de limitar los tamaños de los cuerpos de respuesta de los proveedores, y tanto las implementaciones de límite ad-hoc como las compartidas están en uso. La ruta OAuth de WeChat marcada evita estas protecciones por completo. Luego se verificó el endpoint remoto para determinar si es fijo y si eso cambia la conclusión.

Paso 5: Verificar los límites de confianza y el contexto de código adyacente

Se revisaron las definiciones de constantes y el código adyacente de obtención de información del usuario. El endpoint de token OAuth está codificado a WeChat y no es proporcionado por el usuario, pero el contenido del cuerpo de la respuesta sigue siendo datos de red remotos y se lee sin ningún límite de tamaño. Existe una segunda lectura no limitada de un proveedor en el mismo archivo para la solicitud de información del usuario.

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

Evidencia:

  • backend/internal/handler/auth_wechat_oauth.go:52-54 codifica los endpoints de proveedores a URLs de WeChat.
  • backend/internal/handler/auth_wechat_oauth.go:1197 realiza una segunda lectura no limitada io.ReadAll(resp.Body) dentro de fetchWeChatUserInfo().
  • backend/internal/server/middleware/backend_mode_guard.go:38-55 permite explícitamente /auth/oauth/wechat/callback y /auth/oauth/wechat/payment/callback incluso cuando el modo backend está habilitado.

Análisis: El nombre de host fijo de WeChat reduce el control de un atacante sobre la selección del endpoint, pero no agrega ningún límite de tamaño de respuesta. La debilidad persiste porque la aplicación almacena en búfer bytes arbitrarios de respuestas remotas en memoria sin un límite superior. Este es código de manejador de producción, no código de prueba, demostración o código muerto.

Ruta de decisión

  • P1: ¿El código realiza una lectura no limitada de datos suministrados remotamente? → Sí. exchangeWeChatOAuthCode() emite una solicitud HTTP saliente y luego llama a io.ReadAll(resp.Body) en backend/internal/handler/auth_wechat_oauth.go:1142-1149 sin protección de tamaño.
  • P2: ¿El punto de origen es accesible desde una ruta de solicitud de producción real? → Sí. backend/internal/server/routes/auth.go:73 y backend/internal/server/routes/auth.go:80 registran las rutas públicas de inicio/devolución de llamada de WeChat, y backend/internal/handler/auth_wechat_oauth.go:206 enruta el flujo de devolución de llamada a través de fetchWeChatOAuthIdentity().
  • P3: ¿Existen sanitizadores, validadores o funciones auxiliares/framework que impongan un tamaño máximo del cuerpo de respuesta antes de la lectura? → No. La función auxiliar de límite compartido existe en backend/internal/service/upstream_response_limit.go:26-40 y otros servicios usan io.LimitReader en backend/internal/service/crs_sync_service.go:1166 y backend/internal/service/crs_sync_service.go:1201, pero la función marcada no usa ninguna de ellas.
  • P4: ¿El código está muerto, es solo de prueba o está excluido de producción? → No. El código reside en el archivo de manejador de producción backend/internal/handler/auth_wechat_oauth.go, está registrado en backend/internal/server/routes/auth.go:73-82, y el middleware de modo backend aún permite las rutas de devolución de llamada en backend/internal/server/middleware/backend_mode_guard.go:38-55.
  • → Verdadero positivo

3. Conclusión

Vulnerabilidad real

Evidencia clave:

  • backend/internal/handler/auth_wechat_oauth.go:1149 usa body, err := io.ReadAll(resp.Body) para leer la respuesta completa del token del proveedor después de establecer solo un tiempo de espera — sin límite de tamaño.
  • backend/internal/server/routes/auth.go:73-82 expone /oauth/wechat/callback y /oauth/wechat/payment/callback, y ambas rutas llegan a la función auxiliar marcada a través de backend/internal/handler/auth_wechat_oauth.go:206 y backend/internal/handler/auth_wechat_oauth.go:438.
  • backend/internal/service/upstream_response_limit.go:26-40 y backend/internal/service/crs_sync_service.go:1166 muestran que el código base ya usa io.LimitReader para lecturas limitadas de proveedores — el código OAuth de WeChat simplemente no lo hace.

4. Remediación

  • Agregue un tamaño máximo explícito del cuerpo de respuesta antes de almacenar en búfer la respuesta del token de WeChat — por ejemplo, envuelva resp.Body con io.LimitReader(..., maxBytes+1) y rechace los cuerpos que excedan el límite.
  • Aplique la misma corrección a la lectura adyacente de información del usuario en backend/internal/handler/auth_wechat_oauth.go:1197, que tiene el mismo patrón no limitado.
  • Prefiera reutilizar el patrón de lectura limitada existente de backend/internal/service/upstream_response_limit.go:26-40 para que el flujo OAuth sea consistente con cómo el resto del código base maneja las respuestas de proveedores.
  • Después de la corrección, vuelva a verificar ambas rutas de llamada de producción: la devolución de llamada OAuth principal (backend/internal/handler/auth_wechat_oauth.go:206) y la devolución de llamada de pago (backend/internal/handler/auth_wechat_oauth.go:438).
Descargar herramienta