¿Cómo un PreparedStatement evita o previene la inyección de SQL?

122

Sé que PreparedStatements evita / previene la inyección de SQL. ¿Como hace eso? ¿La consulta de formulario final que se construye utilizando PreparedStatements será una cadena o no?

Prabhu R
fuente
3
Técnicamente, la especificación JDBC no insiste en que no haya fallas de inyección SQL. No conozco ninguna unidad afectada.
Tom Hawtin - tackline
3
@ Jayesh, sugiero agregar el contenido de tu blog como respuesta aquí. La mayoría de las respuestas solo indican las diferencias en la generación dinámica de consultas SQL en blanco y negro y la preparación preparada. No abordan la cuestión de por qué las declaraciones preparadas funcionan mejor, lo que hace su blog.
Pavan Manjunath
1
Agregado como respuesta, espero que ayude.
Jayesh

Respuestas:

78

El problema con la inyección SQL es que se usa una entrada de usuario como parte de la instrucción SQL. Al usar declaraciones preparadas, puede forzar que la entrada del usuario se maneje como el contenido de un parámetro (y no como parte del comando SQL).

Pero si no usa la entrada del usuario como un parámetro para su declaración preparada, sino que construye su comando SQL uniendo cadenas, aún es vulnerable a las inyecciones SQL incluso cuando usa declaraciones preparadas.

tangenos
fuente
1
Claro, pero aún puede codificar algunos o todos sus parámetros.
tangens
16
Ejemplo, por favor: pero si no utiliza la entrada del usuario como parámetro para su declaración preparada, sino que construye su comando SQL uniendo cadenas, aún es vulnerable a las inyecciones SQL incluso cuando usa declaraciones preparadas.
David Blaine
44
Las declaraciones preparadas de FWIW no son cosa de JDBC, son cosas de SQL. Puede preparar y ejecutar declaraciones preparadas desde una consola SQL. PreparedStatement solo los admite desde JDBC.
beldaz
198

Considere dos formas de hacer lo mismo:

PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')");
stmt.execute();

O

PreparedStatement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)");
stmt.setString(1, user);
stmt.execute();

Si "usuario" provenía de la entrada del usuario y la entrada del usuario era

Robert'); DROP TABLE students; --

Luego, en primera instancia, estarías manguera. En el segundo, estarías a salvo y Little Bobby Tables estaría registrado en tu escuela.

Paul Tomblin
fuente
8
Entonces, si acerté, la consulta en el segundo ejemplo que se ejecutará sería: INSERTAR EN LOS VALORES de los estudiantes ("Robert '); DROP TABLE estudiantes; -") - o al menos algo así. ¿Es esto cierto?
Max
18
No, en PRIMERA instancia, obtendría esa declaración. En el segundo, insertaría "Robert '); DROP TABLE estudiantes; -" en la tabla de usuarios.
Paul Tomblin
3
Eso es lo que quise decir, en el segundo ejemplo (el "seguro"), la cadena Robert '); DROP TABLE estudiantes; - se guardará en el campo en la tabla del alumno. ¿Escribí algo más? ;)
Max
77
Lo siento, las citas de anidación son algo que trato de evitar debido a una confusión como esta. Es por eso que me gusta PreparedStatements con parámetros.
Paul Tomblin
59
Mesitas Bobby. XD Gran referencia
Amalgovinus
128

Para comprender cómo PreparedStatement previene la inyección de SQL, debemos comprender las fases de ejecución de la consulta SQL.

1. Fase de compilación. 2. Fase de ejecución.

Cada vez que el motor del servidor SQL recibe una consulta, debe pasar por las siguientes fases,

Fases de ejecución de consultas

  1. Fase de análisis y normalización: en esta fase, se comprueba la sintaxis y la semántica de la consulta. Comprueba si las tablas y columnas de referencias utilizadas en la consulta existen o no. También tiene muchas otras tareas que hacer, pero no vayamos en detalle.

  2. Fase de compilación: en esta fase, las palabras clave utilizadas en la consulta, como select, from, where, etc., se convierten en un formato comprensible para la máquina. Esta es la fase donde se interpreta la consulta y se decide la acción correspondiente a tomar. También tiene muchas otras tareas que hacer, pero no vayamos en detalle.

  3. Plan de optimización de consultas: en esta fase, se crea el Árbol de decisión para encontrar las formas en que se puede ejecutar la consulta. Descubre la cantidad de formas en que se puede ejecutar la consulta y el costo asociado con cada forma de ejecutar la consulta. Elige el mejor plan para ejecutar una consulta.

  4. Caché: el mejor plan seleccionado en el plan de optimización de consultas se almacena en caché, de modo que cada vez que ingrese la misma consulta, no tenga que pasar por la Fase 1, la Fase 2 y la Fase 3 nuevamente. La próxima vez que ingrese la consulta, se comprobará directamente en la caché y se recogerá desde allí para ejecutarse.

  5. Fase de ejecución: en esta fase, la consulta proporcionada se ejecuta y los datos se devuelven al usuario como ResultSetobjeto.

