Segurança do ASP.NET

Protegendo seus aplicativos ASP.NET

Adam Tuliper

Na edição anterior, discutimos sobre a importância de incorporar a segurança nos seus aplicativos da Web e observamos alguns tipos de ataques, inclusive injeção de SQL e violação de parâmetros e o modo de preveni-los (msdn.microsoft.com/magazine/hh580736). Neste artigo, vamos falar de dois ataques comuns para ajudá-lo a refinar o arsenal de proteções para aplicativos, scripts entre sites (XSS) e falsificação de solicitação entre sites (CSRF).

Você deve estar se perguntando: Por que não usar apenas um scanner de segurança de produção? Os scanners são ferramentas excelentes para localizar itens simples e são ideais para localizar questões de configuração do sistema e de aplicativos, mas estes podem não conhecer o aplicativo como você. Assim, é necessário se familiarizar com as questões de segurança em potencial e reservar tempo para verificar os aplicativos e incorporar a segurança no seu ciclo de desenvolvimento de software.

Scripts entre sites

O que é isso? XSS é um ataque em que o script é maliciosamente injetado na sessão de navegação do usuário, geralmente sem o conhecimento deste. Esses tipos de ataques tornaram-se conhecidos para muitas pessoas, devido a incidentes em que um grande site de rede social foi atacado e os seus usuários tiveram mensagens postadas sem a sua autorização. Se uma invasor postar um script malicioso para ser executado pelo navegador, esse script será executado no contexto da sessão da vítima, permitindo, basicamente, que o invasor faça tudo o que desejar com o DOM, inclusive exibir diálogos falsos de logon ou roubar cookies. Um ataque como esse poderia até instalar um key logger de HTML na página atual para enviar continuamente entradas dessa janela para um site remoto.

Como ele é explorado? O XSS é explorado por vários métodos, sendo que todos dependem de uma saída sem escape ou com escape inadequado. Vamos ver o caso de um aplicativo que precise exibir uma mensagem de status simples para o usuário final. Normalmente, essa mensagem é transmitida na cadeia de caracteres de consulta, conforme mostrado na Figura 1.

Query String Message
Figura 1 Mensagem da cadeia de caracteres de consulta

Essa técnica é normalmente usada depois de um redirecionamento para mostrar ao usuário algum tipo de status, tal como a mensagem do perfil salvo na Figura 1. A mensagem é lida a partir da cadeia de caracteres de consulta e escrita na página de imediato. Se a saída não for codificada com HTML, qualquer um poderá facilmente injetar o JavaScript no lugar da mensagem de status. Esse tipo de ataque é considerado um ataque de XSS refletido, uma vez que qualquer que seja o conteúdo da cadeia de caracteres, este será renderizado de volta à página. No caso de um ataque persistente, o script malicioso é armazenado, geralmente num banco de dados ou cookie.

Na Figura 1, você pode observar que esse URI utiliza um parâmetro de mensagem. A página da Web desse URI iria conter um código como o seguinte para simplesmente escrever a variável na página, sem nenhuma codificação:

<div class="messages"> <%=Request.QueryString["msg"]%></div>

Se você substituir “Perfil salvo” pelo script exibido na Figura 2, a função de alerta será exibida no navegador a partir do script incluso na cadeia de caracteres de consulta. A chave a ser explorada aqui é que os resultados não são codificados com HTML e, portanto, a marca <script> é, na realidade, analisada como um JavaScript válido pelo navegador e executada. Obviamente, isso não é o que o desenvolvedor imaginava.

Injecting Script into the URI
Figura 2 Injetando o script no URI

Então, uma alerta é exibido; qual é o problema? Vamos analisar um pouco mais esses exemplos, conforme ilustrado na Figura 3. Note que eu resumi o ataque aqui para mostrar meu ponto de vista, porém essa sintaxe não está exatamente correta, mas uma pequena modificação a transformará num ataque real.

