Aviso de seguridad. SSRF en las herramientas de agente de SiYuan mediante DNS-Rebinding TOCTOU (Bypass de CheckHostSSRF)
| Campo | Valor |
|---|---|
| Divulgado por | joysinleung (joysinleung@gmail.com) |
| Fecha del informe | 2026-08-13 |
| Producto | SiYuan (思源笔记) — siyuan-note/siyuan |
| Módulo Go | github.com/siyuan-note/siyuan/kernel |
| Versiones afectadas | <= 3.8.0 (última versión publicada en el momento del informe; verificado dinámicamente en v3.8.0) |
| Versiones corregidas | 3.8.1 |
| Componente | kernel/util/httprequest.go (CheckHostSSRF), kernel/mcp/tools/http_request.go, kernel/util/webfetch.go, kernel/util/net.go (SSRFSafeDialer) |
| Relación con aviso anterior | Variante de corrección incompleta de GHSA-rg26-cg95-gq6p (corrección del vector principal de SSRF). Véase §Relación. |
| EPSS (probabilidad de explotación) | Baja–Moderada. Requiere que el atacante influya en un Agente de IA / cliente MCP para que acceda a un dominio controlado por el atacante (escenario de inyección de prompt documentado por la propia herramienta). |
| KEV (CISA Known Exploited) | No (no figuraba en CISA KEV en el momento del informe). |
| Alcanzable con configuración predeterminada | Sí — explotable tanto con SafeMode activado como desactivado; solo requiere que la herramienta del agente http_request / web_fetch sea accesible (herramientas de IA predeterminadas). |
Resumen
Las herramientas de Agente de IA de SiYuan http_request (util.HTTPRequest) y web_fetch (util.WebFetch) son la única barrera SSRF para las solicitudes salientes desde el kernel. Esa barrera es CheckHostSSRF, que realiza una única resolución DNS en el momento de la comprobación y verifica si alguna IP devuelta es privada/loopback/link-local. La conexión real, no obstante, realiza una segunda resolución DNS independiente mediante el net.Dialer predeterminado, y en esta ruta no se aplica ninguna comprobación de IP privada en el momento de la conexión.
Como las dos resoluciones no están fijadas al mismo resultado, un dominio controlado por un atacante puede responder a la resolución de comprobación con una IP pública (superando CheckHostSSRF) y a la resolución de conexión con una IP privada/loopback/de metadatos (por ejemplo, 169.254.169.254). Esto es un clásico DNS-rebinding TOCTOU que elude por completo la defensa SSRF. Permite alcanzar los metadatos de instancias en la nube y servicios internos que la protección se añadió específicamente para bloquear.
Relación con avisos anteriores
- GHSA-rg26-cg95-gq6p corrigió el vector principal de SSRF añadiendo
CheckHostSSRF(en tiempo de análisis) ySSRFSafeDialer(en tiempo de conexión). No obstante, las rutas de herramientas del agente (http_request/web_fetch) solo recibieron la mitad correspondiente al análisis: llaman aCheckHostSSRFpero después se conectan mediantehttpclient.NewBrowserRequest(), cuyo transporte no montaSSRFSafeDialer. Incluso allí dondeSSRFSafeDialersí está montado, solo bloquea IP privadas cuandoSafeMode == true(por defecto false), por lo que tampoco ayudaría aquí. La ruta hermanaopenai.go:generatedImageDialersí monta un hookControlen tiempo de conexión (bloqueando private/loopback/link-local/100.64/198.18), lo que demuestra que el proyecto conoce la técnica; la ruta del agente es una omisión clara. Informamos de esto como una variante de corrección incompleta con una reproducción concreta en v3.8.0. - CVE-2026-32110 (GHSA-56cv-c5p2-j2wg) cubría el endpoint antiguo
forwardProxyy no está relacionado con la ruta de herramientas del agente.
Versión afectada
Verificado dinámicamente en v3.8.0 (tag v3.8.0, commit 251596fc0). Se instaló un secuestro DNS a nivel de proceso para que el dominio del atacante rebind.local devolviera una IP pública (203.0.113.1) en las resoluciones impares (comprobación) y 127.0.0.1 en las resoluciones pares (conexión); un servicio "víctima" en loopback devolvió F9_REBIND_PROOF=reached-internal-only-service-via-TOCTOU. Tanto util.HTTPRequest("GET", "http://rebind.local:<port>/secret") como util.WebFetch(...) devolvieron el cuerpo de prueba del servicio interno, mientras que CheckHostSSRF("127.0.0.1") bloqueó directamente y el dominio público legítimo pub.local pasó la comprobación, confirmando que la barrera permitió la solicitud pero la conexión alcanzó la dirección interna. Todas las versiones <= 3.8.0 están afectadas.
Componente
kernel/util/httprequest.go:42CheckHostSSRF— un úniconet.LookupIP+isPrivateIPúnicamente en el momento de la comprobación.kernel/mcp/tools/http_request.go:90→util.HTTPRequest;kernel/util/webfetch.go:50→CheckHostSSRF+httpclient.NewBrowserRequest().github.com/siyuan-note/httpclientclient.go:92NewBrowserRequestutiliza elhttp.Transportpredeterminado →net.Dialerpredeterminado sinSSRFSafeDialer.kernel/util/net.go:151SSRFSafeDialerexiste pero (a) no está montado en la ruta del agente y (b) solo está activo bajoSafeMode.- Ruta hermana correctamente protegida:
kernel/util/openai.go:829generatedImageDialermonta un hookControlen tiempo de conexión.
Vector de ataque
Red + Agente de IA. La url de http_request / web_fetch está completamente controlada por el agente / cliente MCP. En el escenario de red-team documentado por SiYuan ("inyectar un prompt al agente → inducirlo a visitar un dominio del atacante"), el atacante solo necesita un dominio de rebinding (DNS autoritativo propio, primera respuesta pública, después 169.254.169.254 / interna). Sin autenticación, sin posición especial más allá de inducir al agente mediante un prompt. Objetivos reales: metadatos de nube 169.254.169.254 (credenciales IAM temporales) y servicios no autenticados del mismo host/internos.
