Há muito tempo, numa web distante, nascia o SQL Injection

Entenda como funciona o SQL Injection e como se proteger contra ele

Tempest Security
Nov 19 · 12 min read

Por Vinicius Moraes

SQL Injection (ou SQLi) é um tipo de Injeção de código (code injection) em que é possível manipular consultas SQL realizadas por uma aplicação em um banco de dados. Um problema que já era discutido ao menos desde 1998 e que ainda hoje afeta muitas aplicações.

Falhas de Injeção (SQL, NoSQL, OS, LDAP, etc) estão no primeiro lugar do OWASP Top Ten 2017, que atualmente é a versão mais recente de um documento que lista os maiores riscos de segurança para aplicações web.

E de fato, a partir de um SQL Injection é possível realizar uma série de ataques perigosos: como obter o controle de leitura e escrita sobre um banco de dados; e, em alguns casos, é possível conseguir uma execução de comandos remotos na máquina — do inglês Remote Command Execution (RCE), como veremos adiante.

É importante ressaltar que esse problema não se restringe nem a uma linguagem, nem a um banco de dados específicos. Dito isso, todos os exemplos que irão ser demonstrados utilizam a linguagem PHP e o banco de dados MySQL por opção do autor.

Vamos então ao nosso primeiro exemplo. Considere o trecho de código vulnerável abaixo:

Image for post
Image for post
Exemplo 1

Inicialmente, podemos notar que esse código é de uma funcionalidade de autenticação. Ele está recebendo uma credencial (username e password), e a partir daí, usando-a para tentar recuperar dados do usuário no banco de dados. Como o valor fornecido pelo usuário está sendo utilizado diretamente na criação da consulta SQL, sem a realização de qualquer tipo de tratamento, podemos ver que o exemplo está vulnerável ao SQL Injection.

Para explorar a falha do exemplo 1, um atacante pode submeter, como credenciais da aplicação, os valores admin’ — e password123. O símbolo ‘ (aspa simples) causará o fechamento da string que originalmente deveria conter o nome do usuário; e então os sinais de comentário — (dois hífens seguidos por um espaço) farão com que a consulta SQL resultante, que é ilustrada a seguir, não realize a verificação de senha, assim permitindo a autenticação apenas com o nome do usuário:

Image for post
Image for post
Imagem 1 — Consulta realizada ao explorar o SQLi que verifica apenas o nome do usuário para realizar a autenticação.

Além disso, o ponto afetado também poderia ser um campo numérico. Vejamos este outro caso:

Image for post
Image for post
Exemplo 2

Por sua vez, esse código busca um documento pertencente ao usuário que corresponda ao id informado por ele.

Visto que a consulta também é construída a partir da entrada do usuário, sem nenhum tipo de tratamento, um atacante pode submeter o valor 1 OR 1=1 no campo id, resultando em um WHERE de condição sempre verdadeira e consequentemente fazendo com que todos os documentos sejam retornados. Isso não só permite ao atacante o acesso aos documentos de outros usuários; como também, a depender do volume de dados armazenados, pode causar uma negação de serviço (do inglês Denial Of Service, DoS).

Image for post
Image for post
Imagem 2 — A consulta que será executada ao receber o valor malicioso 1 OR 1=1

É interessante destacar que o SQLi não se limita apenas ao SELECT, ele pode ocorrer em outras instruções como INSERT, UPDATE, DELETE. Além disso, analisando a funcionalidade fornecida na aplicação, muitas vezes é fácil deduzir qual instrução foi utilizada.

Image for post
Image for post
Exemplo 3

O exemplo 3 é de uma funcionalidade que cria documentos. Aqui, caso um atacante inserir o valor opa.doc”,user()); — no campo do nome, será criado um documento com o nome opa.doc, que conterá em seu conteúdo o resultado da função user().

Image for post
Image for post
Imagem 3 — O comando SQL resultante de uma exploração no exemplo 3

Até agora, observamos formas simples de exploração de um SQL Injection. A seguir, veremos estratégias mais avançadas que podem ser empregadas de acordo com as características de cada sistema.

Na linguagem SQL existe o operador UNION, que une o resultado de múltiplas instruções SELECT. Assim, quando uma injeção acontece em um SELECT, é possível utilizar esse operador em uma técnica conhecida como Union-based, para criar uma nova consulta.

