Como funciona a injeção de SQL do quadrinho XKCD “Bobby Tables”?

Apenas olhando:

XKCD Strip (Fonte: https://xkcd.com/327/ )

O que esse SQL faz:

Robert'); DROP TABLE STUDENTS; -- 

Eu sei tanto ' quanto ' são para comentários, mas a palavra DROP também não é comentada, já que faz parte da mesma linha?

Deixa cair a mesa dos alunos.

O código original no programa da escola provavelmente parece algo como

 q = "INSERT INTO Students VALUES ('" + FNMName.Text + "', '" + LName.Text + "')"; 

Esta é a maneira ingênua de adicionar input de texto em uma consulta e é muito ruim , como você verá.

Após os valores do primeiro nome, o nome do meio textbox FNMName.Text (que é Robert'); DROP TABLE STUDENTS; -- Robert'); DROP TABLE STUDENTS; -- Robert'); DROP TABLE STUDENTS; -- ) eo último nome textbox LName.Text (vamos chamá-lo Derper ) são concatenados com o resto da consulta, o resultado é agora, na verdade, duas consultas separadas pelo terminador de instrução (ponto e vírgula). A segunda consulta foi injetada na primeira. Quando o código executa essa consulta no database, ele se parecerá com isso

 INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --', 'Derper') 

que, em inglês simples, traduz aproximadamente as duas perguntas:

Adicione um novo registro à tabela Alunos com um valor de Nome de ‘Robert’

e

Excluir a tabela de alunos

Tudo após a segunda consulta é marcado como um comentário : --', 'Derper')

O ' no nome do aluno não é um comentário, é o delimitador de cadeia de fechamento. Como o nome do aluno é uma string, é necessário sintaticamente para concluir a consulta hipotética. Os ataques de injeção só funcionam quando a consulta SQL que eles inserem resulta em um SQL válido .

Editado novamente conforme o comentário astuto de dan04

Digamos que o nome tenha sido usado em uma variável, $Name . Você então executa essa consulta:

 INSERT INTO Students VALUES ( '$Name' ) 

O código está erroneamente colocando qualquer coisa que o usuário forneceu como variável. Você queria que o SQL fosse:

INSERT INTO Students VALUES (‘ Robert Tables ‘)

Mas um usuário inteligente pode fornecer o que quiser:

INSERT INTO Students VALUES (‘ Robert'); DROP TABLE Alunos; - ‘)

O que você recebe é:

 INSERT INTO Students VALUES ( 'Robert' ); DROP TABLE STUDENTS; --' ) 

O -- apenas comenta o restante da linha.

Como todos já apontaram, o '); fecha a declaração original e, em seguida, segue uma segunda declaração. A maioria das estruturas, incluindo linguagens como o PHP, tem configurações de segurança padrão que não permitem várias instruções em uma cadeia SQL. No PHP, por exemplo, você só pode executar múltiplas instruções em uma string SQL usando a function mysqli_multi_query .

Você pode, no entanto, manipular uma instrução SQL existente por meio da injeção de SQL sem precisar adicionar uma segunda instrução. Digamos que você tenha um sistema de login que verifique um nome de usuário e uma senha com esta simples seleção:

 $query="SELECT * FROM users WHERE username='" . $_REQUEST['user'] . "' and (password='".$_REQUEST['pass']."')"; $result=mysql_query($query); 

Se você fornecer peter como o nome de usuário e secret como a senha, a string SQL resultante será semelhante a esta:

 SELECT * FROM users WHERE username='peter' and (password='secret') 

Está tudo bem. Agora imagine que você forneça esta string como a senha:

 ' OR '1'='1 

Então a string SQL resultante seria esta:

 SELECT * FROM users WHERE username='peter' and (password='' OR '1'='1') 

Isso permitiria que você fizesse login em qualquer conta sem saber a senha. Portanto, você não precisa usar duas instruções para usar a injeção SQL, embora possa fazer mais coisas destrutivas se conseguir fornecer várias instruções.

Não, ' não é um comentário no SQL, mas um delimitador.

Mamãe supôs que o programador do database fez um pedido parecido com:

 INSERT INTO 'students' ('first_name', 'last_name') VALUES ('$firstName', '$lastName'); 

(por exemplo) para adicionar o novo aluno, onde o conteúdo da variável $xxx foi retirado diretamente de um formulário HTML, sem verificar o formato nem os caracteres especiais.

Então, se $firstName contiver Robert'); DROP TABLE students; -- Robert'); DROP TABLE students; -- Robert'); DROP TABLE students; -- o programa de database executará a seguinte solicitação diretamente no DB:

 INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD'); 

ie. ele encerrará a instrução insert, executará qualquer código malicioso que o cracker queira e, em seguida, comentará o restante do código que possa existir.

Mmm, eu estou muito devagar, já vejo 8 respostas antes da minha na faixa laranja … 🙂 Um tópico popular, parece.

TL; DR

 - O aplicativo aceita input, neste caso 'Nancy', sem tentar
 - higienizar a input, como por escaping caracteres especiais
 escola => INSERT INTO estudantes VALORES ('Nancy');
 INSERIR 0 1

 - A injeção SQL ocorre quando a input em um comando do database é manipulada para
 - fazer com que o servidor de database execute SQL arbitrário
 escola => INSERT INTO students VALUES ('Robert');  Alunos DROP TABLE;  - ');
 INSERIR 0 1
 DROP TABLE

 - Os registros dos alunos já se foram - poderia ter sido ainda pior!
 escola => SELECIONE * dos alunos;
 ERRO: relação "alunos" não existe
 LINE 1: SELECT * dos alunos;
                       ^

Isso elimina (exclui) a tabela do aluno.

( Todos os exemplos de código nesta resposta foram executados em um servidor de database PostgreSQL 9.1.2. )

Para deixar claro o que está acontecendo, vamos tentar isso com uma tabela simples contendo apenas o campo de nome e adicionar uma única linha:

 escola => alunos CREATE TABLE (nome TEXT PRIMARY KEY);
 AVISO: CREATE TABLE / PRIMARY KEY criará o índice implícito "students_pkey" para a tabela "students"
 CRIAR A TABELA
 escola => INSERT INTO students VALUES ('John');
 INSERIR 0 1

Vamos supor que o aplicativo use o seguinte SQL para inserir dados na tabela:

 INSERT INTO students VALUES ('foobar');

Substitua foobar pelo nome real do aluno. Uma operação de inserção normal ficaria assim:

 - Entrada: Nancy
 escola => INSERT INTO estudantes VALORES ('Nancy');
 INSERIR 0 1

Quando consultamos a tabela, obtemos isso:

 escola => SELECIONE * dos alunos;
  nome
 -------
  John
  Nancy
 (2 linhas)

O que acontece quando inserimos o nome do Little Bobby Tables na mesa?

 - Entrada: Robert ');  Alunos DROP TABLE;  -
 escola => INSERT INTO students VALUES ('Robert');  Alunos DROP TABLE;  - ');
 INSERIR 0 1
 DROP TABLE

A injeção de SQL aqui é o resultado do nome do aluno que termina a instrução e inclui um comando DROP TABLE separado; os dois traços no final da input destinam-se a comentar qualquer código restante que poderia causar um erro. A última linha da saída confirma que o servidor do database derrubou a tabela.

É importante notar que, durante a operação INSERT , o aplicativo não está verificando a input para nenhum caractere especial e, portanto, permitindo que inputs arbitrárias sejam inseridas no comando SQL. Isso significa que um usuário mal-intencionado pode inserir, em um campo normalmente destinado à input do usuário, símbolos especiais, como cotações, juntamente com código SQL arbitrário para fazer com que o sistema de database o execute, portanto, injeção de SQL .

O resultado?

 escola => SELECIONE * dos alunos;
 ERRO: relação "alunos" não existe
 LINE 1: SELECT * dos alunos;
                       ^

A injeção de SQL é o equivalente no database de uma vulnerabilidade de execução de código arbitrária remota em um sistema operacional ou aplicativo. O impacto potencial de um ataque de injeção SQL bem-sucedido não pode ser subestimado – dependendo do sistema do database e da configuração do aplicativo, ele pode ser usado por um invasor para causar perda de dados (como neste caso), obter access não autorizado a dados ou até executar código arbitrário na própria máquina host.

Como observado pelo comic XKCD, uma maneira de proteger contra ataques de injeção SQL é desinfetar as inputs do database, como escaping de caracteres especiais, para que eles não possam modificar o comando SQL subjacente e, portanto, não possam executar código SQL arbitrário. Se você usar consultas parametrizadas, como usando SqlParameter no ADO.NET, a input será, no mínimo, sanitizada automaticamente para proteger contra a injeção de SQL.

No entanto, a limpeza de inputs no nível do aplicativo pode não interromper as técnicas de injeção SQL mais avançadas. Por exemplo, existem maneiras de contornar a function PHP mysql_real_escape_string . Para proteção adicional, muitos sistemas de database suportam instruções preparadas . Se implementadas adequadamente no backend, as instruções preparadas podem tornar a injeção de SQL impossível, tratando as inputs de dados como semanticamente separadas do resto do comando.

Digamos que você tenha escrito ingenuamente um método de criação de alunos como este:

 void createStudent(String name) { database.execute("INSERT INTO students (name) VALUES ('" + name + "')"); } 

E alguém entra no nome Robert'); DROP TABLE STUDENTS; -- Robert'); DROP TABLE STUDENTS; --