Creating a Malicious Attack
Figura 3 Criando um ataque malicioso

Um ataque como esse causaria a exibição de um diálogo de logon falso para os usuários, no qual eles inseririam as suas credenciais tranquilamente. Nesse caso, um script remoto foi baixado, o que é normalmente permitido pelas configurações de segurança do navegador padrão. Teoricamente, esse tipo de ataque pode ocorrer em qualquer lugar onde uma cadeia de caracteres que não tenha sido codificada ou corrigida possa ser reproduzida de novo na tela para um usuário.

AFigura 4 ilustra um ataque usando um script semelhante num site de desenvolvimento que permite que os usuários incluam comentários sobre os seus produtos. Em vez de deixar uma análise real de um produto, alguém inseriu um JavaScript malicioso como comentário. Agora, esse script exibe um diálogo de logon para cada usuário que acessa a página da Web, recolhe e envia as credenciais a um site remoto. Esse é um ataque XSS persistente; o script é armazenado no banco de dados e repetido para todo mundo que visitar a página.

A Persistent XSS Attack Showing a Fake Dialog
Figura 4 Ataque XSS persistente exibindo um diálogo falso

Outro modo de explorar o XSS é usando elementos HTML, como ocorre quando o texto dinâmico é permitido nas marcas HTML, como a seguir:

<img onmouseover=alert([user supplied text])>

Se um invasor injetar um texto como “onmouseout=alert(docu­ment.cookie),” isso criará a seguinte marca no navegador, que acessará o cookie:

<img onmouseover=alert(1) onmouseout=alert(document.cookie) >

Não há uma marca “<script>” para filtrar potencialmente uma entrada e nada para escapar, mas esse é um fragmento totalmente válido de JavaScript que pode ler um cookie, ou seja, um cookie de autenticação em potencial. Há maneiras específicas para cada caso para tornar isso mais seguro, mas devido ao risco presente, é melhor não permitir que a entrada de um usuário alcance esse código embutido.

Como evitar o XSS? Seguir rigorosamente essas regras o ajudará a evitar a maioria ou todos os ataques XSS no seu aplicativo:

  1. Certifique-se de que toda a saída seja codificada no HTML.

  2. Não permita que o texto fornecido pelo usuário termine numa cadeia de caracteres do atributo do elemento HTML.

  3. Evite o uso do Internet Explorer 6 pelo seu aplicativo marcando Request.Browser, como descrito em msdn.microsoft.com/library/3yekbd5b.

  4. Conheça o comportamento do seu controle e saiba se o HTML codifica a sua saída. Se a resposta for negativa, codifique os dados que vão para o controle.

  5. Use o Microsoft Anti-Cross Site Scripting Library (AntiXSS) e configure-o como o seu codificador de HTML padrão.

  6. Use o objeto AntiXSS Sanitizer (você pode fazer o download dessa biblioteca separadamente e ela será abordada nesse artigo posteriormente) para chamar o GetSafeHtml ou o GetSafeHtmlFragment antes de salvar os dados do HTML no banco de dados; não codifique os dados antes de salvá-los.

  7. Nos Web Forms, não configure EnableRequestValidation=false em suas páginas da Web. Infelizmente, a maioria das postagens de grupos de usuários na Web recomenda a desativação dessa configuração em caso de erro. A configuração é determinada por um motivo e interromperá a solicitação se a combinação de caracteres como “<X" for postada de volta no servidor. Se os controles estiverem postando o HTML no servidor novamente e recebendo o erro exibido na Figura 5, você deverá codificar os dados antes de postá-los no servidor. Esse é um cenário comum nos controles com WYSIWYG e as versões mais modernas codificarão adequadamente os dados do HTML antes de postá-los no servidor novamente.

    Server Error from Unencoded HTML
    Figura 5 Erro do servidor devido a HTML não codificado

  8. Nos aplicativos ASP.NET MVC 3, quando você precisar postar o HTML no modelo novamente, não use ValidateInput(false) para desligar a validação da solicitação. Simplesmente adicione [AllowHtml] à propriedade do seu modelo, como a seguir:

