testing session.upload_progress.enabled = Off

Guia de Mitigação de Emergência PHP2Shell: Desativar session.upload_progress e Backports EOL

php2shell.run Research Lab •

Guia de Mitigação de Emergência PHP2Shell: Desativar session.upload_progress e Backports EOL

<time datetime="2026-10-23">Verificado em 23 de outubro de 2026</time>

Quando CVE-2026-109618 e CVE-2026-109619 (PHP2Shell) atingem uma frota com centenas de servidores, um upgrade completo e imediato raramente é possível sem risco. Dependências de pacotes, invalidação de OPcache, compatibilidade de extensões e janelas de manutenção se interpõem. Ao mesmo tempo, a RCE não autenticada permanece enquanto a engine não for corrigida. Este guia descreve três caminhos testados, com diferentes níveis de intervenção.

  1. Opção A — hardening sem downtime via php.ini (cerca de 30 segundos)
  2. Opção B — filtragem na borda via Nginx ou Cloudflare
  3. Opção C — compilar o backport upstream 1d7dec1 para PHP 8.1 EOL

1. Triagem e árvore de decisão

              [ ALERTA: PHP2Shell (CVE-2026-109618 / 109619) ]
                                    │
                 ┌──────────────────┴──────────────────┐
                 │ Você controla php.ini / container?  │
                 └──────────────────┬──────────────────┘
                                    │
                   ┌────────────────┴────────────────┐
                   ▼ SIM                             ▼ NÃO (apenas proxy/CDN)
       ┌───────────────────────────┐     ┌───────────────────────────┐
       │ Estratégia A (preferida)  │     │ Estratégia B (perímetro)  │
       │ Callback off no php.ini    │     │ bloquear multipart no     │
       │ sem downtime, ~30 s       │     │ Nginx / Cloudflare        │
       └─────────────┬─────────────┘     └───────────────────────────┘
                     │
                     ▼
       ┌─────────────────────────────────────────────────────────┐
       │ PHP 8.1 e anteriores (EOL): Estratégia C                │
       │ ➜ compilar commit 1d7dec1 ou usar backport do vendor    │
       └─────────────────────────────────────────────────────────┘

A grande vantagem da Opção A: a vulnerabilidade depende exclusivamente do callback RFC1867. Desativá-lo remove toda a superfície de ataque sem alterar código da aplicação.


2. Opção A: Hardening sem downtime via php.ini

2.1 Configuração global

; /etc/php/8.3/fpm/php.ini
; Desativa o callback de tracking RFC1867
session.upload_progress.enabled = Off
session.upload_progress.cleanup = On

O caminho varia por distribuição. Não assuma; verifique:

php --ini
php-fpm -i | grep -i 'Loaded Configuration File'

2.2 Forçar no nível do pool

; /etc/php/8.3/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off

php_admin_flag não pode ser sobrescrito por ini_set() na aplicação. Em ambientes multi-tenant é essencial, caso contrário um único vHost pode anular a proteção.

2.3 Reload graceful e verificação

sudo systemctl reload php8.3-fpm
sudo systemctl status php8.3-fpm --no-pager
sudo kill -USR2 "$(cat /var/run/php-fpm.pid)"
php -r 'echo "upload_progress=", ini_get("session.upload_progress.enabled"), PHP_EOL;'
php-fpm -tt 2>&1 | grep -i upload_progress

Após o reload, a saída deve ser vazia ou 0. No FPM, verifique Local Value e Master Value, pois configs de pool podem sobrescrever a global.

2.4 Por que uploads normais não quebram

session.upload_progress é uma função de conforto de 2012 para barras de progresso. Não faz parte do pipeline real de arquivos. $_FILES, move_uploaded_file(), verificações de MIME e limites de tamanho permanecem inalterados. SPAs modernas usam a File API do navegador e eventos de progresso XHR/Fetch, independentes dessa diretiva.

2.5 Plano de rollback

sudo cp /etc/php/8.3/fpm/php.ini /etc/php/8.3/fpm/php.ini.bak-2026-10-23
sudo cp /etc/php/8.3/fpm/php.ini.bak-2026-10-23 /etc/php/8.3/fpm/php.ini
sudo systemctl reload php8.3-fpm

Um reload graceful mantém conexões existentes, mas pode causar latência breve dependendo de OPcache e sessão. Execute em janela de baixo tráfego e monitore a taxa de 5xx.


3. Opção B: Filtragem na borda via Nginx ou CDN

Quando não há acesso ao backend, a filtragem no perímetro é a alternativa. É solução temporária, não reparo.

# /etc/nginx/conf.d/php2shell.conf
map $http_content_type $php2shell_multipart {
    default 0;
    "~*multipart/form-data" 1;
}

