Até o momento, você se familiarizou com os conceitos básicos do desenvolvimento de plugins e temas para WordPress. Agora seria um bom momento para também considerar as implicações de segurança ao escrever código.
Nesta lição, você aprenderá a desenvolver levando a segurança em consideração.
Você aprenderá os benefícios de garantir que seu código seja seguro, quais etapas seguir para proteger seu código e onde encontrar mais informações sobre o desenvolvimento com foco em segurança.
Aviso
Esta lição foi criada como uma introdução ao desenvolvimento de plugins e temas com foco em segurança. O código usado nesta lição é muito simplificado. Você não deve usar nenhum código deste tutorial em seus plugins ou temas.
Leia a documentação completa sobre Segurança no manual do desenvolvedor do WordPress em https://developer.wordpress.org/apis/security/ para garantir que você siga os métodos e procedimentos corretos.
O que significa desenvolver levando a segurança em consideração?
Desenvolver levando a segurança em consideração é o processo de garantir que seu código não apenas funcione, mas também não introduza nenhuma vulnerabilidade de segurança.
Se o código do seu plugin ou tema tiver vulnerabilidades de segurança, ele poderá deixar qualquer site WordPress que tenha seu produto instalado exposto a possíveis ataques e fazer com que esse site seja comprometido.
Ao escrever código, é importante desenvolver uma mentalidade voltada para a segurança e pensar em como seu código poderia ser usado de forma mal-intencionada.
- Não confie em nenhum dado, seja uma entrada do usuário, dados de uma API de terceiros ou até mesmo dados do seu banco de dados. Sempre verifique se ele é válido e seguro para ser usado.
- O WordPress possui várias APIs que podem ajudar em tarefas comuns, como sanitizar entradas do usuário, validar dados e escapar saídas. Confie nessas APIs para ajudar a validar e sanitizar seus dados em vez de escrever suas próprias funções.
- Mantenha-se atualizado sobre vulnerabilidades comuns e mantenha seu código atualizado para evitá-las.
Em que etapa do processo de desenvolvimento a segurança se encaixa?
A segurança deve ser considerada em todas as etapas do processo de desenvolvimento.
Geralmente, a maior parte das vulnerabilidades de segurança encontradas ocorre na camada PHP, que é executada no servidor.
Por isso, esta lição se concentrará nas principais medidas preventivas mais comuns no seu código PHP.
Sanitização de entradas
Uma das primeiras etapas ao desenvolver é garantir que qualquer entrada do usuário seja sanitizada. Isso significa que todos os dados passados pelo usuário, como o envio de um formulário ou um parâmetro de URL, são verificados para garantir que sejam seguros para uso.
Neste exemplo de código, os campos de nome e e-mail são enviados por um formulário gerado por um plugin e, em seguida, salvos em uma tabela personalizada do banco de dados chamada form_submissions;
$name = $_POST['name'];
$email = $_POST['email'];
global $wpdb;
$table_name = $wpdb->prefix . 'form_submissions';
$sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
$result = $wpdb->query($sql);
Como você pode ver, os dados estão sendo salvos diretamente no banco de dados, sem nenhuma sanitização.
Isso significa que, se um usuário enviasse o nome John'; DROP TABLE form_submissions;--, a consulta SQL INSERT seria executada, seguida pela consulta DROP, e a tabela seria excluída do banco de dados!
O WordPress possui uma API de sanitização que pode ser usada para sanitizar dados recebidos. Você pode usar as funções sanitize_text_field e sanitize_email para sanitizar os campos de nome e e-mail antes que sejam usados na consulta.
$name = sanitize_text_field( $_POST['name'] );
$email = sanitize_email( $_POST['email'] );
global $wpdb;
$table_name = $wpdb->prefix . 'form_submissions';
$sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
$result = $wpdb->query($sql);
Observe que o código segue um princípio fundamental da sanitização de dados: fazê-la o mais cedo possível.
Para ler mais sobre as funções de sanitização disponíveis para desenvolvedores do WordPress, consulte a página Sanitização de dados na documentação para desenvolvedores do WordPress.
Validação de dados (4:06)
Validar dados é o processo de testá-los em relação a um padrão predefinido (ou padrões predefinidos), com um resultado definitivo: válido ou inválido.
Dados não confiáveis podem vir de muitas fontes: usuários, dados de APIs de terceiros e até mesmo dados do seu banco de dados podem ser considerados não confiáveis, especialmente se algo mais os tiver modificado. Até mesmo administradores do site podem cometer um erro e inserir dados incorretos ou inseguros; por isso, é importante sempre verificar seus dados.
Neste exemplo, uma função de exclusão exige que um ID numérico seja enviado a um callback admin-ajax:
add_action( 'wp_ajax_delete_form_submission', 'wp_learn_delete_form_submission' );
function wp_learn_delete_form_submission() {
if ( ! isset( $_POST['id'] ) ) {
wp_send_json_error( 'Invalid ID' );
}
$id = $_POST['id'];
global $wpdb;
$table_name = $wpdb->prefix . 'form_submissions';
$sql = "DELETE FROM $table_name WHERE id = $id";
$result = $wpdb->get_results( $sql );
return wp_send_json( array( 'result' => $result ) );
}
Aqui, o ID está sendo usado diretamente na consulta SQL, sem nenhuma validação. Novamente, isso significa que, se um usuário enviasse um ID como 1; DROP TABLE form_submissions;, a mesma consulta SQL DROP seria executada após a exclusão, e a tabela seria removida.
Para evitar isso, você pode usar a funcionalidade de conversão de tipo do PHP para garantir que o valor de $id seja sempre um valor inteiro. Isso pode ser feito adicionando (int) antes do nome da variável, assim:
$id = (int) $_POST['id'];
Observe que isso só funcionará se o primeiro caractere da string passada pelo array $_POST puder ser convertido em um inteiro; caso contrário, o valor de $id será 0. Nesse caso, é uma boa ideia atualizar o código para lidar com essa situação.
O código também segue um princípio fundamental da validação de dados: fazê-la o mais cedo possível.
if ($id === 0){
// return early with an error
return wp_send_json( array( 'result' => 'Invalid ID passed' ) );
}
Para ler mais sobre as várias formas de validar dados, consulte a seção sobre Validação de dados na documentação para desenvolvedores do WordPress.
Escape de saídas
Outro aspecto da segurança é garantir que qualquer informação enviada ao navegador seja segura, incluindo texto, HTML ou código JavaScript, ou dados do banco de dados.
Mesmo que seu código não seja responsável pela origem dos dados exibidos, ele é responsável por exibi-los com segurança.
Neste exemplo, o código busca os envios de formulário no banco de dados, percorre os envios e exibe os dados enviados em uma tela administrativa no painel do WordPress:
$submissions = wp_learn_get_form_submissions();
?>
<div class="wrap" id="wp_learn_admin">
<h1>Admin</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<?php foreach ($submissions as $submission){ ?>
<tr>
<td><?php echo $submission->name; ?></td>
<td><?php echo $submission->email; ?></td>
<td><a class="delete-submission" data-id="<?php echo $submission->id?>" style="cursor:pointer;">Delete</a></td>
</tr>
<?php } ?>
</table>
</div>
<?php
Aqui temos três partes de dados que precisam ser escapadas: os campos $submission->name e $submission->email, além de $submission->id.
<td><a class="delete-submission" data-id="<?php echo (int) $submission->id?>" style="cursor:pointer;">Delete</a></td>
Para os campos de nome e e-mail, podemos usar a função integrada de escape do WordPress esc_html(). O ID pode ser escapado convertendo-o em um inteiro, como você faria na validação de dados.
<td><?php echo esc_html( $submission->name ); ?></td>
<td><?php echo esc_html( $submission->email ); ?></td>
<td><a class="delete-submission" data-id="<?php echo (int) $submission->id?>" style="cursor:pointer;">Delete</a></td>
Observe que este código segue um princípio fundamental do escape de dados: fazer o escape dos dados o mais tarde possível.
Para ler mais sobre as várias formas de escapar saídas, consulte a seção sobre Escape de dados na documentação para desenvolvedores do WordPress.
Impedindo solicitações inválidas
Sempre que uma solicitação é feita, é importante verificar se ela é válida. Isso significa verificar se a solicitação vem de uma fonte confiável.
Por exemplo, você pode ter um shortcode que renderiza um formulário no qual os usuários podem enviar suas informações.
A função para renderizar o formulário poderia ser semelhante a esta.
add_shortcode( 'wp_learn_form_shortcode', 'wp_learn_form_shortcode' );
function wp_learn_form_shortcode() {
ob_start();
?>
<form method="post">
<input type="hidden" name="wp_learn_form" value="submit">
<div>
<label for="email">Name</label>
<input type="text" id="name" name="name" placeholder="Name">
</div>
<div>
<label for="email">Email address</label>
<input type="text" id="email" name="email" placeholder="Email address">
</div>
<div>
<input type="submit" id="submit" name="submit" value="Submit">
</div>
</form>
<?php
$form = ob_get_clean();
return $form;
}
?>
Quando o usuário envia o formulário, os dados são enviados ao servidor. Em seguida, eles são processados, sanitizados e armazenados no banco de dados.
add_action( 'wp', 'wp_learn_maybe_process_form' );
function wp_learn_maybe_process_form() {
if (!isset($_POST['wp_learn_form'])){
return;
}
$name = sanitize_text_field( $_POST['name'] );
$email = sanitize_email( $_POST['email'] );
global $wpdb;
$table_name = $wpdb->prefix . 'form_submissions';
$sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
$result = $wpdb->query($sql);
if ( 0 < $result ) {
wp_redirect( WPLEARN_SUCCESS_PAGE_SLUG );
die();
}
wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
die();
}
Como o formulário pode aparecer em qualquer página onde o shortcode seja usado, é possível que um usuário mal-intencionado tente enviar uma solicitação POST ao formulário, procurando uma vulnerabilidade no plugin ou enviando várias solicitações ao formulário.
Para evitar isso, você pode verificar se a solicitação vem de uma fonte confiável. Para fazer isso, você pode implementar algo chamado nonce, ou um número usado uma única vez.
Primeiro, no próprio formulário, você pode adicionar um campo nonce usando a função wp_nonce_field e passando uma ação nonce e um nome nonce para a função:
wp_nonce_field( 'wp_learn_form_nonce_action', 'wp_learn_form_nonce_field' );
Quando o formulário é renderizado no front-end, um campo oculto é adicionado ao formulário, usando o nome nonce como atributo id e name do campo oculto, e o nonce gerado como valor do campo. Esses dados são enviados quando o formulário é submetido.
Em seguida, na função que processa os dados do formulário, você pode verificar se o nonce é válido usando a função wp_verify_nonce e passando o campo nonce enviado via POST e a ação nonce para essa função. Se o resultado dessa verificação for falso, você poderá sair antecipadamente, impedindo a execução de qualquer código adicional.
if ( ! wp_verify_nonce( $_POST['wp_learn_form_nonce_field'], 'wp_learn_form_nonce_action' ) {
wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
die();
}
Sempre que seu código fizer uma solicitação web, seja por meio de um redirecionamento para uma nova URL, do envio de dados a um formulário via POST ou de uma solicitação AJAX, você deverá verificar se a solicitação é válida.
Para ler mais sobre como usar nonces em seus plugins, consulte a seção sobre Nonces na documentação para desenvolvedores do WordPress.
Impedindo usuários não autenticados
Dependendo da funcionalidade do seu código, é uma boa ideia restringir determinados recursos apenas a usuários com um nível específico de permissão. Por exemplo, você pode ter uma função que exclui dados do banco de dados.
function wp_learn_delete_form_submission() {
if ( ! isset( $_POST['id'] ) ) {
wp_send_json_error( 'Invalid ID' );
}
$id = (int) $_POST['id'];
global $wpdb;
$table_name = $wpdb->prefix . 'form_submissions';
$sql = "DELETE FROM $table_name WHERE id = $id";
$result = $wpdb->get_results( $sql );
return wp_send_json( array( 'result' => $result ) );
}
Embora você possa fazer o possível para impedir que alguém sem permissão execute essa função, ainda é uma boa ideia incluir verificações para esse caso.
O WordPress inclui um sistema robusto de funções e capacidades de usuário, que permite usar as funções e capacidades padrão ou criar funções e capacidades personalizadas.
Neste caso, poderia ser tão simples quanto permitir apenas usuários que tenham a capacidade de gerenciar opções do site, uma capacidade padrão incluída na função de administrador.
function wp_learn_delete_form_submission() {
if ( ! current_user_can( 'manage_options' ) ) {
return wp_send_json( array( 'result' => 'Authentication error' ) );
}
// rest of function code
}
A documentação para desenvolvedores do WordPress possui uma seção detalhada sobre funções e capacidades de usuário, que inclui uma lista das capacidades padrão e explica como criar capacidades personalizadas.
Leitura adicional
Para se preparar para desenvolver levando a segurança em consideração, leia a seção da documentação para desenvolvedores do WordPress sobre Segurança, pois ela inclui todos os exemplos desta lição, além de informações adicionais sobre práticas recomendadas de segurança, vulnerabilidades comuns e mais exemplos de código.