Esteja atento: funções PHP e WordPress que podem tornar seu site inseguro
Publicados: 2022-03-10A segurança de um site WordPress (ou qualquer outro) é um problema multifacetado. O passo mais importante que alguém pode tomar para garantir que um site seja seguro é ter em mente que nenhum processo ou método é suficiente para garantir que nada de ruim aconteça. Mas há coisas que você pode fazer para ajudar. Uma delas é estar atento, no código que você escreve e no código de outros que você implanta, para funções que podem ter consequências negativas. Neste artigo, abordaremos exatamente essas: funções que um desenvolvedor do WordPress deve pensar claramente antes de usar.
O próprio WordPress fornece uma biblioteca considerável de funções, algumas das quais podem ser perigosas. Além disso, existem muitas funções PHP que um desenvolvedor WordPress (PHP) usará com alguma frequência que pode ser perigosa quando usada.
Todas essas funções têm usos legítimos e seguros, mas também são funções que podem facilitar o abuso de seu código para fins ruins. Abordaremos as coisas com maior probabilidade de ser a causa de uma vulnerabilidade de segurança e por que você deve estar atento a elas. Vamos primeiro executar as funções PHP em seu código que os maus atores podem usar para o mal e, em seguida, falar sobre as funções PHP específicas do WordPress que também podem causar dores de cabeça.
Funções PHP a serem observadas
Como dissemos, o PHP contém muitas funções fáceis de usar. Algumas dessas funções são notórias pela facilidade com que as pessoas podem fazer coisas ruins com elas. Todas essas funções têm um uso adequado, mas tenha cuidado sobre como você as usa e como outros códigos que você insere em um projeto funcionam.
Vamos supor que você já tenha aspas mágicas e registradores globais desativados se sua versão do PHP as suportar. Eles estavam desativados por padrão no PHP 5 e superior, mas podiam ser ativados para versões abaixo de 5.4. Poucos hosts permitiram isso, mas não acho que um artigo de segurança PHP seja realmente completo sem incluí-los.
Ou uma menção de que o PHP 5.6 é a última versão 5.x ainda com suporte de segurança contínuo. Você deveria estar nele, pelo menos. E você deve ter um plano para mudar para o PHP 7 antes que o suporte 5.6 termine no final de 2018.
Depois disso, você terá apenas algumas funções arriscadas para lidar. Começando com…
extract
é uma má notícia, especialmente em $_POST
ou similar
Felizmente, o uso da função de extract
do PHP caiu em desuso. O núcleo de seu uso era que você poderia executá-lo em uma matriz de dados, e seus pares chave-valor se tornariam variáveis vivas em seu código. assim
$arr = array( 'red' => 5 ); extract($arr); echo $red; // 5
trabalharia. Isso é legal, mas também é muito perigoso se você estiver extraindo $_GET
, $_POST
, etc. Nesses casos, você está essencialmente recriando o problema register_globals
você mesmo: um invasor externo pode alterar seus valores de variável facilmente adicionando uma string de consulta ou campo de formulário. A melhor solução é simples: não use extract
.
Se você mesmo criar a matriz extraída, não é especificamente um problema de segurança usar extract
e, em alguns casos, pode ser útil. Mas todos os usos dele têm o problema de confundir os futuros leitores. Criar um array e chamar extract
é mais confuso do que apenas declarar suas variáveis. Então, eu encorajo você a fazer declarações manuais, a menos que seja completamente inviável.
Se você precisar usar extract
na entrada do usuário, sempre use o sinalizador EXTR_SKIP
. Isso ainda tem o problema de confusão, mas elimina a possibilidade de que seus valores predefinidos possam ser alterados por um estranho mal-intencionado por meio de uma simples string de consulta ou modificação de formulário da web. (Porque ele “pula” valores já definidos.)
“ eval
é mau” porque o código arbitrário é assustador
eval
em PHP e praticamente qualquer outra linguagem que o carregue está sempre no topo de listas como esta. E por um bom motivo. A documentação oficial do PHP no PHP.net diz francamente:
CUIDADO : A construção da linguagem eval() é muito perigosa porque permite a execução de código PHP arbitrário. Seu uso, portanto, é desencorajado. Se você verificou cuidadosamente que não há outra opção a não ser usar essa construção, preste atenção especial para não passar nenhum dado fornecido pelo usuário sem validá-lo adequadamente antes.
eval
permite que qualquer string arbitrária em seu programa seja executada como se fosse um código PHP. Isso significa que é útil para “meta-programação” onde você está construindo um programa que pode construir um programa. Também é muito perigoso porque se você permitir que fontes arbitrárias (como uma caixa de texto em uma página da web) sejam passadas imediatamente para seu avaliador de string eval
, você de repente tornou trivialmente fácil para um invasor malicioso fazer praticamente qualquer coisa que o PHP pode fazer no seu servidor. Isso inclui, obviamente, conectar-se ao banco de dados, excluir arquivos e praticamente qualquer outra coisa que alguém possa fazer quando fizer SSH em uma máquina. Isto é mau.
Se você precisar usar eval
em seu programa, você deve se esforçar para garantir que não permita que entradas arbitrárias do usuário sejam passadas para ele. E se você deve permitir que usuários arbitrários acessem as entradas para eval
, limite o que eles podem fazer por meio de uma lista negra de comandos que você não os deixa executar, ou (melhor, mas muito mais difícil de implementar) uma lista branca que é apenas os comandos que você considere seguro. Melhor ainda, permita apenas um pequeno número de alterações de parâmetros específicos, como apenas números inteiros validados.
Mas sempre tenha em mente esta linha de Rasmus Lerdorf, o fundador do PHP:
"Se `eval()` for a resposta, você quase certamente está fazendo a pergunta errada."
Variações sobre eval
Existem, além do bem conhecido eval
, uma variedade de outras maneiras pelas quais o PHP tem historicamente suportado strings avaliadas como código. Os dois que são mais relevantes para os desenvolvedores do WordPress são preg_replace
com o modificador /e
e create_function
. Cada um funciona um pouco diferente, e preg_replace
após o PHP 5.5.0 não funciona. (PHP 5.5 e abaixo não estão mais recebendo atualizações de segurança oficiais e, portanto, é melhor não serem usados.)
/e
Modificadores em Regex também são “maus”
Se você estiver executando o PHP 5.4.x ou inferior, fique de olho nas chamadas PHP preg_replace
que terminam com um e. Isso pode parecer:
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
O PHP oferece várias maneiras de “fechar” em sua expressão regular, mas a coisa principal que você quer estar atento é “e” como o último caractere para o primeiro argumento de preg_replace
. Se estiver lá, você está realmente passando todo o segmento encontrado como o argumento para o seu PHP embutido e, em seguida, eval
-o. Isso tem exatamente os mesmos problemas que eval
se você estiver permitindo que a entrada do usuário entre em sua função. O código de exemplo pode ser substituído usando preg_replace_callback
. A vantagem disso é que você escreveu sua função, então é mais difícil para um invasor alterar o que é avaliado. Então você escreveria o acima como:
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
Deixar a entrada do usuário create_function
s também é ruim…
O PHP também tem a função create_function
. Isso agora está obsoleto no PHP 7.2, mas era muito semelhante ao eval
e tinha as mesmas desvantagens básicas: ele permite que uma string (o segundo argumento) seja transformada em PHP executável. E tinha os mesmos riscos: é muito fácil para você acidentalmente dar a um cracker inteligente a capacidade de fazer qualquer coisa em seu servidor se você não for cuidadoso.
Este, se você estiver em PHP acima de 5.3, é ainda mais fácil de corrigir do que preg_replace
. Você pode simplesmente criar sua própria função anônima sem usar strings como intermediários. Isso é mais seguro e mais legível, pelo menos aos meus olhos.
assert
Também é eval
-Like
assert
não é uma função que vejo muitos desenvolvedores PHP usarem, no WordPress ou fora dele. Sua intenção é para afirmações muito leves sobre pré-condições para seu código. Mas também suporta uma operação do tipo eval
. Por essa razão, você deve ser tão cauteloso quanto a eval
. Asserções baseadas em strings (o coração do motivo pelo qual isso é ruim) também estão obsoletas no PHP 7.2, o que significa que deve ser menos preocupante no futuro.
Incluir arquivos variáveis é uma maneira de possivelmente permitir a execução descontrolada do PHP
Nós exploramos muito bem porque eval
é ruim, mas algo como include
ou require($filename'.php')
pode, especialmente onde $filename
é definido a partir de um valor controlável pelo usuário, ser igualmente uma má notícia. A razão é sutilmente diferente de eval
. Nomes de arquivos variáveis são frequentemente usados para coisas como roteamento simples de URL para arquivo em aplicativos PHP não WordPress. Mas você pode vê-los usados no WordPress também.
O cerne do problema é que, quando você include
ou require
(ou include_once
ou require_once
), está fazendo seu script executar o arquivo incluído. Está, mais ou menos, eval
conceitualmente esse arquivo, embora raramente pensemos nisso dessa maneira.
Se você escreveu todos os arquivos que sua variável pode include
e considerou o que aconteceria quando isso acontecesse, tudo bem. Mas é uma má notícia se você não considerou o que a include
de password.php
ou wp-config.php
fará. Também é uma má notícia se alguém puder adicionar um arquivo malicioso e depois executar sua include
(embora nesse ponto você provavelmente tenha problemas maiores).
A solução para isso não é muito difícil: o código rígido inclui quando você pode. Quando não puder, tenha uma lista branca (melhor) ou uma lista negra de arquivos que podem ser incluídos. Se os arquivos na lista de permissões (ou seja: você auditou o que ele faz quando adicionado), você saberá que está seguro. Se não estiver na sua lista de permissões, seu script não o incluirá. Com esse simples ajuste, você está bastante seguro. Uma lista de permissões seria algo assim:
$white_list = ['db.php', filter.php', 'condense.php'] If (in_array($white_list, $file_to_include)) { include($file_to_include); }
Nunca passe a entrada do usuário para shell_exec
e variantes
Este é um grande problema. shell_exec
, system
, exec
e backticks em PHP permitem que o código que você está executando converse com o shell subjacente (geralmente Unix). Isso é semelhante ao que torna o eval
perigoso, mas dobrado. Dobrado porque se você deixar a entrada do usuário passar por aqui descuidadamente, o invasor nem estará limitado pelas restrições do PHP.
A capacidade de executar comandos de shell do PHP pode ser muito útil como desenvolvedor. Mas se você deixar a entrada de um usuário lá, eles têm a capacidade de ganhar sub-repticiamente muitos poderes perigosos. Então, eu diria que a entrada do usuário nunca deve ser passada para funções do tipo shell_exec
.
A melhor maneira que eu poderia pensar para lidar com esse tipo de situação, se você estivesse tentado a implementá-lo, seria fornecer aos usuários acesso a um pequeno conjunto de comandos de shell predefinidos e seguros. Isso pode ser possível garantir. Mas, mesmo assim, aconselho-o a ter muito cuidado.
Assista para unserialize
; Ele executa o código automaticamente
A ação principal de chamar serialize
em um objeto PHP vivo, armazenar esses dados em algum lugar e, em seguida, usar esse valor armazenado posteriormente para unserialize
esse objeto de volta à vida é legal. Também é razoavelmente comum, mas pode ser arriscado. Por que arriscado? Se a entrada para essa chamada unserialize
não for completamente segura (digamos que seja armazenada como um cookie em vez de em seu banco de dados…), um invasor pode alterar o estado interno do seu objeto de uma maneira que faça a chamada unserialize
fazer algo ruim.
Essa exploração é mais esotérica e menos provável de ser eval
do que um problema de avaliação. Mas se você estiver usando um cookie como mecanismo de armazenamento para dados serializados, não use serialize
para esses dados. Use algo como json_encode
e json_decode
. Com esses dois, o PHP nunca executará automaticamente nenhum código.
A principal vulnerabilidade aqui é que quando o PHP unserialize
uma string em uma classe, ele chama o método magic __wakeup
naquela classe. Se a entrada de usuário não validada puder ser não unserialized
, algo como uma chamada de banco de dados ou exclusão de arquivo no método __wakeup
pode ser apontado para um local perigoso ou indesejável.
unserialize
é distinto das vulnerabilidades eval
porque requer o uso de métodos mágicos em objetos. Em vez de criar seu próprio código, o invasor é forçado a abusar de seus métodos já escritos em um objeto. Ambos os métodos mágicos __destruct
e __toString
em objetos também são arriscados, como explica esta página do OWASP Wiki.
Em geral, você está bem se não usar os métodos __wakeup
, __destruct
ou __toString
em suas classes. Mas como mais tarde você pode ver alguém adicioná-los a uma classe, é uma boa ideia nunca permitir que um usuário se aproxime de suas chamadas para serialize
e unserialize
e passar todos os dados públicos para esse tipo de uso por meio de algo como JSON ( json_encode
e json_decode
) onde há nunca é a execução automática de código.
Buscar URLs com file_get_contents
é arriscado
Uma prática comum ao escrever rapidamente algum código PHP que precisa chamar uma URL externa é acessar file_get_contents
. É rápido, é fácil, mas não é super seguro.
O problema com file_get_contents
é sutil, mas é bastante comum que os hosts às vezes configurem o PHP para não permitir que você acesse URLs externas. Isso é para protegê-lo.
O problema aqui é que file_get_contents
buscará páginas remotas para você. Mas quando faz isso, não verifica a integridade da conexão do protocolo HTTPS. O que isso significa é que seu script pode ser vítima de um ataque man-in-the-middle que permitiria que um invasor colocasse praticamente o que quisesse no resultado da página file_get_contents
.
Este é um ataque mais esotérico. Mas para me proteger contra isso quando escrevo PHP moderno (baseado no Composer), quase sempre uso o Guzzle para encapsular a API cURL mais segura. No WordPress, é ainda mais fácil: use wp_remote_get
. Ele funciona de forma muito mais consistente do que file_get_contents
, e será o padrão para verificar as conexões SSL. (Você pode desativá-lo, mas, hum, talvez não…) Melhor ainda, mas um pouco mais chato de se falar, são wp_safe_remote_get
, etc. Eles funcionam de forma idêntica às funções sem safe_
em seu nome, mas farão certifique-se de que redirecionamentos e encaminhamentos inseguros não aconteçam ao longo do caminho.
Não confie cegamente na validação de URL de filter_var
Então este é um pouco obscuro, então parabéns a Chris Weigman por explicá-lo nesta palestra do WordCamp. Em geral, filter_var
do PHP é uma ótima maneira de validar ou sanitizar dados. (Embora, não fique confuso sobre o que você está tentando fazer…)
O problema aqui é bem específico: se você está tentando usá-lo para garantir que uma URL seja segura, filter_var
não valida o protocolo. Isso geralmente não é um problema, mas se você estiver passando a entrada do usuário para esse método para validação e estiver usando FILTER_VALIDATE_URL
, algo como javascript://comment%0aalert(1)
passará. Ou seja, isso pode ser um vetor muito bom para um ataque XSS básico em um lugar que você não esperava.
Para validar uma URL, a função esc_url
do WordPress terá um impacto semelhante, mas apenas permite a passagem de protocolos permitidos. javascript
não está na lista padrão, então isso o manteria seguro. No entanto, ao contrário de filter_var
, ele retornará uma string vazia (não um false) para um protocolo não permitido que é passado para ele.
Funções específicas do WordPress para ficar de olho
Além das funções potencialmente vulneráveis do PHP principal, existem algumas funções específicas do WordPress que podem ser um pouco complicadas. Algumas delas são muito semelhantes à variedade de funções perigosas listadas acima, outras um pouco diferentes.
WordPress Unserializa Com maybe_unserialize
Este é provavelmente óbvio se você ler o acima. No WordPress existe uma função chamada maybe_unserialize
e, como você deve imaginar, ela desserializa o que é passado para ela, se necessário.
Não há nenhuma nova vulnerabilidade que isso introduz, o problema é simplesmente que, assim como a função de unserialize
do núcleo, esta pode fazer com que um objeto vulnerável seja explorado quando não é serializado.
is_admin
não responde se um usuário for um administrador!
Este é bem simples, mas a função é ambígua no nome e, portanto, é propensa a confundir as pessoas ou ser perdida se você estiver com pressa. Você deve sempre verificar se um usuário que está tentando realizar uma ação no WordPress tem os direitos e privilégios necessários para realizar essa ação. Para isso, você deve usar a função current_user_can
.
Mas você pode, erroneamente, pensar que is_admin
informará se o usuário atual é uma conta de nível de administrador e, portanto, deve ser capaz de definir uma opção que seu plug-in usa. Isto é um erro. O que o is_admin
faz no WordPress é informar se o carregamento da página atual está no lado da administração do site (vs. no lado frontal). Portanto, todos os usuários que podem acessar uma página de administração (como o “Painel”) poderão passar nessa verificação. Contanto que você lembre que is_admin
é sobre o tipo de página, não o usuário atual, você ficará bem.
add_query_arg()
não limpa URLs
Isso não é tão comum, mas houve uma grande onda de atualizações no ecossistema WordPress há alguns anos porque a documentação pública sobre essas funções não estava correta. A questão central é que a função add_query_arg
(e seu inverso remove_query_arg
) não limpa automaticamente o URL do site se um URL não foi passado para ele, e as pessoas pensaram que sim. Muitos plugins foram enganados pelo Codex e estavam usando isso de forma insegura como resultado.
A coisa principal que eles tinham que fazer diferente: higienizar o resultado de uma chamada para essa função antes de usá-la. Se você fizer isso, estará realmente a salvo dos ataques XSS que pensou que fossem. Então isso se parece com algo como:
echo esc_url( add_query_arg( 'foo', 'bar' ) );
$wpdb->query()
está aberto ao ataque de injeção de SQL
Se você conhece injeção de SQL, isso pode parecer bobo, até mesmo supérfluo de listar. Porque o fato é que de qualquer maneira você acessa um banco de dados (digamos, usando os drivers de banco de dados mysqli
ou PDO do PHP) para criar consultas de banco de dados que permitem ataques de injeção de SQL.
A razão pela qual estou chamando especificamente $wpdb->query
é que alguns outros métodos (como insert
, delete
, etc.) em $wpdb
cuidam dos ataques de injeção para você. Além disso, se você está acostumado a fazer consultas básicas de banco de dados do WordPress com WP_Query
ou similar, não precisará considerar a injeção de SQL. É por isso que estou chamando: para ter certeza de que você entende que um ataque de injeção no banco de dados é possível quando você tenta usar $wpdb
pela primeira vez para fazer sua própria consulta.
O que fazer? Use $wpdb->prepare()
e então $wpdb->query()
. Você também vai querer ter certeza de se preparar antes de outros métodos “getting” de $wpdb
como, get_row()
e get_var()
. Caso contrário, o Bobby Tables pode te pegar.
esc_sql
não protege você da injeção de SQL
Para a maioria dos desenvolvedores do WordPress, eu diria que esc_sql
não se registra com nenhum significado, mas deveria. Como acabamos de mencionar, você deve usar wpdb->prepare()
antes de fazer qualquer consulta ao banco de dados. Isso vai mantê-lo seguro. Mas é tentador e compreensível que um desenvolvedor possa esc_sql
. E eles podem esperar que seria seguro.
O problema é que o esc_sql
não possui proteções tão robustas contra injeção de SQL. É realmente uma versão glorificada da função add_slashes
do PHP, que você foi desencorajado de usar para proteger seu banco de dados por anos.
Há mais a fazer, mas este é um grande começo
Há muito mais na segurança do que apenas estar atento a funções em seu código que os invasores podem usar indevidamente. Não discutimos, por exemplo, com muita profundidade a necessidade de validar e higienizar todos os dados que você recebe dos usuários e escapar deles antes de colocá-los em uma página da web (embora eu tenha publicado recentemente um artigo sobre esse tópico, “Protegendo seu WordPress Site contra um ataque de script entre sites”). Mas você pode e deve usar isso como parte de uma estratégia de segurança mais ampla.
Manter esta lista de funções fáceis de usar indevidamente ao seu lado enquanto você faz uma inspeção final antes de implantar um novo plugin no WordPress é uma ótima ideia. Mas você também vai querer ter certeza de que confia na segurança de sua hospedagem, garantir que seus usuários tenham boas senhas e muito mais.
O principal a ser lembrado sobre segurança é que seu elo mais fraco é o que importa. Boas práticas em uma área reforçam possíveis pontos fracos em outra, mas nunca podem corrigi-lo totalmente. Mas fique atento a essas funções e você terá uma vantagem sobre a maioria.