Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Ferramentas/GitHubGitHub/captain-woof/cve-2026-25243
Vulnerability AnalysisExploitationPost-ExploitationPenetration TestingRed TeamingDatabase SecurityBinary Exploitation
GitHubcaptain-woof/cve-2026-25243

CVE-2026-25243

Stable POC for CVE-2026-25243 (Redis RESTORE double-free -> remote code execution)

Ver Repositório
1há 14 diasAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-25243 — Redis RESTORE double-free → execução remota de código

Verificado contra Rocky Linux 8.10, aarch64, Redis 8.6.2, jemalloc 5.3.0.

Referência: https://www.zeroday.cloud/blog/redis-cve-2026-25243-deep-dive

TLDR; Exploit estável, funciona contra uma variedade de distribuições de SO e arquiteturas.


Resumo Executivo

O que é isto? Uma vulnerabilidade de corrupção de memória no Redis que permite a um atacante autenticado executar comandos arbitrários como o usuário Redis. O ataque é do mundo real e requer apenas um único comando RESTORE — uma operação normal do Redis, não restrita a administradores. Este exploit demonstra RCE completo em menos de um segundo.

Impacto? Qualquer cliente Redis autenticado pode acioná-la, e o dano é total: execução arbitrária de código no processo Redis (frequentemente executando como root em contêineres). Não há como mitigar sem aplicar patch no próprio Redis.

Como funciona em resumo? O Redis tem um recurso de serialização (RESTORE) que pega um blob de dados binários e o reconstrói como um objeto Redis. O código que valida o formato do blob e o código que o desserializa discordam sobre como analisar certas sequências — uma falha que o atacante explora para corromper a heap. Uma vez que a heap é corrompida, o atacante ganha a capacidade de ler e escrever qualquer endereço de memória no processo Redis e, a partir daí, sequestra o estado interno do servidor para executar um comando shell.

A técnica real do exploit: Isto não é um simples crash. É uma cadeia de exploração de heap: corromper → sobrepor → R/W arbitrário → vazamento de informações → encontrar a struct do servidor → sequestrar ponteiros de função → RCE. O exploit executa 9 estágios e exige vazar múltiplos endereços em tempo de execução, analisar estruturas binárias e detectar aliasing de memória. O que o faz funcionar em diferentes arquiteturas (x86-64, aarch64, etc.) é que todos os endereços são vazados do próprio alvo, não presumidos.


1. A vulnerabilidade — em detalhes

CVE-2026-25243 é um par de bugs de double-free alcançáveis a partir de um único comando RESTORE autenticado. RESTORE key ttl <serialized-value> desserializa um blob RDB controlado pelo atacante; ambos os bugs vivem na lacuna entre o validador que verifica o blob e o conversor que o materializa.

Bug 1 — conversão zipmap legada (CWE-415, o caminho que este exploit usa). O validador zipmap (zipmapValidateIntegrity()) e o conversor (zipmapNext()) discordam sobre uma codificação de comprimento redundante. O comprimento pequeno 4 pode ser legalmente escrito na forma longa de cinco bytes FE 04 00 00 00. O validador consome um número de bytes, o conversor outro — uma dessincronização de análise de 4 bytes. O conversor portanto percorre uma estrutura diferente daquela que foi validada, lpSafeToAdd() falha depois que o campo já foi inserido no dicionário, e o caminho de limpeza libera o campo duas vezes: uma via dictRelease() e outra via sdsfree().

Bug 2 — carregamento do PEL do consumidor de stream (CWE-415). Em rdbLoadStreamConsumersGroup(), um PEL de consumidor contendo um ID de entrada duplicado faz a segunda chamada raxTryInsert() falhar, o que chama streamFreeNACK() em um streamNACK que ainda é de propriedade do PEL global do grupo. Liberado duas vezes. (Selecionável com --vuln-type stream.)

Qualquer um dos bugs entrega ao atacante um pedaço de memória que está simultaneamente livre e referenciado — o ponto de partida clássico para um exploit de sobreposição de heap.

Impacto: um cliente Redis autenticado (sem direitos de administrador, RESTORE é um comando de dados normal) obtém execução arbitrária de código como o usuário redis — root na imagem de contêiner padrão.

2. Como o exploit funciona

Nove estágios, cada um dos quais transforma uma primitiva mais fraca em uma mais forte:

Como acionar

root@kitploit:~
python3 exploit.py --host 127.0.0.1 --port 6379 \
    --password mypassword --cmd 'id > /tmp/pwned123.txt'

Verifique:

root@kitploit:~
cat /tmp/pwned123.txt
# uid=0(root) gid=0(root) groups=0(root)

3. Log de alterações

2026-08-06 — reformulação de portabilidade, confiabilidade e velocidade

Ponto de partida: o exploit era exclusivo para x86-64 e morria no estágio 3 no alvo aarch64. Estado final: RCE completo em aarch64 Rocky Linux 8.10 em menos de um segundo, 116 comandos Redis.

a) Identificação de impressão digital do alvo em tempo de execução (novo, estágio 0). Nada sobre o alvo é mais presumido. INFO server + INFO memory fornecem a versão do Redis, a arquitetura da CPU (da linha os:), a família da distribuição (inferida de gcc_version), o alocador e — o mais importante — três âncoras de validação: process_id, executable e o stat_starttime exato (server_time_usec/1e6 - uptime_in_seconds). Estágios posteriores comparam com essas em vez de adivinhar.

b) Layout de memória independente de arquitetura. As quatro constantes x86-64 fixas (BINARY_ADDR_MIN/MAX, HEAP_ADDR_MIN/MAX) são substituídas por uma tabela por arquitetura (ARCH_PROFILES) que cobre x86_64, aarch64 (VA de 39 e 48 bits), riscv64, ppc64le e s390x, com ambas as disposições ET_EXEC e ET_DYN para cada uma, além de um amplo fallback genérico para qualquer item não listado. Esse foi o motivo real da falha do exploit neste alvo: o ponteiro vazado 0x0000ffff8a5fdf32 é um endereço mmap aarch64 perfeitamente válido que a verificação de faixa do x86-64 rejeitou.

c) Validação de vazamento baseada em consenso (estágio 3). Em vez de confiar em uma janela de heap fixa, a varredura agora coleta todos os objetos estruturalmente válidos 1337.NNNNNN na memview e exige que pelo menos dois deles derivem o mesmo endereço base da memview (ptr - offset_of_value). Na prática, 502 candidatos concordam, o que é uma prova que nenhuma tabela de faixas pode oferecer. O ponteiro confirmado então calibra a janela de heap em tempo de execução. A validação de formato também foi movida para antes do teste de controle de escrita (caro em round-trips).

d) Limite da varredura do estágio 3 (correção de bug). A varredura ia até os 10 MB fixos enquanto a memview tem 1 MB, então lia além do final, obtinha uma resposta vazia e abortava com AssertionError: Empty data from memview. Agora ela é limitada pelo STRLEN real da memview, lê 256 KB por round-trip em vez de 64 KB, e o inútil loop de retry-sleep de 6×1s foi removido.

e) Estágio 5 reescrito: guiado por ELF, sem crash (o grande). A implementação antiga escaneava para frente a partir de um ponteiro de imagem, sondando endereços e lendo qualquer comprimento que um cabeçalho SDS de lixo afirmasse. Neste alvo, ela ultrapassava o final do segmento somente leitura até o buraco não mapeado em 0x715000 e matava o servidor (SIGSEGV em getrangeCommand → memcpy). A varredura cega não pode ser tornada segura. A substituição é determinística:

  1. Encontrar a base da imagem. Desça página por página a partir do ponteiro de imagem vazado mais baixo. A sonda é gratuita: os primeiros cinco bytes de toda imagem ELF64 são 7f 45 4c 46 02, e sdslen() pega seu byte de flags de ptr[-1] — então apontar o objeto sequestrado para base+5 faz de e_ident[EI_CLASS]=0x02 o byte de flags, ou seja, SDS_TYPE_16, cujo comprimento é o uint16 em base+0 = 0x457f (0x7f45 big-endian). Um STRLEN de exatamente 17791 é a assinatura ELF. Nenhuma cópia local do binário é necessária — o cabeçalho é lido da própria memória do alvo.
  2. Analisar os program headers para obter os limites exatos em tempo de execução de cada segmento PT_LOAD (tratando o viés de carga ET_DYN para alvos PIE). Cada leitura subsequente é limitada a um mapeamento real, então o crash do buraco não mapeado agora é estruturalmente impossível.

