Os htmlspecialchars e mysql_real_escape_string mantêm meu código PHP protegido contra injeção?

Respostas:

241

Quando se trata de consultas de banco de dados, sempre tente usar consultas parametrizadas preparadas. As bibliotecas mysqlie PDOsuportam isso. Isso é infinitamente mais seguro do que usar funções de escape, como mysql_real_escape_string.

Sim, mysql_real_escape_stringé efetivamente apenas uma função de escape de string. Não é uma bala mágica. Tudo o que fará é escapar caracteres perigosos para que possam ser usados ​​com segurança em uma única string de consulta. No entanto, se você não higienizar suas entradas de antemão, ficará vulnerável a certos vetores de ataque.

Imagine o seguinte SQL:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

Você deve ser capaz de ver que isso é vulnerável a exploração.
Imagine que o idparâmetro contenha o vetor de ataque comum:

1 OR 1=1

Não há caracteres arriscados para codificar, então ele passará direto pelo filtro de escape. Deixando-nos:

SELECT fields FROM table WHERE id= 1 OR 1=1

Que é um adorável vetor de injeção de SQL e permitiria ao invasor retornar todas as linhas. Ou

1 or is_admin=1 order by id limit 1

que produz

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

O que permite que o invasor retorne os detalhes do primeiro administrador neste exemplo completamente fictício.

Embora essas funções sejam úteis, elas devem ser usadas com cuidado. Você precisa garantir que todas as entradas da web sejam validadas em algum grau. Neste caso, vemos que podemos ser explorados porque não verificamos se uma variável que estávamos usando como um número era realmente numérica. Em PHP, você deve usar amplamente um conjunto de funções para verificar se as entradas são inteiros, flutuantes, alfanuméricos, etc. Mas quando se trata de SQL, preste atenção ao valor da instrução preparada. O código acima teria sido seguro se fosse uma instrução preparada, pois as funções do banco de dados saberiam que 1 OR 1=1não é um literal válido.

Quanto a htmlspecialchars(). Esse é um campo minado por si só.

Há um problema real no PHP em que ele tem uma seleção inteira de diferentes funções de escape relacionadas ao html, e nenhuma orientação clara sobre exatamente quais funções fazem o quê.

Em primeiro lugar, se você estiver dentro de uma tag HTML, você está realmente com problemas. Olhe para

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

Já estamos dentro de uma tag HTML, então não precisamos <ou> fazer nada perigoso. Nosso vetor de ataque poderia ser apenasjavascript:alert(document.cookie)

Agora o HTML resultante parece

<img src= "javascript:alert(document.cookie)" />

O ataque é direto.

Fica pior. Por quê? porque htmlspecialchars(quando chamado dessa forma) codifica apenas aspas duplas e não simples. Então, se tivéssemos

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

Nosso atacante malvado agora pode injetar parâmetros totalmente novos

pic.png' onclick='location.href=xxx' onmouseover='...

nos dá

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

Nesses casos, não há fórmula mágica, você apenas precisa santificar a entrada você mesmo. Se você tentar filtrar caracteres ruins, certamente falhará. Use uma abordagem de lista de permissões e deixe passar apenas os caracteres que são bons. Veja a folha de dicas do XSS para exemplos de como diversos vetores podem ser

Mesmo se você usar htmlspecialchars($string)fora das tags HTML, ainda estará vulnerável a vetores de ataque de conjuntos de caracteres multibyte.

O mais eficaz que você pode ser é usar uma combinação de mb_convert_encoding e htmlentities como segue.

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

Mesmo isso deixa o IE6 vulnerável, devido à maneira como ele lida com UTF. No entanto, você pode recorrer a uma codificação mais limitada, como ISO-8859-1, até que o uso do IE6 diminua.

Para um estudo mais aprofundado dos problemas multibyte, consulte https://stackoverflow.com/a/12118602/1820

