
Os voy a contar que son las inyecciones SQL (en adelante SQLi), cómo puede un atacante utilizarlas para obtener información o destruirla y además veremos un par de códigos vulnerables y como dejarían de serlo. Las inyecciones de código son la vulnerabilidad #1 en un ranking que hace OWASP del Top Ten de vulnerabilidades.
Empezaremos con unas nociones muy básicas de la sintáxis SQL.
El lenguaje SQL podríamos decir que tiene 3 grupos importantes de instrucciones (triggers aparte):
- Data Manipulation (DML) es el subgrupo de instrucciones que permite añadir, reescribir y borrar datos. Dentro de este grupo estan:
- INSERT: permite añadir tuplas a la tabla. Por ejemplo:
Si los campos de nuestra tabla users son username, userpass y usermail, al ejecutar esta instrucción creará una nueva tupla con sshMan, password y sshMan@vC.com.INSERT INTO users VALUES('sshMan','password', 'sshMan@vC.com');
- UPDATE: modifica una tupla ya existente. Si tenemos la tupla del apartado anterior en una tabla:
Aquí se cambiaría el valor del campo userpass a nuevapass cuando el username sea sshMan.UPDATE users SET userpass='nuevapass' WHERE username='sshMan';
- DELETE: Elimina tuplas. Si tenemos la tupla del código anterior en nuestra tabla, para borrarla no hay más que hacer:
Y ya el usuario sshMan no estaría en nuestra BD.DELETE FROM users WHERE username='sshMan' AND userpass='nuevapass';
- Data Definition (DDL) es el subgrupo de instrucciones que se ocupa de las tablas y de la estructura de los indices. Permite crear, modificar y borrar tablas completas.
- CREATE: crea una nueva tabla. Por ejemplo:
Esta instrucción crearía la tabla users con los atributos username, userpass y email.CREATE TABLE users ( username CHARACTER(12) PRIMARY KEY, userpass CHARACTER (20) NOT NULL, email CHARACTER (50) NOT NULL);
- ALTER: modifica la estructura de una tabla existente: Crea el campo userwebpage en la tabla users, y si nos fijamos en el codigo anterior...los 3 campos (username, userpass y email) tienen que recibir información. Sin embargo el campo userwebpage puede estar vacio.
ALTER TABLE users ADD userwebpage CHARACTER(50);
- DROP: Borra toda la tabla, no se puede hacer rollback:
Y nuestra tabla users ha sido borrada (y su contenido también).DROP TABLE users;
- Sin embargo, la operación más común y la que más nos interesa para esta entrada son las consultas (queries) con SELECT. Una consulta sencilla sería:
SELECT * FROM users WHERE username='sshMan' AND userpass='mipassword';
Introducción a las SQLi
Ahora imaginemos un escenario más complejo (y es que raramente consultamos las bases de datos nosotros) normalmente, una aplicación web tiene una función de conexión a la base de datos. Un ejemplo en php sería:
<?php
$user = $GET['login'];
$pass = $GET['password'];
$query = "SELECT * FROM users WHERE username='".$user."'and userpass='".$pass."'";
$conexion = pg_connect("dbname=webapp user=usuario");
$result = pg_query($conexion, $consulta);
if($result) {
$rows = pg_num_rows($result);
if($rows == 0) {?&>
<h1><?php echo "Acceso denegado";?></h1>
<?php}else{?>
<h1><?php echo "Bienvenido".$user;?></h1>
<?php}
}?>
}
SELECT * FROM users WHERE username='.$user.'and userpass='.$pass';
Si en los campos user y pass de nuestra aplicación ponemos:
username = Usuario1
userpass = Passwd1
la consulta quedaría:
SELECT * FROM users WHERE username='Usuario1'and userpass='Passwd1';
Sin embargo...si les damos estos valores a los campos de nuestra aplicación:
username = 'OR''='
userpass = 'OR''='
la consulta resultante sería:
SELECT * FROM users WHERE username='' OR ''=''and userpass='' OR ''='';
le decimos que username es ' ' (vacío) y userpass es ' ' (vacío también). Sin embargo estamos diciendo OR ''=''. Queda claro que ''='' devuelve true y además con el OR estamos diciendo que username='' OR ''=''. Y username es distinto de '' pero '' es igual a '' con lo cual la sentencia SQL se validaría correctamente y obtendriamos acceso ilegitimo a la aplicación. Pero esto no queda aquí:
username = loquesea
userpass = ';DROP TABLE users;--
Esta consulta es mucho más divertida, ya que borra la tabla users de la base de datos:
SELECT * FROM users WHERE username='loquesea' and userpass='';DROP TABLE users;--';
¿Porqué ocurre esto?
Ocurre por que no filtramos o escapamos los carácteres que forman la sintaxis de sql, no se filtran las comillas simples ('), el punto y coma(;), los iguales (=) ni los guiones (-).
Solución
La solución pasa por:
- Aplicar validación (lo más estricta posible) a los datos.
- Que no se pueda ejecutar más de una sentencia SQL en el mismo comando.
- Restringir los tipos de dato aceptados.
- Utilizar funciones ya preparadas para realizar el trabajo con la BD.
- Escapar el caracter (').
Una solución posible es con la función de php pg_escape_string()
<?php
$user = $GET['login'];
$pass = $GET['password'];
if(ereg("^[0-9A-Za-z]+$",$user)) && (ereg("^[0-9A-Za-z]+$",$pass)) {
$query = "SELECT * FROM users WHERE username='".pg_escape_string($user)."'and userpass='".pg_escape_string($pass)."'";
$conexion = pg_connect("dbname=webapp user=usuario");
$result = pg_query($conexion, $consulta);
if($result) {
$rows = pg_num_rows($result);
if($rows == 0) {?&>
<h1><?php echo "Acceso denegado";?></h1>
<?php}else{?>
<h1><?php echo "Bienvenido".$user;?></h1>
<?php}
}
}?>
Otra posible solución sería con la función pg_prepare() que es una función que se encarga de interactuar con la BD:
<?php
<?php
$user = $GET['login'];
$pass = $GET['password'];
if(ereg("^[0-9A-Za-z]+$",$user)) && (ereg("^[0-9A-Za-z]+$",$pass)) {
$conexion = pg_connect("dbname=webapp user=usuario");
$result = pg_prepare($conexion, "", 'SELECT * FROM users WHERE username =$1 and userpass=$2');
$resultexec = pg_execute($conexion, "", array($user,$pass));
if($resultexec) {
$rows = pg_num_rows($resultexec);
if($rows == 0) {?&>
<h1><?php echo "Acceso denegado";?></h1>
<?php}else{?>
<h1><?php echo "Bienvenido".$user;?></h1>
<?php}
}}?>
Estos ejemplos son de los más sencillos para ver como funciona la inyección SQL, pero hay muchas más maneras de realizar una SQLi (por ejemplo utilizando la función base64_decode() encodeando los campos en base64 y en la sentencia llamar a base64_decode('JyBPUiAnJz0n'). En esa sentencia no hay carácteres especiales que escapar, pero al hacer el decode se convierte en: ' OR ''='.



comentarios