Como funciona a injeção de SQL dos quadrinhos XKCD "Bobby Tables"?

1093

Apenas olhando para:

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

O que esse SQL faz:

Robert'); DROP TABLE STUDENTS; --

Conheço os dois 'e faço --comentários, mas a palavra DROPtambém não é comentada, pois faz parte da mesma linha?

Blankman
fonte
16
Se você ouvir o Stack Overflow Podcast # 31 (27 de novembro de 2008), eles realmente discutem isso.
EBGreen
93
No MySQL, 'não é para comentários . Mesmo se fosse, não há espaço antes dele, para que ele possa terminar apenas a sequência que o precede.
Lightness Races in Orbit
45
No que diz respeito ao XKCD, se houver alguma dúvida sobre alguns dos quadrinhos, você sempre pode ir para o Explicar XKCD e ter sua resposta definida. Existe ainda um wiki XKCD , o que é muito útil para alguns quadrinhos complicados como geohashing XKCD
Anatoli
13
Eu acredito que este link deve ser registrado aqui: bobby-tables.com
'The
2
beta.companieshouse.gov.uk/company/10542519 é o registro de uma consultoria denominada; DROP TABLE "EMPRESAS"; - LTD
Alex Dupuy

Respostas:

1116

Deixa cair a mesa dos alunos.

O código original no programa da escola provavelmente se parece com

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

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

Depois que os valores do primeiro nome, a caixa de texto do nome do meio FNMName.Text (que é Robert'); DROP TABLE STUDENTS; --) e o sobrenome da caixa de texto LName.Text (vamos chamá-lo Derper) são concatenados com o restante 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 banco de dados, ele se parecerá com este

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

que, em inglês simples, se traduz aproximadamente nas duas consultas:

Adicione um novo registro à tabela Alunos com o valor Nome de 'Robert'

e

Excluir a tabela Alunos

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

O 'nome do aluno não é um comentário, é o delimitador da string de fechamento . Como o nome do aluno é uma sequência, é necessário sintaticamente para concluir a consulta hipotética. Os ataques de injeção funcionam apenas quando a consulta SQL injetada resulta em SQL válido .

Editado novamente de acordo com o comentário astuto de dan04

Will
fonte
3
Mmm, o WHERE com parênteses em torno dos argumentos é bastante incomum, mas pelo menos evita um erro de sintaxe ... :-)
PhiLho
60
@ PhiLho: Se a declaração original fosse uma INSERT, então o parêntese faria mais sentido. Também explicaria por que a conexão com o banco de dados não está no modo somente leitura.
dan04
3
Como @ dan04 explica, o parêntese faz mais sentido com um INSERT. Pensando de trás para frente, SELECTele não funcionaria de qualquer maneira, já que a Inserção das Mesinhas Bobby na mesa já teria largado a mesa.
precisa saber é o seguinte
10
Na verdade, neste exemplo, a primeira consulta ("adicionar um novo registro ...") falhará porque Studentsespera mais do que apenas uma coluna (a instrução original / correta forneceu duas colunas). Dito isto, a presença da segunda coluna é útil para mostrar por que os comentários são necessários; e como não se pode mudar o nome de Bobby, provavelmente é melhor deixar como está com pouco mais do que essa observação como nota de rodapé.
eggyal
7
O sobrenome de Bobby - ou pelo menos o de sua mãe, é Roberts , de acordo com o Explain XKCD . Não tenho certeza de que corrigir isso melhoraria a clareza da resposta.
WBT
611

Digamos que o nome foi usado em uma variável $Name,.

Você então executa esta consulta :

INSERT INTO Students VALUES ( '$Name' )

O código está colocando erroneamente qualquer coisa que o usuário tenha fornecido como variável.

Você queria que o SQL fosse:

INSIRA OS VALORES DOS ALUNOS (' Robert Tables')

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

INSIRA OS VALORES DOS ALUNOS (' Robert'); DROP TABLE Students; --')

O que você recebe é:

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

O --único comenta o restante da linha.

sinoth
fonte
87
Isso é muito melhor do que o mais votado, porque explica os parênteses de fechamento.
Tim Büthe 13/08/10
1
A propósito, não há como o diretor da escola nos quadrinhos estar ciente ou o XSS desde que a tabela do aluno é excluída, ele não pode saber quem fez isso.
xryl669
@ xryl669 Os logs são muito úteis em situações como essa ... Às vezes, todas as consultas são registradas e outras informações podem ajudar a deduzir o culpado.
inemanja 5/03
165

Como todo mundo já apontou, o documento ');fecha a declaração original e, em seguida, segue uma segunda declaração. A maioria das estruturas, incluindo linguagens como PHP, já possui configurações de segurança padrão que não permitem várias instruções em uma string SQL. No PHP, por exemplo, você só pode executar várias instruções em uma sequência SQL usando a mysqli_multi_queryfunção

No entanto, você pode manipular uma instrução SQL existente via injeção 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 petercomo nome de usuário e secretsenha, a sequência SQL resultante será semelhante a esta:

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