Cheekysoft
fonte
24
A única coisa que faltou aqui, é que o primeiro exemplo para a consulta do banco de dados ... um simples intval () resolveria a injeção. Sempre use intval () no lugar de mysqlescape ... () quando precisar de um número e não de uma string.
Robert K,
11
e lembre-se de que usar consultas parametrizadas permitirá que você sempre tenha os dados tratados como dados e não como código. Use uma biblioteca como PDO e use consultas parametrizadas sempre que possível.
Cheekysoft de
9
Duas observações: 1. No primeiro exemplo, você estaria seguro se também colocasse aspas em torno do parâmetro, como $result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";2. No segundo caso (atributo contendo URL), não há uso para htmlspecialchars; nesses casos, você deve codificar a entrada usando um esquema de codificação de URL, por exemplo, usando rawurlencode. Dessa forma, um usuário não pode inserir javascript:et al.
Marcel Korpel
7
“Htmlspecialchars codifica apenas aspas duplas e não simples”: isso não é verdade, depende dos sinalizadores definidos, consulte seus parâmetros .
Marcel Korpel
2
Deve estar em negrito: Take a whitelist approach and only let through the chars which are good.Uma lista negra sempre deixará escapar algo. +1
Jo Smo
10

Além da excelente resposta da Cheekysoft:

  • Sim, eles irão mantê-lo seguro, mas apenas se forem usados ​​de forma absolutamente correta. Use-os incorretamente e você ainda estará vulnerável e poderá ter outros problemas (por exemplo, corrupção de dados)
  • Em vez disso, use consultas parametrizadas (conforme indicado acima). Você pode usá-los através, por exemplo, do PDO ou de um invólucro como o PEAR DB
  • Certifique-se de que magic_quotes_gpc e magic_quotes_runtime estejam desligados o tempo todo, e nunca sejam ativados acidentalmente, nem mesmo brevemente. Estas são uma tentativa inicial e profundamente equivocada por desenvolvedores de PHP de prevenir problemas de segurança (que destrói dados)

Não existe realmente uma solução mágica para prevenir a injeção de HTML (por exemplo, cross site scripting), mas você pode conseguir isso mais facilmente se estiver usando uma biblioteca ou sistema de modelos para gerar HTML. Leia a documentação para saber como escapar das coisas apropriadamente.

Em HTML, as coisas precisam ter um escape diferente, dependendo do contexto. Isso é especialmente verdadeiro para strings sendo colocadas em Javascript.

MarkR
fonte
3

Eu definitivamente concordaria com as postagens acima, mas tenho uma pequena coisa a acrescentar em resposta à resposta da Cheekysoft, especificamente:

Quando se trata de consultas de banco de dados, sempre tente usar consultas parametrizadas preparadas. As bibliotecas mysqli e PDO suportam isso. Isso é infinitamente mais seguro do que usar funções de escape, como mysql_real_escape_string.

Sim, mysql_real_escape_string é efetivamente apenas uma função de escape de string. Não é uma bala mágica. Tudo o que fará é escapar caracteres perigosos para que possam ser usados ​​com segurança em uma única string de consulta. No entanto, se você não higienizar suas entradas de antemão, ficará vulnerável a certos vetores de ataque.

Imagine o seguinte SQL:

$ result = "SELECT campos DA tabela WHERE id =" .mysql_real_escape_string ($ _ POST ['id']);

Você deve ser capaz de ver que isso é vulnerável a exploração. Imagine que o parâmetro id contenha o vetor de ataque comum:

1 OU 1 = 1

Não há caracteres arriscados para codificar, então ele passará direto pelo filtro de escape. Deixando-nos:

SELECT campos DA tabela WHERE id = 1 OR 1 = 1

Eu codifiquei uma pequena função rápida que coloquei em minha classe de banco de dados que eliminará qualquer coisa que não seja um número. Ele usa preg_replace, então há uma função um pouco mais otimizada, mas funciona em uma pitada ...

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

Então, em vez de usar

$ result = "SELECT campos da tabela WHERE id =" .mysqlrealescapestring ("1 OR 1 = 1");

eu usaria

$ result = "SELECT campos DA tabela WHERE id =" .Numbers ("1 OR 1 = 1");

e executaria com segurança a consulta

SELECT campos da tabela WHERE id = 111

Claro, isso apenas o impediu de exibir a linha correta, mas não acho que seja um grande problema para quem está tentando injetar sql em seu site;)

BrilliantWinter
fonte
1
Perfeito! Este é exatamente o tipo de higienização de que você precisa. O código inicial falhou porque não validou que um número era numérico. Seu código faz isso. você deve chamar Numbers () em todos os vars de uso de inteiros cujos valores se originam de fora da base de código.
Cheekysoft
1
Vale a pena mencionar que intval () funcionará perfeitamente bem para isso, já que o PHP automaticamente coage inteiros em strings para você.
Adam Ernst,
11
Eu prefiro intval. Acontece 1abc2 para 1, não 12.
jmucchiello
1
intval é melhor, especialmente no ID. Na maioria das vezes, se foi corrompido, é exatamente como acima, 1 ou 1 = 1. Você realmente não deveria vazar a identidade de outras pessoas. Portanto, intval retornará o ID correto. Depois disso, você deve verificar se os valores originais e limpos são os mesmos. É uma ótima maneira de não apenas parar os ataques, mas também encontrar os atacantes.
triunenature de
2
A linha incorreta seria desastrosa se você estivesse exibindo dados pessoais, você veria as informações de outro usuário! em vez disso, seria melhor verificarreturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
Frank Forte
2

