Situação atual: PHP2Shell. Duas vulnerabilidades críticas do PHP (CVSS 9,2) permitem executar código malicioso sem autenticação. Patches disponíveis desde 21/10/2026.O que fazer agora →

Desserialização insegura em PHP: unserialize e segurança

O unserialize() com dados alheios pode, no pior dos casos, levar à execução de código. Como substituí-lo por JSON, restringi-lo onde tem de permanecer e encontrar pontos arriscados no código.

Atualizado em 6 min de leituraGuias

Em resumo: na segurança do unserialize em PHP aplica-se uma regra simples: nunca processar com unserialize() dados que vêm do exterior. Utilize antes JSON. Onde o unserialize() tiver de permanecer por código antigo, limite-o com allowed_classes => false e verifique a autenticidade dos dados com uma assinatura.

O que pode correr mal com o unserialize()

O PHP pode converter arrays e objetos numa cadeia de caracteres com serialize() e restaurá-los com unserialize(). É cómodo para guardar dados em cookies, caches ou campos de bases de dados. O problema: o unserialize() cria também objetos de classes arbitrárias que possam ser carregadas na aplicação.

Ao criar e, o mais tardar, ao destruir um objeto desses, o PHP chama automaticamente determinados métodos, como __wakeup(), __unserialize() ou __destruct(). Se classes do projeto ou de bibliotecas tiverem tais métodos com ações sobre ficheiros, bases de dados ou o sistema, um atacante pode desviá-los do seu fim através de dados manipulados. Os especialistas falam em PHP Object Injection. Consoante as classes existentes, o efeito vai da eliminação de ficheiros isolados à execução de código malicioso.

Resumido para não técnicos: uma única chamada irrefletida no sítio errado pode deixar um atacante entrar fundo na aplicação. Esta fragilidade faz por isso parte há anos dos temas do OWASP Top 10.

A alternativa segura: JSON em vez de unserialize

Em quase todos os casos em que os dados vêm do exterior ou saem do servidor, o JSON é a escolha certa. O json_decode() cria apenas arrays, números, cadeias de caracteres e valores booleanos, não objetos de classes próprias.

<?php
declare(strict_types=1);

function leseEinstellungen(string $eingabe): array
{
    try {
        $daten = json_decode($eingabe, true, 16, JSON_THROW_ON_ERROR);
    } catch (JsonException $e) {
        throw new InvalidArgumentException('Ungültige Daten.');
    }

    if (!is_array($daten)) {
        throw new InvalidArgumentException('Erwartet wurde ein Objekt.');
    }

    // Aceitar apenas campos conhecidos com os tipos esperados
    return [
        'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
        'seiten'  => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
    ];
}

Importante é a segunda parte: também os dados JSON são entradas. Aceite apenas os campos esperados e verifique tipos e intervalos de valores antes de trabalhar com eles.

Quando o unserialize é inevitável: allowed_classes e assinatura

Em aplicações mais antigas, os dados serializados estão muitas vezes em bases de dados ou caches que não se podem converter de imediato. Limite então o risco em dois passos. Primeiro, impeça com allowed_classes que sejam criados objetos. Segundo, garanta com uma assinatura HMAC que os dados provêm realmente da sua aplicação e não foram alterados pelo caminho.

<?php
declare(strict_types=1);

const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // nunca no repositório de código

function verpacken(array $daten): string
{
    $roh = serialize($daten);
    return hash_hmac('sha256', $roh, SCHLUESSEL) . '.' . base64_encode($roh);
}

function auspacken(string $paket): array
{
    [$signatur, $b64] = array_pad(explode('.', $paket, 2), 2, '');
    $roh = base64_decode($b64, true);

    if ($roh === false || !hash_equals(hash_hmac('sha256', $roh, SCHLUESSEL), $signatur)) {
        throw new RuntimeException('Signatur ungültig.');
    }

    $daten = unserialize($roh, ['allowed_classes' => false, 'max_depth' => 16]);
    if (!is_array($daten)) {
        throw new RuntimeException('Unerwartetes Format.');
    }
    return $daten;
}

Se forem mesmo necessárias determinadas classes, indique-as uma a uma em allowed_classes, em vez de permitir todas. A chave pertence à configuração do servidor ou a um armazém de segredos e tem de ser renovada após um incidente.

Converter gradualmente os dados existentes para JSON

Em aplicações crescidas, os valores serializados ficam muitas vezes anos na base de dados. Uma conversão num só passo é então arriscada. Uma transição tem-se revelado eficaz: a aplicação lê ambos os formatos, mas só escreve JSON. Ao ler um valor antigo, este é desembalado uma única vez com allowed_classes => false e imediatamente gravado de novo como JSON. Passado algum tempo, a maioria das entradas está convertida, e um script único trata do resto.

