Como evitar o XSS com HTML / PHP?

256

Como evito o XSS (script entre sites) usando apenas HTML e PHP?

Eu já vi várias outras postagens sobre esse tópico, mas não encontrei um artigo que indique de forma clara e concisa como realmente impedir o XSS.

Saúde
fonte
3
Apenas observe que isso não resolverá o caso em que você poderá usar a entrada do usuário como um atributo HTML. Por exemplo, o URL de origem de uma imagem. Não é um caso comum, mas fácil de esquecer.
Michael Mior 16/05
@MichaelMior aqui é uma solução para evitar XSS em hrefou srcatributo HTML: stackoverflow.com/questions/19047119/...
baptx
Há um bom artigo aqui que explica o XSS e como evitá-lo em diferentes idiomas (incluindo PHP).
XCore 14/04

Respostas:

296

Basicamente, você precisa usar a função htmlspecialchars()sempre que quiser produzir algo para o navegador que veio da entrada do usuário.

A maneira correta de usar esta função é algo como isto:

echo htmlspecialchars($string, ENT_QUOTES, 'UTF-8');

A Google Code University também possui esses vídeos educativos sobre segurança na Web:

Alix Axel
fonte
7
@ TimTim: Na maioria dos casos, sim. No entanto, quando você precisa para permitir que as coisas de entrada HTML ficar um pouco mais complicado e se este é o caso, eu recomendo que você use algo como htmlpurifier.org
Alix Axel
@Alix Axel, então é sua resposta para usar htmlspecialchars ou usar htmlpurifier.org ?
TimTim
3
Se você precisar aceitar a entrada HTML, use o HTML Purifier, se não estiver htmlspecialchars().
Alix Axel
9
htmlspecialchars ou htmlentities? Verifique aqui stackoverflow.com/questions/46483/…
kiranvj
4
Na maioria das vezes, é correto, mas não é tão simples assim. Você deve colocar uma string não confiável em HTML, Js, Css e colocar HTML não confiável em HTML. Veja isso: owasp.org/index.php/…
bronze man
41

Uma das minhas referências favoritas do OWASP é a explicação de Cross-Site Scripting porque, embora haja um grande número de vetores de ataque XSS, algumas das regras a seguir podem se defender muito contra a maioria deles!

Esta é a folha de dicas de segurança do PHP

Wahyu Kristianto
fonte
7
Eu também .. Esta é XSS Filtro Evasão Cheat Sheet owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
1
Não exatamente XSS, mas acho que XSS e CSRF são comumente misturado e ambos são realmente perigosos: owasp.org/index.php/...
Simon
2
Esta página não existe mais
Mazzy 4/18
1
@Mazzy last cache web.archive.org/web/20180817180409/owasp.org/index.php/…
Wahyu Kristianto
15

Uma das etapas mais importantes é higienizar qualquer entrada do usuário antes de ser processada e / ou renderizada novamente no navegador. O PHP possui algumas funções de " filtro " que podem ser usadas.

A forma que os ataques XSS costumam ter é inserir um link para algum javascript externo que contém intenções maliciosas para o usuário. Leia mais sobre isso aqui .

Você também deseja testar seu site - eu posso recomendar o complemento do Firefox XSS Me .

James Kolpack
fonte
Do que preciso para garantir a limpeza exata da entrada. Existe um caractere / string em particular que eu tenha cuidado?
TimTim
27
@TimTim - não. Toda entrada do usuário deve sempre ser considerada inerentemente hostil.
zombat
Além disso, dados internos (funcionários, administrador de sistemas etc.) podem não ser seguros. Você deve identificar e monitorar (com data e usuário do log) dados exibidos com interpretação.
Samuel Dauzon
9

