2009-08-11 15 views
25

Me siento un poco tonto por preguntar esto ya que parezco ser la única persona en el mundo que no lo entiende, pero aquí va de todos modos. Voy a usar Python como ejemplo. Cuando uso consultas SQL primas (que suelen utilizar ORM) utilizo parametrización, como en este ejemplo usando SQLite:¿Cómo funciona la parametrización de consultas SQL?

Método A:

username = "wayne" 
query_params = (username) 
cursor.execute("SELECT * FROM mytable WHERE user=?", query_params) 

Sé que esto funciona y sé que esto es el recomendado en general forma de hacerlo Una forma de inyección SQL vulnerables a hacer la misma cosa sería algo como esto:

Método B:

username = "wayne" 
cursor.execute("SELECT * FROM mytable WHERE user='%s'" % username) 

Por lo que puedo decir que entiendo la inyección de SQL, como se explica en this Wikipedia article. Mi pregunta es simple: ¿cómo es el método A realmente diferente al método B? ¿Por qué el resultado final del método A no es el mismo que el método B? Supongo que el método cursor.execute() (parte de la especificación DB-API de Python) se encarga de escanear correctamente y verificar el tipo de entrada, pero esto nunca se menciona explícitamente en ninguna parte. ¿Es eso todo lo que la parametrización en este contexto es? Para mí, cuando decimos "parametrización", todo lo que significa es "sustitución de cadena", como% -formateando. Es eso incorrecto?

+1

lo usaría con procedimientos almacenados, pero solo es una buena pregunta. Después de leer un poco, descubrí que se debe a que Calsbeek dijo a continuación. La consulta parametrizada tomaría 'wayne; drop table users' como todo el nombre de usuario para buscar y no lo verá como un conjunto adicional de instrucciones. El último comentario en esta página me mostró que: http://taylorza.blogspot.com/2009/04/sql-injection-are-parameterized-queries.html – johnny

Respuesta

34

Una consulta parametrizada en realidad no reemplaza cadenas. Si utiliza sustitución de cadenas, entonces el motor de SQL realmente ve una consulta que se parece a

SELECT * FROM mytable WHERE user='wayne' 

si se utiliza un parámetro ?, entonces el motor de SQL ve una consulta que se parece a

SELECT * FROM mytable WHERE user=<some value> 

Lo que significa antes de que incluso vea la cadena "wayne", puede analizar completamente la consulta y comprender, en general, lo que hace la consulta. Pega "wayne" en su propia representación de la consulta, no en la cadena SQL que describe la consulta. Por lo tanto, la inyección SQL es imposible, ya que ya pasamos la etapa SQL del proceso.

(Lo anterior se generaliza, pero más o menos transmite la idea.)

+2

Por lo tanto, si tuviera cuentas de tablas de drop, ¿qué tipo de error? ¿daría? Simplemente no hay resultados? – johnny

+5

@johnny: Encontraría todo desde 'mytable' donde' user' era 'wayne; drop table accounts'. Retiraría el registro real de Little Bobby Tables. – Eric

+0

@johny: bien, no hay resultados. porque ese es un valor válido, incluso si es feo. El protocolo de seguridad binaria entre el cliente y el servidor no se preocupa por las comillas, los puntos y comas ni nada de eso. – Javier

-1

Cuando se envía una consulta sobre SQL Server, primero comprueba la caché de procedimientos. Si encuentra algo exactamente EXACTAMENTE igual, entonces usará el mismo plan, y no recompilará la consulta, solo reemplazará los marcadores de posición (variables) pero en el lado del servidor (db).

revise la tabla del sistema master.dbo.syscacheobjects y realice algunas pruebas para que conozca un poco más sobre este tema.

+0

Si bien es específico de SQL Server, la mayoría de los motores de base de datos hacen cosas como esta. Sin embargo, no estoy seguro si SQLite (el motor mencionado) lo hace o no. –

+0

Este es un completo aparte del concepto de entender qué hacen las consultas parametrizadas y por qué proporcionan una ventaja de seguridad. – Cheekysoft

+0

