¿Cómo funciona la inyección SQL del cómic XKCD "Bobby Tables"?

1094

Solo mirando:

Tira XKCD (Fuente: https://xkcd.com/327/ )

¿Qué hace este SQL?

Robert'); DROP TABLE STUDENTS; --

Sé ambos 'y --son para comentarios, pero ¿la palabra no DROPse comenta también ya que es parte de la misma línea?

Blankman
fuente
16
Si escuchas el Stack Overflow Podcast # 31 (27 de noviembre de 2008), en realidad lo discuten.
EBGreen
93
En MySQL, 'no es para comentarios . Incluso si lo fuera, no hay espacio antes, por lo que solo puede terminar la cadena que lo precede.
Carreras de ligereza en órbita el
45
En lo que respecta a XKCD, si hay alguna pregunta sobre algunos de los cómics, siempre puede ir a Explicar XKCD y averiguar su respuesta. Incluso hay un wiki de XKCD , que es muy útil para algunos cómics complicados como XKCD geohashing
Anatoli
13
Creo que este enlace debe registrarse aquí: bobby-tables.com
Arioch 'The
2
beta.companieshouse.gov.uk/company/10542519 es el registro para una consultoría llamada; DROP TABLE "EMPRESAS"; - LTD
Alex Dupuy

Respuestas:

1117

Se cae la mesa de los alumnos.

El código original en el programa de la escuela probablemente se parece a

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

Esta es la forma ingenua de agregar entrada de texto en una consulta, y es muy mala , como verá.

Después de que los valores del primer nombre, el cuadro de texto del segundo nombre FNMName.Text (que es Robert'); DROP TABLE STUDENTS; --) y el cuadro de texto del apellido LName.Text (llamémoslo Derper) se concatenan con el resto de la consulta, el resultado ahora es en realidad dos consultas separadas por el terminador de enunciado (punto y coma). La segunda consulta se ha inyectado en la primera. Cuando el código ejecuta esta consulta en la base de datos, se verá así

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

que, en inglés simple, se traduce aproximadamente a las dos consultas:

Agregue un nuevo registro a la tabla Estudiantes con un valor de Nombre de 'Robert'

y

Eliminar la tabla de alumnos

Todo lo que haya pasado la segunda consulta se marca como un comentario : --', 'Derper')

El 'nombre del alumno no es un comentario, es el delimitador de cadena de cierre . Como el nombre del alumno es una cadena, se necesita sintácticamente para completar la consulta hipotética. Los ataques de inyección solo funcionan cuando la consulta SQL que inyectan da como resultado un SQL válido .

Editado nuevamente según el astuto comentario de dan04

Will
fuente
3
Mmm, el DÓNDE con paréntesis alrededor de los argumentos es bastante inusual, pero al menos evita un error de sintaxis ... :-)
PhiLho
6060
@PhiLho: Si la declaración original fuera un INSERT, entonces el paréntesis tendría más sentido. También explicaría por qué la conexión de la base de datos no está en modo de solo lectura.
dan04
3
Como explica @ dan04, el paréntesis tiene más sentido con un INSERT. Pensando en retrospectiva, el SELECTno correría de todos modos ya que el Insert de las Mesitas Bobby en la mesa ya habría dejado caer la mesa.
ypercubeᵀᴹ
10
En realidad, en este ejemplo, la primera consulta ("agregar un nuevo registro ...") fallará porque Studentsespera más que solo una columna (la declaración original / correcta proporcionó dos columnas). Dicho esto, la presencia de la segunda columna es útil para mostrar por qué se requieren comentarios; y como uno no puede cambiar el nombre de Bobby, probablemente sea mejor dejarlo tal cual con poco más que esta observación como nota al pie.
eggyal
77
El apellido de Bobby, o al menos el de su madre, es Roberts , según Explain XKCD . Sin embargo, no estoy seguro de que corregir eso mejoraría la claridad de la respuesta.
WBT
611

Digamos que el nombre fue utilizado en una variable, $Name.

Luego ejecuta esta consulta :

INSERT INTO Students VALUES ( '$Name' )

El código está colocando por error cualquier cosa que el usuario proporcionó como variable.

Querías que el SQL fuera:

INSERTAR EN LOS VALORES DE LOS ESTUDIANTES (' Robert Tables')

Pero un usuario inteligente puede suministrar lo que quiera:

INSERTAR EN LOS VALORES DE LOS ESTUDIANTES (' Robert'); DROP TABLE Students; --')

Lo que obtienes es:

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

Los --únicos comentarios son el resto de la línea.

sinoth
fuente
87
Esto es mucho mejor que el más votado, porque explica el paréntesis de cierre.
Tim Büthe el
1
Por cierto, no hay forma de que el director de la escuela en los cómics esté al tanto o el XSS ya que la tabla de estudiantes se elimina, no puede saber quién ha hecho esto.
xryl669
@ xryl669 Los registros son muy útiles en situaciones como esta ... A veces, todas las consultas se registran y, a veces, otra información registrada puede ayudarlo a deducir el culpable.
inemanja
165

Como todos los demás ya han señalado, ');cierra la declaración original y luego sigue una segunda declaración. La mayoría de los marcos, incluidos los lenguajes como PHP, tienen configuraciones de seguridad predeterminadas que no permiten múltiples declaraciones en una cadena SQL. En PHP, por ejemplo, solo puede ejecutar varias declaraciones en una cadena SQL utilizando la mysqli_multi_queryfunción.

Sin embargo, puede manipular una declaración SQL existente mediante inyección SQL sin tener que agregar una segunda declaración. Digamos que tiene un sistema de inicio de sesión que verifica un nombre de usuario y una contraseña con esta simple selección:

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

Si proporciona petercomo nombre de usuario y secretcomo contraseña, la cadena SQL resultante se vería así:

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

Todo está bien. Ahora imagine que proporciona esta cadena como contraseña:

' OR '1'='1

Entonces la cadena SQL resultante sería esta:

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

Eso le permitiría iniciar sesión en cualquier cuenta sin conocer la contraseña. Por lo tanto, no necesita poder usar dos declaraciones para usar la inyección de SQL, aunque puede hacer cosas más destructivas si puede suministrar varias declaraciones.

Johannes Fahrenkrug
fuente
71

No, 'no es un comentario en SQL, sino un delimitador.

Mamá supuso que el programador de la base de datos hizo una solicitud con el siguiente aspecto:

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

(por ejemplo) para agregar el nuevo alumno, donde los $xxxcontenidos variables se tomaron directamente de un formulario HTML, sin verificar el formato ni escapar caracteres especiales.

Entonces, si $firstNamecontiene Robert'); DROP TABLE students; --el programa de base de datos, ejecutará la siguiente solicitud directamente en la base de datos:

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

es decir. finalizará antes la declaración de inserción, ejecutará el código malicioso que quiera el cracker y luego comentará el resto del código que pueda haber.

Mmm, soy demasiado lento, ya veo 8 respuestas antes que las mías en la banda naranja ... :-) Parece un tema popular.

