Inyección de SQL

Muchos desarrolladores web no son conscientes de cómo las consultas SQL pueden ser manipuladas, y asumen que una consulta SQL es una orden fiable. Esto significa que las consultas SQL son capaces de eludir controles de acceso, evitando así las comprobaciones de autenticación y autorización estándar, e incluso algunas veces, que las consultas SQL podrían permitir el acceso a comandos al nivel del sistema operativo del equipo anfitrión.

La inyección directa de comandos SQL es una técnica donde un atacante crea o altera comandos SQL existentes para exponer datos ocultos, sobrescribir los valiosos, o peor aún, ejecutar comandos peligrosos a nivel de sistema en el equipo que hospeda la base de datos. Esto se logra a través de la práctica de tomar la entrada del usuario y combinarla con parámetros estáticos para elaborar una consulta SQL. Los siguientes ejemplos están basados en historias reales, desafortunadamente.

Debido a la falta de validación en la entrada de datos y a la conexión a la base de datos con privilegios de superusuario o de alguien con privilegios para crear usuarios, el atacante podría crear un superusuario en la base de datos.

Ejemplo #1 Dividir el conjunto de resultados en páginas ... y crear superusuarios (PostgreSQL)

<?php

$índice    
$argv[0]; // ¡Cuidado, no hay validación en la entrada de datos!
$consulta  "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $índice;";
$resultado pg_query($conexión$consulta);

?>
Un usuario común hace clic en los enlaces 'siguiente' o 'atrás' donde el $índice está codificado en el URL. El script espera que el $índice entrante sea un número décimal. Sin embargo, ¿qué pasa si alguien intenta irrumpir anteponiendo a la URL algo como lo siguiente empleando urlencode()?
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
    select 'crack', usesysid, 't','t','crack'
    from pg_shadow where usename='postgres';
--
Si esto sucediera, el script podría otrogar un acceso de superusuario al atacante. Observe que 0; es para proveer un índcie válido a la consulta original y así finalizarla.

Nota:

Es una técnica común forzar al analizador de SQL a ignorar el resto de la consulta escrita por el desarrollador con --, lo cual representa un comentario en SQL.

Una forma factible de obtener contraseñas es burlar las páginas de búsqueda de resultados. Lo único que el atacante necesita hacer es ver si hay variables que hayan sido enviadas y sean empleadas en sentencias SQL que no sean manejadas apropiadamente. Estos filtros se pueden establecer comunmente en un formulario anterior para personalizar las cláusulas WHERE, ORDER BY, LIMIT y OFFSET en las sentencias SELECT. Si la base de datos admite el constructor UNION, el atacante podría intentar anteponer una consulta entera a la consulta original para enumerar las contraseñas de una tabla arbitraria. Se recomienda encarecidamente utilizar campos de contraseña encriptadas.

Ejemplo #2 Enumerar artículos ... y algunas contraseñas (cualquier servidor de bases de datos)

<?php

$consulta  
"SELECT id, name, inserted, size FROM products
              WHERE size = '
$tamaño'";
$resultado odbc_exec($conexión$consulta);

