¿Cómo pueden las declaraciones preparadas protegerse de los ataques de inyección SQL?

Resuelto Aan asked hace 12 años • 10 respuestas

¿ Cómo nos ayudan las declaraciones preparadas a prevenir ataques de inyección SQL ?

Wikipedia dice:

Las declaraciones preparadas son resistentes a la inyección de SQL, porque no es necesario escapar correctamente de los valores de los parámetros, que se transmiten más tarde utilizando un protocolo diferente. Si la plantilla de declaración original no se deriva de una entrada externa, no se puede producir la inyección SQL.

No veo muy bien el motivo. ¿Cuál sería una explicación sencilla en un inglés fácil y algunos ejemplos?

Aan avatar Nov 25 '11 06:11 Aan
Aceptado

La idea es muy simple: la consulta y los datos se envían al servidor de la base de datos por separado .
Eso es todo.

La raíz del problema de la inyección SQL está en la mezcla del código y los datos.

De hecho, nuestra consulta SQL es un programa legítimo . Y estamos creando un programa de este tipo de forma dinámica, agregando algunos datos sobre la marcha. Por lo tanto, los datos pueden interferir con el código del programa e incluso alterarlo, como lo muestra cada ejemplo de inyección SQL (todos los ejemplos en PHP/Mysql):

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";

producirá una consulta regular

SELECT * FROM users where id=1

mientras este código

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";

producirá una secuencia maliciosa

SELECT * FROM users where id=1; DROP TABLE users;

Funciona porque agregamos los datos directamente al cuerpo del programa y se convierten en parte del programa, por lo que los datos pueden alterar el programa y, dependiendo de los datos pasados, tendremos una salida normal o una tabla userseliminada.

Mientras que en el caso de declaraciones preparadas no modificamos nuestro programa, este permanece intacto.
Ése es el punto.

Primero enviamos un programa al servidor.

$db->prepare("SELECT * FROM users where id=?");

donde los datos se sustituyen por alguna variable llamada parámetro o marcador de posición.

Tenga en cuenta que se envía exactamente la misma consulta al servidor, ¡sin ningún dato! Y luego enviamos los datos con la segunda solicitud, esencialmente separados de la consulta misma:

$db->execute($data);

por lo que no puede alterar nuestro programa y causar ningún daño.
Bastante simple, ¿no?

Lo único que tengo que agregar que siempre se omite en cada manual:

Las declaraciones preparadas solo pueden proteger literales de datos , pero no se pueden utilizar con ninguna otra parte de la consulta.
Entonces, una vez que tenemos que agregar, digamos, un identificador dinámico (un nombre de campo, por ejemplo), las declaraciones preparadas no pueden ayudarnos. He explicado el asunto recientemente , así que no me repetiré.

Your Common Sense avatar Nov 25 '2011 06:11 Your Common Sense

Aquí hay una declaración SQL para configurar un ejemplo:

CREATE TABLE employee(name varchar, paymentType varchar, amount bigint);

INSERT INTO employee VALUES('Aaron', 'salary', 100);
INSERT INTO employee VALUES('Aaron', 'bonus', 50);
INSERT INTO employee VALUES('Bob', 'salary', 50);
INSERT INTO employee VALUES('Bob', 'bonus', 0);

La clase Inject es vulnerable a la inyección SQL. La consulta se pega dinámicamente junto con la entrada del usuario. La intención de la consulta era mostrar información sobre Bob. Ya sea salario o bonificación, según la aportación del usuario. Pero el usuario malintencionado manipula la entrada y corrompe la consulta añadiendo el equivalente de "o verdadero" a la cláusula dónde para que se devuelva todo, incluida la información sobre Aaron que se suponía estaba oculta.

import java.sql.*;

public class Inject {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd";
        Connection conn = DriverManager.getConnection(url);

        Statement stmt = conn.createStatement();
        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'";
        System.out.println(sql);
        ResultSet rs = stmt.executeQuery(sql);

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Al ejecutar esto, el primer caso es con uso normal y el segundo con inyección maliciosa:

c:\temp>java Inject salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary'
salary 50

c:\temp>java Inject "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b'
salary 100
bonus 50
salary 50
bonus 0

No debe crear sus sentencias SQL con concatenación de cadenas de entrada del usuario. No solo es vulnerable a la inyección, sino que también tiene implicaciones de almacenamiento en caché en el servidor (la declaración cambia, por lo que es menos probable que se obtenga un acceso al caché de la declaración SQL, mientras que el ejemplo de vinculación siempre ejecuta la misma declaración).

Aquí hay un ejemplo de Binding para evitar este tipo de inyección:

import java.sql.*;

public class Bind {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres";
        Connection conn = DriverManager.getConnection(url);

        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?";
        System.out.println(sql);

        PreparedStatement stmt = conn.prepareStatement(sql);
        stmt.setString(1, args[0]);

        ResultSet rs = stmt.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Al ejecutar esto con la misma entrada que en el ejemplo anterior, se muestra que el código malicioso no funciona porque no hay ningún tipo de pago que coincida con esa cadena:

c:\temp>java Bind salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
salary 50

c:\temp>java Bind "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
Glenn avatar Nov 25 '2011 03:11 Glenn