f) Estágio 4 endurecido. O validador Lua recebe a lista completa de faixas por arquitetura (então uma imagem não-PIE em 0x400000 e uma imagem PIE em 0xaaaa… são ambas reconhecidas) e exclui a janela de heap calibrada. Ele retorna vários candidatos em vez de um, então uma escolha ruim custa uma nova tentativa em vez da execução.

g) Estágio 7 autoverificável. enable_debug_cmd era localizado por um stat_starttime - 0x3c fixo. Agora o valor esperado de stat_starttime é conhecido exatamente a partir do INFO (uma janela de 3 segundos em vez de 30 dias), a janela de leitura da struct cresceu de 4 KB para 32 KB (stat_starttime está no offset 0x9e0, bem além do limite antigo) e — de forma decisiva — cada offset candidato é verificado com um oráculo ao vivo: defina o byte, envie DEBUG SET-ACTIVE-EXPIRE 1 e veja se o servidor aceita. Suposições erradas são restauradas antes da próxima tentativa, então a flag é encontrada em qualquer build em vez de presumida. -0x3c ainda é tentado primeiro e confirmado como correto para 8.6.2 (offset 0x9a4).

h) As escritas realmente chegam ao destino (estágio 5). setrangeCommand() chama dbUnshareStringValue(), que duplica o valor a menos que encoding == RAW && refcount == 1. O byte de encoding agora é zerado antes da primeira escrita através do ponteiro sequestrado, então as escritas alcançam o endereço de destino em vez de uma cópia privada.

i) Payload simplificado. Toda a maquinaria de backconnect/reverse-shell, o banner ASCII e o ;sleep 5 anexado foram removidos. O payload é exatamente /bin/sh -c '<--cmd>' e nada mais. --cmd tem como padrão id > /tmp/pwned123.txt.

j) Velocidade. O estágio 4 coleta 3 candidatos em vez de 8; o estágio 5 substitui ~10^5 sondas de byte por ~40 leituras em massa; o estágio 3 usa leituras de 256 KB e pula round-trips para candidatos que falham na validação local. Cadeia inteira: 116 comandos, <1 s.

Resultado: uid=0(root) gid=0(root) groups=0(root) em /tmp/pwned123.txt no contêiner alvo.

Notas de portabilidade

  • Arquitetura é detectada, não presumida. O estágio 5 baseado em ELF é neutro em relação à arquitetura por construção (ele lê os próprios program headers do alvo) e lida com imagens PIE e não-PIE, little- e big-endian.
  • Distro é reportada para benefício do operador; o exploit não tem dependência funcional dela. A única suposição de sistema de arquivos é /bin/sh, que POSIX e FHS exigem.
  • Versão: a versão RDB e o tamanho da struct de stream são selecionados a partir de redis_version (7.x e 8.x suportados). Os offsets de campos da struct (executable=24, exec_argv=32) decorrem da ABI LP64, e enable_debug_cmd é descoberto e verificado em tempo de execução em vez de fixo no código.
  • Alvos de 32 bits são rejeitados explicitamente no estágio 0 (o payload constrói ponteiros de 64 bits) em vez de falhar de forma obscura mais tarde.

2026-08-06 (posterior) — endurecimento de estabilidade após testes de execução repetida

Medidos 13/13 execuções bem-sucedidas no caminho zipmap padrão (5 + 8 seguidas), cada uma concluindo em ≤1 segundo. Três problemas surgiram apenas sob execução repetida e agora estão corrigidos:

k) Corrida de SAVE no estágio 0. Uma execução podia abortar com ERR Background save already in progress quando um save em segundo plano de uma execução anterior (ou do próprio redis) ainda estava em andamento. SAVE agora é tentado novamente por até 15 segundos e, se falhar, a execução prossegue sem o checkpoint em vez de abortar.