Em ordem de preferência:

  1. Se você estiver usando um mecanismo de modelagem (por exemplo, Twig, Smarty, Blade), verifique se ele oferece escape sensível ao contexto. Sei por experiência própria que Twig sabe.{{ var|e('html_attr') }}
  2. Se você deseja permitir HTML, use o HTML Purifier . Mesmo se você acha que aceita apenas Markdown ou ReStructuredText, ainda assim deseja purificar o HTML que essas linguagens de marcação geram.
  3. Caso contrário, use htmlentities($var, ENT_QUOTES | ENT_HTML5, $charset)e verifique se o restante do documento usa o mesmo conjunto de caracteres que $charset. Na maioria dos casos, 'UTF-8'é o conjunto de caracteres desejado.

Além disso, certifique-se de escapar na saída, não na entrada .

Scott Arciszewski
fonte
7

Coloque uma postagem cruzada como uma referência consolidada da versão beta da Documentação do SO, que ficará offline.

Problema

O script entre sites é a execução não intencional de código remoto por um cliente da Web. Qualquer aplicativo da Web pode se expor ao XSS se receber entrada de um usuário e enviá-lo diretamente em uma página da Web. Se a entrada incluir HTML ou JavaScript, o código remoto poderá ser executado quando esse conteúdo for renderizado pelo cliente da web.

Por exemplo, se um lado de terceiros contiver um arquivo JavaScript:

// http://example.com/runme.js
document.write("I'm running");

E um aplicativo PHP gera diretamente uma string passada para ele:

<?php
echo '<div>' . $_GET['input'] . '</div>';

Se um parâmetro GET desmarcado contiver <script src="http://example.com/runme.js"></script>, a saída do script PHP será:

<div><script src="http://example.com/runme.js"></script></div>

O JavaScript de terceiros será executado e o usuário verá "Estou executando" na página da web.

Solução

Como regra geral, nunca confie na entrada de um cliente. Todo valor GET, POST e cookie pode ser qualquer coisa e, portanto, deve ser validado. Ao emitir qualquer um desses valores, escape-os para que não sejam avaliados de maneira inesperada.

Lembre-se de que, mesmo nos aplicativos mais simples, os dados podem ser movidos e será difícil acompanhar todas as fontes. Portanto, é uma prática recomendada sempre escapar à saída.

O PHP fornece algumas maneiras de escapar da saída, dependendo do contexto.

Funções de filtro

As funções de filtro do PHPs permitem que os dados de entrada no script php sejam higienizados ou validados de várias maneiras . Eles são úteis ao salvar ou enviar a entrada do cliente.

Codificação HTML

htmlspecialcharsconverterá quaisquer "caracteres especiais HTML" em suas codificações HTML, o que significa que eles não serão processados ​​como HTML padrão. Para corrigir nosso exemplo anterior usando este método:

<?php
echo '<div>' . htmlspecialchars($_GET['input']) . '</div>';
// or
echo '<div>' . filter_input(INPUT_GET, 'input', FILTER_SANITIZE_SPECIAL_CHARS) . '</div>';

Saída:

<div>&lt;script src=&quot;http://example.com/runme.js&quot;&gt;&lt;/script&gt;</div>

Tudo dentro da <div>tag não será interpretado como uma tag JavaScript pelo navegador, mas como um simples nó de texto. O usuário verá com segurança:

<script src="http://example.com/runme.js"></script>

Codificação de URL

Ao gerar uma URL gerada dinamicamente, o PHP fornece a urlencodefunção para gerar URLs válidas com segurança. Portanto, por exemplo, se um usuário puder inserir dados que se tornem parte de outro parâmetro GET:

<?php
$input = urlencode($_GET['input']);
// or
$input = filter_input(INPUT_GET, 'input', FILTER_SANITIZE_URL);
echo '<a href="http://example.com/page?input="' . $input . '">Link</a>';

Qualquer entrada maliciosa será convertida em um parâmetro de URL codificado.

Usando bibliotecas externas especializadas ou listas OWASP AntiSamy

Às vezes, você deseja enviar HTML ou outro tipo de entrada de código. Você precisará manter uma lista de palavras autorizadas (lista branca) e não autorizadas (lista negra).