No entanto, deve-se atentar que, para usar o UNION, a segunda consulta tem que retornar a mesma quantidade de colunas da consulta original; além disso, elas devem ter tipos de dados compatíveis. Isso pode ser resolvido por tentativa e erro, ao incrementar o número de elementos da segunda consulta com valores null. Como é demonstrado abaixo, em uma exploração contra o exemplo 2:

Image for post
Image for post
Imagem 4 — Exploração Union-based

Adicionalmente, o UNION pode ser utilizado em conjunto com outras técnicas, como será ilustrado na Imagem 12. E ele também pode criar a necessidade de se conhecer os nomes das tabelas e colunas do banco de dados, o que pode ser resolvido através da leitura de tabelas padrões que contém essas informações (information_schema.tables em MSSQL e MySQL, all_tables em Oracle).

Stacked queries é o nome de uma funcionalidade que permite a realização de múltiplas operações SQL de uma vez só. Então, no cenário de exploração de uma falha SQLi, é possível utilizar o operador ; (ponto e vírgula) para finalizar a consulta original e então criar uma nova.

No entanto, esse método nem sempre retorna o resultado da consulta injetada, visto que a aplicação pode ter sido desenvolvida para devolver apenas a saída de uma consulta. Logo, pode ser interessante utilizá-lo em conjunto com outras técnicas, como as Out-Of-Band, que serão abordadas mais adiante. Além disso, stacked queries só são suportadas por alguns bancos de dados (MSSQL suporta, enquanto MySQL e Oracle não suportam).

Image for post
Image for post
Imagem 5 — Uma URL que se aproveita de stacked queries para deletar os dados da tabela users

Quando uma aplicação expõe mensagens de erro referentes ao banco de dados, a técnica Error-based pode ser utilizada, com o objetivo de se aproveitar dessas mensagens para realizar uma exploração.

Considere o código vulnerável abaixo:

Image for post
Image for post
Exemplo 4

Ao inserir caracteres que quebram a consulta, é possível notar o surgimento de mensagens de erro do banco de dados na resposta da aplicação.

Image for post
Image for post
Imagem 6 — Mensagem de erro do banco de dados na resposta da aplicação web

Para realizar esse tipo de exploração, o banco de dados é um fator que precisa ser considerado. No caso do MySQL, podemos forçar uma mensagem de erro no parser XML dele, através da função extractvalue que espera um XML no primeiro parâmetro; e no segundo, uma expressão que será usada para buscar um valor no XML, isto é, um XPath.

Como o XPath tem uma sintaxe bem definida, é possível iniciá-lo com um caractere inesperado, como um ponto final ‘.’, e o concatenar com uma consulta válida, que terá seu resultado retornado na mensagem de erro. Veja o caso a seguir:

Image for post
Image for post
Imagem 7 — Exploração Error-based

Quando uma aplicação web vulnerável não envia nas suas respostas (o resultado das consultas SQL ou mensagens de erro do banco de dados) o SQL Injection é categorizado como Blind, e por isso, pode ser chamado de Blind SQL Injection.

Como ilustrado a seguir, é possível reproduzir esse cenário ao modificar um pouco o exemplo 4:

Image for post
Image for post
Exemplo 5 — Aplicação vulnerável a Blind SQL Injection.

Nos próximos tópicos, veremos alguns subtipos que existem no Blind.

No exemplo 5, a aplicação busca documentos pelo nome e se comporta de forma distinta de acordo com a existência deles. Nesse contexto, é interessante o uso da técnica Boolean-based, que se aproveita dessa diferença de respostas para avaliar expressões, e com isso, extrair informações.

Então, ao se inserir um nome de documento que já existe na aplicação (doc1), seguido por um operador AND com uma expressão a ser avaliada, é possível descobrir caractere por caractere do resultado de uma expressão, como demonstrado abaixo:

Image for post
Image for post
Imagem 8 — Procedimento para obter o primeiro caractere de @@version. É interessante utilizar o algoritmo de busca binária para reduzir a quantidade de requisições realizadas

Prosseguindo para o nosso sexto exemplo vulnerável, desta vez a aplicação retorna sempre a mesma resposta, independente do valor submetido pelo usuário. Para isso, as funções mysqli_fetch_array e die do exemplo anterior foram removidas, como é demonstrado abaixo:

Image for post
Image for post
Exemplo 6 — Um código vulnerável que sempre vai fornecer a mesma resposta.

Com isso, o retorno da aplicação independe da consulta SQL, de modo a ser necessário utilizar uma outra fonte de dados para a exploração — o tempo de resposta — em uma técnica conhecida como Time-based.