Comportamiento de la API de PreparedStatement en los pasos anteriores

  1. Las declaraciones preparadas no son consultas SQL completas y contienen marcadores de posición, que en tiempo de ejecución se reemplazan por datos reales proporcionados por el usuario.

  2. Cada vez que un PreparedStatment que contiene marcadores de posición se pasa al motor de SQL Server, pasa por las siguientes fases

    1. Fase de análisis y normalización
    2. Fase de compilación
    3. Plan de optimización de consultas
    4. Caché (la consulta compilada con marcadores de posición se almacena en la caché).

ACTUALIZAR usuario set username =? y contraseña =? DONDE id =?

  1. La consulta anterior se analizará, compilará con marcadores de posición como tratamiento especial, se optimizará y se almacenará en caché. La consulta en esta etapa ya está compilada y convertida en un formato comprensible para la máquina. Por lo tanto, podemos decir que la consulta almacenada en caché está precompilada y solo los marcadores de posición deben reemplazarse con datos proporcionados por el usuario.

  2. Ahora, en tiempo de ejecución, cuando entran los datos proporcionados por el usuario, la consulta precompilada se recoge de la caché y los marcadores de posición se reemplazan con datos proporcionados por el usuario.

PrepareStatementWorking

(Recuerde, después de que los marcadores de posición se reemplazan con datos del usuario, la consulta final no se vuelve a compilar / interpretar y el motor de SQL Server trata los datos del usuario como datos puros y no como un SQL que debe analizarse o compilarse nuevamente; esa es la belleza de PreparedStatement. )

Si la consulta no tiene que pasar por la fase de compilación nuevamente, los datos reemplazados en los marcadores de posición se tratan como datos puros y no tienen ningún significado para el motor de SQL Server, y ejecuta la consulta directamente.

Nota: es la fase de compilación después de la fase de análisis, que comprende / interpreta la estructura de la consulta y le da un comportamiento significativo. En el caso de PreparedStatement, la consulta se compila solo una vez y la consulta compilada en caché se recoge todo el tiempo para reemplazar los datos del usuario y ejecutarlos.

Debido a la función de compilación única de PreparedStatement, está libre de ataques de inyección SQL.

Puede obtener una explicación detallada con un ejemplo aquí: https://javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html

Jayesh
fuente
3
buena explicación
Dheeraj Joshi
44
Literalmente, la respuesta más completa sobre la pieza CÓMO funciona
jouell
Fue de mucha ayuda. Gracias por la explicación detallada.
Desconocido el
26

El SQL utilizado en un PreparedStatement está precompilado en el controlador. A partir de ese momento, los parámetros se envían al controlador como valores literales y no como partes ejecutables de SQL; por lo tanto, no se puede inyectar SQL utilizando un parámetro. Otro efecto secundario beneficioso de PreparedStatements (precompilación + envío de solo parámetros) es el rendimiento mejorado cuando se ejecuta la instrucción varias veces, incluso con diferentes valores para los parámetros (suponiendo que el controlador admite PreparedStatements) ya que el controlador no tiene que realizar el análisis y compilación de SQL cada uno vez que cambian los parámetros.

Travis Heseman
fuente
No tiene que implementarse así, y creo que a menudo no lo es.
Tom Hawtin - tackline
44
En realidad, el SQL normalmente está precompilado en la base de datos. Es decir, se prepara un plan de ejecución en la base de datos. Cuando ejecuta la consulta, el plan se ejecuta con esos parámetros. El beneficio adicional es que la misma declaración se puede ejecutar con diferentes parámetros sin que el procesador de consultas tenga que compilar un nuevo plan cada vez.
beldaz
3