public class BlogEntry
{
  public int UserId {get;set;}
  [AllowHtml]
  public string BlogText {get;set;}
}

Alguns produtos tentam detectar o <script> e outras combinações de palavras ou padrões de expressões regulares numa cadeia de caracteres para tentar detectar o XSS. Esses produtos podem fornecer verificações adicionais, mas eles não são completamente confiáveis devido às diversas variantes criadas pelos invasores. Examine o roteiro do XSS em ha.ckers.org/xss.html para ver como a detecção pode ser difícil.

Para entender as correções, vamos supor que um invasor tenha injetado algum script que terminou numa variável em nosso aplicativo a partir da cadeia de caracteres ou um campo de formulário, conforme exibido aqui:

string message = Request.QueryString["msg"];

ou:

string message = txtMessage.Text;

Observe que mesmo que um TextBox controle as codificações HTML da saída, ele não codifica a propriedade Texto quando essa é lida no código. Com uma dessas linhas de código, você terá a seguinte cadeia de caracteres na variável da mensagem:

message = "<script>alert('bip')</script>"

Numa página da Web contendo um código semelhante ao seguinte, o JavaScript será executado no navegador do usuário, simplesmente porque esse texto foi escrito na página:

<%=message %>

A codificação HTML da saída interrompe esse ataque nos seus roteiros. AFigura6 exibe as principais opções para a codificação de dados perigosos.

Essas opções evitam o tipo de ataque exibido no exemplo e devem ser usadas nos seus aplicativos.

Figura 6 Opções de codificação HTML

ASP.NET (MVC ou Web Forms) <%=Server.HtmlEncode(message) %>
Web Forms (sintaxe ASP.NET 4) <%: message %>
Razor do ASP.NET MVC 3 @message
Ligação de dados

Infelizmente, a sintaxe de ligação de dados ainda não contém uma sintaxe de codificação integrada, essa será lançada na próxima versão do ASP.NET como <%#: %>. Até lá, use:

<%# Server.HtmlEncode(Eval("PropertyName")) %>

Melhor codificação

A partir da Biblioteca AntiXSS no namespace Microsoft.Security.Application:

Encoder.HtmlEncode(message)

É importante conhecer os seus controles. Quais controles HTML codificam seus dados e quais não codificam? Por exemplo, o controle TextBox executa a codificação HTML da saída renderizada, mas o LiteralControl não faz isso. Essa é uma distinção importante. Uma caixa de texto atribuída:

yourTextBoxControl.Text = "Test <script>alert('bip')</script>";

renderiza corretamente o texto na página dessa forma:

