Inyecciones SQL, (como prevenir)

sql_injection_vulnerable
Es común encontrarnos con aplicaciones vulnerables a inyecciones SQL, estos ataques se deben a que los desarrolladores omiten la tarea de validar los datos introducidos por los usuarios, veamos un ejemplo de una aplicación vulnerable y luego veamos como prevenir inyecciones, usted puede descargar el código fuente desde:
https://gist.github.com/fitorec/9b8312d83a2b4aa902b38aee4a067962

La aplicación consta de la siguiente estructura:

src/
`-- inyeccionsql
    |-- BD.java
    |-- InyeccionSQL.java
    |-- LoginVista.fxml
    |-- LoginVistaController.java
    |-- base_datos.sql
    `-- style.css

El archivo de base_datos.sql contiene el esquema la cual contiene una sola tabla con nombre usuarios:

CREATE TABLE `usuarios` (
	`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
	`username` VARCHAR(15) NOT NULL UNIQUE KEY,
	`password` CHAR(32) NOT NULL ,
	`fecha_nacimiento` DATE NOT NULL
) ENGINE = InnoDB;

-- Agregando dos usuarios

INSERT INTO `usuarios`
	(`id`, `username`, `password`, `fecha_nacimiento`) VALUES
	(NULL, 'fitorec', 'ABC123456...', NOW()),
	(NULL, 'pepe_grillo', 'ABC123456...', NOW());

BD.java Es una clase singletón que se encarga de la conexión a la base de datos, mientras que InyeccionSQL.java es la clase principal y solo se encarga de iniciar la aplicación, mandando abrir una escena con el contenido de LoginVista.fxml de la cual su controlador es LoginVistaController.java  esta tiene el método accionLogin encargado de validar el login como podemos ver en el siguiente código:

    @FXML
    private TextField username;
    @FXML
    private PasswordField password;
    @FXML
    private void accionLogin(ActionEvent event) {
        try {
            String sql = "SELECT * FROM usuarios "
                + "WHERE username='"+username.getText() + "'"
                + " AND password='"+password.getText()+"'";
            PreparedStatement ps = BD.getConexion().prepareStatement(sql);
            ResultSet r =  ps.executeQuery();
            if (r.next()) {
                String uname = r.getString("username");
                label.setText("Bienvenido: " + uname);
            } else {
                label.setText("Login invalido");
            }
        } catch(Exception e){
            label.setText("Error SQL");
        }
    }

En el ejemplo podemos ver como concatenamos literalmente el contenido de los inputs username y password, ¿pero que pasa si un usuario mal intencionado agrega los siguientes datos, para el username x y para el password x' OR 1='1?, esto nos genera la consulta:

SELECT * FROM usuarios
       WHERE username='x' AND password='x' OR 1='1';

login_correcto

Dicha consulta es conocida como “siempre cierta” y nos dará un acceso correcto al sistema,incluso podríamos concatenar la consulta con un DROP o TRUNCATE, como se muestra en la siguiente imagen =).
exploits_of_a_mom.png

Pero.. como evitar esto

private void accionLogin(ActionEvent event) {
        try {
            String sql = "SELECT * FROM usuarios "
                + "WHERE username=?"
                + " AND password=?;";
            PreparedStatement ps = BD.getConexion().prepareStatement(sql);
            ps.setString(1, username.getText());
            ps.setString(2, password.getText());
            ResultSet r =  ps.executeQuery();
            if (r.next()) {
                String uname = r.getString("username");
                label.setText("Bienvenido: " + uname);
            } else {
                label.setText("Login invalido");
            }
        } catch(Exception e){
            label.setText("Error SQL");
        }
    }

Del código previo podemos ver como creamos la consulta dejando el valor de los parámetros username y password como argumentos que posteriormente pasaremos(ver ? en las lineas 4,5) luego posteriormente en las lineas 7 y 8 pasamos los valores por medio de los métodos setText este metodo realiza los escapes adecuados y si agregamos como password el valor x' OR 1='1 al realizar los escapes se convertirá en algo como :

SELECT * FROM usuarios
      WHERE username='x' AND password='x\' OR 1=\'1';

Como conclusión en general debemos de limpiar todos los datos introducidos por el usuario ya que este es el medio por el cual las aplicaciones suelen fallar.

Anuncios