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 →

Type juggling em PHP: comparações laxas como risco de segurança

Porque é que as comparações laxas com == em PHP podem levar a falhas de segurança, o que mudou com o PHP 8.0 e como escrever comparações robustas em inícios de sessão, tokens e permissões.

Atualizado em 6 min de leituraDesenvolvimento seguro

Em resumo: O PHP Type Juggling designa a conversão automática de tipos que, na comparação laxa com ==, decide o resultado. Desde o PHP 8.0 as regras são mais estritas, mas as cadeias numéricas continuam a ser comparadas como números, e true, false e null seguem regras próprias. Para verificações relevantes para a segurança aplica-se, por isso: ===, in_array(..., true), match em vez de switch, hash_equals() para tokens e password_verify() para palavras-passe.

Uma comparação verdadeira quando não devia ser

Muitas falhas de segurança em aplicações PHP não estão em funções complicadas, mas num único carácter: um == onde devia estar um ===. O código parece correto, os testes com entradas normais passam e, mesmo assim, a verificação deixa passar, em certas condições, algo que devia travar.

A causa é uma propriedade do PHP que no dia a dia é prática: os valores são convertidos para outro tipo quando necessário. Uma cadeia de carateres de um formulário pode assim ser comparada sem rodeios com um número da base de dados. Em inícios de sessão, tokens, perfis ou campos de estado, é precisamente esta generosidade que pode tornar-se perigosa.

Como o PHP compara com ==

O manual do PHP descreve detalhadamente as regras das comparações laxas. Para a segurança, são importantes sobretudo estes casos:

Comparação PHP 7 PHP 8 Razão
0 == "a" verdadeiro falso desde o PHP 8.0 comparadas como cadeias
"1" == "01" verdadeiro verdadeiro ambas numéricas, comparação como número
"10" == "1e1" verdadeiro verdadeiro a notação exponencial é numérica
100 == "1e2" verdadeiro verdadeiro número contra cadeia numérica
null == false verdadeiro verdadeiro comparação como valor lógico
"abc" == true verdadeiro verdadeiro uma cadeia não vazia conta como verdadeira

A alteração no PHP 8.0 atenuou toda uma classe de erros: antes, uma cadeia não numérica passava a 0 na comparação com um número. Isso afetava também as instruções switch. As restantes linhas da tabela mostram, porém, que as comparações laxas continuam a esconder surpresas.

Também os operadores <, > e o operador de comparação <=> seguem estas regras. Em ordenações isso é normalmente inofensivo; em verificações como “montante inferior ao limite” com valores do pedido, o tipo deve estar fixado de antemão.

Particularmente traiçoeira é a linha da notação exponencial. Valores de hash e tokens são muitas vezes compostos por algarismos e letras. Se um valor destes parecer por acaso um número em notação exponencial, o == pode comparar dois hashes diferentes como iguais. Esta é a origem de muitos relatos de contornos de início de sessão em aplicações PHP mais antigas.

PHP Type Juggling em verificações de segurança típicas

As comparações laxas escondem-se em mais sítios do que apenas no operador ==:

  1. in_array() e array_search() comparam de forma laxa sem o terceiro parâmetro. Uma lista de permissão com perfis ou IDs fica assim mais frouxa do que se pensa.
  2. switch compara de forma laxa. A expressão match, disponível desde o PHP 8.0, compara, segundo o manual, com ===, de forma segura quanto ao tipo, e lança um erro quando nenhum ramo corresponde.
  3. As entradas JSON fornecem tipos reais. Um campo que espera como cadeia pode chegar como true ou como número e coincidir de forma surpreendente com ==.
  4. strcmp() para segredos é inadequado, porque o tempo de execução depende do conteúdo e os casos de erro são facilmente avaliados de forma errada.
  5. Verificação de valores de retorno: funções como strpos() devolvem 0 para uma ocorrência no início e false para nenhuma ocorrência. if (!strpos(...)) confunde ambos.

Comparações seguras: o confronto

Os exemplos seguintes mostram, em cada caso, a variante insegura e a segura:

<?php
declare(strict_types=1);

// Verificar o perfil
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // inseguro: laxo
in_array($rolle, $erlaubteRollen, true);    // seguro: estrito

// Avaliar o estado
$aktion = match ($status) {                 // estrito, erro perante valor desconhecido
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Verificar o token de um link
if ($tokenAusLink == $gespeichertesToken) { /* inseguro */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* seguro */ }

// Verificar a palavra-passe
if (md5($eingabe) == $hash) { /* inseguro: método fraco e comparação laxa */ }
if (password_verify($eingabe, $hash)) { /* seguro */ }

// Procurar uma subcadeia
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }

O hash_equals() espera, segundo o manual, o segredo conhecido como primeiro parâmetro e o valor do utilizador como segundo, e compara em tempo constante. Como gerar tokens com segurança é descrito em Números aleatórios e tokens seguros em PHP.

Importante

O declare(strict_types=1) não torna as comparações estritas. A instrução afeta apenas a conversão de tipos em chamadas de funções e valores de retorno. As comparações laxas têm de ser substituídas por si no código.

As comparações estritas têm um reverso: falham quando os tipos não coincidem. '42' === 42 é falso. Os valores de formulários e de URLs chegam ao PHP como cadeias, os valores da base de dados, consoante o controlador e a configuração, como cadeias ou números. Quem introduz === por todo o lado sem harmonizar os tipos gera erros que, em operação, se notam como rejeição de utilizadores legítimos.

A solução é uma fronteira clara: as entradas são validadas na receção e convertidas para o tipo certo, por exemplo com filter_var($wert, FILTER_VALIDATE_INT), e depois tratadas no código apenas com declarações de tipo e comparações estritas. As declarações de tipo em parâmetros, retornos e propriedades ajudam, porque os tipos errados são detetados cedo com uma mensagem de erro, em vez de desaparecerem em silêncio numa comparação.

Verificar sistematicamente o código existente

Em projetos que cresceram ao longo do tempo encontram-se centenas de comparações laxas. Nem todas são um problema. Concentre-se nos pontos em que se decide sobre o acesso:

# Comparações laxas em linhas com termos relevantes para a segurança
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array sem comparação estrita
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"

Ferramentas de análise estática como o PHPStan ou o Psalm podem, se assim o desejar, assinalar de forma permanente as comparações laxas. Assim, os novos casos nem sequer chegam ao ramo principal. Os fundamentos sobre tipos são descritos em O sistema de tipos do PHP; como levar antes as entradas ao tipo certo é mostrado em Validar e filtrar entradas em PHP com filter_var.

O que isto significa para sistemas legados

As aplicações que ainda correm em PHP 7 estão sujeitas às regras de comparação antigas e mais laxas. Acresce um segundo problema: para o PHP 8.1 e anteriores já não há atualizações de segurança oficiais do projeto PHP, nem sequer contra o PHP2Shell. A situação é descrita em PHP 8.1 e anteriores: o que se aplica agora.

Numa migração para o PHP 8, alguns resultados de comparação mudam. Isso é, em regra, uma melhoria, mas pode quebrar código que dependia do comportamento antigo. Planeie por isso testes que cubram especificamente comparações com números, valores vazios e null.

Como ajuda concretamente a PHP SOS?

  • Percorremos o seu código à procura de comparações laxas em pontos que decidem sobre autenticação, permissões ou pagamentos.
  • Substituímo-las por comparações estritas, expressões match e funções de verificação adequadas.
  • Configuramos a análise estática de modo a que novas comparações laxas sejam detetadas no build.
  • Acompanhamos migrações para versões atuais do PHP com testes para as regras de comparação alteradas.

Uma verificação do seu software é descrita em Auditoria de segurança e teste de intrusão.

Perguntas frequentes

O que é o type juggling em PHP?

Em algumas operações, o PHP converte automaticamente valores de tipos diferentes, por exemplo uma cadeia de carateres num número. Na comparação laxa com ==, é essa conversão que decide o resultado. É cómodo, mas pode levar a comparações verdadeiras mesmo que os valores não pareçam iguais.

O PHP 8 resolveu o problema?

Em parte. Desde o PHP 8.0, um número comparado com uma cadeia não numérica é comparado como cadeia, pelo que 0 == 'a' já não é verdadeiro. Mas duas cadeias numéricas continuam a ser comparadas como números, e as comparações com true, false e null seguem regras próprias.

O declare(strict_types=1) ajuda contra comparações laxas?

Não. O strict_types controla se os tipos escalares são convertidos automaticamente em chamadas de funções e retornos. Não tem qualquer influência sobre os operadores de comparação == e !=.

Como comparo corretamente palavras-passe e tokens?

As palavras-passe verificam-se com password_verify, os tokens e assinaturas com hash_equals. Ambos comparam de forma exata e, no caso do hash_equals, em tempo constante. Uma comparação com == ou strcmp é inadequada para isso.

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