Test &lt;script&gt;alert(&#39;bip&#39;)&lt;/script&gt;

Em contraste:

yourLiteralControl.Text = "Test <script>alert('bip')</script>";

causa a exibição de um alerta do JavaScript na página, confirmando a vulnerabilidade do XSS. A correção aqui é simplesmente:

yourLiteralControl.Text = Server.HtmlEncode(
    "Test <script>alert('bip')</script>");

Isso é um pouco mais complexo quando usamos ligação de dados nos Web Forms. Dê uma olhada no seguinte exemplo:

<asp:Repeater ID="Repeater1" runat="server">
    <ItemTemplate>
      <asp:TextBox ID="txtYourField" Text='<%# Bind("YourField") %>'
        runat="server"></asp:TextBox>
    </ItemTemplate>
  </asp:Repeater>

Este é vulnerável? Não, não é. Mesmo que o código embutido pareça ter a capacidade de escrever o script ou interromper as aspas do controle, ele está de fato codificado. 

Sobre isso:

<asp:Repeater ID="Repeater2" runat="server">
  <ItemTemplate>
    <%# Eval("YourField") %>
  </ItemTemplate>
</asp:Repeater>

Ele é vulnerável? Sim, ele é. A sintaxe de ligação de dados <%# %> não faz a codificação HTML. Aqui está a correção:

<asp:Repeater ID="Repeater2" runat="server">
  <ItemTemplate>
    <%#Server.HtmlEncode((string)Eval("YourText"))%>
  </ItemTemplate>
</asp:Repeater>

Saiba que se você usar o Bind nesse cenário, não será possível encapsulá-lo com um Server.HtmlEncode devido ao modo de compilação em segundo plano do Bind, como duas chamadas separadas. Isso falhará:

<asp:Repeater ID="Repeater2" runat="server">
  <ItemTemplate>
    <%#Server.HtmlEncode((string)Bind("YourText"))%>
  </ItemTemplate>
</asp:Repeater>

Se você usar o Bind e não atribuir o texto a um controle codificado pelo HTML (tal como o controle TextBox), considere usar Eval para poder encapsular a chamada no Server.HtmlEncode, como no exemplo anterior.

O mesmo conceito de ligação de dados não existe no ASP.NET MVC, então você precisa saber se os auxiliares HTML serão codificados. Os auxiliares para rótulos e caixas de texto são codificados com HTML. Por exemplo, este código:

@Html.TextBox("customerName", "<script>alert('bip')</script>")
@Html.Label("<script>alert('bip')</script>")

renderiza da seguinte maneira:

<input id="customerName" name="customerName" type="text"
  value="&lt;script>alert(&#39;bip&#39;)&lt;/script>" />
<label for="">&lt;script&gt;alert(&#39;bip&#39;)&lt;/script&gt;</label>

O AntiXSS já foi mencionado anteriormente. Atualmente, na versão 4.1 beta 1, a Biblioteca AntiXSS passou por uma reescrita excelente e, no que diz respeito à segurança, ela fornece um codificador HTML melhor do que o que acompanha o ASP.NET. Não que haja algo errado com o Server.HtmlEncode, mas ele foca em compatibilidade e não em segurança. O AntiXSS usa uma abordagem diferente para codificação. Você poderia ler mais sobre isso em msdn.microsoft.com/security/aa973814.

A versão beta está disponível em bit.ly/gMcB5K. Verifique se o AntiXSS já está fora da versão beta. Se não, você precisará fazer o download do código e compilá-lo. Jon Galloway possui uma postagem excelente sobre isto no endereço bit.ly/lGpKWX.

Para usar o codificador AntiXSS, você pode simplesmente fazer a seguinte chamada:

<%@ Import Namespace="Microsoft.Security.Application" %>
...
...
<%= Encoder.HtmlEncode(plainText)%>

O ASP.NET MVC 4 acrescentou um grande recurso que permite substituir o codificador ASP HTML padrão pelo codificador AntiXSS. No momento da elaboração deste artigo, você precisa da versão 4.1; uma vez que esta se encontra atualmente na versão beta, você pode fazer o download do código, compilá-lo e adicionar a biblioteca ao seu aplicativo como referência, o que leva no máximo cinco minutos. Em seguida, no seu web.config, adicione a seguinte linha na seção <system.web>:

<httpRuntime encoderType=
  "Microsoft.Security.Application.AntiXssEncoder, AntiXssLibrary"/>

Agora, qualquer chamada de codificação HTML realizada com as sintaxes listadas na Figura 6, inclusive a sintaxe do Razor do ASP.NET MVC 3, será codificada pela biblioteca AntiXSS. Como isso ocorre num recurso conectável?

Essa biblioteca também inclui um objeto Sanitizer que pode ser usado para limpar o HTML antes de armazená-lo no banco de dados, o que será muito útil se você fornecer um editor WYSIWYG ao usuário para editar o HTML. Essa chamada tenta remover o script da cadeia de caracteres:

using Microsoft.Security.Application;
...
...
string wysiwygData = "before <script>alert('bip ')</script> after ";
string cleanData = Sanitizer.GetSafeHtmlFragment(wysiwygData);
This results in the following cleaned string that can then be saved to the database:
cleanData = "before  after ";

Falsificação de solicitação entre sites (CSRF)

O que é isso? A falsificação de solicitação entre sites ou CSRF (pronunciado como sea-surf) consiste num ataque que ocorre quando alguém se aproveita da confiança entre o navegador e um site da Web para executar um comando usando a sessão do usuário inocente. Esse ataque é um pouco mais difícil de imaginar sem ver os detalhes, então vamos direto a ele.

Como ele é explorado? Suponha que John fez a autenticação como admin­istrador no site PureShoppingHeaven. O PureShoppingHeaven possui uma URL restrita ao acesso do administrador e permite que as informações sejam passadas à URL para executar uma ação, tal como criar um novo usuário, como ilustrado na Figura 7.

Passing Information on the URL
Figura 7 Passando informações na URL

Se um invasor puder fazer com que John solicite essa URL por vários métodos, o seu navegador fará a solicitação ao servidor e enviará as informações de autenticação armazenadas em cache ou em uso no navegador de John, tal como cookies de autenticação ou outros elementos, inclusive a Autenticação do Windows.

Esse é um exemplo simples, mas os ataques CSRF podem ser muito mais sofisticados e podem incorporar POSTs de formulário, além de solicitações GET e se aproveitar de outros ataques tais como de XSS ao mesmo tempo.

Vamos supor que John visite uma rede social vulnerável que foi explorada. Talvez um invasor tenha colocado um fragmento de JavaScript na página por meio de uma vulnerabilidade do XSS, que solicita agora a URL AddUser.aspx na sessão de John. Esse despejo a partir do Fiddler (fiddler2.com) após a visita de John à página da Web mostra o navegador enviando também um cookie de autenticação de site personalizado:

GET http://pureshoppingheaven/AddUser.aspx?userName=hacked&pwd=secret HTTP/1.1
Host: pureshoppingheaven
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Cookie: CUSTOMERAUTHCOOKIE=a465bc0b-e1e2-4052-8292-484d884229ab

Isso tudo ocorre sem o conhecimento de John. O que é importante entender é que, pela natureza do seu projeto, o navegador enviará qualquer cookie válido ou informação de autenticação. Você já percebeu que seu cliente de email normalmente não carrega imagens por padrão? Um dos motivos para isso é evitar o CSRF. Se você tivesse recebido um email com formatação HTML e uma marca de imagem anexada tal como a seguinte, essa URL seria solicitada e o servidor executaria essa ação se você possuísse a autenticação para esse site da Web:

<img src='yoursite/createuser.aspx?id=hacked&pwd=hacked' />

Se você for um administrador já autenticado em “seu site", o navegador enviará a solicitação GET juntamente com as credenciais. O servidor enxerga isso como uma solicitação válida, feita por um usuário autenticado. e executará essa solicitação sem que você sequer tenha ciência disso, porque não há uma resposta de imagem válida para ser renderizada no seu cliente de email.

Como evitar o CSRF? Para evitar o CSRF, comece seguindo as regras abaixo:

  1. Certifique-se de que uma solicitação não seja reproduzida novamente pelo simples clique num link de solicitação GET. A especificação de HTTP para solicitações GET indicam que as solicitações GET só devem ser usadas para recuperação, não para a modificação do estado.
  2. Certifique-se de que uma solicitação não possa ser reproduzida se um invasor tiver usado o JavaScript para imitar uma solicitação POST de formulário.
  3. Evite executar ações via GET. Por exemplo, não permita que registros sejam criados ou excluídos via URL. De preferência, esses devem exigir alguma interação com o usuário. Ao mesmo tempo que isso não impede um ataque mais inteligente baseado em formulário, isso limita um host de ataques mais fáceis, tais como os descritos no exemplo da imagem de email, como também os de links básicos anexados em sites comprometidos por XSS.

A prevenção de ataques via Web Forms é um pouco diferente do ASP.NET MVC. Com Web Forms, o atributo ViewState MAC pode ser assinado, o que ajuda a evitar falsificações, desde que você não execute a configuração EnableViewStateMac=false. Você também assina o ViewState com a sessão de usuário atual e evita que o ViewState seja transmitido para a cadeia de caracteres de consulta para bloquear o que algumas pessoas denominam ataque de um clique (consulte a Figura 8).

Figura 8 Impedindo um ataque de um clique

void Page_Init(object sender, EventArgs e)
{
  if (Session.IsNewSession)
  {
    // Force session to be created;
    // otherwise the session ID changes on every request.
    Session["ForceSession"] = DateTime.Now;
  }
  // 'Sign' the viewstate with the current session.
  this.ViewStateUserKey = Session.SessionID;
  if (Page.EnableViewState)
  {
    // Make sure ViewState wasn't passed on the querystring.
    // This helps prevent one-click attacks.
    if (!string.IsNullOrEmpty(Request.Params["__VIEWSTATE"]) &&
      string.IsNullOrEmpty(Request.Form["__VIEWSTATE"]))
    {
      throw new Exception("Viewstate existed, but not on the form.");
    }
  }
}

O motivo da atribuição de um valor de sessão aleatório aqui é garantir que a sessão seja estabelecida. Você poderia utilizar um identificador de sessão temporário, mas a ID de sessão do ASP.NET mudará a cada solicitação até que você crie realmente uma sessão. Você não pode ter a ID de sessão mudando a cada solicitação, portanto você precisa mantê-la criando uma nova sessão.

O ASP.NET MVC contém o seu próprio conjunto de auxiliares integrados que garantem a proteção contra CSRF usando elementos exclusivos transmitidos com a solicitação. Os auxiliares usam não apenas um campo de formulário oculto necessário, mas também um valor de cookie, dificultando bem mais a falsificação de uma solicitação. Essas proteções são fáceis de implementar e sua incorporação nos aplicativos é absolutamente essencial. Para adicionar o @Html.AntiForgery­Token() ao <form> na exibição, basta:

@using (Html.BeginForm())
{
  @Html.AntiForgeryToken();
  @Html.EditorForModel();
  <input type="submit" value="Submit" />
}
Decorate any controllers that accept post data with the [Validate­AntiForgeryToken], like so:
[HttpPost]
[ValidateAntiForgeryToken()]
public ActionResult Index(User user)
{
  ...
}

Entender a vulnerabilidade

Esse artigo descreveu duas maneiras comuns de invasões nos aplicativos da Web: scripts entre sites e falsificação de solicitação entre sites. Além dos dois tipos abordados no mês passado, a injeção de SQL e a violação de parâmetros, agora você possui um bom entendimento de como os aplicativos podem ser vulneráveis.

Você também já tomou conhecimento de como é fácil garantir a segurança nos seus aplicativos para se proteger contra a maioria dos ataques comuns. Se você já tiver incorporado a segurança no seu ciclo de desenvolvimento de software, excelente! Caso contrário, não há momento melhor para iniciar do que agora. Você pode auditar os aplicativos por página ou por módulo e, na maioria dos casos, é possível refatorá-los com muita facilidade. Proteja os seus aplicativos com o SSL para evitar a detecção de suas credenciais. Lembre-se de pensar em segurança antes, durante e depois do desenvolvimento.

Adam Tuliper é arquiteto de software na Cegedim e desenvolve software há mais de 20 anos. Ele é palestrante da comunidade INETA local e participa com frequência de conferências e grupos de usuários do .NET. Conheça mais sobre o Adam no Twitter em twitter.com/AdamTuliper, em seu blog completedevelopment.blogspot.com ou no novo site secure-coding.com. Para obter informações mais detalhadas sobre a proteção de seus aplicativos ASP.NET contra hackers, consulte a futura série de vídeos Pluralsight de Adam.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Barry Dorrans