Impedir scripts entre sites (XSS) no ASP.NET Core

Por Rick Anderson

Cross-Site Scripting (XSS) é uma vulnerabilidade de segurança que permite que um ciberatacante coloque scripts do lado do cliente (geralmente JavaScript) em páginas da Web. Quando outros utilizadores carregam páginas afetadas, os scripts do ciberatacante são executados. O ciberatacante pode então roubar cookies e tokens de sessão, alterar o conteúdo da página web através de manipulação do DOM ou redirecionar o navegador para outra página. As vulnerabilidades XSS geralmente ocorrem quando uma aplicação recebe a entrada do utilizador e a envia para uma página sem a validar, codificar ou escapar.

Este artigo aplica-se principalmente a ASP.NET Core MVC com visualizações, Razor Pages e outras aplicações que retornam HTML e que podem ser vulneráveis ao XSS. APIs web que devolvem dados sob a forma de HTML, XML ou JSON podem desencadear ataques XSS nas suas aplicações clientes se não higienizarem devidamente a entrada dos utilizadores. Este comportamento depende da confiança que a aplicação cliente deposita na API. Se uma API aceitar conteúdo gerado pelo utilizador e o devolver numa resposta HTML, os dados estão vulneráveis a ataques. Um ciberatacante pode injetar scripts maliciosos no conteúdo que são executados quando a resposta é renderizada no navegador do utilizador.

Para evitar ataques XSS, as APIs da Web devem implementar validação de entrada e codificação de saída. A validação de entrada garante que a entrada do usuário atenda aos critérios esperados e não inclua código mal-intencionado. A codificação de saída garante que quaisquer dados devolvidos pela API são devidamente higienizados para que não possam ser executados como código pelo navegador do utilizador. Para mais informações, consulte GitHub dotnet/aspnetcore.docs edição #28789.

Proteja a sua aplicação contra o XSS

Em um nível básico, o XSS funciona enganando seu aplicativo para inserir uma tag <script> em sua página renderizada ou inserindo um evento On* em um elemento.

