¿Las declaraciones preparadas son 100% seguras contra la inyección SQL?

72

¿Están las declaraciones preparadas en realidad 100% seguras contra la inyección de SQL, asumiendo que todos los parámetros proporcionados por el usuario se pasan como parámetros de enlace de consulta?

Cada vez que veo a personas que usan las antiguas funciones de mysql_ en StackOverflow (que es, lamentablemente, con demasiada frecuencia), generalmente les digo a las personas que las declaraciones preparadas son el Chuck Norris (o Jon Skeet) de las medidas de seguridad de inyección de SQL.

Sin embargo, nunca he visto ninguna documentación que diga categóricamente "esto es 100% seguro" . Mi comprensión de ellos es que separan el lenguaje de consulta y los parámetros hasta la puerta principal del servidor, que luego los trata como entidades separadas.

¿Tengo razón en este supuesto?

    
pregunta Polynomial 21.05.2012 - 17:51
fuente

2 respuestas

47

¿Garantía de 100% seguro de inyección SQL? No lo conseguiré (de mí).

En principio, su base de datos (o biblioteca en su idioma que interactúa con la base de datos) podría implementar sentencias preparadas con parámetros enlazados de una manera insegura susceptible de algún tipo de ataque avanzado, por ejemplo, explotar los desbordamientos de búfer o tener caracteres que terminen en cero. en cadenas proporcionadas por el usuario, etc. (Podría argumentar que estos tipos de ataques no deberían llamarse inyección SQL, ya que son fundamentalmente diferentes; pero eso es solo semántica).

Nunca he oído hablar de ninguno de estos ataques en declaraciones preparadas en bases de datos reales en el campo y sugiero encarecidamente el uso de parámetros enlazados para evitar la inyección de SQL. Sin parámetros enlazados ni saneamiento de entrada, es trivial hacer inyección de SQL. Con solo el saneamiento de entrada, a menudo es posible encontrar una laguna oscura alrededor del saneamiento.

Con los parámetros enlazados, el plan de ejecución de su consulta SQL se resuelve con anticipación sin depender de la entrada del usuario, lo que debería hacer que la inyección de SQL no sea posible (ya que las comillas, los símbolos de comentarios, etc. se insertan solo dentro de la declaración de SQL ya compilada ).

El único argumento en contra del uso de declaraciones preparadas es que desea que su base de datos optimice sus planes de ejecución en función de la consulta real. La mayoría de las bases de datos, cuando se realiza la consulta completa, son lo suficientemente inteligentes como para hacer un plan de ejecución óptimo; por ejemplo, si la consulta devuelve un gran porcentaje de la tabla, querría recorrer toda la tabla para encontrar coincidencias; mientras que si solo va a obtener algunos registros, puede hacer una búsqueda basada en índices [1] .

EDITAR: Respondiendo a dos críticas (que son un poco demasiado largas para comentarios):

Primero, como otros anotaron sí, todas las bases de datos relacionales que admiten declaraciones preparadas y parámetros enlazados no necesariamente compilan previamente la declaración preparada sin mirar el valor de los parámetros enlazados. Muchas bases de datos suelen hacer esto, pero también es posible que las bases de datos analicen los valores de los parámetros enlazados al determinar el plan de ejecución. Esto no es un problema, ya que la estructura de la declaración preparada con parámetros enlazados separados, facilita que la base de datos pueda diferenciar claramente la instrucción SQL (incluidas las palabras clave SQL) de los datos en los parámetros enlazados (donde no habrá nada en un parámetro enlazado). interpretado como una palabra clave SQL). Esto no es posible cuando se construyen sentencias de SQL a partir de concatenación de cadenas donde las variables y las palabras clave de SQL se entremezclan.

En segundo lugar, como lo señala la otra respuesta , utiliza parámetros enlazados al llamar a una declaración SQL en un momento dado un programa evitará con seguridad la inyección de SQL al realizar esa llamada de nivel superior. Sin embargo, si tiene vulnerabilidades de inyección de SQL en otra parte de la aplicación (por ejemplo, en las funciones definidas por el usuario que almacenó y ejecutó en su base de datos que escribió de manera insegura para construir consultas SQL mediante concatenación de cadenas).

Por ejemplo, si en su aplicación escribió pseudocódigo como:

sql_stmt = "SELECT create_new_user(?, ?)"
params = (email_str, hashed_pw_str)
db_conn.execute_with_params(sql_stmt, params)

No puede haber inyección SQL cuando se ejecuta esta declaración SQL en el nivel de la aplicación. Sin embargo, si la función de la base de datos definida por el usuario fue escrita de forma poco segura (utilizando la sintaxis PL / pgSQL):

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES (' || email_str || ', ' || hashed_pw_str || ');'
     EXECUTE sql_str;