A ideia do Time-based é semelhante a da técnica Boolean-based; porém agora é necessária a utilização de uma função que irá executar um sleep (gerando uma diferença no tempo de resposta), a depender do resultado da expressão avaliada.

No MySQL, existe a função IF, que recebe uma expressão a ser avaliada no primeiro argumento; e de acordo com o resultado dela, escolhe o segundo ou o terceiro argumento para ser retornado. Assim, é possível realizar a exploração Time-based, como demonstrado a seguir:

Image for post
Image for post
Imagem 9 — Exploração Time-based

Na imagem acima, é possível notar que quando a expressão do IF está incorreta, a função sleep aumenta 2 segundos no tempo de resposta; quando não, a resposta retorna no seu tempo normal, então essa diferença de comportamento possibilita a identificação do primeiro caractere retornado pela função user(), ‘r’.

Por fim, chegamos ao último exemplo de exploração, que acontece quando o ponto vulnerável não permite nem mesmo a contabilização do tempo da consulta SQL. O que pode ser reproduzido ao modificar a função mysqli_query que foi utilizada no exemplo 6, como ilustrado a seguir:

Image for post
Image for post
Exemplo 7

Nesse exemplo, a funcionalidade é assíncrona, isto é, a aplicação não aguarda o término da consulta no banco para responder ao usuário. Então, é necessário tentar um grupo de técnicas conhecidas como Out-of-Band, que para obter os dados do ataque utilizam um canal de comunicação diferente do que é utilizado para realizar o ataque.

Uma boa opção é criar uma consulta que ao ser executada envia o seu resultado para um servidor externo na internet. E por mais que o MySQL não tenha diretamente implementado uma função para realizar esse tipo de comunicação, ele fornece funcionalidades de manipulação de arquivos, que no Windows utilizam a função CreateFile.

CreateFile é um API do Windows que, através da implementação realizada pelo MySQL, permite não só o acesso aos arquivos da própria máquina (nesse caso o servidor rodando o MySQL), como também possibilita o acesso a arquivos na rede. Isso significa que quando o MySQL está rodando no Windows, é possível criar uma consulta que envia dados pela internet. Como é demonstrado abaixo:

Image for post
Image for post
Imagem 10 — Exploração Out-of-Band de SQLi

Na imagem 10, através da função load_file, o MySQL tenta ler um arquivo do domínio hrcxgmpghhpj6spml8g1nklullrff4.burpcollaborator.net pelo protocolo SMB; porém, antes ele precisa realizar uma consulta DNS desse domínio. E ao fazer isso, o valor de @@version é enviado para um servidor DNS, como se fosse o nome de um subdomínio, o que pode ser observado na imagem a seguir:

Image for post
Image for post
Imagem 11 — Servidor DNS recebendo o resultado de @@version, “8.0.21”.

Mesmo que a exploração anterior não possa ser realizada em distribuições Linux, ainda existe a possibilidade de manipular arquivos locais, que não se restringe a um sistema operacional. Nesse cenário, se a máquina estiver rodando um servidor web e for possível escrever em um diretório dele, o resultado de uma consulta pode ser salvo em um arquivo e posteriormente acessado, como demonstrado abaixo:

Image for post
Image for post
Imagem 12 — Obtendo o resultado de uma consulta através da escrita de arquivos.

Uma opção ainda mais interessante nesse caso, é utilizar a permissão de escrita em arquivos para obter um RCE. Para isso, basta enviar um código feito em uma linguagem suportada pelo servidor web (no nosso exemplo, o PHP), que irá executar, no sistema operacional do alvo, um comando enviado pelo usuário, de modo a retornar o resultado: enviar o payload de uma web shell.

Image for post
Image for post
Imagem 13 — Criando uma web shell a partir de um SQLi

Nesse momento, é possível pensar que técnicas Out-of-Band são sempre as melhores opções, ao menos quando se fala de Blind SQL Injection. No entanto, isso não é verdade, pois são muitos os fatores que podem torná-las inviáveis, como por exemplo, a falta de permissão para escrever em diretórios. O que no MySQL pode ser causada por um valor restritivo (que já é o padrão de instalação) na variável secure_file_priv; pela falta da permissão FILE no usuário do banco; ou ainda, pela falta da permissão de escrita para o usuário que executa o banco de dados no sistema operacional.

Logo, quando o assunto é exploração de SQLi “balas de prata” realmente não existem; no lugar delas, existem diversas técnicas cada qual ideal para lugar e momento específicos.

