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

Solo mirando:

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

¿Qué hace este SQL?

Robert'); DROP TABLE STUDENTS; -- 

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

Se cae la mesa de los estudiantes.

El código original en el progtwig de la escuela probablemente se parece más a

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

Esta es la forma ingenua de agregar texto a una consulta, y es muy malo , como verán.

Después de los valores del primer nombre, segundo nombre cuadro de texto FNMName.Text (que es Robert'); DROP TABLE STUDENTS; -- Robert'); DROP TABLE STUDENTS; -- Robert'); DROP TABLE STUDENTS; -- ) y el último cuadro de texto LName.Text (llamémoslo Derper ) están concatenados con el rest de la consulta, el resultado ahora es en realidad dos consultas separadas por el terminador de la statement (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 Estudiantes

Todo lo que está más allá de la segunda consulta está marcado como un comentario : --', 'Derper')

El ' en el nombre del estudiante no es un comentario, es el delimitador de cadena de cierre. Como el nombre del alumno es una cadena, se necesita sintácticamente 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 de nuevo según el astuto comentario de dan04

Digamos que el nombre se usó en una variable, $Name . A continuación, ejecuta esta consulta:

 INSERT INTO Students VALUES ( '$Name' ) 

El código está colocando erróneamente todo lo que el usuario suministró como la variable. Querías que el SQL sea:

INSERT INTO Students VALUES (‘ Robert Tablas ‘)

Pero un usuario inteligente puede proporcionar lo que quiera:

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

Lo que obtienes es:

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

El -- solo comenta el rest de la línea.

Como todos los demás ya han señalado, el '); cierra la statement original y luego sigue una segunda statement. 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 sentencias en una cadena SQL utilizando la función mysqli_multi_query .

Sin embargo, puede manipular una instrucción SQL existente mediante inyección SQL sin tener que agregar una segunda instrucció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 peter como el nombre de usuario y el secret como la 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 la 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 es necesario que pueda usar dos instrucciones para usar la inyección SQL, aunque puede hacer más cosas destructivas si puede proporcionar varias declaraciones.

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

Mamá supuso que el progtwigdor 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 contenidos de la variable $xxx se tomaron directamente de un formulario HTML, sin verificar el formato ni escapar caracteres especiales.

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

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

es decir. terminará pronto la statement de inserción, ejecutará el código malicioso que quiera y luego comentará el rest del código que pueda existir.

Mmm, soy demasiado lento, veo ya 8 respuestas antes que la mía en la banda naranja … 🙂 Un tema popular, parece.

TL; DR

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

 - La inyección de SQL ocurre cuando se manipula la entrada en un comando de base de datos para
 - hacer que el servidor de base de datos ejecute SQL arbitrario
 school => INSERT INTO students VALUES ('Robert');  DROP TABLE estudiantes;  - ');
 INSERTAR 0 1
 MESA PLEGABLE

 - Los registros estudiantiles ahora se han ido - ¡podría haber sido aún peor!
 escuela => SELECCIONAR * DE estudiantes;
 ERROR: la relación "estudiantes" no existe
 LINE 1: SELECT * FROM estudiantes;
                       ^

Esto suelta (elimina) la tabla de estudiantes.

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

Para aclarar 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 TECLA PRIMARIA);
 AVISO: CREATE TABLE / PRIMARY KEY creará el índice implícito "students_pkey" para la tabla "students"
 CREAR MESA
 school => INSERT INTO students VALUES ('John');
 INSERTAR 0 1

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

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

Reemplace foobar con el nombre real del alumno. Una operación de inserción normal se vería así:

 - Entrada: Nancy
 school => INSERT INTO students VALUES ('Nancy');
 INSERTAR 0 1

Cuando consultamos la tabla, obtenemos esto:

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

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

 - Entrada: Robert ');  DROP TABLE estudiantes;  -
 school => INSERT INTO students VALUES ('Robert');  DROP TABLE estudiantes;  - ');
 INSERTAR 0 1
 MESA PLEGABLE

La inyección SQL aquí es el resultado del nombre del estudiante que termina la instrucción e incluye un comando DROP TABLE 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 del resultado confirma que el servidor de la base de datos ha abandonado la tabla.

Es importante observar que durante la operación INSERT , la aplicación no está verificando la entrada de ningún carácter especial y, por lo tanto, permite introducir entradas arbitrarias 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 código SQL arbitrario para hacer que el sistema de la base de datos lo ejecute, por lo tanto, inyección de SQL .

¿El resultado?

 escuela => SELECCIONAR * DE estudiantes;
 ERROR: la relación "estudiantes" no existe
 LINE 1: SELECT * FROM estudiantes;
                       ^

La inyección SQL es la base de datos equivalente a 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: dependiendo del sistema de base de datos y la configuración de la aplicación, puede ser utilizado por un atacante para causar la 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.

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 escapando 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 utiliza consultas parametrizadas, como mediante el uso de SqlParameter en 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 no pueden detener las técnicas de inyección SQL más avanzadas. Por ejemplo, hay maneras de eludir la función de PHP mysql_real_escape_string . Para mayor protección, muchos sistemas de bases de datos admiten declaraciones preparadas . Si se implementa correctamente en el backend, las declaraciones preparadas pueden imposibilitar la inyección de SQL al tratar las entradas de datos semánticamente separadas del rest del comando.

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 de Robert'); DROP TABLE STUDENTS; -- 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 y comienza otro; el – comenta el rest de la línea. El comando DROP TABLE se ejecuta …

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

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

 Select * From Students Where (Name = '') 

El SQL se convertiría en:

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

En algunos sistemas, el select se ejecutará primero seguido de la statement drop . El mensaje es: NO INCLUYER VALORES EN SU SQL. En su lugar usa parámetros!

El '); finaliza la consulta, no comienza un comentario. A continuación, descarta la tabla de alumnos y comenta el rest de la consulta que se suponía que debía ejecutarse.

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 dado, que hace la selección con el nombre “Robert” y luego suelta la tabla. La parte “-” cambia el rest de la consulta dada en un comentario.

En este caso, ‘no es un personaje de comentario. Se usa para delimitar literales de cadena. El dibujante de cómics confía en la idea de que la escuela en cuestión tiene SQL dynamic en algún lugar que se ve así:

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

Así que ahora el carácter ‘termina la cadena literal antes de que el progtwigdor lo esperara. Combinado con el; carácter para finalizar la statement, un atacante ahora puede agregar cualquier sql que desee. El comentario al final es para asegurarse de que cualquier SQL restante en la statement 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 equivocado: si estás pensando en desinfectar las entradas de tu base de datos, como sugiere el cómic, aún lo estás haciendo mal. En su lugar, debe pensar en términos de poner en cuarentena las entradas de su base de datos, y la forma correcta de hacerlo es a través de consultas parametrizadas.

El caracter ' en SQL se usa para constantes de cadena. En este caso, se usa para terminar la constante de cadena y no para hacer comentarios.

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

 Robert'); DROP TABLE STUDENTS; -- 

Como 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 de estudiantes)

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

La consulta resultante se convierte

 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 doble guión (-) 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 eliminará al alumno de la mesa si la cuenta tiene suficientes privilegios (en general, la cuenta de administrador de la escuela ejecutará dicha consulta y tendrá los privilegios mencionados anteriormente).