Injeção de SQL

Origem: Wikipédia, a enciclopédia livre.
Exemplo de ataque usando comandos SQL.

Injeção de SQL (do inglês SQL Injection) é um tipo de ameaça de segurança que se aproveita de vulnerabilidades em sistemas que trabalham com bases de dados realizando ataques com comandos SQL; onde o atacante consegue inserir uma instrução SQL personalizada e indevida através da entrada de dados de uma aplicação, como formulários ou URL de uma aplicação online.[1]

Danos[editar | editar código-fonte]

Um usuário por meio de ataques com injeção SQL, é possível obter qualquer tipo de dado sigiloso mantido no banco de dados de um computador servidor.[1] Dependendo da versão do banco, também é possível inserir comandos maliciosos e conseguir permissão total (acesso root) à máquina em que o banco está em execução.[1]

Veja abaixo um exemplo de um sistema de login (na linguagem de programação PHP) com vulnerabilidade:[2]

$usuario = $_POST['usuario'];
$senha = $_POST['senha'];
$sql = "SELECT * FROM usuarios WHERE usuario = '".$usuario."' AND senha = '".$senha."' ";
$processa = mysql_query($sql);

Esta instrução "SQL query" de login funciona da seguinte forma:

SELECIONE TODOS (select *) da tabela "usuarios" (nome da tabela) ONDE obrigatoriamente o "usuario" digitado no formulário é igual ao cadastrado no banco de dados "E" (and) a "senha" digitada no formulário também é igual ao cadastrado;

...onde as variaveis $usuario e $senha recebem os conteúdos digitados nos campos de um formulário através do método POST.[2] Imagine que o conteúdo da variável $senha seja:[2]

  • $senha = " 'or 1='1 " " 'or 1='1 ".

Se nenhuma verificação de dados (validação) for realizada, o usuário atacante conseguirá efetuar o login no sistema sem ter um cadastro válido, devido a uma falha gerada na instrução SQL.[2]

Funcionamento[editar | editar código-fonte]

Para exemplificar o funcionamento da injeção de SQL, consideremos o comando básico de consuta abaixo, a instrução SQL query:

SELECT id, nome, sobrenome FROM autores;

Essa instrução representa uma consulta na base de dados, funcionará da seguinte forma:

SELECIONE (select) todos os "ids", "nomes" e "sobrenome" DA TABELA (from) "autores" (nome da tabela);

Esta retorna todos os registros das colunas "id", "nome" e "sobrenome" da tabela "autores". A partir desta mesma instrução, os registros a serem retornados podem ser restritos, usando um filtro com a inclusão da cláusula WHERE, como é visto no exemplo abaixo:

SELECT id, nome, sobrenome FROM autores WHERE nome = 'josé' AND sobrenome = 'silva';

Esta instrução de consulta na base de dados, funcionará da seguinte forma:

SELECIONE (select) todos os "ids", "nomes" e "sobrenome" DA TABELA (from) "autores" (nome da tabela) ONDE (where) os nomes deverão ser iguais a 'josé' E os sobrenomes iguais a 'silva' (condições do filtro);

Com base nesta instrução, é fácil supor que os itens "josé" e "silva" são do tipo texto (strings), solicitados por algum usuário que esteja usando a aplicação.

Problema[editar | editar código-fonte]

Portanto, supondo que a aplicação não faça o entendimento apropriado do conteúdo inserido pelo usuário, o mesmo pode fazer o uso acidental do caractere apóstrofo. Gerando a entrada:

  • nome = jo'sé
  • sobrenome = silva

E fazendo com que a aplicação gere o código:

SELECT id, nome, sobrenome FROM autores WHERE nome = 'jo'' AND sobrenome = 'silva';

De acordo com a especificação da linguagem SQL, o uso de apóstrofo na consulta causa uma quebra na consulta, ocorrendo um erro de sintaxe nessa instrução, a string será considerada no campo nome apenas a palavra "jo" (dentro da primeira dupla de apóstrofo 'texto'). O interpretador do SQL espera que a continuação da instrução sejam outros comandos SQL válidos que completam a instrução principal. No entanto, como a outra parte do texto, o "sé" não é um identificador válido, essa instrução não será executada e retornará um erro inesperado.

Ataque[editar | editar código-fonte]

Assim, um atacante pode personalizar os dados de entrada a fim de gerar um comportamento inesperado na base de dados. Para exemplificar este conceito, consideremos na consulta apresentada, a entrada dos seguintes dados através da aplicação:

  • nome = jo'; DROP TABLE autores ; --
  • sobrenome = silva

