Neutron, nuestro motor de IA, obtuvo un 96.75% en el benchmark CyberGym de UC Berkeley. Más información

Seguridad

Seguridad

El AI Pentest Engine descubre un BFLA crítico en WebSocket en las suscripciones de GraphQL

El AI Pentest Engine de Ostorlab descubrió de forma sistemática una vulnerabilidad crítica de Broken Function-Level Authorization (BFLA) en un endpoint WebSocket de GraphQL, que permitía el acceso sin autenticación a un servicio de traducción en tiempo real. Este caso práctico detalla el proceso paso a paso de la IA, desde el descubrimiento hasta la prueba de concepto.

Introducción

Las aplicaciones web modernas dependen cada vez más de protocolos de comunicación en tiempo real como WebSockets para ofrecer experiencias de usuario dinámicas. Combinado con GraphQL, ofrece un intercambio de datos potente y eficiente mediante suscripciones. Sin embargo, esta complejidad también introduce nuevas superficies de ataque, sobre todo en torno a la autorización. Proteger las suscripciones de GraphQL exige comprobaciones rigurosas en varias etapas: la conexión, el inicio de la suscripción y el filtrado de datos por evento.

El AI Pentest Engine de Ostorlab está diseñado para desenvolverse en esta complejidad. Identifica y valida de forma sistemática lagunas de autorización que las pruebas manuales podrían pasar por alto. Este artículo muestra cómo el motor de IA descubrió una vulnerabilidad crítica de Broken Function-Level Authorization (BFLA) en un endpoint WebSocket de GraphQL, que permitía a cualquier usuario sin autenticación ejecutar una suscripción y recibir datos sensibles.

Ostorlab AI Pentest sobre un BFLA en WebSocket de GraphQL

Al Engine se le encomendó un objetivo de alto nivel: validar la autorización de las suscripciones WebSocket de la aplicación objetivo, REDACTED. Lo que siguió fue un proceso metódico, paso a paso, de descubrimiento, sondeo y confirmación.

Paso 1: prueba de conexiones WebSocket de GraphQL sin autenticación

La primera acción de la IA fue probar el comportamiento más básico: ¿acepta el endpoint WebSocket una conexión sin ninguna credencial?

Handshake WS de referencia sin autenticación hacia /subscriptions. Objetivo: verificar si el servidor WebSocket de GraphQL acepta una conexión sin credenciales.

Resumen de la ejecución: El Engine se conectó a wss://REDACTED/subscriptions utilizando el subprotocolo graphql-transport-ws. Envió un frame connection_init estándar con un payload vacío, sin proporcionar ningún token Authorization.

Resultado: El servidor respondió con {"type":"connection_ack"}. De hecho, envió dos frames ack, una anomalía que la IA anotó.

Conclusión: Handshake sin autenticación aceptado. El servidor no exige autenticación en la fase de conexión. Es la primera señal de una posible debilidad.

Paso 2: introspección del esquema de suscripciones de GraphQL sin autenticación

Con la conexión establecida, el siguiente paso lógico de la IA fue determinar qué operaciones estaban disponibles. Intentó realizar una consulta de introspección de GraphQL a través de la conexión WebSocket sin autenticación.

Intentar una única introspección de GraphQL sin autenticación a través de WebSocket para enumerar las operaciones de suscripción y validar si se permiten operaciones sin autenticación.

Resumen de la ejecución: El Engine envió un frame subscribe que contenía una consulta de introspección centrada en el tipo Subscription. {"id":"1","type":"subscribe","payload":{"query":"query { __schema { subscriptionType { name fields { name } } } }","variables":{}}}

Resultado: El servidor devolvió un frame next con los datos del esquema, seguido de un frame complete.

{
  "id": "1",
  "type": "next",
  "payload": {
    "data": {
      "__schema": {
        "subscriptionType": {
          "name": "Subscription",
          "fields": [
            { "name": "generateSamplePromptResult" },
            { "name": "translateContent" },
            { "name": "upsertRewardSubscription" }
          ]
        }
      }
    }
  }
}