PhiLho
fuente
39

TL; DR

- La aplicación acepta la entrada, en este caso 'Nancy', sin intentar - desinfectar la entrada, como escapando de caracteres especiales 
school => INSERT INTO estudiantes VALUES ( 'Nancy' ); INSERTAR 0 1
   
  

- La inyección de SQL se produce cuando la entrada en un comando de base de datos se manipula para - hacer que el servidor de base de datos ejecute SQL 
school arbitrario => INSERTAR EN LOS VALORES de los estudiantes ( 'Robert' ); DROP TABLE estudiantes ; - '); INSERTAR 0 1 TABLA DE GOTA
      
  
 

- Los registros de los estudiantes ya no están, ¡podría haber sido aún peor! 
escuela => SELECCIONAR * DE los estudiantes ; 
ERROR :   relación "estudiantes" no no existir   
LÍNEA 1 : SELECCIONAR * DE los estudiantes ; ^   
                      

Esto elimina (elimina) la tabla del alumno.

( Todos los ejemplos de código en esta respuesta se ejecutaron en un servidor de base de datos PostgreSQL 9.1.2 ) .

Para dejar en claro lo que está sucediendo, intentemos esto con una tabla simple que contenga solo el campo de nombre y agregue una sola fila:

escuela => CREAR TABLA estudiantes ( nombre TEXTO PRIMARIA CLAVE ); 
AVISO : CREAR TABLA / PRIMARIA CLAVE va a crear implícita índice "students_pkey" de mesa "estudiantes" CREAR TABLA 
escuela => INSERTAR EN estudiantes VALORES ( 'John' ); INSERTAR 0 1             
    
  