A instrução completa ficaria:

SELECT id, nome, sobrenome FROM autores WHERE nome = 'jo'; DROP TABLE autores ; --' AND sobrenome = 'silva';

A instrução personalizada funcionará da seguinte forma:

SELECIONE (select) todos os "ids", "nomes" e "sobrenome" DA TABELA (from) "autores" (nome da tabela) ONDE (where) os nomes deverão ser iguais a 'josé'; (quebra, novo comando) Em seguida EXCLUA (drop) a tabela "autores"; -- (continuação) E os sobrenomes iguais a 'silva' (condições do filtro);

Neste caso, a instrução será executada normalmente, pois a adição do caractere ponto-e-vírgula ";" na instrução representa o fim de uma SQL query e o começo de outra. Assim no exemplo acima, a SQL query será reconhecida como completa - não ocorrendo erro de sintaxe - de modo prematuro dando espaço para uma nova instrução. de livre escolha do atacante. Podendo retornar dados confidenciais armazenados na base de dados ou de executar instruções que comprometam o sistema, como a remoção de dados e/ou tabelas, como apresentado no exemplo acima.

A seqüência de caracteres "--" representa um comentário em uma linha de SQL. O "--" no fim do campo username é obrigatório para que a SQL query continue sendo executada sem erros.

Possíveis soluções[editar | editar código-fonte]

Aparentemente um método para prevenir esse problema seria a retirada dos apóstrofos dos campos de entrada da aplicação, ou simplesmente ignorar a SQL query nestas situações. Mas, apareceram outras dificuldades com esse método. Primeiro, nem todos os usuários inserem dados em forma de strings. Por exemplo, se o usuário tiver a opção de selecionar um autor pelo 'id' (presumivelmente um número), nossa query aparecerá como abaixo:

SELECT id, forename, surname FROM authors WHERE id=1234

A instrução funcionará da seguinte forma:

SELECIONE (select) todos os "ids", "forenames e "surnames" DA TABELA (from) "autors" (nome da tabela) ONDE (where) id deverá ser igual a "1234";

Nesta situação, o atacante pode simplesmente adicionar uma instrução SQL no fim do 'input' numérico. Verificando as variações do SQL (dialetos), por exemplo, vários delimitadores podem ser usados no Microsoft Jet DBMS engine, onde as datas podem ser delimitadas com o caracter sustenido. Assim, escapando da execução com apóstrofos, não necessariamente uma solução como demonstrado anteriormente.

Pode-se ilustrar esse ponto usando um exemplo de página de login na linguagem Active Server Pages (ASP), que acessa um servidor de banco de dados SQL e tenta autenticar o acesso em uma aplicação.

Abaixo está um pedaço de código de uma página de formulário (process_login.ascp), onde um usuário insere o username e o password para autenticação:

(...)

function Login( cn )
{
   var username;
   var password;
   username = Request.form("username");
   password = Request.form("password");
   var rso = Server.CreateObject("ADODB.Recordset");
   var sql = "select * from users where username = '" + username + "' and password = '" + password + "'";
   trace( "query: " + sql );
   rso.open( sql, cn );
   if (rso.EOF)  {
      rso.close();
   }
}

function Main()
{
//Set up connection
   var username
   var cn = Server.createobject( "ADODB.Connection" );
   cn.connectiontimeout = 20;
   cn.open( "localserver", "sa", "password" );
   username = new String( Request.form("username") );
   if( username.length > 0)  {
      Login( cn );
   }
   cn.close();
}

A parte critica é o código "var sql" que cria uma SQL query string:

var sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";

A instrução funcionará da seguinte forma:

SELECIONE todos os itens (select *) DA TABELA (from) "users" ONDE (where) o username é igual ao texto "username" digitado no formulario E (and) password igual ao texto "password" digitado;

Se o usuário digitar no formulário os seguintes dados:

* Username: '; drop table users--
* Password:

A instrução SQL será modificada da seguinte forma:

var sql = "SELECT * FROM users WHERE username = '" + ; (quebra, novo comando) drop table users-- + "'  AND password = '" + password + "'";

A instrução funcionará da seguinte forma:

SELECIONE todos os itens (select *) DA TABELA (from) "users" ONDE (where) o username é igual ; (quebra, novo comando) EXCLUA (drop) a TABELA users (nome da tabela) -- (comentário, continuação) E (and) password igual ao texto "password" digitado;