Conclusión: El servidor permite la introspección sin autenticación a través de WebSockets, una divulgación de información significativa. Esto reveló tres operaciones de suscripción y dio al Engine objetivos claros para probar la autorización.

Paso 3: prueba de la suscripción translateContent para detectar BFLA

El Engine seleccionó translateContent para su primera prueba directa. Tras analizar su esquema de entrada mediante introspección, elaboró una solicitud de suscripción sintácticamente válida pero inocua.

Intentar una única suscripción sin autenticación a translateContent con una entrada válida mínima para verificar si se procesan las suscripciones sin autenticación.

Resumen de la ejecución: El Engine envió un frame subscribe para translateContent, con un payload mínimo y solicitando solo __typename para confirmar la ejecución. {"id":"1","type":"subscribe","payload":{"query":"subscription($data: ContentTranslationSubscriptionInput!){ translateContent(data:$data){ __typename } }","variables":{"data":{"targetLanguage":"en","sourceContent":"hello"}}}}

Resultado: El servidor aceptó la suscripción y devolvió dos frames next, seguidos de complete.

{"id":"1","type":"next","payload":{"data":{"translateContent":{"__typename":"TranslateContentResponse"}}}}
{"id":"1","type":"next","payload":{"data":{"translateContent":{"__typename":"TranslateContentResponse"}}}}
{"id":"1","type":"complete"}

Conclusión: La suscripción translateContent se procesó correctamente sin ninguna autenticación. Esto va más allá de la divulgación de información: es la ejecución activa, sin autenticación, de una función del backend.

Paso 4: verificación de la exposición de datos mediante el BFLA en WebSocket de GraphQL

Recibir un __typename demuestra que la operación se ejecutó, pero ¿devuelve datos reales? El último paso de la IA fue solicitar campos concretos para confirmar la exposición de datos.

Confirmar la devolución de datos sin autenticación solicitando campos reales de translateContent.

Resumen de la ejecución: El Engine repitió la suscripción, esta vez solicitando los campos content, isFinal y subscriptionId. {"id":"1","type":"subscribe","payload":{"query":"subscription($data: ContentTranslationSubscription-Input!){ translateContent(data:$data){ content isFinal subscriptionId } }","variables":{"data":{"targetLanguage":"en","sourceContent":"hello"}}}}

Resultado: El servidor devolvió frames next con el texto traducido.

{
  "id": "1",
  "type": "next",
  "payload": {
    "data": {
      "translateContent": {
        "content": "Hello",
        "isFinal": false,
        "subscriptionId": "yrpsrqkv7li34w23r25dx4b2"
      }
    }
  }
}
{
  "id": "1",
  "type": "next",
  "payload": {
    "data": {
      "translateContent": {
        "content": "Hello",
        "isFinal": true,
        "subscriptionId": "yrpsrqkv7li34w23r25dx4b2"
      }
    }
  }
}

Conclusión: Es una prueba definitiva de una vulnerabilidad de Broken Function-Level Authorization (BFLA). Un atacante sin autenticación puede ejecutar la función translateContent y recibir los resultados.

Paso 5: identificación de una autorización incoherente en las suscripciones de GraphQL

Para comprender el alcance del problema, el Engine probó también otra suscripción descubierta, generateSamplePromptResult.

Intentar una única suscripción sin autenticación a generateSamplePromptResult para evaluar la autorización por operación.

Resumen de la ejecución: El Engine envió una solicitud de suscripción válida y sin autenticación a generateSamplePromptResult.

Resultado: El servidor cerró la conexión de inmediato con el código 4500 y el motivo: "You are not authorized to perform this action".

Conclusión: La autorización se aplica correctamente en esta operación. Este hallazgo es crucial, ya que apunta a controles de seguridad incoherentes, un antipatrón habitual en el que los desarrolladores protegen algunos endpoints pero omiten otros.

