Como: proteger-se contra a injeção de SQL no ASP.NET

Por J.D. Meier, Alex Mackman, Blaine Wastell, Prashant Bansode, Andy Wigley

Microsoft Corporation

Maio de 2005

Aplica-se a

ASP.NET versão 1.1

ASP.NET versão 2.0

Resumo

Este artigo mostra várias maneiras que podem ajudá-lo a proteger seu aplicativo ASP.NET contra injeção de SQL. As técnicas incluem restringir as entradas e usar parâmetros SQL com segurança de tipos para o acesso a dados e uma conta com menos privilégios, que tenha permissões restritas no banco de dados. A injeção de SQL pode ocorrer quando um aplicativo usa a entrada para criar instruções SQL dinâmicas ou quando usa procedimentos armazenados para conectar-se ao banco de dados. As medidas de segurança convencionais, como o uso de SSL e IPSec, não protegem seu aplicativo contra os ataques de injeção de SQL. Os ataques bem-sucedidos de injeção de SQL permitem que usuários mal-intencionados executem comandos no banco de dados de um aplicativo.

Nesta página
ObjetivosObjetivos
Visão geralVisão geral
Resumo das etapasResumo das etapas
Etapa 1. Restringir entradasEtapa 1. Restringir entradas
Etapa 2. Usar parâmetros com procedimentos armazenadosEtapa 2. Usar parâmetros com procedimentos armazenados
Etapa 3. Usar parâmetros com SQL dinâmicoEtapa 3. Usar parâmetros com SQL dinâmico
Considerações adicionaisConsiderações adicionais

Objetivos

Aprender como funcionam os ataques de injeção de SQL.

Restringir as entradas para evitar a injeção de SQL.

Usar parâmetros de comandos SQL com segurança de tipos para evitar a injeção de SQL.

Aprender contramedidas adicionais para reduzir ainda mais o risco.

Início da páginaInício da página

Visão geral

Um ataque bem-sucedido de injeção de SQL permite que um usuário mal-intencionado execute comandos no banco de dados do seu aplicativo usando os privilégios concedidos para logon no seu aplicativo. O problema será mais grave se o aplicativo usar uma conta com privilégios muito altos para conectar-se ao banco de dados. Por exemplo, se o logon do seu aplicativo tiver privilégios para eliminar um banco de dados, sem a proteção adequada um invasor poderia executar essa operação.

As vulnerabilidades comuns que tornam seu código de acesso a dados suscetível a ataques de injeção de SQL incluem:

Validação de entrada fraca.

Construção dinâmica de instruções SQL sem a utilização de parâmetros com segurança de tipos.

Uso de logons de banco de dados com privilégios muito altos.

Exemplo de injeção de SQL

Veja o que acontece quando um usuário digita a seguinte seqüência de caracteres na caixa de texto SSN (Número da previdência social), que espera um número no formato nnn-nn-nnnn.

' ; DROP DATABASE pubs  --

Usando a entrada, o aplicativo executa a seguinte instrução SQL dinâmica ou procedimento armazenado, que executa internamente uma instrução SQL semelhante.

// Use dynamic SQL
SqlDataAdapter myCommand = new SqlDataAdapter(
          "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + 
          SSN.Text + "'", myConnection);

// Use stored procedures
SqlDataAdapter myCommand = new SqlDataAdapter(
                                "LoginStoredProcedure '" + 
                                 SSN.Text + "'", myConnection);

A intenção do desenvolvedor era a de que, quando o código fosse executado, ele inserisse a entrada do usuário e gerasse um SQL com a seguinte instrução.

SELECT au_lname, au_fname FROM authors WHERE au_id = '172-32-9999'

Contudo, o código insere a entrada mal-intencionada do usuário e gera a seguinte consulta.

SELECT au_lname, au_fname FROM authors WHERE au_id = ''; DROP DATABASE pubs --'

Nesse caso, o caractere ' (aspas simples) que inicia a entrada não autorizada encerra o literal da seqüência de caracteres atual na instrução SQL. Ela fechará a instrução atual apenas se o próximo token analisado não fizer sentido como uma continuação da instrução atual, mas fizer sentido como o início de uma nova instrução. Sendo assim, o caractere de aspas simples de abertura da entrada não autorizada resulta na seguinte instrução.

SELECT au_lname, au_fname FROM authors WHERE au_id = ''

O caractere ; (ponto-e-vírgula) informa o SQL de que este é o fim da instrução atual, sendo seguido, então, pelo seguinte código SQL mal-intencionado.

; DROP DATABASE pubs

Observação: o ponto-e-vírgula não é necessariamente obrigatório para separar instruções SQL. Isso depende do fornecedor ou da implementação, mas o Microsoft SQL Server não o exige. Por exemplo, o SQL Server analisa o seguinte como duas instruções separadas:

SELECT * FROM MinhaTabela DELETE FROM MinhaTabela

Finalmente, a seqüência de caracteres -- (traço duplo) é um comentário SQL que informa o SQL para ignorar o restante do texto. Nesse caso, o SQL ignora o caractere ' (aspas simples) de fechamento, que, de outra maneira, ocasionaria um erro do analisador de SQL.

--'

Diretrizes

Para impedir os ataques de injeção de SQL, você precisa:

Restringir e corrigir os dados de entrada. Verifique os dados válidos conhecidos, validando o tipo, o comprimento, o formato e o intervalo.

Usar parâmetros SQL com segurança de tipos para o acesso a dados. Você pode usar esses parâmetros com procedimentos armazenados ou seqüências de caracteres de comandos SQL criadas dinamicamente. Coleções de parâmetros, como SqlParameterCollection, oferecem a verificação de tipos e a validação de comprimento. Se você usar uma coleção de parâmetros, a entrada será tratada como um valor literal e o SQL Server não a tratará como código executável. Um benefício adicional do uso da coleção de parâmetros é que a verificação de tipo e de comprimento pode ser imposta. Valores fora do intervalo disparam uma exceção. Esse é um bom exemplo de defesa eficiente.

Usar uma conta que tenha permissões restritas no banco de dados. De maneira ideal, você deve conceder apenas permissões de execução a determinados procedimentos armazenados no banco de dados e não fornecer nenhum acesso direto a tabelas.

Evitar revelar informações sobre erros do banco de dados. No caso de erros do banco de dados, certifique-se de não revelar as mensagens de erros detalhadas ao usuário.

Observação: as medidas de segurança convencionais, como o uso de SSL (Secure Socket Layer) e IPSec (Segurança IP), não protegem seu aplicativo contra os ataques de injeção de SQL.

Início da páginaInício da página

Resumo das etapas

Para proteger seu aplicativo contra injeções de SQL, execute as seguintes etapas:

Etapa 1. Restringir entradas.

Etapa 2. Usar parâmetros com procedimentos armazenados.

Etapa 3. Usar parâmetros com SQL dinâmico.

Início da páginaInício da página

Etapa 1. Restringir entradas

Você deve validar todas as entradas nos aplicativos ASP.NET por tipo, comprimento, formato e intervalo. Ao restringir as entradas usadas nas suas consultas de acesso a dados, você protege seu aplicativo contra injeções de SQL.

Restrinja entradas nas páginas da Web do ASP.NET

Inicie restringindo entradas no código do lado do servidor para suas páginas da Web do ASP.NET. Não conte com a validação do lado do cliente, pois ela pode ser facilmente ignorada. Use a validação do lado do cliente apenas para reduzir viagens de ida e volta e para aperfeiçoar a experiência do usuário.

Se você usar controles de servidor, use os controles do validador do ASP.NET, como os controles RegularExpressionValidator e RangeValidator, para restringir entradas. Se você usar controles de entrada HTML comuns, use a classe Regex no código do lado do servidor para restringir entradas.

Se, no exemplo de código anterior, o valor de SSN for capturado por um controle TextBox do ASP.NET, você poderá restringir sua entrada usando um controle RegularExpressionValidator, como mostrado a seguir.

<%@ language="C#" %>
<form id="form1" runat="server">
    <asp:TextBox ID="SSN" runat="server"/>
    <asp:RegularExpressionValidator ID="regexpSSN" runat="server"         
                                    ErrorMessage="Incorrect SSN Number" 
                                    ControlToValidate="SSN"         
                                    ValidationExpression="^\d{3}-\d{2}-\d{4}$" />
</form>

Se a entrada de SSN tiver outra origem, como um controle HTML, um parâmetro de seqüência de caracteres de consulta ou um cookie, você poderá limitá-la usando a classe Regex do namespace System.Text.RegularExpressions. O seguinte exemplo presume que a entrada é obtida de um cookie.

using System.Text.RegularExpressions;

if (Regex.IsMatch(Request.Cookies["SSN"], "^\d{3}-\d{2}-\d{4}$"))
{
    // access the database
}
else
{
    // handle the bad input
}

Para obter mais informações sobre como restringir entradas nas páginas da Web do ASP.NET, consulte Como: proteger-se contra ataques de injeção no ASP.NET.

Restrinja entradas no código de acesso a dados

Em algumas situações, é necessário fornecer validação em seu código de acesso a dados, talvez como uma adição à validação no nível de página do ASP.NET. Duas situações comuns nas quais você precisa fornecer validação no código de acesso a dados são:

Clientes não confiáveis. Se houver a possibilidade de os dados virem de uma origem não confiável ou se você não puder garantir a eficiência com que os dados foram validados e restritos, adicione uma lógica de validação que restrinja a entrada às suas rotinas de acesso a dados.

Código de biblioteca. Se o seu código de acesso a dados for empacotado como uma biblioteca criada para ser usada por vários aplicativos, seu código de acesso a dados deverá executar sua própria validação, porque você não pode fazer suposições seguras quanto aos aplicativos cliente.

O seguinte exemplo mostra como uma rotina de acesso a dados pode validar seus parâmetros de entrada usando expressões regulares antes de usar os parâmetros em uma instrução SQL.

using System;
using System.Text.RegularExpressions;

public void CreateNewUserAccount(string name, string password)
{
    // Check name contains only lower case or upper case letters, 
    // the apostrophe, a dot, or white space. Also check it is 
    // between 1 and 40 characters long
    if ( !Regex.IsMatch(userIDTxt.Text, @"^[a-zA-Z'./s]{1,40}$"))
      throw new FormatException("Invalid name format");

    // Check password contains at least one digit, one lower case 
    // letter, one uppercase letter, and is between 8 and 10 
    // characters long
    if ( !Regex.IsMatch(passwordTxt.Text, 
                      @"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$" ))
      throw new FormatException("Invalid password format");

    // Perform data access logic (using type safe parameters)
    ...
}
Início da páginaInício da página

Etapa 2. Usar parâmetros com procedimentos armazenados

A utilização de procedimentos armazenados não impede necessariamente a injeção de SQL. O que se deve fazer é usar parâmetros com procedimentos armazenados. Se você não usar parâmetros, os procedimentos armazenados poderão estar suscetíveis à injeção de SQL se usarem entrada não filtrada como descrito na seção "Visão geral" deste documento.

O seguinte código mostra como usar o SqlParameterCollection ao chamar um procedimento armazenado.

using System.Data;
using System.Data.SqlClient;

using (SqlConnection connection = new SqlConnection(connectionString))
{
  DataSet userDataset = new DataSet();
  SqlDataAdapter myCommand = new SqlDataAdapter( 
             "LoginStoredProcedure", connection);
  myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
  myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11);
  myCommand.SelectCommand.Parameters["@au_id"].Value = SSN.Text;

  myCommand.Fill(userDataset);
}

Nesse caso, o parâmetro @au_id é tratado como um valor literal e não como código executável. Além disso, o parâmetro é verificado quanto ao tipo e ao comprimento. No exemplo de código anterior, o valor da entrada não pode ter mais do que 11 caracteres. Se os dados não estiverem de acordo com o tipo ou o comprimento definido pelo parâmetro, a classe SqlParameter gerará uma exceção.

Analise o uso de procedimentos armazenados parametrizados pelo seu aplicativo

Como a utilização de procedimentos armazenados com parâmetros não impede necessariamente a injeção de SQL, você deve analisar o uso desse tipo de procedimento armazenado pelo seu aplicativo. Por exemplo, o seguinte procedimento armazenado parametrizado tem várias vulnerabilidades de segurança.

CREATE PROCEDURE dbo.RunQuery
@var ntext
AS
        exec sp_executesql @var
GO

Um aplicativo que use um procedimento armazenado semelhante ao do exemplo de código anterior tem as seguintes vulnerabilidades:

O procedimento armazenado executa qualquer instrução passada para ele. Considere a variável @var sendo definida como:

DROP TABLE ORDERS;

Nesse caso, a tabela ORDERS será eliminada.

O procedimento armazenado é executado com privilégios de dbo.

O nome do procedimento armazenado (RunQuery) é uma escolha ruim. Se um invasor puder sondar o banco de dados, ele ou ela verá o nome do procedimento armazenado. Com um nome como RunQuery, é possível supor que o procedimento armazenado provavelmente execute a consulta fornecida.

Início da páginaInício da página

Etapa 3. Usar parâmetros com SQL dinâmico

Se você não puder usar procedimentos armazenados, ainda deverá usar parâmetros ao criar instruções SQL dinâmicas. O seguinte código mostra como usar o SqlParameterCollection com SQL dinâmico.

using System.Data;
using System.Data.SqlClient;

using (SqlConnection connection = new SqlConnection(connectionString))
{
  DataSet userDataset = new DataSet();
  SqlDataAdapter myDataAdapter = new SqlDataAdapter(
         "SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", 
         connection);                
  myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11);
  myCommand.SelectCommand.Parameters["@au_id"].Value = SSN.Text;
  myDataAdapter.Fill(userDataset);
}

Usando o processamento de parâmetros em lotes

Um engano comum é pensar que se você concatenar várias instruções SQL para enviar um lote de instruções para o servidor em uma única viagem de ida e volta, não poderá usar parâmetros. Entretanto, você poderá usar essa técnica se certificar-se de que os nomes dos parâmetros não sejam repetidos. Você poderá fazer isso facilmente certificando-se de usar nomes de parâmetros exclusivos durante a concatenação de texto SQL, como mostrado aqui.

using System.Data;
using System.Data.SqlClient;
. . .
using (SqlConnection connection = new SqlConnection(connectionString))
{
  SqlDataAdapter dataAdapter = new SqlDataAdapter(
       "SELECT CustomerID INTO #Temp1 FROM Customers " +
       "WHERE CustomerID > @custIDParm; SELECT CompanyName FROM Customers " +
       "WHERE Country = @countryParm and CustomerID IN " +
       "(SELECT CustomerID FROM #Temp1);",
       connection);
  SqlParameter custIDParm = dataAdapter.SelectCommand.Parameters.Add(
                                          "@custIDParm", SqlDbType.NChar, 5);
  custIDParm.Value = customerID.Text;

  SqlParameter countryParm = dataAdapter.SelectCommand.Parameters.Add(
                                      "@countryParm", SqlDbType.NVarChar, 15);
  countryParm.Value = country.Text;

  connection.Open();
  DataSet dataSet = new DataSet();
  dataAdapter.Fill(dataSet);
}
. . .
Início da páginaInício da página

Considerações adicionais

Outros pontos a serem considerados ao desenvolver contramedidas para impedir a injeção de SQL incluem:

Usar uma conta de banco de dados com o mínimo possível de privilégios.

Evitar revelar informações sobre erros.

Use uma conta de banco de dados com o mínimo possível de privilégios

Seu aplicativo deve conectar-se ao banco de dados usando uma conta com o mínimo possível de privilégios. Se você usar a autenticação do Windows para se conectar, a conta do Windows deverá ter o mínimo possível de privilégios da perspectiva do sistema operacional e deverá ter privilégios e capacidade limitados para acessar os recursos do Windows. Além disso, usando ou não a autenticação do Windows ou a autenticação SQL, o logon correspondente do SQL Server deverá ser restrito pelas permissões no banco de dados.

Considere o exemplo de um aplicativo ASP.NET executado no Microsoft Windows Server 2003 que acessa um banco de dados em um servidor diferente no mesmo domínio. Por padrão, o aplicativo ASP.NET é executado em um pool de aplicativos sob a conta de serviço de rede. Essa conta tem o mínimo possível de privilégios.

Para acessar o SQL Server com a conta de serviço de rede

1.

Crie um logon do SQL Server para a conta de serviço de rede do servidor Web. A conta de serviço de rede tem credenciais de rede que são apresentadas no servidor do banco de dados como a identidade DOMÍNIO\NOMEDOSERVIDORWEB$. Por exemplo, se o domínio é chamado de XYZ e o servidor Web de 123, crie um logon de banco de dados para XYZ\123$.

2.

Conceda o novo acesso de logon ao banco de dados necessário criando um usuário e adicionando-o a uma função do banco de dados.

3.

Estabeleça permissões para permitir que essa função do banco de dados chame os procedimentos armazenados necessários ou acesse as tabelas necessárias no banco de dados. Conceda acesso apenas aos procedimentos armazenados que o aplicativo precisa usar, e conceda apenas o acesso suficiente às tabelas com base nos requisitos mínimos do aplicativo.

Por exemplo, se o aplicativo ASP.NET apenas executa consultas no banco de dados e não atualiza dados, você precisa conceder apenas acesso de leitura às tabelas. Isso limita os danos que um invasor poderá causar se ele for bem-sucedido em um ataque de injeção de SQL.

Evite revelar informações sobre erros

Use a manipulação de exceções estruturada para detectar erros e evitar que se propaguem de volta para o cliente. Registre informações detalhadas sobre erros localmente, mas retorne detalhes limitados sobre os erros ao cliente.

Se ocorrerem erros enquanto o usuário está se conectando ao banco de dados, certifique-se de fornecer ao usuário apenas informações limitadas sobre a natureza do erro. Se você revelar informações relacionadas a erros do banco de dados e de acesso a dados, poderá fornecer a um usuário mal-intencionado informações úteis que poderão ser usadas para comprometer a segurança do seu banco de dados. Os invasores usam as informações em mensagens de erro detalhadas para ajudar a desconstruir uma consulta SQL na qual estejam tentando injetar código mal-intencionado. Uma mensagem de erro detalhada pode revelar informações valiosas como a seqüência de conexão, o nome do servidor SQL ou convenções de nomenclatura do banco de dados e da tabela.

Comentários

Envie comentários usando um Wiki ou email:

Wiki. Use a página Security Guidance Feedback Wiki em http://channel9.msdn.com/wiki/default.aspx/Channel9.SecurityGuidanceFeedback

Email. Envie um email para mailto:%20secguide@microsoft.com.

Estamos particularmente interessados em comentários relativos ao seguinte:

Problemas técnicos específicos a nossas recomendações

Problemas de uso e de aplicação

Suporte técnico

O suporte técnico para produtos e tecnologias da Microsoft mencionados nestas orientações é fornecido pelos Serviços de suporte da Microsoft. Para obter informações sobre suporte, visite o site de Suporte da Microsoft no endereço http://msdn.microsoft.com/security/default.aspx?pull=/isapi/gosupport.asp?Target=/.

Comunidade e grupos de notícias

O suporte da comunidade é fornecido nos fóruns e grupos de notícias:

Grupos de notícias do MSDN:http://msdn.microsoft.com/security/default.aspx?pull=/newsgroups/default.asp

Fóruns do ASP.NET:http://forums.asp.net/

Para obter o benefício máximo, localize o grupo de notícias correspondente à sua tecnologia ou ao seu problema. Por exemplo, se você tiver um problema com os recursos de segurança do ASP.NET, deverá usar o fórum de segurança do ASP.NET.

Colaboradores e revisores

Colaboradores e revisores externos: Andy Eunson; Chris Ullman, Content Master; David Raphael, Foundstone Professional Services; Rudolph Araujo, Foundstone Professional Services; Manoranjan M. Paul

Colaboradores e revisores dos Serviços de consultoria e PSS da Microsoft: Wade Mascia, Tom Christian, Adam Semel, Nobuyuki Akama, Microsoft Corporation

Colaboradores e revisores do Grupo de produtos da Microsoft: Stefan Schackow, Vikas Malhotra, Microsoft Corporation

Colaboradores e revisores do MSDN: Kent Sharkey, Microsoft Corporation

Colaboradores e revisores de TI da Microsoft: Eric Rachner, Rob Beck, Shawn Veney (Equipe ACE), Microsoft Corporation

Equipe de teste: Larry Brader, Microsoft Corporation; Nadupalli Venkata Surya Sateesh, Sivanthapatham Shanmugasundaram, Sameer Tarey, Infosys Technologies Ltd

Equipe de edição: Nelly Delgado, Microsoft Corporation; Sharon Smith, Tina Burden McGrayne, Linda Werner & Associates Inc

Gerenciamento de versões: Sanjeev Garg, Microsoft Corporation


Início da páginaInício da página