l) Reconectar enquanto o alvo reinicia (--connect-retries, padrão 10). Uma tentativa fracassada deixa a heap corrompida, então o FLUSHALL da próxima execução libera os blocos envenenados e derruba o servidor. Ele reinicia segundos depois e é perfeitamente explorável, então o estágio 0 agora reconecta e tenta novamente em vez de falhar. Nossos próprios erros de validação (versão/arquitetura não suportada) nunca são repetidos. Isso removeu o intermitente "stage 0 failed with an empty error" visto em cerca de 1 em 3 execuções durante os testes de estresse.

m) sizeof(streamNACK) corrigido para 8.6.x. O caminho --vuln-type stream pulverizava a classe de tamanho errada do jemalloc porque a struct era presumida como tendo 24 ou 32 bytes. Em 8.6.2 ela tem 64 bytes (delivery_time, delivery_count, consumer, cgroup_ref_node, streamID id, pel_prev, pel_next). Com o tamanho correto, o caminho de stream agora alcança o estágio 5 em vez de falhar no estágio 2 com "key overlap not found".

Limitações conhecidas

  • --vuln-type stream não é confiável no 8.6.2. Com a correção de tamanho, ele passa pelo double-free, pela sobreposição, pela primitiva R/W e pela análise ELF, então desestabiliza o keyspace: o servidor morre em setrangeCommand lendo o->ptr em NULL+8, ou seja, uma busca de chave retorna um objeto corrompido. O bloco de 64 bytes que ele libera é compartilhado com outras alocações vivas, o que o torna muito mais pesado em danos colaterais do que o caminho zipmap. Use o padrão --vuln-type zipmap, que está 13/13.
  • --random-heap-massage (100k chaves aleatórias primeiro) tem sucesso, mas não invariavelmente — a heap pulverizada às vezes coloca o bloco duplamente liberado onde nenhuma chave marcadora cai. Executar novamente tem sucesso.
  • Verificado apenas em aarch64 / Rocky Linux 8.10 / Redis 8.6.2 (ET_EXEC não-PIE). Os caminhos x86-64 e PIE são implementados e neutros em relação à arquitetura por construção, mas não foram executados contra um alvo vivo nesta sessão.
Baixar ferramenta
EstágioPrimitiva obtidaMecanismo
0perfil do alvoINFO server / INFO memory → versão, arquitetura, distribuição, pid, caminho do executável, hora de início, alocador
1double freezipmap malformado (ou stream) RESTORE
2duas chaves compartilhando memóriaespalhar chaves marcadoras sobre o pedaço liberado, detectar aliasing, então sobrescrever o cabeçalho SDS de uma chave através de sua gêmea para inflá-lo para uma "memview" de 1 MB
3R/W arbitrárioencontrar um objeto INCRBYFLOAT dentro da memview, sequestrar seu campo ptr: GETRANGE/SETRANGE nessa chave agora lê/escreve qualquer endereço
4ponteiro de imagemescanear a heap de trás para frente em busca de um valor dentro da imagem do redis-server
5&serverdescer até o cabeçalho ELF, analisar os program headers, despejar o segmento gravável, corresponder a server.pid
6payload na memóriaescrever "/bin/sh", "-c", "<cmd>" mais um array argv na memview
7struct sequestradasobrescrever server.executable, server.exec_argv e server.enable_debug_cmd
8RCEDEBUG CRASH-AND-RECOVER → restartServer() → execve(server.executable, server.exec_argv, environ)
  • Forjar um cabeçalho SDS em um slot zerado do segmento gravável, o que torna todo .data/.bss legível em um punhado de round-trips em vez de centenas de milhares de sondas de byte. Os bytes sobrescritos são salvos e restaurados.
  • Corresponder server.pid ao pid do INFO — um teste de igualdade exato de 8 bytes — e então confirmar desreferenciando server.executable e comparando a string com o executable do INFO. O código antigo aceitava uma heurística frouxa de formato de sete campos; a struct agora é positivamente identificada.