SQL Injection
É uma vulnerabilidade web que permite ao atacante interferir nas queries (consultas) feitas para o banco de dados de certa aplicação.
Em geral, o atacante consegue visualizar dados armazenados no banco de dados que ele não deveria poder ver (informação de outros usuários, dados da aplicação, senhas etc.). Além disso, pode ser que o atacante consiga modificar e deletar dados do DB e, em casos mais extremos, escalar o ataque para comprometer o funcionamento do back-end da aplicação ou performar um ataque DoS. De forma similar, ele pode subverter a lógica de certas aplicações e, por exemplo, passar pelo portal de login de algum site sem ter uma conta.
Exemplos de SQL Injections
Vulnerabilidades desse tipo apresentam-se nas mais diferentes formas. Para cada situação de ataque, é possível que haja uma abordagem diferente.
Usaremos como base os exemplos da Portswigger, a fim de explicar um pouco sobre diferentes tipos de SQL Injections.
Exemplo 1: modificando uma query para obter dados adicionais
Considere uma aplicação que distribui seus produtos em categorias. Quando o usuário clica na categoria "Presentes", o navegador manda uma requisição para o servidor da forma:
Em seguida, a aplicação fará uma consulta no banco de dados, buscando os produtos da categoria "Presentes". A query utilizada seria, por exemplo:
Essa consulta quer obter todas as informações (*), ou seja, todas as colunas da base de dados, referentes à tabela "products" nas quais a categoria do produto ("category" é uma das colunas) é "Gifts".
Considerando que a aplicação não implementa defesas contra SQL Injections, um atacante poderia construir uma entrada do tipo Gifts'+OR+1=1--
e colocá-la na URL:
Isso resultado na query:
Obs.: o +
transforma-se em espaço
Nesse caso, a consulta quer obter todas as informações (*) da tabela de produtos ("products"), mas só da categoria "Gifts" ou 1 = 1. Num primeiro momento isso pode parecer estranho, mas essa query está buscando todas as informações da tabela de produtos de todos os produtos, porque a condição para um produto ser selecionado pela consulta é que ele deve ser da categoria "Gifts" ou 1 deve ser igual à 1, o que sempre é verdade!. Dessa forma, o atacante recebe como resposta todas as entradas da tabela produtos.
Além disso, note que há um --
ao final da entrada do atacante. Isso representa, em certos bancos de dados (e nesse caso), um comentário. Logo, tudo que estiver escrito após isso será considerado um comentário. Isso é necessário para que a query termine com o código do atacante e ignore todo o resto. Por exemplo, se a consulta fosse
Se o resto da consulta (' AND (price < 100)
) não for comentado, o atacante não obterá tudo que quer ou a consulta dará erro. Entretanto, se houver um --
ao final da entrada, não haverá problemas (blabla' OR 1=1 --
):
Exemplo 2: acessando uma conta de administrador sem saber a senha
Considere um portal de login, com usuário e senha. Se alguém submeter o usuário "mineboy2007" e a senha "sadgoat123", a aplicação checa as credenciais a partir da query SQL:
Se ela retornar as informações do usuário, o login é um sucesso. Do contrário, ele falha.
Nesse cenário, um atacante poderia logar como qualquer usuário ao remover a checagem de senha ao inserir um comentário na query. Ele pode submeter, por exemplo, o usuário admin' --
e qualquer coisa na senha (pois ela não será verificada). Assim, query SQL fica:
Essa query irá retornar o usuário cujo nome é "admin" (independentemente da senha estar correta ou não) e logará o atacante no portal (como administrador).
Se o resultado da query é retornado pela resposta da aplicação e ela é vulnerável à SQL Injection, provavelmente é possível utilizar um UNION Attack para obter informações de outras tabelas do banco de dados. Esse ataque consiste em utilizar o UNION
, que permite a execução de SELECTs adicionais em uma query. Por exemplo, a query abaixo retorna 4 colunas: a, b (da table_1) e c, d (da table_2).
Para uma query com UNION funcionar, é necessário que:
Cada SELECT, antes e depois do UNION, tenha a mesma quantidade de colunas (ex.: na query acima, estamos buscando 2 colunas no primeiro SELECT e 2 colunas no segundo SELECT);
O tipo de dado de cada coluna seja correspondente entre as queries individualmente (ex.: na query acima, a coluna 'a' deve ser do mesmo tipo da coluna 'c' e a coluna 'b' deve ser do mesmo tipo da coluna 'd').
Dicas para esse tipo de ataque
Descobrir número de colunas da tabela
Para descobrir o número de colunas presentes na tabela, podemos usar o ORDER BY
+ índice da coluna na tabela
. Esse comando ordena as respostas (por ordem alfabética por exemplo) de acordo com certa coluna. Para descobrir a quantidade de colunas da tabela, basta ir aumentando o índice da coluna no ORDER BY
. Quando o servidor responder com um erro para o ORDER BY X
, sabemos que a coluna X não existe e, portanto, a tabela tem X-1 colunas.
Se o erro for enviado ao cliente de forma explícita, ele deve ser algo como "The ORDER BY position number 3 is out of range of the number of items in the select list."
Outra opção para descobrir o número de colunas é utilizar ORDER BY passando vários NULL como "colunas" de ordenação:
Descobrir tipo de dado de certa coluna
Sabendo o número de colunas da tabela é possível descobrir o tipo de dado em cada coluna. Para isso, é necessário realizar testes com queries do tipo:
Ao submeter, por exemplo, ' UNION SELECT 'a',NULL,NULL,NULL --
, testaremos se a 1ª coluna contém strings, pois estamos fazendo um UNION, ou seja, o dado da 1ª coluna do 1º SELECT deve ser do mesmo tipo que o da 1ª coluna do 2º SELECT (o mesmo vale para as outras colunas). Caso o servidor responda com um erro (como Conversion failed when converting the varchar value 'a' to data type int.
), sabemos que a primeira coluna não contém strings (varchar). Do contrário, se a query for um sucesso, a primeira coluna armazena strings.
NULL
pode ser convertido para qualquer tipo, portanto não haverá problemas relacionados a tipos de dado incompatíveis ao utilizá-lo num UNION.
Já escrevi muita coisa para essa introdução, agora é com vocês: pesquisem!
Checar o começo de uma senha com
SUBSTRING
:SELECT TrackingId FROM TrackedUsers WHERE TrackingId = '
+pwn' UNION SELECT 'a' FROM Users WHERE Username = 'Administrator' and SUBSTRING(Password, 1, 1) > 'm'--
->TRUE
=> 1ª letra da senha é maior que 'm'(usar SUBSTR ou SUBSTRING - depende do DB)Outra possibilidade de teste é:
SELECT TrackingId FROM TrackedUsers WHERE TrackingId = '
+pwn' UNION SELECT 'a' WHERE 1=1--
Extra
Para exibir a lista de tabelas do DB (exceto DBs da Oracle):
-Para exibir lista de colunas de uma tabela:
Em DBs da Oracle, usa-se:
Em DBs da Oracle, um
SELECT
sempre deve conter umFROM <nome_da_tabela>
. Para isso, uma tabela "padrão" para se usar éDUAL
Para concatenar resultados (colunas diferentes) em uma coluna, pode-se usar o
||
:' UNION SELECT username || '~' || password FROM users -- -
(ORACLE e POSTGRE) ou'foo'+'bar'
(MICROSOFT) ou'foo' 'bar'
eCONCAT('foo','bar')
(MYSQL)
Para ver mais, procure por "SQL Injection Cheat Sheets" como essa ou essa.
Como detectar uma SQL Injection?
Ferramentas como Burpsuite ou Sqlmap são capazes de detectar diversas vulnerabilidades relacionadas à SQL Injection de forma automática, mas também podemos realizar alguns testes simples para verificar se há falhas no sistema responsável pela comunicação com o banco de dados. Alguns desses testes são:
Enviar aspas simples (
'
);Enviar condicionais como
OR 1=1
eOR 1=2
;Enviar payloads com triggers de tempo (delay) como
'; IF (1=2) WAITFOR DELAY '0:0:10'--
e analisar o tempo de resposta;Enviar payloads de OAST (Out-of-band Application Security Testing) que causam certos triggers e monitorar interações com sistemas externos.
Já que não explicamos sobre isso, segue um exemplo:
Links externos
Last updated