Está tudo bem. Agora imagine que você forneça essa sequência 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ê efetue 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 coisas mais destrutivas se conseguir fornecer várias instruções.

Johannes Fahrenkrug
fonte
71

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

Mamãe supôs que o programador de banco de dados fez uma solicitação parecida com:

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

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

Portanto, se $firstNamecontiver Robert'); DROP TABLE students; --o programa de banco de dados, executará a seguinte solicitação diretamente no banco de dados:

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

ie ele encerrará precocemente a instrução de inserção, executará o código malicioso que o cracker desejar e depois comentará o restante do código que houver.

Mmm, eu sou muito lento, já vejo 8 respostas antes da minha na faixa laranja ... :-) Parece um tópico popular.

PhiLho
fonte
39

TL; DR

- A aplicação aceita a entrada, neste caso 'Nancy', sem tentar - higienizar a entrada, como por escapar caracteres especiais 
escola => INSERÇÃO EM estudantes VALORES ( 'Nancy' ); INSERIR 0 1
   
  

- injeção SQL ocorre quando a entrada em um comando de banco de dados é manipulado para - causa o servidor de banco de dados para executar SQL arbitrárias 
escola => INSERÇÃO EM estudantes VALORES ( 'Robert' ); Alunos da DROP TABLE ; - '); INSERIR 0 1 TABELA DE QUEDA
      
  
 

- Os registros dos alunos já se foram - poderia ter sido ainda pior! 
escola => SELECIONE * DOS ALUNOS ; 
ERRO :   relação "estudantes" que não existem   
LINHA 1 : SELECIONE * DOS ALUNOS ; ^   
                      

Isso elimina (exclui) a tabela do aluno.

( Todos os exemplos de código nesta resposta foram executados em um servidor de banco de dados 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 => CRIAR TABELA alunos ( nome CHAVE PRIMÁRIA DO TEXTO ); 
AVISO : CRIAR TABELA / PRIMARY KEY vai criar implícita índice "students_pkey" por tabela "estudantes" CRIAR TABELA 
escola => INSERÇÃO EM estudantes VALORES ( 'John' ); INSERIR 0 1             
    
  

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

INSERÇÃO EM estudantes VALORES ( 'foobar' );  

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

- Entrada: Nancy 
escola => INSERÇÃO EM estudantes VALORES ( 'Nancy' ); INSERIR 0 1   
  

Quando consultamos a tabela, obtemos o seguinte:

escola => SELECIONE * DOS ALUNOS ;   
 nome
-------
 John
 Nancy
( 2 linhas ) 

O que acontece quando inserimos o nome de Little Bobby Tables na tabela?

- Entrada: Robert '); Alunos da DROP TABLE; - 
escola => INSERÇÃO EM estudantes VALORES ( 'Robert' ); Alunos da DROP TABLE ; - '); INSERIR 0 1 TABELA DE QUEDA      
  
 

A injeção de SQL aqui é o resultado do nome do aluno encerrando a instrução e incluindo um DROP TABLEcomando separado ; os dois traços no final da entrada devem comentar qualquer código restante que, de outra forma, causaria um erro. A última linha da saída confirma que o servidor de banco de dados descartou a tabela.

É importante observar que, durante a INSERToperação, o aplicativo não verifica os caracteres especiais da entrada e, portanto, permite que entradas arbitrárias sejam inseridas no comando SQL. Isso significa que um usuário mal-intencionado pode inserir, em um campo normalmente destinado à entrada do usuário, símbolos especiais como aspas e código SQL arbitrário para fazer com que o sistema de banco de dados o execute, daí a injeção de SQL  .

O resultado?

escola => SELECIONE * DOS ALUNOS ; 
ERRO :   relação "estudantes" que não existem   
LINHA 1 : SELECIONE * DOS ALUNOS ; ^   
                      

A injeção de SQL é o equivalente ao banco de dados de uma vulnerabilidade de execução remota de código arbitrário 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 de banco de dados e da configuração do aplicativo, ele pode ser usado por um invasor para causar perda de dados (como neste caso), obter acesso não autorizado a dados ou até mesmo executar código arbitrário na própria máquina host.

Conforme observado pelo quadrinho do XKCD, uma maneira de proteger contra ataques de injeção de SQL é higienizar as entradas do banco de dados, como escapar caracteres especiais, para que eles não possam modificar o comando SQL subjacente e, portanto, não possam causar a execução de código SQL arbitrário. Se você usar consultas parametrizadas, como SqlParameterno ADO.NET, a entrada será, no mínimo, higienizada automaticamente para proteger contra injeção de SQL.

No entanto, a limpeza das entradas no nível do aplicativo pode não parar as técnicas mais avançadas de injeção de SQL. Por exemplo, existem maneiras de contornar a mysql_real_escape_stringfunção PHP . Para proteção adicional, muitos sistemas de banco de dados suportam instruções preparadas . Se implementadas corretamente no back-end, as instruções preparadas podem impossibilitar a injeção de SQL tratando as entradas de dados como semanticamente separadas do restante do comando.

bwDraco
fonte
30

Digamos que você escreveu 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; --

O que é executado no banco de dados é esta consulta:

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

O ponto e vírgula termina o comando de inserção e inicia outro; o - comenta o resto da linha. O comando DROP TABLE é executado ...

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

Dan Vinton
fonte
26

Uma aspas simples é o início e o fim de uma sequência. Um ponto e vírgula é o fim de uma declaração. Então, se eles estavam fazendo uma seleção como esta:

Select *
From Students
Where (Name = '<NameGetsInsertedHere>')

O SQL se tornaria:

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

Em alguns sistemas, o selectprimeiro seria executado, seguido pela dropinstrução! A mensagem é: NÃO INCORPORAR VALORES NO SEU SQL. Em vez disso, use parâmetros!

CodeAndCats
fonte
18

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

Jorn
fonte
17

O escritor do banco de dados provavelmente fez uma

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

Se student_name é o escolhido, a seleção é feita com o nome "Robert" e a tabela é descartada. A parte "-" altera o restante da consulta fornecida em um comentário.

Paul Tomblin
fonte
Foi o meu primeiro pensamento, mas você recebe um erro de sintaxe com o parêntese de fechamento à direita, não?
PhiLho
3
É por isso que existe um - no final, indicando que o texto restante é um comentário e deve ser ignorado.
17

Nesse caso, 'não é um caractere de comentário. É usado para delimitar literais de strings. O artista de quadrinhos está apostando na idéia de que a escola em questão possui sql dinâmico em algum lugar que se parece com isso:

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

Então agora o caractere 'termina a string literal antes que o programador a estivesse esperando. Combinado com o; Para encerrar a declaração, um invasor agora pode adicionar o sql que desejar. O comentário - no final é para garantir que qualquer sql restante na instrução original não impeça a compilação da consulta no servidor.

FWIW, também acho que o quadrinho em questão tem um detalhe importante errado: se você está pensando em higienizar as entradas do banco de dados, como sugere o quadrinho, ainda está fazendo errado. Em vez disso, você deve pensar em termos de quarentena de suas entradas de banco de dados, e a maneira correta de fazer isso é através de consultas parametrizadas.

Joel Coehoorn
fonte
16

O 'caractere no SQL é usado para constantes de seqüência de caracteres. Nesse caso, é usado para finalizar a constante da string e não para comentar.

Rockcoder
fonte
7

Funciona assim: suponha que o administrador esteja procurando registros de alunos

Robert'); DROP TABLE STUDENTS; --

Como a conta de administrador possui privilégios elevados, é possível excluir a tabela desta conta.

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

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

String query="Select * from student where username='"+student_name+"'";

statement.executeQuery(query); //Rest of the code follows

A consulta resultante se torna

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

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

Select * from student where username='Robert'); 

