SQL Injection

De FdIwiki ELP
Saltar a: navegación, buscar

SQL Injection, o una inyección SQL, es un ataque en el cual se manipula y se "inyecta" comandos SQL en una sentencia SQL preestablecida mediante una entrada de usuario (de ahí el nombre). Usualmente esto se puede hacer ya que las entradas que se reciben de parte de los usuarios no han sido desinfectadas (sanitize en inglés) y un usuario malicioso aprovecha esta vulnerabilidad para introducir su código, a fin de alterar el funcionamiento normal del programa y robar, modificar o eliminar información, e inclusive ganar acceso a acciones que sólo debería de tener un administrador de la BDD, como modificar su estructura, apagar la base de datos, etc.

Una inyección SQL es considerado un ataque por la (Open Web Application Security Project), y por ende, puede ser penalizado como cualquier otro delito informático que infrinja contra las leyes establecidas.


Exploits of a mom.png

Imagen cortesía de xkcd

Ejemplo

Para entender lo que es una inyección SQL, lo mejor es visualizarlo con un ejemplo en código. Se utilizará Python para explicar el siguiente ejemplo.

Imaginemos un caso típico en el que un usuario quiere iniciar sesión en una aplicación web. Este usuario sólo debe introducir su nombre de usuario y su contraseña para poder acceder a ella. Entonces, la aplicación web podría recibir el usuario y contraseña a través de una solicitud POST de la siguiente manera:


def login():
    username = request.forms['username'] # username = 'one_user'
    password = request.forms['password'] # password = 'one_password'

Ahora, imaginemos que tenemos declarada una sentencia SQL como la siguiente:

    query = "SELECT id FROM Users WHERE username = '{0}' AND password = '{1}'"

Un desarrollador simplemente podría dar formato a esta sentencia añadiendo las variables username y password de la siguiente manera:

    query = query.format(username, password)

Y el resultado sería que ahora el valor de query cambia a:

    # query = "SELECT id FROM Users WHERE username = 'one_user' AND password = 'one_password'"

Con esto, si se tiene un buen usuario que ha introducido su nombre de usuario y su contraseña correcta, esta consulta regresaría su id correspondiente y se podría acceder a los servicios que ofrece la aplicación sin ningún problema. Pero que pasa si nuestro usuario no es un buen usuario, sino que es un usuario malicioso y nos da las siguientes entradas:

def login():
    username = request.forms['username'] # username = 'hacked'
    password = request.forms['password'] # password = 'hacked' OR '1'='1'-- '

Al dar formato a la sentencia SQL anteriormente descrita, ésta quedaría de la siguiente forma:

    # query = "SELECT id FROM Users WHERE username = 'hacked' AND password = 'hacked' OR '1'='1'--'"
    # La última comilla simple quedaría comentada por "--" y no causaría ningún problema de sintaxis

Recordando un poco de álgebra booleana, el AND tiene precedencia sobre el OR. De este modo, la condición sería WHERE (username = 'hacked' AND password = 'hacked') OR '1'='1'. La primera parte de la cláusula WHERE no nos importa ya que sabemos que la segunda parte siempre va a ser True. Así que con esto, la consulta regresaría todos los id's de usuarios que existen en la base de datos. Si un usuario malicioso llega a coger estos id's, podría hacerse pasar por algún usuario válido en la aplicación web y actuar a gusto.

Prevención

  • Primero que nada, hay que ser conscientes de que cualquier entrada puede terminar en una consulta SQL. Por ende, hay que revisar todas las entradas. Esto incluye no sólo las entradas en las que un usuario puede escribir cualquier cosa, sino que también hay que revisar campos ocultos de formularios y campos cuyo valor no puede ser "malo" porque se elige a través de elementos del formulario como: radio buttons, combobox, campos numéricos, etc.
  • Hay que recordad que las solicitudes GET puede generar una URL con cualquier valor para cualquier parámetro. Además, utilizar solicitudes POST no nos protege de nada ya que cualquiera puede realizar una petición POST maliciosa.
  • Es importante desinfectar las entradas. Esto es, escapar todos los caracteres problemáticos que puedan inyectar código SQL malicioso. Además, comprobar que las entradas sean del tipo esperado es una práctica muy recomendada cuando se esperan, por ejemplo, entradas numéricas.
  • Otra práctica que se recomienda es parametrizar sentencias SQL ya preparadas. Los parámetros SQL son valores que se agregan a la sentencia SQL en tiempo de ejecución y no alteran la estructura de la sentencia (o al menos es más difícil inyectar código SQL).
  • Aplicar el principio de mínimos privilegios en la Base de Datos y segregar usuarios. Segregar usuarios se refiere a utilizar diferentes usuarios para los distintos accesos a la base de datos, y no que un solo usuario sea omnipotente (superusuario o propietario de la base de datos) y tenga acceso total.
  • Y por último, capturar los errores y mostrar mensajes inteligentes que no revelen información que algún usuario malicioso pueda utilizar a su favor. Ejemplo básico: cuando se hace un login y se introduce usuario y contraseña.
    • Imaginemos que el usuario no existe y se despliega un mensaje algo así como "El usuario no existe". El usuario malicioso sabrá que ese usuario no le sirve y buscará otro.
    • Imaginemos ahora que el usuario sí existe, pero la contraseña es incorrecta y se despliega un mensaje algo así como "La contraseña es incorrecta". El usuario malicioso ahora """sabe""" que ese nombre de usuario sí existe, y se puede concentrar en atacar sólo la contraseña.
    • Si en lugar de estos mensajes se pone "Usuario o contraseña incorrectas", el usuario malicioso no sabe si es el usuario o si es la contraseña la que está incorrecta y le costaría mucho más trabajo.
    • De igual forma, si al realizar una consulta SQL te regresa un error que dice que "no existe tal registro en la tabla de Usuarios", se le está dando información valiosa acerca de la estructura de la base de datos al usuario malicioso.

Referencias y Enlaces Externos