END;
$$
LANGUAGE plpgsql;

entonces sería vulnerable a los ataques de inyección de SQL, ya que ejecuta una instrucción SQL construida a través de la concatenación de cadenas que mezcla la declaración SQL con cadenas que contienen los valores de las variables definidas por el usuario.

Dicho esto, a menos que intentara ser inseguro (construir sentencias de SQL mediante concatenación de cadenas), sería más natural escribir el definido por el usuario de una manera segura como:

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
BEGIN
     INSERT INTO users VALUES (email_str, hashed_pw_str);
END;
$$
LANGUAGE plpgsql;

Además, si realmente sintió la necesidad de componer una declaración SQL a partir de una cadena en una función definida por el usuario, aún puede separar las variables de datos de la declaración SQL de la misma manera que en los parámetros_contenidos / contenidos almacenados incluso dentro de una función definida por el usuario . Por ejemplo, en PL / pgSQL :

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES($1, $2)'
     EXECUTE sql_str USING email_str, hashed_pw_str;
END;
$$
LANGUAGE plpgsql;

Por lo tanto, el uso de sentencias preparadas está a salvo de la inyección de SQL, siempre y cuando no solo hagas cosas inseguras en otro lugar (es decir, la construcción de sentencias de SQL por concatenación de cadenas).

    
respondido por el dr jimbob 21.05.2012 - 18:31
fuente
22

100% seguro? Ni siquiera cerca. Los parámetros enlazados (preparados con instrucciones o de otra manera) pueden prevenir, al 100%, una clase de vulnerabilidad de inyección de SQL (suponiendo que no haya errores de la base de datos y una implementación sensata). De ninguna manera evitan otras clases. Tenga en cuenta que PostgreSQL (mi db de elección) tiene la opción de vincular los parámetros a las declaraciones ad hoc, lo que ahorra un viaje de ida y vuelta con respecto a las declaraciones preparadas si no necesita ciertas características de estas.

Tienes que entender que muchas bases de datos grandes y complejas son programas en sí mismas. La complejidad de estos programas varía bastante, y la inyección de SQL es algo que hay que tener en cuenta dentro de las rutinas de programación. Dichas rutinas incluyen activadores, funciones definidas por el usuario, procedimientos almacenados y similares. No siempre es obvio cómo interactúan estas cosas desde un nivel de aplicación, ya que muchos dba's buenos proporcionan cierto grado de abstracción entre el nivel de acceso de la aplicación y el nivel de almacenamiento.

Con los parámetros enlazados, se analiza el árbol de consultas, luego, al menos en PostgreSQL, se miran los datos para planificar. El plan se ejecuta. Con las declaraciones preparadas, el plan se guarda para que pueda volver a ejecutar el mismo plan con datos diferentes una y otra vez (esto puede o no ser lo que quiere). Pero el punto es que con parámetros enlazados, un parámetro no puede inyectar nada en el árbol de análisis. Por lo tanto, esta clase de problema de inyección de SQL está debidamente resuelta.

Pero ahora necesitamos registrar quién escribe qué en una tabla, así que agregamos activadores y funciones definidas por el usuario para encapsular la lógica de estos activadores. Estos plantean nuevos problemas. Si tiene algún SQL dinámico en estos, entonces debe preocuparse por la inyección de SQL allí. Las tablas en las que escriben pueden tener sus propios desencadenantes, y así sucesivamente. De manera similar, una llamada de función podría invocar otra consulta que podría invocar otra llamada de función y así sucesivamente. Cada uno de estos está planificado independientemente del árbol principal.

Lo que esto significa es que si ejecuto una consulta con un parámetro enlazado como foo'; drop user postgres; -- , entonces no puede implicar directamente el árbol de consulta de nivel superior y hacer que agregue otro comando para eliminar al usuario de Postgres. Sin embargo, si esta consulta llama a otra función directamente o no, es posible que en algún lugar de la línea, una función sea vulnerable y el usuario de Postgres sea eliminado. Los parámetros enlazados ofrecían protección no a consultas secundarias. Esas consultas secundarias deben asegurarse de utilizar los parámetros vinculados también en la medida de lo posible y, en caso contrario, deberán utilizar las rutinas de cotización adecuadas.

El agujero del conejo se adentra.

BTW para una pregunta sobre Desbordamiento de pila donde este problema es evidente, consulte enlace

También es una versión más problemática (debido a la limitación de las declaraciones de utilidad) en enlace

    
respondido por el Chris Travers 04.11.2013 - 12:27
fuente

Lea otras preguntas en las etiquetas