... a tabela 'users' será apagada, negando o acesso para todos os usuários.

O atacante pode entrar no sistema (logar) como qualquer usuário, se souber o nome do usuário, digitando o comando abaixo no formulário:

  • Username: admin'--

O atacante pode logar como o primeiro usuário da tabelas 'users', com a inserção abaixo:

  • Username: ' or 1=1--

O atacante pode ainda logar como um usuário completamente fictício com o input abaixo:

  • Username: ' union select 1, 'fictional_user', 'some_password', 1--

A razão para que isso funcione, é que a aplicação acredita que a linha 'constante' que o atacante especificou é parte do 'recordset' recuperado da base de dados.

Assim funciona uma injeção manual de SQL. Também podem ser usadas ferramentas que automatizam as consultas para vários tipo de banco de dados, descobrindo a vulnerabilidade injetável.[1]

Injeção de SQL Avançado[editar | editar código-fonte]

Termo do inglês Advanced Sql Injection, método consiste em explorar a injeção de instruções SQL através do método GET existente na URL da página.

Através do mesmo, o invasor pode conseguir por exemplo, os dados de usuário e senha do painel administrativo do site.

Injeção "Cega" de SQL[editar | editar código-fonte]

Termo do inglês Blind Sql Injection, método assemelha-se ao anterior, a diferença é que nesse caso a página possui uma certa segurança e o invasor não visualiza os dados da base de dados através da página web. O invasor descobre os dados desejados de 1 em 1 caractere, utilizando a função SUBSTRING do SQL em conjunto com uma verificação booliana.

Por exemplo, supondo que o usuário do sistema seja JOAO, o invasor vai ter um retorno falso com a seguinte tentativa injetada na cláusula WHERE:

... AND SELECT 1 FROM USUARIO WHERE SUBSTRING(USUARIO,1,1) = 'A'

No entanto, vai receber um retorno verdadeiro quando chegar na letra J:

... AND SELECT 1 FROM USUARIO WHERE SUBSTRING(USUARIO,1,1) = 'J'

Sabe-se que é um retorno verdadeiro porque a página é carregada normalmente, ao contrário de quando o retorno é falso.

Muitos invasores conseguem dados de usuário e senha sem ao menos ter conhecimento da teoria do método, mas apenas por utilizar a ferramenta Havij.

Injeção Duplamente "Cega" de SQL[editar | editar código-fonte]

Termo do inglês Double Blind Sql Injection, neste método além do invasor não visualizar os dados da base de dados (usuário e senha, por exemplo) através da página web (expostos no html, visível em tela), ele também não obtêm diferença visível na página em caso de retorno verdadeiro. Enquanto no método anterior o invasor sabe se sua verificação booleana é verdadeira ou falsa pelo carregamento ou não da página, nesse método chamado de duplamente cego o invasor faz uma comparação de tempo de carregamento de página para saber se sua comparação que ele está enviando ao banco de dados (via método GET, por exemplo) é verdadeira ou falsa.

Ou seja, se a página demora 1 segundo para carregar o site "http://wikipedia.org/produto=1 and 1=1" e demora metade do tempo para carregar "http://wikipedia.com/produto=1 and 1=0", o invasor considera que o servidor está aceitando os comandos de injeção de SQL e sempre que tiver 1 segundo de tempo de carregamento significa que teve um retorno verdadeiro, dessa forma o invasor consegue obter caractere por caractere da senha do administrador do site.

Analisemos outro exemplo de vulnerabilidade, onde diversos sites usam sistemas que organizam as páginas internas usando a função include(), usando uma variável do método $_GET:[2]

if (isset($_GET['pagina'])) { 
    $arquivo = $_GET['pagina']; 
}else {
    $arquivo = 'home.php'; 
} 
include ($arquivo);

... verifica se a variável pagina ($_GET['pagina']) existe usando a função isset(), em seguida guarda o valor da pagina na variável $arquivo. Caso não exista uma variável, a variável $arquivo recebe a página inicial "home.php". Por fim é exibido a página usando a função include().[2]

Referências

  1. a b c d TecMundo (5 de janeiro de 2017). «SQL Injection: saiba tudo sobre um ataque simples que pode ser devastador». TecMundo - Descubra e aprenda tudo sobre tecnologia 
  2. a b c d e f Cardoso, Philipe. «SQL Injection: o que é e como evitar». Destaques. Zoom Digital. Consultado em 27 de abril de 2018 

Ver também[editar | editar código-fonte]