O que é executado no database é esta consulta:

 INSERT INTO students (name) VALUES ('Robert'); DROP TABLE STUDENTS --') 

O ponto e vírgula termina o comando insert e inicia outro; o – comenta o resto da linha. O comando DROP TABLE é executado …

É por isso que os parâmetros de binding são uma coisa boa.

Uma aspa simples é o início e o fim de uma string. Um ponto e vírgula é o final de uma declaração. Então, se eles estavam fazendo um select assim:

 Select * From Students Where (Name = '') 

O SQL se tornaria:

 Select * From Students Where (Name = 'Robert'); DROP TABLE STUDENTS; --') -- ^-------------------------------^ 

Em alguns sistemas, o select seria executado primeiro seguido pela instrução drop ! A mensagem é: NÃO INCORPORAR OS VALORES EM SEU SQL. Em vez disso, use parâmetros!

O '); termina a consulta, não inicia um comentário. Em seguida, ele solta a tabela de alunos e comenta o restante da consulta que deveria ser executada.

O escritor do database provavelmente fez um

 sql = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff"; execute(sql); 

Se student_name é o dado, isso faz a seleção com o nome “Robert” e, em seguida, descarta a tabela. A parte “-” muda o resto da consulta dada em um comentário.

Nesse caso, ‘não é um caractere de comentário. É usado para delimitar literais de string. O quadrinista está apostando na ideia de que a escola em questão tem um sql dynamic em algum lugar que se pareça com isso:

 $sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')"; 