Para evitar a introdução de XSS na aplicação, os programadores devem implementar as seguintes técnicas de prevenção:

  • Nunca coloque dados não confiáveis na sua entrada HTML, a menos que siga as outras técnicas listadas nesta secção.

    Dados não confiáveis são quaisquer dados controláveis por um ciberatacante. Exemplos incluem entradas de formulários HTML, strings de consulta, cabeçalhos HTTP ou até dados provenientes de uma base de dados. Um ciberatacante pode conseguir invadir a tua base de dados mesmo que não consiga invadir a tua aplicação.

  • Antes de colocar dados não confiáveis num elemento HTML, certifique-se de que os dados estão codificados em HTML.

    A codificação HTML utiliza caracteres como o parêntese angular esquerdo ou menor do que (<) e converte-os numa forma segura como (&lt;).

  • Antes de colocar dados não confiáveis num atributo HTML, certifique-se de que os dados são codificados por atributos HTML.

    Esta forma especializada de codificação HTML trata aspas duplas ("), aspas simples ('), e& e caracteres menores que ().< Ao lidar com entradas não confiáveis, use a codificação HTML para conteúdo HTML geral e a codificação de atributos HTML para atributos HTML.

  • Antes de colocar dados não confiáveis em JavaScript, coloque os dados num elemento HTML cujo conteúdo recupera em tempo de execução.

    Se não conseguir seguir esta técnica, certifique-se de que os dados estão codificados em JavaScript. A codificação JavaScript converte caracteres perigosos para JavaScript num valor equivalente hexadecimal. Por exemplo, a codificação em JavaScript transforma o carácter de menor que (<) no valor hexadecimal \u003C.

  • Antes de colocar dados não confiáveis numa cadeia de consulta de URL, certifique-se de que os dados estão codificados por URL.

Explore a codificação em HTML com Razor

O Razor motor usado no MVC codifica automaticamente toda a saída proveniente de variáveis, a menos que trabalhe para evitar este comportamento. Utiliza regras de codificação de atributos HTML sempre que usa a diretiva at symbol @ . Como a codificação de atributos HTML é um superconjunto da codificação HTML, não precisa de considerar se deve usar codificação HTML ou codificação de atributos HTML. Deve certificar-se de que utiliza o símbolo @ @ apenas num contexto HTML, e não ao tentar inserir diretamente dados de entrada não fiáveis em JavaScript. Razor Os Assistentes de Tag também codificam a entrada que usas nos parâmetros da tag.

Considere a seguinte Razor perspetiva:

@{
    var untrustedInput = "<\"123\">";
}

@untrustedInput

Esta vista produz o conteúdo da untrustedInput variável. A variável inclui alguns caracteres usados em ataques XSS: menor que (<), aspas duplas ("), e colchete em ângulo reto ou maior que (>). Examinando a fonte mostra a saída renderizada codificada como:

&lt;&quot;123&quot;&gt;

Advertência

ASP.NET Core MVC fornece uma classe HtmlString que não é automaticamente codificada ao ser lançada. Esta classe nunca deve ser usada em combinação com entrada não confiável porque expõe uma vulnerabilidade XSS.

Explore a codificação em JavaScript com Razor

Em alguns casos, pode querer inserir um valor em JavaScript para processar na sua visão. Existem duas formas de concretizar esta tarefa. A maneira mais segura de inserir valores é colocar o valor em um atributo de dados de uma tag e recuperá-lo em seu JavaScript. Por exemplo:

@{
    var untrustedInput = "<script>alert(1)</script>";
}

<div id="injectedData"
     data-untrustedinput="@untrustedInput" />

<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />

<script>
    var injectedData = document.getElementById("injectedData");

    // All clients
    var clientSideUntrustedInputOldStyle =
        injectedData.getAttribute("data-untrustedinput");

    // HTML 5 clients only
    var clientSideUntrustedInputHtml5 =
        injectedData.dataset.untrustedinput;

    // Put the injected, untrusted data into the scriptedWrite div tag.
    // Do NOT use document.write() on dynamically generated data as it can lead to XSS.

    document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;

    // Or, you can use createElement() to dynamically create document elements.
    // This instance uses textContent to ensure the data is properly encoded.
    var x = document.createElement("div");
    x.textContent = clientSideUntrustedInputHtml5;
    document.body.appendChild(x);

    // You can also use createTextNode on an element to ensure data is properly encoded.
    var y = document.createElement("div");
    y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
    document.body.appendChild(y);

</script>

A marcação anterior gera o seguinte HTML:

<div id="injectedData"
     data-untrustedinput="&lt;script&gt;alert(1)&lt;/script&gt;" />

<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />

<script>
    var injectedData = document.getElementById("injectedData");

    // All clients
    var clientSideUntrustedInputOldStyle =
        injectedData.getAttribute("data-untrustedinput");

    // HTML 5 clients only
    var clientSideUntrustedInputHtml5 =
        injectedData.dataset.untrustedinput;

    // Put the injected, untrusted data into the scriptedWrite div tag.
    // Do NOT use document.write() on dynamically generated data as it can lead to XSS.

    document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;

    // Or, you can use createElement() to dynamically create document elements.
    // This instance uses textContent to ensure the data is properly encoded.
    var x = document.createElement("div");
    x.textContent = clientSideUntrustedInputHtml5;
    document.body.appendChild(x);

    // You can also use createTextNode on an element to ensure data is properly encoded.
    var y = document.createElement("div");
    y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
    document.body.appendChild(y);

</script>

O código anterior gera a seguinte saída:

<script>alert(1)</script>
<script>alert(1)</script>
<script>alert(1)</script>

Advertência

Não concatene entradas não confiáveis em JavaScript para criar elementos DOM ou usar document.write() em conteúdos gerados dinamicamente.

Em vez disso, utilize uma das seguintes abordagens para evitar que o código seja exposto a XSS baseado em DOM:

  • Chamar createElement() e atribuir valores de propriedades com métodos ou propriedades apropriadas, como node.textContent= ou node.InnerText=.
  • Chame o document.CreateTextNode() método e anexe-o na localização DOM apropriada.
  • Chame o método element.SetAttribute().
  • Utilize a atribuição element[attribute]=.

Codificadores de acesso no código

Pode usar codificadores HTML, JavaScript e URL no seu código de duas formas:

  • Injete-os por meio de injeção de dependência através de .
  • Use os codificadores padrão contidos no namespace System.Text.Encodings.Web.

Quando utiliza os codificadores predefinidos, quaisquer personalizações aplicadas aos intervalos de caracteres (para que os caracteres neles contidos sejam tratados como seguros) não produzem efeito. Os codificadores padrão usam as regras de codificação mais seguras possíveis.

Para usar os codificadores configuráveis via injeção de dependências, os seus construtores devem aceitar um parâmetro HtmlEncoder, JavaScriptEncoder e UrlEncoder, conforme adequado.

Por exemplo:

public class HomeController : Controller
{
    HtmlEncoder _htmlEncoder;
    JavaScriptEncoder _javaScriptEncoder;
    UrlEncoder _urlEncoder;

    public HomeController(HtmlEncoder htmlEncoder,
                          JavaScriptEncoder javascriptEncoder,
                          UrlEncoder urlEncoder)
    {
        _htmlEncoder = htmlEncoder;
        _javaScriptEncoder = javascriptEncoder;
        _urlEncoder = urlEncoder;
    }
}

Codificar parâmetros de URL

Se quiser construir uma cadeia de consulta de URL com entrada não confiável como valor, use o UrlEncoder parâmetro para codificar o valor:

var example = "\"Quoted Value with spaces and &\"";
var encodedValue = _urlEncoder.Encode(example);

Após a codificação, a encodedValue variável contém a cadeia %22Quoted%20Value%20with%20spaces%20and%20%26%22. Espaços, aspas, pontuação e outros caracteres inseguros são codificados em percentagem nos respetivos valores hexadecimais. Por exemplo, um carácter de espaço é convertido em %20.

Advertência

Não use entradas não confiáveis como parte de um caminho de URL. Sempre passe entrada não confiável como um valor de parâmetro de consulta.

Personalizar os codificadores

Por defeito, os codificadores usam uma lista segura limitada ao intervalo Unicode de Latim Básico. Todos os caracteres fora do intervalo indicado são codificados como equivalentes ao seu código de caracteres. Este comportamento também afeta a renderização efetuada pelos Razor Tag Helpers e HTML Helpers, porque utilizam os codificadores para produzir as suas cadeias de caracteres.

O objetivo deste comportamento é proteger contra bugs desconhecidos ou futuros no navegador. Bugs anteriores do navegador dificultaram a análise baseada no processamento de caracteres não ingleses. Se o seu site faz uso intensivo de caracteres não latinos, como chinês, cirílico ou outros, este comportamento provavelmente não é adequado para a sua configuração.

Pode personalizar as listas seguras dos codificadores para incluir intervalos Unicode apropriados à aplicação durante o arranque. Faz as personalizações no ficheiro Program.cs .

Por exemplo, pode usar a configuração padrão com um Razor HTML Helper semelhante ao seguinte HTML:

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

A marcação anterior é processada com texto chinês codificado:

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

Para alargar a gama de caracteres tratados como seguros pelo codificador, insira a seguinte linha no ficheiro Program.cs :

builder.Services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

Você pode personalizar as listas seguras do codificador para incluir intervalos Unicode apropriados ao seu aplicativo durante a inicialização, em ConfigureServices().

Por exemplo, usando a configuração padrão, você pode usar um Razor HtmlHelper assim;

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

Ao visualizar a fonte da página da web, verá que ela foi processada da seguinte forma, com o texto chinês codificado.

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

Para ampliar os caracteres tratados como seguros pelo codificador, você deve inserir a seguinte linha no método ConfigureServices() em startup.cs;

services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

Este exemplo alarga a lista segura para incluir os Ideogramas Unificados do Intervalo Unicode CJK. O resultado seguinte mostra a visualização renderizada para a gama mais ampla de caracteres seguros:

<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>

Os intervalos de listas seguras são especificados como gráficos de código Unicode, não idiomas. O padrão Unicode tem uma lista de quadros de códigos que pode usar para encontrar o gráfico que contém os seus caracteres. Cada codificador (HTML, JavaScript, URL) deve ser configurado separadamente.

Observação

A personalização da lista segura afeta apenas codificadores obtidos via injeção de dependências. Se aceder diretamente a um codificador via System.Text.Encodings.Web.*Encoder.Default, só é usada a lista de segurança padrão, Latim Básico.

Determinar quando e onde codificar

Em geral, a prática aceite é que a codificação ocorre no ponto de saída e que os valores codificados nunca devem ser armazenados numa base de dados.

A codificação no ponto de saída permite alterar o uso dos dados. Por exemplo, mudar de HTML para valor de uma string de consulta. Esta abordagem permite-lhe pesquisar facilmente os seus dados sem ter de codificar valores antes de pesquisar. Também permite tirar partido de quaisquer alterações ou correções de bugs feitas aos codificadores.

Use a validação como técnica de prevenção de XSS

A validação pode ser uma ferramenta útil para limitar ataques XSS. Por exemplo, uma cadeia numérica contendo apenas os caracteres 0-9 não desencadeia um ataque XSS.

A validação é mais complicada quando HTML é aceite na entrada do utilizador. Analisar a introdução HTML pode ser difícil e, por vezes, impossível. Markdown, juntamente com um analisador que remove HTML incorporado, é uma opção mais segura para aceitar entradas avançadas.

Nunca confie apenas na validação. Codifica sempre entrada não confiável antes da saída, independentemente da validação ou higienização realizada.