Supongamos que la aplicación usa el siguiente SQL para insertar datos en la tabla:

INSERTAR EN LOS VALORES de los estudiantes ( 'foobar' );  

Reemplazar foobarcon el nombre real del alumno. Una operación de inserción normal se vería así:

- Entrada: 
escuela Nancy => INSERTAR EN LOS VALORES de los estudiantes ( 'Nancy' ); INSERTAR 0 1   
  

Cuando consultamos la tabla, obtenemos esto:

escuela => SELECCIONAR * DE los estudiantes ;   
 nombre
-------
 Juan
 Nancy
( 2 filas ) 

¿Qué sucede cuando insertamos el nombre de Little Bobby Tables en la tabla?

- Entrada: Robert '); DROP TABLE estudiantes; - 
escuela => INSERTAR EN LOS VALORES de los estudiantes ( 'Robert' ); DROP TABLE estudiantes ; - '); INSERTAR 0 1 TABLA DE GOTA      
  
 

La inyección SQL aquí es el resultado del nombre del estudiante que termina la declaración e incluye un DROP TABLEcomando separado ; los dos guiones al final de la entrada están destinados a comentar cualquier código sobrante que, de lo contrario, causaría un error. La última línea de la salida confirma que el servidor de bases de datos ha descartado la tabla.

Es importante tener en cuenta que durante la INSERToperación la aplicación no verifica la entrada de caracteres especiales y, por lo tanto, permite la entrada arbitraria en el comando SQL. Esto significa que un usuario malintencionado puede insertar, en un campo normalmente destinado a la entrada del usuario, símbolos especiales como comillas junto con un código SQL arbitrario para hacer que el sistema de base de datos lo ejecute, por lo tanto, la inyección SQL  .

¿El resultado?

escuela => SELECCIONAR * DE los estudiantes ; 
ERROR :   relación "estudiantes" no no existir   
LÍNEA 1 : SELECCIONAR * DE los estudiantes ; ^   
                      

La inyección SQL es el equivalente de la base de datos de una vulnerabilidad de ejecución remota de código arbitrario en un sistema operativo o aplicación. El impacto potencial de un ataque de inyección SQL exitoso no puede subestimarse; según el sistema de la base de datos y la configuración de la aplicación, un atacante puede usarlo para causar pérdida de datos (como en este caso), obtener acceso no autorizado a los datos o incluso ejecutar código arbitrario en la máquina host misma.

Como señaló el cómic XKCD, una forma de protegerse contra los ataques de inyección SQL es desinfectar las entradas de la base de datos, como escapar caracteres especiales, para que no puedan modificar el comando SQL subyacente y, por lo tanto, no puedan causar la ejecución de código SQL arbitrario. Si usa consultas parametrizadas, como SqlParameteren ADO.NET, la entrada, como mínimo, se desinfectará automáticamente para evitar la inyección de SQL.

Sin embargo, las entradas de desinfección a nivel de aplicación pueden no detener las técnicas de inyección SQL más avanzadas. Por ejemplo, hay formas de eludir la mysql_real_escape_stringfunción PHP . Para mayor protección, muchos sistemas de bases de datos admiten declaraciones preparadas . Si se implementa correctamente en el backend, las declaraciones preparadas pueden hacer que la inyección de SQL sea imposible al tratar las entradas de datos como semánticamente separadas del resto del comando.

bwDraco
fuente
30

Digamos que ingenuamente escribiste un método de creación de estudiantes como este:

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

Y alguien ingresa el nombre Robert'); DROP TABLE STUDENTS; --

Lo que se ejecuta en la base de datos es esta consulta:

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

El punto y coma finaliza el comando de inserción e inicia otro; el - comenta el resto de la línea. El comando DROP TABLE se ejecuta ...

Es por eso que los parámetros de enlace son algo bueno.

Dan Vinton
fuente
26

Una comilla simple es el comienzo y el final de una cadena. Un punto y coma es el final de una declaración. Entonces, si estuvieran haciendo una selección como esta:

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

El SQL se convertiría en:

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

En algunos sistemas, el selectprimero se ejecuta seguido de la dropdeclaración. El mensaje es: NO INCORPORE VALORES EN SU SQL. En su lugar, use parámetros.

CodeAndCats
fuente
18

Al ');finalizar la consulta, no comienza un comentario. Luego, suelta la tabla de estudiantes y comenta el resto de la consulta que se suponía que debía ejecutarse.

Jorn
fuente
17

El escritor de la base de datos probablemente hizo un

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

Si student_name es el indicado, se realiza la selección con el nombre "Robert" y luego se descarta la tabla. La parte "-" cambia el resto de la consulta dada en un comentario.

Paul Tomblin
fuente
Fue mi primer pensamiento, pero obtienes un error de sintaxis con el paréntesis de cierre, ¿no?
PhiLho
3
Es por eso que hay un - al final, que indica que el texto restante es un comentario y debe ignorarse.
17

En este caso, 'no es un carácter de comentario. Se utiliza para delimitar literales de cadena. El dibujante de cómics confía en la idea de que la escuela en cuestión tiene un sql dinámico en algún lugar que se parece a esto:

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

Entonces, el carácter 'finaliza el literal de cadena antes de que el programador lo estuviera esperando. Combinado con el; carácter para finalizar la declaración, un atacante ahora puede agregar cualquier sql que desee. El comentario al final es para asegurarse de que cualquier sql restante en la declaración original no impida que la consulta se compile en el servidor.

FWIW, también creo que el cómic en cuestión tiene un detalle importante incorrecto: si está pensando en desinfectar las entradas de su base de datos, como sugiere el cómic, todavía lo está haciendo mal. En cambio, debe pensar en términos de poner en cuarentena las entradas de su base de datos, y la forma correcta de hacerlo es mediante consultas parametrizadas.

Joel Coehoorn
fuente
16

El 'carácter en SQL se usa para constantes de cadena. En este caso, se utiliza para finalizar la cadena constante y no para comentarios.

Rockcoder
fuente
7

Así es como funciona: supongamos que el administrador está buscando registros de estudiantes

Robert'); DROP TABLE STUDENTS; --

Dado que la cuenta de administrador tiene altos privilegios, es posible eliminar la tabla de esta cuenta.

El código para recuperar el nombre de usuario de la solicitud es

Ahora la consulta sería algo como esto (para buscar en la tabla del alumno)

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

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

La consulta resultante se convierte en

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

Dado que la entrada del usuario no se desinfecta, la consulta anterior se manipula en 2 partes

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

DROP TABLE STUDENTS; --

El guión doble (-) solo comentará la parte restante de la consulta.

Esto es peligroso ya que puede anular la autenticación de contraseña, si está presente

El primero hará la búsqueda normal.

El segundo dejará la mesa del estudiante si la cuenta tiene suficientes privilegios (en general, la cuenta de administrador de la escuela ejecutará dicha consulta y tendrá los privilegios mencionados anteriormente).

Sin nombre
fuente
SELECT* FROM sutdents ...- olvidaste una "s". Esto es lo que estás dejando caer. DROP TABLE STUDENTS;
DevWL
4

No necesita ingresar datos de formulario para realizar una inyección SQL.

Nadie señaló esto antes, así que podría alertar a algunos de ustedes.

Principalmente intentaremos parchear los formularios de entrada. Pero este no es el único lugar donde puede ser atacado con inyección SQL. Puede hacer un ataque muy simple con URL que envía datos a través de la solicitud GET; Considere el siguiente ejemplo:

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

Tu URL se vería http://yoursite.com/show?id=1

Ahora alguien podría intentar algo como esto

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

Intente reemplazar table_name con el nombre real de la tabla. Si acertaba el nombre de su mesa, ¡vaciarían su mesa! (Es muy fácil forzar esta URL con un script simple)

Su consulta se vería así ...

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

Ejemplo de código vulnerable PHP usando 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!!! :( *************/
...

Solución: utilice los métodos PDO prepare () y bindParam ():

<?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
fuente