?>
La parte estática de la consulta se puede combinar con otra sentencia SELECT la cual revelará todas las contraseñas:
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
Si esta consulta (jugando con ' y --) fuera asignada a una de las variables utilizadas en $consulta, despertaría a la consulta "monstruo".

Las sentecias UPDATE de SQL también son susceptibles a ataques. Estas consultas también están amenazadas por el corte y la anteposición de una consulta completamente nueva. El atacante podría juguetear con la cláusula SET, aunque en este caso, debe poseer algo de información sobre los esquemas para manipular la consulta con éxito. Esta información puede adquirirse examinando los nombres de las variables del formulario, o sencillamente mediante la fuerza bruta. No hay muchas convenciones de nombres para campos que almacenen contraseñas o nombres de usuarios.

Ejemplo #3 Desde restablecer una contraseña ... hasta obtener más privilegios (cualquier servidor de bases de datos)

<?php
$consulta 
"UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
Pero un usuario malicioso podría enviar el valor ' or uid like'%admin% a $uid para cambiar la contraseña del administrador, o simplemente cambiar $pwd a jejeje', trusted=100, admin='yes para obtener más privilegios. Entonces, la consulta se tornaría:
<?php

// $uid: ' or uid like '%admin%
$consulta "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";

// $pwd: jejeje', trusted=100, admin='yes
$consulta "UPDATE usertable SET pwd='jejeje', trusted=100, admin='yes' WHERE
...;"
;

?>

Un ejemplo turbador de cómo se puede acceder a los comandos a nivel del sistema operativo en algunos equipos anfitriones de bases de datos.

Ejemplo #4 Atacar el sistema operativo que hospeda la base de datos (Servidor MSSQL)

<?php

$consulta  
"SELECT * FROM products WHERE id LIKE '%$prod%'";
$resultado mssql_query($consulta);

?>
Si un atacante envía el valor a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- a $prod, la $consulta será:
<?php

$consulta  
"SELECT * FROM products
              WHERE id LIKE '%a%'
              exec master..xp_cmdshell 'net user test testpass /ADD' --%'"
;
$resultado mssql_query($consulta);

?>
El servidor MSSQL ejecuta la sentencia SQL en el lote incluyendo un comando para añadir un usuario nuevo a la base de datos de cuentas local. Si esta aplicación estuviera ejecutándose como sa y el servicio MSSQLSERVER se estuviera ejecutando con los privilegios suficientes, el atacante ahora podría tener una cuenta con la cual acceder a esta máquina.

Nota:

Algunos de los ejemplos citados están vinculados a un servidor de bases de datos específico. Esto no significa que un ataque similar sea imposible en otros productos. Su servidor de base de datos también podría ser vulnerable de otra manera.

Un ejemplo comprobado de los problemas con respecto a las inyecciones de SQL
Imagen cortesía de » xkcd

Técnicas de evitación

Pese a que pueda parecer obvio que un atacante debe tener al menos algún conocimiento de arquitecturas de bases de datos para poder realizar un ataque con éxito, la obtención de esta información suele ser muy sencilla. Por ejemplo, cuando la base de datos forma parte de un software de código abierto o disponible públicamente con una instalación predefinida, dicha información se encuentra completamente libre y utilizable. Esta información podría haber sido divulgada en proyectos de código cerrado (incluso si está codificada, ofuscada o compilada), o incluso por el propio código mediante la visualización de mensajes de error. Otros métodos incluyen el uso de nombres frecuentes de tablas y columnas. Por ejemplo, un formulario de inicio de sesión que utiliza una tabla 'usuarios' con los nombres de columna 'id', 'username', y 'password'.

Estos ataques se basan principalmente en explotar el código que no ha sido escrito teniendo en cuenta la seguridad. Nunca se ha de confiar en ningún tipo de entrada, especialmente la que viene del lado del cliente, aún cuando esta venga de un cuadro de selección, un campo oculto o una cookie. El primer ejemplo muestra cómo una inofensiva consulta puede causar desastres.

  • Nunca se conecte como superusuario o como propietario de la base de datos. Siempre utilice usuarios personalizados con privilegios muy limitados.
  • Emplee sentencias preparadas con variables vinculadas. Son proporcionadas por PDO, MySQLi y otras bibliotecas.
  • Compruebe si la entrada proporcionada tiene el tipo de datos previsto. PHP tiene un amplio rango de funciones para validar la entrada de datos, desde las más simples, encontradas en Funciones de variables y en Funciones del tipo carácter (p.ej., is_numeric(), ctype_digit() respectivamente), hasta el soporte para 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 emplee su representación numérica por medio de sprintf().

    Ejemplo #5 Una forma más segura de componer una consulta para paginación

    <?php

    settype
    ($índice'integer');
    $consulta "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $índice;";

    // Observe %d en el string de formato; el uso de %s podría no tener un resultado significativo
    $consulta sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
                        
    $índice);

    ?>

  • 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 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 juego de caracteres monobyte con NO_BACKSLASH_ESCAPES deshabilitada), por lo que es mejor evitarlas.
  • Sea como sea, no muestre ninguna información específica de la base de datos, especialmente sobre el esquema. Vea también Notificación de errores y Manejo de errores y funciones de registro.
  • Su pueden utilizar procedimientos almacenados y cursores previamente definidos para abstraer el acceso a datos para que los usuarios no tengan acceso directo a las tablas o vistas, aunque que esta solución tiene otros impactos.