Disculpe por cualquier malentendido, así es como inicialmente comencé a entender por qué tuve que reemplazar mis consultas de reemplazo de cadenas, mirando a esta tabla del sistema, y ​​rastreando si mis consultas están cercenando el caché de procedimientos en el servidor. –

2

Cuando reemplazas texto (como tu método B), tienes que desconfiar de las comillas y demás, porque lo que el servidor obtendrá es una sola pieza de texto, y debe determinar dónde termina el valor.

Con declaraciones parametrizadas, OTOH, el servidor de bases de datos obtiene la instrucción tal como está, sin el parámetro. El valor se envía al servidor como datos diferentes, utilizando un protocolo seguro binario simple. Por lo tanto, su programa no tiene que poner comillas alrededor del valor y, por supuesto, no importa si ya había comillas en el valor en sí.

Una analogía es sobre código fuente y compilado: en su método B, está construyendo el código fuente de un procedimiento, por lo que debe asegurarse de seguir estrictamente la sintaxis del lenguaje.Con el Método A, primero crea y compila un procedimiento, luego (inmediatamente después, en su ejemplo), llama a ese procedimiento con su valor como parámetro. Y, por supuesto, los valores en memoria no están sujetos a limitaciones de sintaxis.

hum ... eso no fue realmente una analogía, es realmente lo que está sucediendo debajo del capó (más o menos).

+0

La analogía me ayudó a obtener la imagen, * aproximadamente *. +1 –

2

El uso de consultas parametrizadas es una buena manera de despejar la tarea para evitar y evitar inyecciones en la biblioteca del cliente de BD. Hará el escape antes de que reemplace la cadena con "?". Esto se hace en la biblioteca del cliente, antes del servidor de BD.

Si tiene MySQL ejecutándose, active el registro SQL, y pruebe algunas consultas parametrizadas, y verá que el servidor MySQL está recibiendo consultas totalmente sustituidas sin "?" en él, pero la biblioteca de cliente MySQL ya ha escapado de cualquier cotización en su "parámetro" para usted.

Si utiliza el método B con cadena de reemplazo simplemente, "s no son escapados automáticamente.

sinérgicamente, con MySQL, se puede preparar una consulta con parámetros antes de tiempo, y luego usar la declaración preparada en varias ocasiones después. Cuando usted prepara una consulta, MySQL la analiza y le devuelve una declaración preparada, alguna representación analizada que MySQL entiende. Cada vez que utiliza la declaración preparada, no solo está protegido contra la inyección, sino que también evita el costo de analizar la consulta nuevamente.

Y, si realmente quiere estar seguro, puede modificar su acceso DB/capa ORM para que 1) el código del servidor web solo pueda usar declaraciones preparadas, y 2) solo puede preparar declaraciones antes de que comience su servidor web. Entonces, incluso si su aplicación web está pirateada (por ejemplo, a través de un exploit), el pirata informático solo puede seguir utilizando las declaraciones preparadas, pero nada más. Para esto, necesita bloquear su aplicación web y solo permitir el acceso a la base de datos a través de su acceso DB/capa ORM.

0

Solo una advertencia aquí. Esto? la sintaxis funcionará bien y escapa correctamente las comillas simples o dobles incrustadas en las cadenas.

Sin embargo, encontré un caso en el que no funciona. Tengo una columna que rastrea una cadena de versión del formulario "n.n.n", p. "1.2.3" Parece que el formato causa un error porque parece un número Real hasta el segundo ".". Por ejemplo:

rec = (some_value, '1.2.3') 
    sql = ''' UPDATE some_table 
       SET some_column=? 
       WHERE version=? ''' 
    cur = self.conn.cursor() 
    cur.execute(sql, rec) 

con un error "Número incorrecto de fijaciones suministradas El estado de cuenta actual utiliza 1, 2 y hay suministra."

Esto funciona bien:

vers = '1.2.3' 
    rec = (some_value) 
    sql = ''' UPDATE some_table 
       SET some_column=? 
       WHERE version='%s' ''' % (vers) 
    cur = self.conn.cursor() 
    cur.execute(sql, rec)