server {
    client_max_body_size 8m;
    client_body_buffer_size 128k;

    location ~ ^/(api|health|static|assets)/ {
        if ($php2shell_multipart = 1) { return 415; }
        if ($arg_PHP_SESSION_UPLOAD_PROGRESS) { return 403; }
        if ($http_php_session_upload_progress) { return 403; }
        client_max_body_size 256k;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_read_timeout 20s;
    }

    location ^~ /upload/ {
        client_max_body_size 64m;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}
sudo nginx -t && sudo systemctl reload nginx

No Cloudflare, crie regra WAF customizada combinando Content-Type: multipart/form-data com a presença de PHP_SESSION_UPLOAD_PROGRESS. Lembre-se de que atacantes podem codificar, fragmentar ou mover o nome do campo para headers. A regra é sinal, não cerca. Adicione rate-limits por IP de origem e bloqueie temporariamente IPs com muitos 415.

Também considere terminação TLS e cadeias de proxy. Se um CDN termina a conexão e repassa sem checagem, o worker PHP não vê o IP original. Para rate-limits e Fail2ban, confie em X-Forwarded-For ou CF-Connecting-IP apenas se o origin for acessível exclusivamente via proxy. Caso contrário, atacantes podem forjar headers e contornar bloqueios. Endureça o origin com firewall limitando às redes do proxy e rejeite conexões diretas da internet.


4. Opção C: Patch EOL PHP 8.1 (commit 1d7dec1)

PHP 8.1 não recebe mais releases upstream. O core team forneceu o patch mínimo 1d7dec1, que altera duas coisas.

4.1 Mudanças principais

/* ext/session/session.c — callback permanentemente desativado */
- if (!PS(rfc1867_enabled)) {
+ if (!PS(rfc1867_enabled) || true) {
      return retval;
  }
/* Zend/zend_alloc.c — OOM encerra imediatamente */
- zend_error_noreturn(E_ERROR, "Allowed memory size exhausted");
+ _exit(1);

A segunda mudança encerra o worker imediatamente em OOM ou overflow de inteiro. Isso remove a janela de tempo em que UAF e heap grooming poderiam ocorrer. O preço é um aborto duro do processo; disponibilidade cede lugar à integridade. Ajuste pm.max_children, emergency_restart_threshold e monitoramento conforme.

4.2 Procedimento de build

sudo apt build-dep php8.1 -y 2>/dev/null || sudo dnf builddep php -y
git clone --depth 1 -b PHP-8.1-CVE-2026-109618-109619 https://github.com/php/php-src.git
cd php-src
./buildconf --force
./configure --prefix=/usr/local/php81-patched --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --with-openssl --with-zlib --enable-mbstring
make -j"$(nproc)"
sudo make install
sudo /usr/local/php81-patched/sbin/php-fpm -t

4.3 Verificação antes da produção

/usr/local/php81-patched/bin/php -v
/usr/local/php81-patched/bin/php -r 'echo ini_get("session.upload_progress.enabled"), PHP_EOL;'
systemctl daemon-reload
systemctl restart php81-patched-fpm
curl -sS https://sua-dominio.example/health -o /dev/null -w '%{http_code}\n'

Um interpretador compilado por você transfere a responsabilidade por futuras atualizações de segurança totalmente para sua equipe. Prefira backports de vendor (Ubuntu Pro, CloudLinux, Debian LTS). O build próprio é a última opção para ambientes que não podem migrar.


5. FAQ operacional

P1: session.upload_progress.enabled = Off quebra formulários de upload legados? Apenas a barra de progresso desaparece. Sucesso, tratamento de erro, limites de tamanho e validação de MIME permanecem.

P2: Como confirmar que a mitigação funcionou? CLI: php -r 'var_dump(ini_get("session.upload_progress.enabled"));'. FPM: página temporária phpinfo() verificando Local e Master Value. Adicionalmente, use regras Suricata/Falco para detectar tentativas de gatilho.

P3: Por que um limite de tamanho de body no WAF não basta? Payloads reais do PHP2Shell frequentemente têm 450–500 bytes. Limites de 2 MB ou 5 MB não capturam nada. A desativação no nível da engine é o único corte confiável.

P4: 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 são totalmente seguros? Para as duas CVE publicadas, contêm as correções. Se você não usa upload progress, mantenha-o desativado mesmo após o patch para minimizar a superfície de ataque.

P5: Preciso reverter a configuração após o patch? Não. A desativação pode permanecer. Documente a decisão para que não seja revertida acidentalmente em um futuro pedido de feature.


6. Comunicação em incidente

Medidas técnicas não bastam. Informe clientes de hosting, áreas de negócio e gestão em três níveis: (1) mitigação ativa, (2) cronograma de patch e janela de manutenção, (3) relatório final com risco residual. Para clientes com aplicações PHP próprias, forneça um folheto curto explicando a desativação de session.upload_progress e os comandos de verificação. Assim evita-se avalanche de suporte e garante-se que configurações por tenant também sejam ajustadas.

Documente também quais sistemas estão protegidos apenas pela Opção B. Esses sistemas têm maior risco residual e devem ser priorizados no backlog de patch. Um relatório de status simples por servidor — patcheado, mitigado, desprotegido — é a base para qualquer decisão subsequente.