Además, se puede beneficiar del registro de consultas, ya sea dentro de un script o mediante la base de datos en sí misma, si es que lo soporta. Obviamente, llevar un registro no previene los intentos dañinos, aunque puede ser útil para realizar un seguimiento de las aplicación que han sido eludidas. El registro no es útil por sí mismo, sino por la información que contiene. Normalmente cuantos más detalles, mejor.

add a note add a note

User Contributed Notes 8 notes

up
24
Richard dot Corfield at gmail dot com
3 years ago
The best way has got to be parameterised queries. Then it doesn't matter what the user types in the data goes to the database as a value.

A quick search online shows some possibilities in PHP which is great! Even on this site - http://php.net/manual/en/pdo.prepared-statements.php
which also gives the reasons this is good both for security and performance.
up
6
valerylourie at gmail dot com
7 years ago
Note that PHP 5 introduced filters that you can use for untrusted user input:
http://us.php.net/manual/en/intro.filter.php
up
4
ctm at etheon dot net
8 years ago
This is a very helpful document from the MySQL site (in .pdf format) :

http://dev.mysql.com/tech-resources/articles/
guide-to-php-security-ch3.pdf
up
3
wang dot liang dot com at gmail dot com
5 years ago
another way to stop sql injection when you odbc_*: create two users,
one has only select permission,
the other has only delete, update, and insert permission,

so you can use select-only user to call odbc_exec while you don't have to check the sql injection; and you use d/u/i only user to update database by calling odbc_prepare and odbc_execute.
up
-1
Nikolay Mihaylov
1 year ago
Because we host several websites, written by different clients, I prepend this file on every php file on my server:

function armor_1234567890_abc(){
    foreach($_REQUEST as $key => $data){
        $data = strtolower($data);

        if (strpos($data, "base64_") !== false)
            exit;

        if (strpos($data, "union") !== false && strpos($data, "select") !== false)
            exit;
    }
}

armor_1234567890_abc();
up
-3
jaimthorn at yahoo dot com
6 years ago
dark dot avenger at email dot cz wrote:

"I think that easy way to protect against SQL injection is to convert inputted data into binary format, so that whatever input is, in sql query it will consist only of 1s and 0s."

Unless there is a 1-to-1 correspondence between your inputted data and the characters in your 'binary' format, a SELECT query wouldn't work anymore.  Not a binary format, but it makes my point: MIME encoding the text 'Dark Avenger' results in 'RGFyayBBdmVuZ2Vy'.  If I wanted to look up anyone with 'Avenger' in his/her name, then 'Avenger' would be encoded as 'QXZlbmdlcg==' which clearly wouldn't result in a hit on 'RGFyayBBdmVuZ2Vy'.

If there IS a 1-to-1 correspondence, then EITHER your solution only makes it a bit harder to perform a SQL injection (a hacker would have to figure out what mapping was used between the text and the 'binary' format), OR you've come up with simply another way to escaping your data.  Either isn't a terribly good solution to the SQL injection problem.
up
-26
nemeth dot zsolt dot dr at gmail dot com
1 year ago
You can avoid the sql injection using views and stored procedures. Never give direct access to tables! Stored procedures can be protected by special parameters (e.g user id + password)
up
-33
fyrye
5 years ago
Another way to prevent SQL injections as opposed to binary, is to use URL Encoding or Hex Encoding.
I haven't seen a complete example of stopping SQL Injections, most refer to use the mysql_real_escape_string function or param statements.

Several examples at http://en.wikipedia.org/wiki/SQL_injection

Which will stop \x00, \n, \r, \, ', " and \x1a based attacks.
Alot depends on your SQL query structure, though vector level attacks are still viable.

Other than that build your own regex replacement to protect specific queries that could alter or compromise your database/results for specific sections of your processing pages.
Also use unique table and field names. Not just putting _ infront of them...
Example, don't store User/s or Customer/s information in a table named the same.
And NEVER use the same form field names for database field names.
To Top