Resumen
En un Sistema de gestión de bases de datos relacionales (RDBMS), hay un lenguaje específico, llamado SQL (lenguaje de consulta estructurada), que se utiliza para comunicarse con la base de datos. Las declaraciones de consulta escritas en SQL se utilizan para manipular el contenido y la estructura de la base de datos. Una declaración SQL específica que crea y modifica la estructura de la base de datos se denomina declaración DDL (lenguaje de definición de datos) y las declaraciones que manipulan el contenido de la base de datos se denomina declaración DML (lenguaje de manipulación de datos). El motor asociado con el paquete RDBMS analiza e interpreta la instrucción SQL y devuelve el resultado correspondiente. Este es el proceso típico de comunicación con RDBMS:disparar una instrucción SQL y obtener el resultado, eso es todo. El sistema no juzga la intención de ningún enunciado que se adhiera a la sintaxis y estructura semántica del lenguaje. Esto también significa que no hay procesos de autenticación o validación para verificar quién disparó la declaración y el privilegio que uno tiene para obtener la salida. Un atacante puede simplemente lanzar una instrucción SQL con intenciones maliciosas y recuperar información que no debería obtener. Por ejemplo, un atacante puede ejecutar una instrucción SQL con una carga maliciosa con una consulta de apariencia inofensiva para controlar el servidor de base de datos de una aplicación web.
Cómo funciona
Un atacante puede aprovechar esta vulnerabilidad y usarla en su propio beneficio. Por ejemplo, uno puede eludir el mecanismo de autenticación y autorización de una aplicación y recuperar los llamados contenidos seguros de toda la base de datos. Se puede usar una inyección SQL para crear, actualizar y eliminar registros de la base de datos. Por lo tanto, se puede formular una consulta limitada a la propia imaginación con SQL.
Por lo general, una aplicación lanza con frecuencia consultas SQL a la base de datos para numerosos propósitos, ya sea para obtener ciertos registros, crear informes, autenticar usuarios, transacciones CRUD, etc. El atacante simplemente necesita encontrar una consulta de entrada SQL dentro de algún formulario de entrada de la aplicación. La consulta preparada por el formulario se puede usar para entrelazar el contenido malicioso de modo que, cuando la aplicación active la consulta, también lleve la carga útil inyectada.
Una de las situaciones ideales es cuando una aplicación le pide al usuario que ingrese, como el nombre de usuario o la identificación del usuario. La aplicación abrió un punto vulnerable allí. La instrucción SQL se puede ejecutar sin saberlo. Un atacante se aprovecha inyectando una carga útil que se usará como parte de la consulta SQL y será procesada por la base de datos. Por ejemplo, el pseudocódigo del lado del servidor para una operación POST para un formulario de inicio de sesión puede ser:
uname = getRequestString("username"); pass = getRequestString("passwd"); stmtSQL = "SELECT * FROM users WHERE user_name = '" + uname + "' AND passwd = '" + pass + "'"; database.execute(stmtSQL);
El código anterior es vulnerable al ataque de inyección SQL porque la entrada proporcionada a la declaración SQL a través de la variable 'uname' y 'pass' se puede manipular de una manera que alteraría la semántica de la declaración.
Por ejemplo, podemos modificar la consulta para que se ejecute en el servidor de la base de datos, como en MySQL.
stmtSQL = "SELECT * FROM users WHERE user_name = '" + uname + "' AND passwd = '" + pass + "' OR 1=1";
Esto da como resultado la modificación de la declaración SQL original en un grado que permite omitir la autenticación. Esta es una vulnerabilidad grave y debe evitarse desde el código.
Defensa contra un ataque de inyección SQL
Una de las formas de reducir la posibilidad de un ataque de inyección SQL es garantizar que no se permita que las cadenas de texto sin filtrar se agreguen a la instrucción SQL antes de la ejecución. Por ejemplo, podemos usar PreparedStatement para realizar las tareas de base de datos requeridas. El aspecto interesante de PreparedStatement es que envía una declaración SQL precompilada a la base de datos, en lugar de una cadena. Esto significa que la consulta y los datos se envían por separado a la base de datos. Esto evita la causa raíz del ataque de inyección SQL, porque en la inyección SQL, la idea es mezclar código y datos en los que los datos son en realidad parte del código en forma de datos. En DeclaraciónPreparada , hay múltiples setXYZ() métodos, como setString() . Estos métodos se utilizan para filtrar caracteres especiales, como una cita contenida en las declaraciones SQL.
Por ejemplo, podemos ejecutar una instrucción SQL de la siguiente manera.
String sql = "SELECT * FROM employees WHERE emp_no = "+eno;
En lugar de poner, digamos, eno=10125 como un número de empleado en la entrada, podemos modificar la consulta con la entrada como:
eno = 10125 OR 1=1
Esto cambia por completo el resultado devuelto por la consulta.
Un ejemplo
En el siguiente código de ejemplo, hemos mostrado cómo PreparedStatement se puede utilizar para realizar tareas de base de datos.
package org.mano.example; import java.sql.*; import java.time.LocalDate; public class App { static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver"; static final String DB_URL = "jdbc:mysql://localhost:3306/employees"; static final String USER = "root"; static final String PASS = "secret"; public static void main( String[] args ) { String selectQuery = "SELECT * FROM employees WHERE emp_no = ?"; String insertQuery = "INSERT INTO employees VALUES (?,?,?,?,?,?)"; String deleteQuery = "DELETE FROM employees WHERE emp_no = ?"; Connection connection = null; try { Class.forName(JDBC_DRIVER); connection = DriverManager.getConnection (DB_URL, USER, PASS); }catch(Exception ex) { ex.printStackTrace(); } try(PreparedStatement pstmt = connection.prepareStatement(insertQuery);){ pstmt.setInt(1,99); pstmt.setDate(2, Date.valueOf (LocalDate.of(1975,12,11))); pstmt.setString(3,"ABC"); pstmt.setString(4,"XYZ"); pstmt.setString(5,"M"); pstmt.setDate(6,Date.valueOf(LocalDate.of(2011,1,1))); pstmt.executeUpdate(); System.out.println("Record inserted successfully."); }catch(SQLException ex){ ex.printStackTrace(); } try(PreparedStatement pstmt = connection.prepareStatement(selectQuery);){ pstmt.setInt(1,99); ResultSet rs = pstmt.executeQuery(); while(rs.next()){ System.out.println(rs.getString(3)+ " "+rs.getString(4)); } }catch(Exception ex){ ex.printStackTrace(); } try(PreparedStatement pstmt = connection.prepareStatement(deleteQuery);){ pstmt.setInt(1,99); pstmt.executeUpdate(); System.out.println("Record deleted successfully."); }catch(SQLException ex){ ex.printStackTrace(); } try{ connection.close(); }catch(Exception ex){ ex.printStackTrace(); } } }
Un vistazo a PreparedStatement
Estos trabajos también se pueden realizar con una Declaración de JDBC. interfaz, pero el problema es que a veces puede ser bastante inseguro, especialmente cuando se ejecuta una instrucción SQL dinámica para consultar la base de datos donde los valores de entrada del usuario se concatenan con las consultas SQL. Esta puede ser una situación peligrosa, como hemos visto. En la mayoría de las circunstancias ordinarias, Statement es bastante inofensivo, pero PreparedStatement parece ser la mejor opción entre los dos. Evita que las cadenas maliciosas se concatenen debido a su enfoque diferente al enviar la declaración a la base de datos. Declaración Preparada utiliza la sustitución de variables en lugar de la concatenación. Colocar un signo de interrogación (?) en la consulta SQL significa que una variable sustituta ocupará su lugar y proporcionará el valor cuando se ejecute la consulta. La posición de la variable de sustitución toma su lugar de acuerdo con la posición del índice del parámetro asignado en setXYZ() métodos.
Esta técnica evita ataques de inyección SQL.
Además, PreparedStatement implementa AutoCloseable. Esto le permite escribir dentro del contexto de un probar-con-recursos bloquea y se cierra automáticamente cuando sale del alcance.
Conclusión
Un ataque de inyección SQL solo se puede prevenir escribiendo el código de manera responsable. De hecho, en cualquier solución de software, la seguridad se viola principalmente debido a malas prácticas de codificación. Aquí, hemos descrito qué evitar y cómo PreparedStatement puede ayudarnos a escribir un código seguro. Para obtener una idea completa sobre la inyección de SQL, consulte los materiales apropiados; Internet está lleno de ellos y, para PreparedStatement , consulte la documentación de la API de Java para obtener una explicación más detallada.