Então agora o caractere termina a string literal antes que o programador esteja esperando por ela. Combinado com o; para finalizar a declaração, um invasor pode adicionar o sql desejado. O – comentário no final é certificar-se de que qualquer sql remanescente na declaração original não impeça a consulta de compilar no servidor.

FWIW, eu também acho que o quadrinho em questão tem um detalhe importante errado: se você está pensando em sanear suas inputs de database, como o comic sugere, você ainda está fazendo errado. Em vez disso, você deve pensar em colocar em quarentena as inputs do database e a maneira correta de fazer isso é por meio de consultas parametrizadas.

O caractere em SQL é usado para constantes de string. Neste caso, é usado para finalizar a constante da string e não para comentário.

É assim que funciona: Vamos supor que o administrador esteja procurando por registros de alunos

 Robert'); DROP TABLE STUDENTS; -- 

Como a conta de administrador tem altos privilégios, é possível excluir a tabela dessa conta.

O código para recuperar o nome de usuário da solicitação é

Agora a consulta seria algo assim (para pesquisar a tabela do aluno)

 String query="Select * from student where username='"+student_name+"'"; statement.executeQuery(query); //Rest of the code follows 

A consulta resultante torna-se

 Select * from student where username='Robert'); DROP TABLE STUDENTS; -- 

Como a input do usuário não é higienizada, a consulta acima é manipulada em duas partes

 Select * from student where username='Robert'); DROP TABLE STUDENTS; -- 

O traço duplo (-) apenas comentará a parte restante da consulta.

Isso é perigoso, pois pode anular a autenticação de senha, se presente

O primeiro fará a pesquisa normal.

O segundo dropará o aluno da mesa se a conta tiver privilégios suficientes (Geralmente, a conta admin da escola executará essa consulta e terá os privilégios mencionados acima).