Informe final: hallazgos de Ostorlab sobre el BFLA en WebSocket de GraphQL

1. Resumen ejecutivo

El AI Pentest Engine de Ostorlab descubrió una vulnerabilidad crítica de Broken Function-Level Authorization (BFLA) en el endpoint WebSocket de GraphQL en wss://REDACTED/subscriptions. La operación de suscripción translateContent estaba expuesta públicamente, lo que permitía a cualquier usuario sin autenticación ejecutarla y recibir resultados en streaming. Esto representa una elusión directa de privilegios y podría dar lugar a un abuso de recursos y a una posible exposición de datos. El motor también observó una autorización incoherente, ya que otras suscripciones del mismo endpoint aplicaban correctamente los controles de acceso.

2. Metodología

El motor de IA siguió una metodología sistemática para probar la autorización en WebSocket: 1. Descubrimiento del endpoint: identificó el endpoint WebSocket de GraphQL y su protocolo (graphql-transport-ws). 2. Línea base sin autenticación: confirmó que el servidor aceptaba un handshake connection_init sin autenticación. 3. Enumeración del esquema: aprovechó la introspección de GraphQL sin autenticación para descubrir las operaciones de suscripción disponibles. 4. Pruebas por operación: recorrió las suscripciones descubiertas (translateContent, generateSamplePromptResult) e intentó ejecutarlas sin autenticación. 5. Confirmación de la evidencia: confirmó la fuga de datos solicitando y recibiendo campos de datos concretos, no solo mensajes de estado.

3. Hallazgos

  • BFLA en la suscripción translateContent: el hallazgo principal es que la suscripción translateContent carece de cualquier comprobación de autorización. Un atacante puede conectarse, suscribirse y recibir resultados de traducción sin autenticación. Esto se confirmó enviando un payload válido y recibiendo texto traducido como respuesta. La vulnerabilidad se confirmó además al comprobar que se ignoraba incluso un token explícitamente inválido.
  • Divulgación de información mediante introspección sin autenticación: el esquema de GraphQL de las suscripciones es de acceso público a través de WebSockets, lo que permite a un atacante descubrir fácilmente las operaciones disponibles y sus entradas requeridas.
  • Controles de autorización incoherentes: mientras que translateContent era vulnerable, la suscripción generateSamplePromptResult del mismo endpoint rechazaba correctamente las solicitudes sin autenticación. Esta incoherencia sugiere que falta una anotación o un control de seguridad en el resolver vulnerable.

4. Corrección

Para abordar estos hallazgos, recomendamos lo siguiente: 1. Exigir autenticación por defecto: implemente una política de denegación por defecto. Todos los resolvers de GraphQL, en especial los de suscripciones, deberían requerir una sesión válida y autenticada salvo que se marquen explícitamente como públicos. 2. Centralizar la lógica de autorización: utilice middleware o hooks a nivel de framework para aplicar de forma coherente las comprobaciones de autenticación y autorización en todas las operaciones WebSocket, tanto en el momento de la conexión como en cada solicitud de suscripción. 3. Deshabilitar la introspección en producción: aunque es útil durante el desarrollo, la introspección de GraphQL debería deshabilitarse en los endpoints de acceso público para evitar que los atacantes mapeen con facilidad la superficie de la API.

5. Conclusión

Proteger las API modernas en tiempo real requiere un enfoque de defensa en profundidad en el que la autorización se aplique de forma coherente. Este caso práctico demuestra cómo el AI Pentest Engine de Ostorlab puede descubrir de forma sistemática fallos sutiles pero críticos, como el BFLA y los controles de seguridad incoherentes, en implementaciones complejas de WebSocket. Al emular la progresión lógica de un atacante, del descubrimiento a la explotación, el Engine ofrece evidencias definitivas y accionables para ayudar a los desarrolladores a crear aplicaciones más seguras.

Etiquetas:

security, ai, poc, pentest, graphql