DROP TABLE STUDENTS; --

O traço duplo (-) comentará apenas 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 eliminará o aluno da tabela se a conta tiver privilégios suficientes (geralmente a conta de administrador da escola executará essa consulta e terá os privilégios mencionados acima).

Noname
fonte
SELECT* FROM sutdents ...- você esqueceu um "s". Isto é o que você está deixando cair. DROP TABLE STUDENTS;
DevWL
4

Você não precisa inserir dados do formulário para fazer a injeção de SQL.

Ninguém apontou isso antes, então posso alertar alguns de vocês.

Principalmente tentaremos corrigir a entrada de formulários. Mas este não é o único lugar onde você pode ser atacado com injeção de SQL. Você pode fazer um ataque muito simples com a URL que envia dados através da solicitação GET; Considere o seguinte exemplo:

<a href="/show?id=1">show something</a>

Seu URL seria http://yoursite.com/show?id=1

Agora alguém poderia tentar algo como isto

http://yoursite.com/show?id=1;TRUNCATE table_name

Tente substituir table_name pelo nome da tabela real. Se ele acertar o nome da sua mesa, eles esvaziarão a sua mesa! (É muito fácil forçar essa URL com um script simples)

Sua consulta seria mais ou menos assim ...

"SELECT * FROM page WHERE id = 4;TRUNCATE page"

Exemplo de código vulnerável do PHP usando o PDO:

<?php
...
$id = $_GET['id'];

$pdo = new PDO($database_dsn, $database_user, $database_pass);
$query = "SELECT * FROM page WHERE id = {$id}";
$stmt = $pdo->query($query);
$data = $stmt->fetch(); 
/************* You have lost your data!!! :( *************/
...

Solução - use os métodos prepare () e bindParam () do PDO:

<?php
...
$id = $_GET['id'];

$query = 'SELECT * FROM page WHERE id = :idVal';
$stmt = $pdo->prepare($query);
$stmt->bindParam('idVal', $id, PDO::PARAM_INT);
$stmt->execute();
$data = $stmt->fetch();
/************* Your data is safe! :) *************/
...
DevWL
fonte