Explorando vulnerabilidade em operadores lógicos “isset(…) && !AlgumaCoisa”

Veja esse código:

 

if ( isset( $_POST['cartflows-action-nonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['cartflows-action-nonce'] ) ), 'cartflows-action-nonce' ) ) {
return;
}

Esse trecho de código foi retirado de um plugin do WordPress com mais de 100 mil downloads ativos, o Cartflows ( https://wordpress.org/plugins/cartflows/ ).

Essa condição é uma validação de segurança, para evitar CSRF, serve para saber que você é você mesmo, o wp_verify_nonce verifica se você veio realmente da página anterior e não uma requisição vinda de qualquer lugar, ela guarda uma “sequência”.

Mas essa validação está errada, e esse item de segurança pode facilmente ser ultrapassado. Vale lembrar que essa não é uma falha tão grave, mas se juntar com outros erros pode ser fatal.

Esse vídeo na sequencia varre o código de todos os plugins para validar quem possue esse mesmo tipo de problema, e encontramos um total de 420 plugins possivelmente com o mesmo problema.

 

Vamos entender o que se passa:

 

Acho que todos já viram uma tabela de operadores lógicos como essa:
https://www.php.net/manual/pt_BR/language.operators.logical.php

Sempre achamos que sabemos, somos experientes e nunca erramos, então vou fazer uma pergunta e me responda de cabeça. 

Como avaliamos essa variável, $_GET[‘test’]? 

Devemos validar se ela existe e se o índice possui a letra a, caso contrário, pare a execução, lembrando que não pode quebrar ( Erro de Undefined ) caso não usemos ela.

 

Vou te dar algumas opções:

 

I-if( strpos($_GET['test'],'a') === false ) die();

II-if( isset($_GET['test']) && strpos($_GET['test'],'a') === false ) die();

III-if( ! isset($_GET['test']) && strpos($_GET['test'],'a') === false ) die();

IV-if( ! empty($_GET['test']) && strpos($_GET['test'],'a') === false ) die();

 

Resposta:

I – gera erro de Undefined.

II – se não usar o parâmetro test ele passa;

III – use ?test=bbb e ele vai passar

IV – se não usar o parâmetro test ele passa;

Resultado, somente a primeira vai funcionar, mas vai gerar Undefined, todas as outras não agirá da forma que precisamos, então nenhum resultado é o que esperamos.

 

Mas qual o motivo para isso acontecer tecnicamente e na cabeça dos desenvolvedores?

 

  • _GET, _POST e _REQUEST são arrays e como todo array precisa passar por uma checagem do índice ou ele sempre vai gerar Undefined caso não exista, ou seja precisa de um isset ou empty antes.
  • Nesse caso, como a “verdade do if” é para o sistema não continuar, o código tem que checar todas as validações separadamente ( com || ). Pensando de forma simples, seria como estivesse um if para cada condição.
  • Muita das vezes isso ocorre porque indentificamos um Undefined depois de criar a condição e depois disso e a primeira coisa que vem a cabeça é validar se ele existe e adicionar com a primeira condição, mas vimos que não é bem assim.

 

Então uma das respostas correta seria. 

if(! isset($_GET['test']) || strpos($_GET['test'],'a') === false ) die();

 

Algumas dicas para isso não acontecer:

  • Atenção quando quiser usar return early, ele pode confundir as condições e qualidade pode se transformar em falha de segurança.
  • Se estiver na dúvida para um return early, por segurança, crie um if para cada condição ( Pessoal que gosta de padrão não vai gostar disso ).
  • Verifique todos os _POST, _GET, _REQUEST antes, caso não exista preencha variável com false. Frameworks já fazem isso.
  • Teste não enviando parâmetros.
  • Na maioria das vezes o empty vai servir, ele já faz o serviço do isset.

 

Obs:

O PHP 8 existe um método só para isso, str_contains.

if (str_contains(‘How are you’, ‘are’)) { echo ‘true’; }

References:

25 WordPress plugins vulnerable to CSRF attacks.

https://www.php.net/manual/en/index.php

Tahnks for revision by Thiago Dieb and Leonn Leite

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *