SQL Injection, como o DBA pode se proteger?

Depois da empolgante (e até um pouco aterrorizante) palestra “Invadindo o SQL Server? DBA Vs. Hacker” apresentada pelo Luan Moreno (blog|twitter) e pelo Lenon Leite (twitter) no XIX encontro do grupo SQLServerDF, me senti quase obrigado a falar sobre segurança. Logo, neste post irei falar sobre algumas dicas e formas para o DBA se prevenir de um possível ataque através de uma SQL Injection.

No entanto, antes de qualquer coisa, o que é SQL Injection? SQL Injection, ou Injeção de SQL, é uma técnica de inserção de código malicioso que explora vulnerabilidades na segurança da aplicação ou da base de dados. Geralmente esses ataques ocorrem quando as entradas de dados dos usuários não são devidamente tratadas pela aplicação ou quando a consulta é construída dinamicamente, mas é claro que os ataques não são limitados apenas a essas formas.

SQL dinâmico é um código ou comando que é executado dinamicamente (Duh?!). Em outras palavras, é quando se concatena linhas de código com entradas do usuário para montar um comando SQL “na hora”, ou, dinamicamente. Codificar utilizando o SQL dinâmico traz grande flexibilidade e, quando bem aplicado, pode gerar planos de execução mais eficientes. Mas, como nem tudo na vida são flores, a manutenção de códigos com SQL dinâmico é bem mais complicada e esse código, quando utilizado indevidamente, pode gerar brechas enormes na segurança do banco de dados e uma baixíssima taxa de reutilização de planos de execução. Sem prolongar mais esta introdução, vamos demonstrar como pode acontecer um ataque por SQL Injection.

Vamos supor que uma locadora de filmes empresa que oferece serviços de TV pela internet tem um sistema de busca de filmes no seu catálogo. Nesse sistema o usuário pode procurar o filme por título, diretor ou gênero. Agora, vamos supor que esta busca é feita por uma stored procedure como a abaixo.

CREATE PROCEDURE [dbo].[RetornaFilme](
	@Titulo  VARCHAR(MAX) = NULL,
	@Diretor VARCHAR(MAX) = NULL,
	@Genero  VARCHAR(MAX) = NULL)
AS

DECLARE @sql VARCHAR(MAX);

SET @sql =      'SELECT [Titulo],[Diretor],[Genero] '+
                'FROM [dbo].[Filmes] '+
                'WHERE (1=1) ';