Você pode baixar listas padrão disponíveis no site da OWASP AntiSamy . Cada lista é adequada para um tipo específico de interação (ebay api, tinyMCE, etc ...). E é de código aberto.

Existem bibliotecas para filtrar HTML e impedir ataques XSS para o caso geral e para executar pelo menos as listas AntiSamy com uso muito fácil. Por exemplo, você tem purificador HTML

Matt S
fonte
5

Muitas estruturas ajudam a lidar com o XSS de várias maneiras. Ao criar sua própria conta ou se houver alguma preocupação com o XSS, podemos aproveitar a filter_input_array (disponível no PHP 5> = 5.2.0, PHP 7.) Normalmente adicionarei esse trecho ao meu SessionController, porque todas as chamadas passam por ele antes de qualquer outro controlador interage com os dados. Dessa maneira, toda a entrada do usuário é higienizada em um local central. Se isso for feito no início de um projeto ou antes que seu banco de dados seja envenenado, você não deverá ter nenhum problema no momento da saída ... interrompe a entrada e saída de lixo.

/* Prevent XSS input */
$_GET   = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
$_POST  = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
/* I prefer not to use $_REQUEST...but for those who do: */
$_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST;

O item acima removerá TODAS as tags HTML e de script. Se você precisar de uma solução que permita tags seguras, com base em uma lista de permissões, consulte o HTML Purifier .


Se o seu banco de dados já estiver envenenado ou você desejar lidar com o XSS no momento da saída, a OWASP recomenda criar uma função de wrapper personalizado para echo, e usá-la EM TODA PARTE DESSE LUGAR, valores fornecidos pelo usuário:

//xss mitigation functions
function xssafe($data,$encoding='UTF-8')
{
   return htmlspecialchars($data,ENT_QUOTES | ENT_HTML401,$encoding);
}
function xecho($data)
{
   echo xssafe($data);
}
webaholik
fonte
2

Você também pode definir alguns cabeçalhos de resposta HTTP relacionados ao XSS via header(...)

Proteção X-XSS "1; mode = block"

para ter certeza, o modo de proteção XSS do navegador está ativado.

Política de segurança de conteúdo "default-src 'self'; ..."

para habilitar a segurança do conteúdo do navegador. Consulte este aqui para obter detalhes sobre a Política de segurança de conteúdo (CSP): http://content-security-policy.com/ Especialmente, configurar o CSP para bloquear scripts embutidos e fontes externas de script é útil no XSS.

para um monte de cabeçalhos de resposta HTTP úteis relacionados à segurança do seu aplicativo da web, consulte OWASP: https://www.owasp.org/index.php/List_of_useful_HTTP_headers

chris
fonte
1
<?php
function xss_clean($data)
{
// Fix &entity\n;
$data = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $data);
$data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
$data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
$data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');

// Remove any attribute starting with "on" or xmlns
$data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);

// Remove javascript: and vbscript: protocols
$data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);

// Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);

// Remove namespaced elements (we do not need them)
$data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);

do
{
    // Remove really unwanted tags
    $old_data = $data;
    $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
}
while ($old_data !== $data);

// we are done...
return $data;
}
Abdo-Host
fonte
5
Você não deve usar preg_replacecomo ele usa evalem sua entrada. owasp.org/index.php/PHP_Security_Cheat_Sheet#Code_Injection
CrabLab
0

Use htmlspecialcharsem PHP. No HTML, tente evitar o uso de:

element.innerHTML = “…”; element.outerHTML = “…”; document.write(…); document.writeln(…);

onde varé controlado pelo usuário .

Obviamente, também tente evitar eval(var), se você precisar usar algum deles, tente JS escapar deles, HTML escapá-los e talvez você precise fazer mais um pouco, mas para o básico isso deve ser suficiente.

Pablo
fonte
0

A melhor maneira de proteger sua entrada é usar a htmlentitiesfunção. Exemplo:

htmlentities($target, ENT_QUOTES, 'UTF-8');

Você pode obter mais informações aqui .

Marco Concas
fonte