La inyección SQL es una de las técnicas de hacking utilizadas para la inserción y ejecución de código SQL no deseado dentro de aplicaciones web basadas en bases de datos.
Lo que hace que la inyección SQL sea especialmente peligrosa es que no requiere ninguna herramienta especial, sino sólo un PC y cualquiera de los navegadores web más utilizados.
Por lo tanto, cualquier tipo de arquitectura de software utilizada para construir sitios web interconectados con una base de datos es potencialmente vulnerable a un ataque de inyección SQL. De hecho, este riesgo no está vinculado a la utilización de un determinado lenguaje de programación web (PHP, Java, etc.), ni a la de un determinado servidor de BD (Oracle, Mysql, SQL, etc.).
Sin embargo, esto no excluye la posibilidad de que un tipo de ataque de inyección SQL sea más eficaz en un sistema determinado que en otro, pero sólo posiblemente debido a factores estrictamente relacionados con la sintaxis específica del lenguaje utilizado.
Hay que tener en cuenta que una programación poco consciente de la seguridad puede aumentar considerablemente las vulnerabilidades al poner en grave peligro la lógica de procesamiento que constituye las transacciones SQL.
Inyección SQL: cómo funciona
Para entender en qué consiste esta técnica de ataque, es necesario hacer una pequeña premisa sobre cómo funciona la comunicación entre un servidor proveedor de servicios, en el que reside una base de datos (BD), y un cliente que, mediante las consultas oportunas, utiliza esos servicios.
Como ya se ha mencionado, aunque existen diferentes tipos de sistemas de gestión de bases de datos relacionales, todos ellos son potencialmente vulnerables a la inyección SQL. En este artículo, nos referiremos al software de gestión de bases de datos MySQL, y al lenguaje de programación PHP para desarrollar aplicaciones del lado del servidor.
Escribamos el mecanismo de consulta y diálogo entre el cliente, el servidor y la BD, suponiendo que el usuario accede a su perfil personal para la prestación de un servicio:
- el visitante teclea la dirección de la página de acceso que reside en un servidor (sitio web);
- el servidor web procesa la página localmente y produce, como resultado, una página HTML enviándola al navegador del cliente, que se encargará de mostrarla en la pantalla del visitante.
Por lo tanto, para la prestación del servicio:
- el visitante introducirá sus credenciales en el formulario de acceso;
- el formulario de acceso enviará los datos al servidor web, que consultará su BD (MySQL) ejecutando un script PHP;
- la BD sólo responderá al servidor con los datos solicitados si las credenciales coinciden con las almacenadas;
- el servidor web maquetará localmente la respuesta y la enviará al navegador del cliente;
- el visitante, como resultado de este proceso, mostrará una página con información sobre el servicio solicitado.
Puertos de acceso y objetivos de ataque
En un contexto de este tipo, es imprescindible, a nivel de programación, adoptar al menos unas medidas mínimas de protección para evitar la explotación de cualquier vulnerabilidad que pueda acechar al mecanismo de diálogo entre cliente, servidor y BD.
Hay que prestar especial atención a la seguridad del código responsable de la gestión de los formularios de autenticación y de las páginas de búsqueda, que suelen implementarse almacenando e interactuando con una base de datos y que representan potenciales puertas de enlace.
De hecho, un delincuente experto en sintaxis SQL puede enviar instrucciones especiales a través de las páginas del sitio que impliquen un diálogo con la BD, con el objetivo de obtener un acceso no autorizado a la propia aplicación, para recuperar información, datos sensibles y modificarlos o borrarlos.
Inyección SQL: los pasos previos a un ataque
Es habitual que el primer paso de un atacante sea entender cuándo y cómo la aplicación interactúa con un servidor de BD para acceder a ciertos datos. Las observaciones preliminares son esenciales para encontrar la información necesaria para trazar la estructura de la aplicación objetivo.
Los indicadores de que la aplicación se comunica con una BD pueden ser:
- Formularios web: cuando la autenticación se realiza mediante un formulario, es probable que las credenciales del usuario se verifiquen en una base de datos que almacena esta información;
- motores de búsqueda: la cadena de búsqueda enviada por el usuario puede utilizarse en una consulta SQL que extrae de una base de datos los registros que responden a la consulta.
- Sitios de comercio electrónico: lo más probable es que la información sobre los productos y sus características se almacene en una base de datos.
El siguiente paso en la investigación puede ser interpretar los mensajes de error que devuelve una aplicación en caso de entradas anómalas. Si algún tipo de mensaje de retroalimentación ha sido deshabilitado u ocultado a propósito por los desarrolladores, todavía es posible tratar de entender el comportamiento del servidor de BD por medio de estrategias más precisas como las técnicas de sniffing para interceptar el tráfico de red.
Anatomía de un ataque de inyección SQL
Un ataque de inyección SQL consiste, por ejemplo, en insertar términos especializados en los campos de un formulario web para enviar comandos inusuales e inesperados a la base de datos aprovechando la propia aplicación.
Como ejemplo, mostrado sólo para uso educativo, consideremos el caso muy simple pero ilustrativo en el que una consulta pide a la BD que devuelva información sobre el indicativo del usuario, enviada a través del método POST de un formulario HTML (recordemos que el problema es completamente independiente del tipo de plataforma utilizada).
El siguiente ejemplo fue tomado de la página del OSWAP sobre pruebas de inyección SQL.
La consulta Mysql es la siguiente:
Con este comando, enviado por ejemplo a través de un formulario web, se pretende recuperar información de la tabla de usuarios correspondiente, pero sólo si las credenciales especificadas aparecen en la propia tabla.
Si, como se ha visto, no hay ninguna forma de validación de entrada, un potencial atacante podría intentar introducir el siguiente nombre de usuario y contraseña:
$_POST[“indicativo”]= 1 ‘OR’ 1 ‘=’ 1
$_POST[“contraseña”]= 1 ‘OR’ 1 ‘=’ 1
La combinación del uso de comillas con el operador lógico ‘OR’ genera el siguiente comando SQL modificado y enviado a la BD:
Esta consulta puede hacer que se devuelvan los primeros o varios valores de la tabla de usuarios, porque la cláusula WHERE se satisface ahora con cada entrada de la tabla de usuarios, ya que la parte introducida por la sentencia OR lógica se comprueba siempre.
También es posible, mediante la inclusión de delimitadores de comentario (/*…*/), intentar que ciertas partes de las consultas, como la relativa al campo de la contraseña, sean incluso ignoradas.
Estos dos ejemplos expuestos deberían hacernos reflexionar:
- es posible robar las credenciales administrativas de una BD, ya que normalmente el primer usuario de la lista es la cuenta de administrador;
- El hash de las contraseñas siempre es útil para asegurar su confidencialidad, pero no siempre es suficiente para garantizar la autenticación, ya que, como se ha visto, el acceso por contraseña puede ser burlado;
- Una vez encontrados los puntos de acceso e identificadas las vulnerabilidades, el usuario malintencionado puede proceder a ataques de inyección SQL más pesados, como la manipulación de archivos digitales y/o la ejecución de rutinas personalizadas.
Inyección SQL: cómo defenderse
Para evitar la inyección de consultas arbitrarias en aquellas aplicaciones web que interactúan con una BD, es ciertamente básico, en la fase de implementación, que la programación controle todos los posibles puertos de acceso al repositorio de gestión de datos, como formularios, páginas de búsqueda y cualquier otro módulo que implique una consulta SQL.
La validación de las entradas, las consultas parametrizadas a través de plantillas y la gestión adecuada de los informes de errores pueden ser buenas prácticas de programación que sirven para este propósito. Aquí tienes algunos consejos:
- Preste atención al uso de elementos de código SQL potencialmente arriesgados (comillas simples y paréntesis) que podrían ser complementados con caracteres de control apropiados y explotados para un uso no autorizado;
- utilizar la extensión MySQLi;
- desactivar la visibilidad de las páginas de error en los sitios. Esta información a menudo resulta valiosa para el atacante, que puede rastrear la identidad y la estructura de los servidores de BD que interactúan con la aplicación objetivo.
La extensión de MySql
Una codificación cuidadosa puede reducir significativamente la vulnerabilidad de una aplicación web a la inyección arbitraria de código SQL. Una buena solución es utilizar, entre las librerías que proporciona PHP para la interacción con MySQL, la extensión MySQLi (MySQL mejorado).
Mysqli, como su nombre lo indica, hace mejoras a Mysql en particular proporcionando dos enfoques de programación:
- procesal (uso de funciones tradicionales);
- orientado a objetos (uso de clases y métodos).
A continuación, un ejemplo de validación de entradas y otro de consultas parametrizadas utilizando el paradigma de la POO (interfaz orientada a objetos) y refiriéndose a las versiones 5/7 de PHP.
Validación de entradas
Para sanear caracteres especiales y de control potencialmente peligrosos en una cadena para su uso en una sentencia SQL, se puede utilizar el método mysqli::real_escape_string.
Si se necesita sanear determinados caracteres que no son manejados por el método, aún se puede intervenir mediante operaciones manuales de reemplazo. En la figura se muestra un script comentado y su resultado en la salida estándar.
El algoritmo implementado demuestra y verifica cómo el uso de la función de escape evita que una frase lógica (‘1’ OR ‘1’ = ‘1’) convierta una sentencia SQL en ambigua. En el caso que nos ocupa, la variable $frase gracias a la operación de sanitización (colocando una barra invertida en cada carácter especial) se inserta sin ninguna ambigüedad/error en la tabla de pruebas.
Otra solución para la validación de entradas es convertir los caracteres que constituyen una sentencia SQL en hexadecimal.
En PHP se utiliza la función bin2hex(). Según esta práctica, la instrucción hexadecimal, antes de ser ejecutada, se reconvierte en una cadena mediante la función unhex() de Mysql. Al hacerlo, la secuencia de caracteres reconvertida, al no ser interpretada como un comando, deja de representar un peligro potencial.
Consultas parametrizadas
Otra técnica de defensa que puede utilizarse para mitigar las consecuencias de la inyección SQL son las consultas parametrizadas.
Con este tipo de enfoque, el programador debe definir la sintaxis de la instrucción antes de pasar los parámetros a la consulta, distinguiendo el código de los datos independientemente de la entrada recibida. Esto garantiza que el propósito de una consulta no pueda ser alterado por una intervención maliciosa externa.
En nuestro ejemplo, si el atacante introdujera la cadena ‘‘1” O ‘1’ = ‘1” como nombre de usuario, la consulta debidamente parametrizada sería inmune al engaño porque la consulta buscaría un nombre de usuario que se correspondiera letra a letra con la misma cadena.
Este formato implica tres pasos principales (se omiten las partes de conexión, las comprobaciones, la creación del objeto $mysqli y el cierre de la conexión a la BD):
- preparación: se crea una plantilla de sentencia SQL (se utilizan parámetros de marcador de posición ? ” para indicar los valores de los datos a especificar) y se envía a la base de datos a través del método prepare().
$mysqli->prepare(“SELECT * FROM login WHERE username = ? AND password = ?”);
- análisis: el modelo de sentencia sql recibido de la db se analiza, se compila y se almacena sin ejecutarlo a través del método bind(). Se asocian variables con marcadores de posición y el tipo de datos esperado, respetando estrictamente el orden de posición.
$mysqli->bind_param(‘ss’,$username,$password);
- En este caso, las variables esperadas $username y $password deben ser cadenas ‘ss’. Otros tipos previstos son:
- i: tipo entero dado;
- d: escribir los números dados como dobles;
- b: Tipo de datos binarios;
- ejecución: la aplicación, una vez asociados los valores a los parámetros, ejecuta la instrucción a través del método execute().
$resultado = $mysqli->ejecutar();
Después de evaluar el resultado de la variable $resultado, es posible continuar con el resto del código para cualquier inserción, modificación o eliminación de los registros en cuestión.
La desactivación de la notificación de errores
La notificación y visualización de errores son operaciones muy útiles, por razones obvias, en la fase de desarrollo: resultan ser una verdadera fuente de peligro para la seguridad de las aplicaciones y de incertidumbre y confusión para los usuarios en la fase de producción.
Es fácil ver cómo, en las fases de investigación, las funciones de informe pueden proporcionar a un usuario bien intencionado y experimentado información valiosa y pistas sobre los mecanismos de funcionamiento y la arquitectura de una aplicación basada en la web en sí.
Esto puede hacerse de varias maneras. En el caso del lenguaje PHP (las indicaciones dadas pueden cambiar dependiendo de la versión de PHP que se utilice):
- estableciendo adecuadamente los ajustes de los archivos de configuración (los principales archivos de configuración que pueden personalizarse según los permisos permitidos por el proveedor de alojamiento donde reside el sitio en producción son php.ini, .htaccess, httpd.conf):
- En el archivo php.ini, podría ser útil establecer la variable dispaly_error de 1 a 0 y la variable error_reporting de 1 a 0.
- En el archivo .htaccess, podría ser útil establecer la variable php_value_error_reporting de 1 a 0.
- configurando, a través de las funciones estándar ini_set() y error_reporting(), un archivo php ad hoc que se incluirá en las cabeceras de las páginas correspondientes, con la función include(“archivo_errores_ad_hoc.php”):