Você está visualizando atualmente Segurança no WordPress: Nonces

Segurança no WordPress: Nonces

  • Tempo de leitura: 15 min.

Este post é o primeiro de uma série onde vamos falar sobre segurança no desenvolvimento de temas e plugins do WordPress. Depois deste texto sobre nonces, em breve vamos falar também de higienização de entrada e saída de dados, ou seja, input and output sanitization para os amantes do idioma da rainha. Não se esqueça de assinar o canal no YouTube e a newsletter no fim da página para receber as atualizações.

O que são nonces?

O nome nonces vem de number used once, ou seja, um número usado só uma vez. No WordPress os nonces não são formados só por números nem necessariamente são usados só uma vez, mas o nome ficou.

Para que servem os nonces?

Os nonces servem para verificar a intenção do usuário. Se não existissem os nonces, por exemplo, se um usuário administrador visitasse o endereço /wp-admin/users.php?action=delete&user=<ID> ele excluiria um usuário.

Acha que ele só visitaria esse endereço se ele realmente quisesse excluir o usuário? Pense que alguém achou o seu site, descobriu o seu e-mail (coisas bem fáceis de fazer) e enviou para você um e-mail qualquer com um botão “Clique aqui”, onde o endereço do botão é exatamente aquele. Se você estiver logado no site e os nonces não existissem, adeus usuário com id=<ID>.

Os nonces não são importantes só para ações executadas através de URLs, eles também são úteis para ações feitas através de AJAX ou formulários comuns. Sem eles seria possível enganar um usuário administrador logado e fazê-lo, sem que ele realmente quisesse, criar um post ou um usuário.

Como usar os nonces?

Usar nonces envolve duas etapas:

  1. Criar o nonce e
  2. Verificar o nonce.

Cada nonce está associado a uma action, uma string que relaciona o nonce à ação sendo executada. Esta action é usada tanto na criação quanto na verificação do nonce, caso contrário qualquer nonce serviria para executar qualquer ação. Um nonce criado para editar um post não pode ser usado para excluir um usuário, por exemplo.

Criando nonces

Talvez existam mais, mas as três principais funções para criação de nonces são as seguintes:

wp_create_nonce( $action )

Esta função é a mais simples de todas. Ela só cria e retorna o valor do nonce. O código abaixo foi retirado da documentação oficial da função:

$nonce = wp_create_nonce( 'minha-action' );
?>
<a href='/endereco?_wpnonce=<?php echo esc_attr( $nonce ); ?>'>Fazer alguma coisa</a>
 

$nonce = $_REQUEST['_wpnonce']; // mesmo nome do parâmetro passado no URL.
if ( ! wp_verify_nonce( $nonce, 'minha-action' ) ) {
    // Este nonce não é válido.
    die( __( 'Security check', 'textdomain' ) ); 
} else {
    // O nonce é válido
    // Faça o que precisa aqui.
}

Também é possível nomear actions concatenando o ID do que você está manipulando:

// Em algum lugar você cria o nonce:
wp_create_nonce( 'delete_post-' . $post_id );

// Em outro você verifica:
wp_verify_nonce( $nonce, "delete_post-{$_REQUEST['post_id']}" );

wp_nonce_url( $url, $action, $nome_parametro )

Dado um URL, esta função cria um nonce e o inclui no endereço como valor de um parâmetro com nome $nome_parametro. Atenção: esta função já escapa o URL.

Este exemplo também foi adaptado da documentação oficial da função:

// Um endereço normal.
$url = 'http://localhost/?arg1=valor1&arg2=valor2';
 
// Isso vai gerar http://localhost/?arg1=valor1&amp;arg2=valor2&amp;meu_parametro_nonce=abcdef
echo wp_nonce_url( $url, 'minha_action', 'meu_parametro_nonce' );
 
// Isso vai gerar http://localhost/?arg1=valor1&arg2=valor2&meu_parametro_nonce=abcdef
echo add_query_arg( 'meu_parametro_nonce', wp_create_nonce( 'minha_action' ), $url );

wp_nonce_field( $action, $nome_campo, $referer = true, $echo = true )

Esta função cria um campo hidden com $nome_campo como atributo name e o nonce como valor. Quando $referer é true (o padrão), também é criado um campo para registrar o endereço atual, que pode ou não ser usado para verificar se o usuário realmente veio da página certa. Com $echo como true (também é o padrão), a função já imprime os campos.

Chamando a função desta forma:

<form method="post">
   <?php wp_nonce_field( 'nome_da_action', 'nome_do_campo_nonce' ); ?>
   <!-- seus outros campos aqui ... -->
</form>

O resultado final será:

<form method="post">
   <input type="hidden" id="nome_do_campo_nonce" name="nome_do_campo_nonce" value="abcdef" />
   <input type="hidden" name="_wp_http_referer" value="/" />
   <!-- seus outros campos aqui ... -->
</form>

Você pode saber mais conferindo a documentação oficial desta função.

Validando nonces

As três principais funções para validação dos nonces criados são as seguintes:

wp_verify_nonce( $nonce, $action );

Essa função é a mais simples de todas. Dado um nonce e uma action, ela verifica se o nonce é válido e retorna 1 ou 2 dependendo de quando o nonce foi gerado ou false caso o nonce não exista. Na maioria dos casos não importa muito se o nonce foi gerado há 12 ou há 24 horas, então o uso mais comum da função é uma verificação por booleano mesmo.

Lembre-se: A função espera O VALOR do nonce. Onde o valor do nonce está depende da forma como você escreveu seu código.

Se você usou a função wp_nonce_url() passando como terceiro parâmetro a string meu_parametro, o valor estará em $_GET['meu_parametro'].