IF(@Titulo IS NOT NULL)
SET @sql = @sql + 'AND [Titulo] LIKE '''+@Titulo+'''';

IF(@Diretor IS NOT NULL)
SET @sql = @sql + 'AND [Diretor] LIKE '''+@Diretor+'''';

IF(@Genero IS NOT NULL)
SET @sql = @sql + 'AND [Genero] LIKE '''+@Genero+'''';

EXEC(@sql)
GO

Esse tipo de consulta acaba sendo bem comum quando utilizamos filtros dinâmicos. Vale ressaltar que esta SP não segue boas práticas de programação. Em breve ficarão evidentes os motivos.

Então, supondo que estejamos atrás de filmes de aventura, basta executarmos nossa busca.

RetornaFilme_01

Tudo dentro do esperado! Agora que vemos que ela funciona, que tal vermos se aquele filme Rock ‘n’ Rolla está no catálogo?

RetornaFilme_02

Com esta mensagem de erro, evidenciamos uma brecha na segurança em nossa SP. “Mas… o que deu errado?” Um dos problemas aqui é o tratamento (ou falta de) da entrada do usuário. Se imprimirmos o comando que está sendo executado, temos o seguinte.

SELECT [Titulo],[Diretor],[Genero]
FROM [dbo].[Filmes]
WHERE (1=1) AND [Titulo] LIKE 'Rock 'n' Rolla';

Aqui fica claro que a letra “n” ficou fora da string. Isso já foi o suficiente para gerar um erro que demonstra que esta SP não trata nomes que contenham aspas simples. Através deste tipo brechas que hackers vão inserir trechos de código malicioso. Eis um exemplo de código malicioso para obter mais informação que o esperado.

RetornaFilme_03

“O que é esse result… Essas são as tabelas no banco e suas colunas?!” Exatamente. Uma relação de todas as tabelas criadas e suas colunas. Depois de algumas tentativas e um pouco de conhecimento em banco de dados – e pode ter certeza que hackers tem tanto tempo quanto o google à disposição deles – um usuário malicioso poderia entrar com esse parâmetro e voilà. Agora ele já sabe o que tem dentro da sua base de dados. Agora basta ajustar novamente o parâmetro e…

RetornaFilme_04

Neste momento o sorriso está estampado na cara do hacker, afinal de contas, agora ele tem todos os dados de todos os usuários cadastrados nesta empresa. Quanto será que isso deve valer para a empresa concorrente? Bastante, imagino. Mas vamos ver o que ocorreu com essa SP para que houvesse esta brecha. Imprimindo o parâmetro “@sql” para este último caso temos:

SELECT [Titulo],[Diretor],[Genero]
FROM [dbo].[Filmes] WHERE (1=1) AND [Titulo] LIKE 'a'
UNION
SELECT nome,email,conta_bancaria
FROM Usuario --'

Pelo fato da execução do código ser baseada na concatenação de strings, observa-se que o trecho de código malicioso fecha a busca por título – com a’ – e então utiliza o operador UNION para trazer informações adicionais que neste caso são bem sensíveis. Depois ele utiliza o comentário de linha “- -“ para anular qualquer código posterior.

Outro exemplo mais agressivo de ataque é quando o código malicioso tenta alterar ou apagar algo dentro da sua base ou até em seu servidor. O objetivo pode ser apagar o catálogo de filmes, os registros dos usuários ou só dificultar a sua vida como DBA.

EXEC [dbo].[RetornaFilme] @Titulo = 'a''; DROP TABLE Filmes; --';

Mas é claro que sempre é possível piorar. Então, aviso antes mesmo de colocar o comando abaixo: Não execute o código abaixo em lugar nenhum (Não, nem mesmo assim). A SP xp_cmdshell é capaz de executar comandos em shell como se estivesse no command prompt do Windows. Dependendo do nível de permissão do usuário que executa esta SP, ele pode fazer o que quiser com a máquina. Então, depois de avisar que você não deve brincar com essa SP, eis a possível injeção de SQL.

EXEC [dbo].[RetornaFilme]
@Titulo = 'a''; EXEC master.dbo.xp_cmdshell ''format d:''--';

Calma. A SP xp_cmdshell é desativada por padrão a partir do SQL Server 2005, mas se o invasor conseguir privilégios de sysadmin ele pode mudar isso (Aí sim, já era). No entanto, apesar do potencial destrutivo desta SP, é comum encontrá-la ativada para usos administrativos – o que também não é considerado uma boa prática de segurança.

Como vimos, a utilização de SQL dinâmico pode gerar uma brecha enorme na segurança e nosso objetivo neste artigo é saber como reduzir essa superfície de ataque ao máximo para evitarmos uma injeção de SQL. Então vejamos algumas medidas que podemos tomar!

1.Dimensione e determine as variáveis apenas de acordo com o necessário. Variáveis fortemente tipadas diminuem a liberdade do invasor ao inserir código malicioso. No nosso caso, o tipo VARCHAR(MAX) dá liberdade total para o ataque.

2.Valide toda e qualquer entrada do usuário. Aproveitando até os tipos bem definidos, utilize funções de verificação. Se o tipo deve ser numérico, use ISNUMERIC(). Se for data, ISDATE(), e assim por diante. Em caso de strings, utilize REPLACE() para substituir aspas simples por aspas duplas.

3.Utilize prints para debugar o código. Ao acrescentar uma forma de debugar o resultado da concatenação de strings, vemos com muito mais clareza possíveis brechas no código. Além disso, fica muito mais fácil realizar uma manutenção!

4.Dê o mínimo de permissão ao usuário que executar a SP. Aqui é mais um controle de danos. Pois, por mais que o invasor consiga entrar com algum código malicioso, ele não conseguirá fazer muito estrago.

Então se ajustarmos nossa consulta de acordo com essas premissas, teríamos algo como o código abaixo.

CREATE PROCEDURE [dbo].[RetornaFilme](
	@Titulo  VARCHAR(100) = NULL,
	@Diretor VARCHAR(50)  = NULL,
	@Genero  VARCHAR(20)  = NULL,
	@Debug   BIT          = 1)
AS

DECLARE @sql VARCHAR(MAX);

SET @sql =      'SELECT [Titulo],[Diretor],[Genero] '+
                'FROM [dbo].[Filmes] '+
                'WHERE (1=1) ';

IF(@Titulo IS NOT NULL)
SET @sql = @sql + 'AND [Titulo] LIKE '''+REPLACE(@Titulo,'''','''''')+'''';

IF(@Diretor IS NOT NULL)
SET @sql = @sql + 'AND [Diretor] LIKE '''+REPLACE(@Diretor,'''','''''')+'''';

IF(@Genero IS NOT NULL)
SET @sql = @sql + 'AND [Genero] LIKE '''+REPLACE(@Genero,'''','''''')+'''';

IF(@Debug = 1) PRINT @sql;
EXEC(@sql) AS USER = 'usuario_acesso_minimo';
GO

Desta forma diminuímos consideravelmente a superfície de ataque do invasor. No entanto, ainda estamos gerando uma baixíssima taxa de reutilização dos planos de execução. Afinal de contas, somente se os parâmetros usados forem idênticos o plano de execução poderá ser reutilizado. Então vamos para a última – e talvez mais importante – dica:

1.Sempre que possível, utilize sp_executesql ao invés de EXEC(). A stored procedure sp_executesql, por ser parametrizada, facilita a reutilização dos planos de execução e, como iremos ver, acaba blindando as entradas do usuário dentro das variáveis.

Reformulando nossa consulta para utilizar a sp_executesql, temos o código a seguir.

CREATE PROCEDURE [dbo].[RetornaFilme](
	@Titulo  VARCHAR(100) = NULL,
	@Diretor VARCHAR(50)  = NULL,
	@Genero  VARCHAR(20)  = NULL,
	@Debug   BIT          = 1)
AS

DECLARE @sql NVARCHAR(MAX);
DECLARE @params NVARCHAR(MAX);

SET @sql =  'SELECT [Titulo],[Diretor],[Genero] '+
            'FROM [dbo].[Filmes] '+
            'WHERE (1=1) ';

SET @params = '@Titulo  VARCHAR(100) = NULL,'+
              '@Diretor VARCHAR(50)  = NULL,'+
              '@Genero  VARCHAR(20)  = NULL';

IF(@Titulo IS NOT NULL)
SET @sql = @sql + 'AND [Titulo] LIKE @Titulo ';

IF(@Diretor IS NOT NULL)
SET @sql = @sql + 'AND [Diretor] LIKE @Diretor ';

IF(@Genero IS NOT NULL)
SET @sql = @sql + 'AND [Genero] LIKE @Genero ';

IF(@Debug = 1) PRINT @sql;
EXEC sp_executesql @sql, @params, @Titulo, @Diretor, @Genero
GO

Pronto! Além de termos um código mais limpo e simples, nossos planos de execução serão reutilizados com muito mais frequência graças à parametrização que agora é possível!

Neste artigo, vimos como o DBA pode se proteger melhor de uma SQL Injection e digo “se proteger melhor” pois, é muito difícil – se não impossível – se proteger 100% contra esse tipo de ataque. No entanto, diminuímos a superfície de ataque de possíveis invasores e ainda parametrizamos nossa consulta para uma maior reutilização dos planos de execução. Mas, como esse tópico me deixou com mais vontade de ler e estudar sobre o assunto, deixo aqui alguns lugares para que você continue sua leitura.

1.Consultas parametrizadas, ISNULL e SQL dinâmica, um post do Gustavo Maia Aguiar que explora um pouco mais sobre implementações de consultas parametrizadas e seus efeitos nos planos de execução.

2.The Curse and Blessings of Dynamic SQL, um artigo excelente do Erland Sommarskog que menciona SQL Injection, boas práticas quando utilizar SQL dinâmico e casos comuns de quando não se utilizar SQL dinâmico.

3.Inside Microsoft SQL Server 2008: T-SQL Programming, livro de Itzik Ben-Gan e companhia que considero leitura obrigatória para qualquer DBA/AD/Desenvolvedor que trabalhe com T-SQL.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s