Sqlmap é o nome de uma ferramenta muito popular, escrita em Python e de código aberto, que serve para identificação e exploração de SQL Injection. Ele é muito interessante para automatizar ataques; pois permite, por exemplo, explorar de forma simples um ponto que precisa da técnica Time-based, abstraindo todo o trabalho necessário para a leitura de cada caractere.

Então, vamos ver um caso de uso do sqlmap contra o exemplo 6, que foi explorado anteriormente com a técnica Time-based:

Image for post
Image for post
Image for post
Image for post
Imagem 14 .1 e 14.2— Executando o sqlmap contra o exemplo 6.

As opções — dump e — stop 3 fazem, respectivamente, com que os dados das tabelas do banco sejam recuperados e que eles se limitem a 3 entradas.

Já a opção -u (abreviação de — url) define o alvo, para casos mais complexos ela pode ser substituída por -r, seguida pelo caminho de um arquivo que contém a requisição HTTP que será realizada.

Além disso, por padrão, o sqlmap já testa todos os parâmetros da URL de um GET, e todos do body de um POST, mas é possível especificar outros pontos.

Quando o objetivo é corrigir o SQL Injection, a melhor opção é conhecida como consultas parametrizadas, do inglês parameterized queries, também chamada de prepared statements.

Consultas parametrizadas são uma forma que a aplicação tem de previamente estruturar as operações que serão realizadas no banco de dados. Elas não são um recurso exclusivo de uma tecnologia, e ao serem utilizadas necessitam que seja indicado o local onde a entrada do usuário será inserida, isto é, um placeholder. Dessa forma, é possível corrigir completamente falhas de SQLi, como no caso do exemplo 1, agora corrigido:

Image for post
Image for post
Exemplo 8 — Código protegido contra SQLi. Aqui, ainda há o problema de armazenamento de senhas em texto plano, mas isso é assunto para outra ocasião

Adicionalmente, consultas parametrizadas também trazem uma melhoria de performance. Isso ocorre pois existem diversos passos no tratamento de uma consulta SQL. Basicamente, primeiro é realizada uma checagem que verifica se ela faz sentido (análise sintática e semântica); em seguida ela é compilada, gerando uma linguagem que máquinas entendem; para então ela ser executada.

Todas essas etapas se repetem sempre que uma consulta padrão acontece. Já no caso das consultas parametrizadas, o banco de dados pode guardar o código que foi gerado em cada operação, e assim pular direto para a etapa de execução. Como a compilação não é realizada novamente, todos os placeholders são lidos puramente como dados através de um protocolo binário, diferente do padrão em que a consulta completa é enviada ao banco de dados. Assim, os dados do usuário não conseguem alterar a lógica da consulta e causar injeções de SQL. Porém, isso também limita as partes da consulta que podem receber as entradas do usuário. Por exemplo, no caso de uma aplicação que define a tabela utilizada no ORDER BY, utilizando diretamente o dado enviado pelo usuário, não seria possível fazer uso de consultas parametrizadas.

Para resolver isso, é possível utilizar uma allowlist que irá restringir os dados recebidos, aceitando apenas um número conhecido e limitado de valores, e assim impedindo que comandos SQL maliciosos sejam injetados. O código abaixo ilustra essa solução, em uma funcionalidade que lista usuários:

Image for post
Image for post
Exemplo 9 — Uma listagem de usuários que utiliza consultas parametrizadas e allowlist para se proteger de SQLi

A imagem, a seguir, demonstra o exemplo 9 em funcionamento:

Image for post
Image for post
Imagem 15 — Testando a listagem de usuários do exemplo 9

Por fim, é importante lembrar que todos os dados externos recebidos por terceiros são perigosos, e por tanto, nunca devem ser utilizados na construção de uma consulta SQL sem os devidos tratamentos que foram abordados.

Até a próxima!

Notícias e análises sobre segurança da informação…

Tempest Security

Written by

Empresa líder no mercado brasileiro de segurança da informação e combate a fraudes digitais, atuando desde o ano 2000.

SideChannel-BR

Notícias e análises sobre segurança da informação produzidas pela equipe e por amigos da Tempest Security Intelligence

Tempest Security

Written by

Empresa líder no mercado brasileiro de segurança da informação e combate a fraudes digitais, atuando desde o ano 2000.

SideChannel-BR

Notícias e análises sobre segurança da informação produzidas pela equipe e por amigos da Tempest Security Intelligence

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store