← Writeups

Forced OAuth profile linking

CSRF en el flujo de vinculación de perfil OAuth: ausencia del parámetro state permite forzar al admin a vincular el perfil social del atacante mediante un iframe con el code de oauth-linking.

Target Overview & Vulnerability Summary

El laboratorio permite adjuntar un perfil de red social a la cuenta del blog para poder iniciar sesión vía OAuth en lugar de usuario/password. La implementación insegura del flujo OAuth por parte de la app cliente permite a un atacante vincular por la fuerza su propio perfil social a la cuenta de otro usuario (el admin) mediante una CSRF: el endpoint de vinculación no emite ni valida el parámetro state, por lo que un oauth-linking code emitido para la cuenta del atacante puede ser ejecutado por el admin.

Campo Valor
Severidad Alta (account linking forzado → acceso a la cuenta admin del blog y eliminación de carlos)
Endpoint vulnerable GET /oauth-linking?code=... (vinculación de perfil OAuth)
Root cause Ausencia del parámetro state en el flujo de account linking → el code no está ligado a la sesión que inició el flujo (CSRF)

Technical Breakdown & Exploitation Flow

Credenciales de laboratorio:

  • Cuenta del blog: wiener:peter
  • Perfil social: peter.wiener:hotdog

Al observar el flujo, la app usa el authorization code grant type para el linking de perfiles, pero no envía el parámetro state. state es el mecanismo que previene ataques tipo CSRF: sin él, un flujo de OAuth que no fue iniciado por el cliente puede completarse igualmente. Concretamente, el code de vinculación emitido por el proveedor no está ligado a la sesión del navegador que lo solicitó.

Explotación

  1. Iniciar el flujo de vinculación de un perfil social en la cuenta wiener (opción "Attach a social profile").
  2. Interceptar la request de callback de vinculación que contiene el code de oauth-linking y droppearla (no consumir el code), para conservarlo.
  3. Montar el code en un iframe servido desde el exploit server, de modo que el navegador del admin (que siempre tiene una sesión activa en el blog) ejecute la vinculación:
<iframe src="https://0ab500ab03372b7c81abcaf80077006e.web-security-academy.net/oauth-linking?code=2ylpuwjVq2S16PLpGpZEAAfjHHPrmLPcC9giRMoNSy-"></iframe>
  1. Cuando el admin abre la página del exploit server, el iframe completa la vinculación: el perfil social peter.wiener queda adjuntado a la cuenta admin del blog.
  2. Iniciar sesión en el blog vía OAuth con el perfil social peter.wiener:hotdog → se accede a la cuenta del admin.
  3. Entrar al panel de administración y ejecutar DELETE sobre carlos para resolver el laboratorio.

Root Cause Analysis

La vinculación de perfiles es una operación sensible (une una identidad externa a una cuenta local) y debe estar protegida como cualquier cambio de estado. Aquí el endpoint GET /oauth-linking?code=... no valida que el code corresponda a la sesión que inició el flujo. Al faltar state (o su equivalente de binding session↔code), el code emitido para el atacante se puede ejecutar en el contexto del admin:

// defectuoso: el code no se liga a la sesión iniciadora
// (state nunca fue generado ni verificado en el callback)
code := r.URL.Query().Get("code")
profile, err := oauth.Exchange(code)   // code del atacante
linkAccount(currentSession, profile)   // vinculado a la sesión admin

Como la vinculación ocurre con la sesión autenticada del admin (vía cookies), el iframe cross-site completa el linking y el atacante obtiene login por OAuth en una cuenta que no controla.

Secure Remediation

  • Generar y verificar state en el flujo de linking, ligándolo a la sesión del navegador que inició el flujo:
state := newRandomState()
sessions.Set(state, sessionID) // estado ligado a la sesión

// en el callback
if r.URL.Query().Get("state") != state || sessions.Get(state) != sessionID {
    return errors.New("state mismatch: flujo no iniciado por esta sesión")
}
  • Requerir interacción del usuario en el momento de vincular (doble confirmación, re-autenticación o CAPTCHA) para que el linking forzado no sea silencioso.
  • Para el id_token/code de OpenID Connect, validar nonce y aud además de state.
  • Considerar que los endpoints de vinculación en iframe cross-site deben bloquearse (X-Frame-Options: DENY o CSP frame-ancestors 'none' como capa defensiva adicional, no como único control).