Uma peça importante desse quebra-cabeça são os contextos. Alguém enviando "1 OR 1 = 1" como o ID não é um problema se você citar todos os argumentos em sua consulta:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

O que resulta em:

SELECT fields FROM table WHERE id='1 OR 1=1'

o que é ineficaz. Já que você está escapando da string, a entrada não pode sair do contexto da string. Eu testei isso até a versão 5.0.45 do MySQL, e usar um contexto de string para uma coluna inteira não causa problemas.

Lucas omã
fonte
15
e então começarei meu vetor de ataque com o char 0xbf27 multibyte que em seu banco de dados latin1 será convertido pela função de filtro como 0xbf5c27 - que é um caractere multibyte único seguido por uma aspa simples.
Cheekysoft
8
Tente não se proteger contra um único vetor de ataque conhecido. Você vai acabar perseguindo seu rabo até o fim do tempo, aplicando patch após patch em seu código. Ficar para trás e olhar para os casos gerais levará a um código mais seguro e a uma mentalidade mais focada na segurança.
Cheekysoft
Concordo; idealmente, o OP usará declarações preparadas.
Lucas Oman
1
Embora a citação de argumentos sugeridos por este post não seja infalível, ela irá mitigar muitos dos ataques do tipo 1 OR 1 = 1 comuns, portanto, vale a pena mencionar.
Night Owl
2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

Funciona bem, ainda melhor em sistemas de 64 bits. No entanto, tome cuidado com as limitações de seu sistema ao lidar com grandes números, mas para IDs de banco de dados isso funciona bem 99% das vezes.

Você deve usar uma única função / método para limpar seus valores também. Mesmo que esta função seja apenas um wrapper para mysql_real_escape_string (). Por quê? Porque um dia, quando uma exploração do seu método preferido de limpeza de dados for encontrada, você só terá que atualizá-la em um local, em vez de localizar e substituir em todo o sistema.

cnizzardini
fonte
-3

por que, oh POR QUE, você não incluiria aspas em torno da entrada do usuário em sua instrução sql? parece muito bobo não o fazer! incluir aspas em sua instrução sql tornaria "1 ou 1 = 1" uma tentativa infrutífera, não?

então agora, você dirá, "e se o usuário incluir uma aspa (ou aspas duplas) na entrada?"

bem, solução fácil para isso: apenas remova as citações inseridas pelo usuário. por exemplo: input =~ s/'//g;. agora, parece-me de qualquer maneira, que a entrada do usuário seria protegida ...

Jarett L
fonte
"por que, oh POR QUE, você não incluiria aspas em torno da entrada do usuário em sua instrução sql?" - A pergunta não diz nada sobre não citar a entrada do usuário.
Quentin,
1
"bem, solução fácil para isso" - Solução terrível para isso. Isso joga dados fora. A solução mencionada na própria pergunta é uma abordagem melhor.
Quentin,
embora eu concorde que a pergunta não aborda a citação da entrada do usuário, ainda parece não citar a entrada. e prefiro jogar fora os dados do que inserir dados ruins. geralmente, em um ataque de injeção, você NÃO quer esses dados de qualquer maneira .... certo?
Jarett L
"embora eu concorde que a questão não aborda a citação da entrada do usuário, ainda parece que não citar a entrada." - Não, não faz. A pergunta não demonstra isso de uma forma ou de outra.
Quentin,
1
@JarettL Acostume-se a usar declarações preparadas ou acostume-se com o Bobby Tables destruindo seus dados toda terça-feira . SQL parametrizado é a melhor maneira de se proteger contra injeção de SQL. Você não precisa fazer "verificações de injeção SQL" se estiver usando uma instrução preparada. Eles são extremamente fáceis de implementar (e na minha opinião, tornam o código MUITO mais fácil de ler), protegem de várias idiossincrasias de concatenação de string e injeção de sql e, o melhor de tudo, você não precisa reinventar a roda para implementá-lo .
Siyual,