// Se a criação do nonce foi assim:
<a href="<?php echo wp_nonce_url( $url, 'minha_action', 'meu_parametro' ); ?>">meu link</a>

// A verificação será assim:
if ( ! wp_verify_nonce( $_GET['meu_parametro'], 'minha_action' ) ) {
    die( __( 'Verificação de segurança', 'textdomain' ) ); 
}

Se você usou wp_nonce_field() passando como segundo parâmetro a string campo_meu_form, o valor estará em $_POST['campo_meu_form'].

// Se a criação do nonce foi assim:
<form method="post">
   <?php wp_nonce_field( 'nome_da_action', 'campo_meu_form' ); ?>
   <!-- seus outros campos aqui ... -->
</form>

// A verificação será assim:
if ( ! wp_verify_nonce( $_POST['campo_meu_form'], 'nome_da_action' ) ) {
    die( __( 'Security check', 'textdomain' ) ); 
}

Você pode saber mais conferindo a documentação oficial desta função.

check_ajax_referer( $action, $query_arg, $die = true )

Esta função e a próxima são a wp_verify_nonce() com superpoderes. A check_ajax_referer() deve ser usada para verificar nonces usados em solicitações AJAX. Diferente da função anterior, esta aqui vai tentar encontrar o valor do nonce no array $_REQUEST (junção de $_GET e $_POST) com o que você passar como valor de $query_arg, ou seja, ela vai tentar usar o valor presente em $_REQUEST[ $query_arg ].

Mais uma vez, esse exemplo de código foi adaptado da documentação oficial:

// Criação do nonce:
$ajax_nonce = wp_create_nonce( 'nome-da-sua-action' );
?>
<script type="text/javascript">
jQuery(document).ready(function($){
    var data = {
        action: 'sua_action_ajax',
        nonce: '<?php echo $ajax_nonce; ?>',
        parametro: 'Hello World!'
    };
    $.post(ajaxurl, data, function(resposta) {
        alert("Resposta: " + resposta);
    });
});
</script>

// Validação do nonce no callback AJAX:
function seu_callback_ajax() {
    check_ajax_referer( 'nome-da-sua-action', 'nonce' );
    echo sanitize_text_field( $_POST['parametro'] );
    die;
}
add_action( 'wp_ajax_sua_action_ajax', 'seu_callback_ajax' );
add_action( 'wp_ajax_nopriv_sua_action_ajax', 'seu_callback_ajax' );

muito tempo, eu escrevi um post completo sobre AJAX no WordPress, mas acho que já está na hora de falarmos sobre isso no canal do YouTube também.

check_admin_referer( $action, $query_arg )

Esta função deve ser usada ao trabalhar com nonces no Painel do WordPress. Assim como a função anterior, esta aqui também tenta encontrar o valor do nonce em $_REQUEST[ $query_arg ]. Além disso, ela verifica se o usuário realmente veio de uma página de administração (usando o campo _wp_http_referer se ele existir ou o valor presente em $_SERVER['HTTP_REFERER']).

Aqui vai mais um exemplo de código adaptado da documentação oficial:

// Em uma página de opções de um plugin você pode usar
<form method="post">
   <?php wp_nonce_field( 'nome_da_action','nome_do_campo' ); ?>
   <!-- outros campos aqui ... -->
</form>

// E no submit do formulário você pode usar
if ( ! empty( $_POST ) && check_admin_referer( 'nome_da_action', 'nome_da_action' ) ) {
   // e então fazer alguma coisa com os dados aqui.
}

Detalhes importantes ao usar nonces

Pare de executar se o nonce não for válido

Se você usar a função wp_verify_nonce() para validar um nonce, não se esqueça de chamar a função die() caso ele seja inválido. As funções check_ajax_referer() e check_admin_referer() já fazem isso por você.

function submit_meu_form() {
   if ( ! wp_verify_nonce( $_REQUEST['meu_parametro'], 'minha_action' ) ) {
      die();
   }
   // faça as coisas que precisa aqui.
}

O código do WordPress segue um padrão de early return, ou seja, ele sempre tenta resolver os casos de erro antes de realmente executar a função. Isso aumenta muito a legibilidade do código e também evita um zilhão de ifs aninhados.

// Use isto:
function minha_funcao( $parametro ) {
   if ( empty( $parametro ) ) {
      return false;
   }
   if ( ! $condicao ) {
      return false;
   }
   // faça as coisas aqui.
}

// Ao invés disto:
function minha_funcao( $parametro ) {
   if ( ! empty( $parametro ) ) {
      if ( $condicao ) {
         // faça as coisas aqui.
      }
   }
}

O nome de campo padrão para nonces é _wpnonce

Ao criar e validar nonces, se o desenvolvedor não passar nenhum nome de campo, o WordPress usará _wpnonce como padrão. Este será o nome do parâmetro, e não da action. O nome da action é sempre obrigatório.

O nome da action e o campo que armazena o nonce são coisas diferentes

Comentei isso algumas vezes durante o texto, mas essa é provavelmente a parte mais complicada. Entenda que você está criando um campo em algum lugar com o valor do nonce. Este valor está associado a uma action, mas o nome do campo em si não tem nada a ver com isso.


Se você achou esse post útil, não esqueça de compartilhar com os seus amigos. Para receber notícias sobre próximos posts, não esqueça de assinar o canal do YouTube e assinar a newsletter!

Felipe Elia

Associate Director of Platform Engineering na 10up, WordPress Core Contributor, Global Polyglots Mentor na comunidade internacional do WordPress e Locale Manager na comunidade WordPress Brasil.