me conjetura que será una cadena. Pero los parámetros de entrada se enviarán a la base de datos y se aplicarán las conversiones / conversiones apropiadas antes de crear una declaración SQL real.

Para darle un ejemplo, podría intentar ver si CAST / Conversion funciona.
Si funciona, podría crear una declaración final a partir de él.

   SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))

Pruebe un ejemplo con una instrucción SQL que acepte un parámetro numérico.
Ahora, intente pasar una variable de cadena (con contenido numérico que sea aceptable como parámetro numérico). ¿Se plantea algún error?

Ahora, intente pasar una variable de cadena (con contenido que no sea aceptable como parámetro numérico). ¿Mira qué pasa?

shahkalpesh
fuente
3

La declaración preparada es más segura. Convertirá un parámetro al tipo especificado.

Por ejemplo stmt.setString(1, user);convertirá eluser parámetro en una cadena.

Supongamos que el parámetro contiene una cadena SQL que contiene un comando ejecutable : el uso de una instrucción preparada no lo permitirá.

Agrega metacaracteres (también conocido como conversión automática) a eso.

Esto hace que sea más seguro.

Guru R Handa
fuente
2

Inyección SQL: cuando el usuario tiene la oportunidad de ingresar algo que podría ser parte de la instrucción sql

Por ejemplo:

Consulta de cadena = “INSERTAR EN LOS VALORES de los estudiantes ('” + usuario + “')”

cuando el usuario ingresa "Robert"); DROP TABLE estudiantes; - "como entrada, provoca la inyección SQL

¿Cómo la declaración preparada evita esto?

Consulta de cadena = “INSERTAR EN LOS VALORES de los estudiantes ('” + “: nombre” + “')”

parámetros.addValue ("nombre", usuario);

=> cuando el usuario ingresa nuevamente “Robert '); DROP TABLE estudiantes; - ", la cadena de entrada está precompilada en el controlador como valores literales y supongo que se puede convertir como:

CAST ('Robert'); DROP TABLE estudiantes; - «AS varchar (30))

Entonces, al final, la cadena se insertará literalmente como el nombre de la tabla.

http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/

Jack
fuente
1
Si no me equivoco, la parte CAST(‘Robert’);de CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))se rompería, luego procedería a abandonar la tabla si ese fuera el caso. Detiene la inyección, por lo que creo que el ejemplo no es lo suficientemente completo como para explicar el escenario.
Héctor Álvarez
1

Declaración preparada:

1) La precompilación y el almacenamiento en caché del lado DB de la instrucción SQL conduce a una ejecución general más rápida y la capacidad de reutilizar la misma instrucción SQL en lotes.

2) Prevención automática de ataques de inyección SQL por escape integrado de comillas y otros caracteres especiales. Tenga en cuenta que esto requiere que utilice cualquiera de los métodos PreparedStatement setXxx () para establecer el valor.

Mukesh Kumar
fuente
1

Como se explica en esta publicación , elPreparedStatement solo no te ayuda si todavía estás concatenando cadenas.

Por ejemplo, un atacante deshonesto aún puede hacer lo siguiente:

  • llame a una función de suspensión para que todas las conexiones de su base de datos estén ocupadas, por lo que su aplicación no estará disponible
  • extraer datos confidenciales de la base de datos
  • sin pasar por la autenticación de usuario

No solo SQL, sino incluso JPQL o HQL pueden verse comprometidos si no está utilizando parámetros de enlace.

En pocas palabras, nunca debe usar la concatenación de cadenas al crear sentencias SQL. Use una API dedicada para ese propósito:

Vlad Mihalcea
fuente
1
Gracias por señalar la importancia de utilizar el enlace de parámetros, en lugar de solo PreparedStatement. Sin embargo, su respuesta parece implicar que es necesario usar una API dedicada para protegerse contra la inyección de SQL. Como este no es el caso, y el uso de PreparedStatement con enlace de parámetros también funciona, ¿le gustaría reformular?
Wild Pottok
-3

En las declaraciones preparadas, el usuario se ve obligado a ingresar datos como parámetros. Si el usuario ingresa algunas declaraciones vulnerables como DROP TABLE o SELECT * FROM USERS, los datos no se verán afectados, ya que estos se considerarían como parámetros de la declaración SQL

Shreyas K
fuente
Misma respuesta que la respuesta seleccionada con menos precisiones.
Julien Maret