Durante a transição, registe quantas vezes ainda é lido o formato antigo e também quando as assinaturas não coincidem. Tais falhas podem indicar tentativas de manipulação e pertencem à monitorização. Só quando já não aparecer nenhum formato antigo é que remove por completo o ramo do unserialize() do código.

Desserialização escondida: sessões, caches e caminhos de ficheiros

Nem toda a desserialização está visível no código próprio. Verifique também estes pontos:

  • Sessões: o PHP serializa automaticamente os dados de sessão. Enquanto os atacantes não puderem escrever nos ficheiros de sessão, isso não é crítico. Caminhos de sessão separados por site são descritos no guia Proteger sessões em PHP.
  • Serializador de sessões: o aviso sobre o CVE-2026-115560 diz respeito a uma configuração invulgar, em que o serialize_handler é alterado em tempo de execução por ini_set() e o session upload progress está ativo. Mitigação segundo o php.net: session.upload_progress.enabled=0 no php.ini ou no pool FPM e, depois, reiniciar os workers.
  • Caches e filas de espera: Redis, Memcached ou tabelas de bases de dados com valores serializados são tão seguros quanto o acesso a eles. Não pertencem abertos à rede.
  • Funções de ficheiros com caminhos vindos de entradas: nunca passe às funções de ficheiros caminhos vindos do pedido. Os stream wrappers como phar:// podem, de outro modo, desencadear um processamento inesperado. Uma lista de permissão para nomes de ficheiros protege aqui com mais fiabilidade do que qualquer filtragem.
  • Dependências: as bibliotecas com fragilidades conhecidas ampliam as possibilidades de um atacante. Mantenha-as atualizadas, como descrito no guia Verificar vulnerabilidades nas dependências do Composer.

Encontrar pontos arriscados no código

Um primeiro inventário consegue-se com uma pesquisa simples. Não altera nada no sistema e mostra todas as chamadas fora do diretório vendor:

grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
  | grep -v '/vendor/'

Avalie cada resultado com três perguntas: de onde vêm os dados? Pode um utilizador alterá-los? O allowed_classes está definido? Os resultados com entradas de cookies, $_GET, $_POST, $_REQUEST ou interfaces externas têm prioridade. Substitua-os por JSON e teste a conversão antes de a pôr em produção.

Outro indício está no navegador. Os dados PHP serializados têm uma estrutura típica, com identificadores de tipo e indicações de comprimento. Se encontrar tais valores em cookies, campos de formulário ocultos ou parâmetros do seu próprio site, a aplicação entregou dados serializados ao visitante e espera provavelmente recebê-los de volta. São precisamente estes pontos que devem ser verificados.

Para a gestão basta uma pergunta simples aos programadores ou à agência: “A nossa aplicação processa em algum lado dados serializados que vêm do exterior e, se sim, como estão protegidos?” Uma resposta clara, com locais concretos, é um bom sinal. Uma resposta evasiva é motivo para mandar verificar a aplicação.

Como ajuda concretamente a PHP SOS?

  • Pesquisamos no seu código a desserialização com dados alheios e avaliamos cada achado segundo a acessibilidade e o impacto.
  • Propomos conversões concretas para JSON ou chamadas protegidas e acompanhamos os seus programadores na implementação.
  • Verificamos as dependências quanto a fragilidades conhecidas e documentamos o estado.
  • Se desejar, mandamos verificar a aplicação de forma independente no âmbito de uma auditoria de segurança e teste de intrusão.

Perguntas frequentes

O unserialize() em PHP é inseguro por princípio?

Não para dados que só a sua própria aplicação gera e que ninguém pode alterar. Torna-se perigoso assim que utilizadores ou terceiros podem influenciar a entrada, por exemplo através de cookies, campos de formulário ou interfaces externas.

O allowed_classes => false basta como proteção?

Impede que sejam criados objetos arbitrários e fecha, assim, o caminho de ataque mais importante. Para entradas vindas do exterior, o JSON continua a ser a melhor escolha, porque o analisador é mais simples e não reconstrói objetos.

Atualizar bibliotecas ajuda contra ataques de desserialização?

Em parte. Os ataques usam muitas vezes classes de dependências que executam ações ao destruir ou ao acordar um objeto. Pacotes atuais fecham cadeias conhecidas, mas a verdadeira causa é a chamada ao unserialize com dados alheios.

O PHP2Shell tem alguma relação com a desserialização?

As duas vulnerabilidades principais estão no processamento de pedidos do PHP. O aviso sobre o CVE-2026-115560 diz, em contrapartida, respeito a uma configuração invulgar da serialização de sessões. A mitigação aí é session.upload_progress.enabled=0.

Fontes

Contacto imediato

Cada hora conta. Escreva-nos agora.

Descreva brevemente o sistema, a versão do PHP e o que observou. Entramos em contacto consigo, esclarecemos a situação e trabalhamos remotamente até o incidente estar concluído e documentado.

Ajuda imediata