rulando sqlmap (remediando los problemas)

6 de noviembre de 2008

Continuación de "rulando sqlmap".

El problemón de poder acceder al directorio de passwords del servidor Linux o poder ver todos los usuarios mysql dados de alta es más jodido porque la inyección se ejecuta con permisos root que por la propia inyección.

Primero hay que dar los permisos adecuados a tu aplicación web para que ataque contra su propia base de datos y no tenga acceso al restos de bases de datos (como por ejemplo information_schema, MySQL u otras).

Por tanto, lo que haremos es crear un usuario especifico para la aplicación PHP y que sólo puede hacer consultas desde la propia localhost. Creamos un usuario “userdb_midb" con contraseña "123psw_midb_456":

mysql> GRANT select ON midb.* TO 'userdb_midb'@'localhost' identified 
by '123psw_midb_456';
mysql> FLUSH PRIVILEGES;

Otra recomendación importante es cambiar la contraseña que tiene el usuario root por defecto. No confundir el usuario root del mysql con el usuario root de Linux. Son usuarios, pero en contextos diferentes.
# mysqladmin -u root password "un_buen_password_para_root"

Ahora ya estamas listo para comenzar a programar tú aplicación web. Notar que los privilegios que hemos metido en la base de datos "midb" es solo para hacer consultas con SELECT. Si la aplicación PHP necesitara operaciones de DML (no operaciones DDL), lógicamente tendríamos que cambiar los permisos de tú mysql, pero siempre dando los mínimos que necesitemos:
mysql> GRANT select, insert, delete, update ON midb.* TO 'userdb_midb'@'localhost' 
identified by '123psw_midb_456';
mysql> FLUSH PRIVILEGES;

Comenzamos ahora a evitar las injecciones SQL. Hay mucha información por Internet sobre que son y como evitarlas así que no me extenderé. Si hablamos de aplicaciones web (PHP, ASP, python...), básicamente se trata de validaciones incorrectas de las variables que llegan al servidor ya sea por el método POST, GET, cookies, etc.

Lo que hay que tener claro es que no solo hay inyecciones de SQL, los hay de muchas cosas. Tenemos las inyecciones de xpath, las inyecciones de ldap, las inyecciones nulas... sea lo que sea, son provocadas por la mala validación de los datos de entrada. Como solo somos programadores web y no somos hackers pues tenemos que saber que existen y programar un poquito más para filtrar los datos que nos llegan.

La primera medida anti-sql-injection es modificar el php.ini con:
magic_quotes_gpc=On

Esta opción lo que hace escapar automáticamente todos los caracteres peligros. Estos caracteres que se consideran peligrosos con la comilla simple, la doble, la barra invertida y el NULL. Es importante saber que esta propiedad desaparece en PHP v6. En su lugar hay que escarpar los datos en tiempo de ejecución con la función addslashes().
Las magic_quotes son una primera medida de seguridad con PHP pero son evitables a cuanto inyección de SQL se refiere en algunos casos muy concretos según la base de datos que tengamos funcionando.

Por ejemplo un caso real (level 7 de Blindsec). Tenemos esta validación de usuario bajo un SQLlite:
$sql= 'SELECT * FROM users WHERE login="$login" and password="$psw" ';

Si ponemos como $login=admin e inyectamos el $psw=" or ROWID=1 --, tendremos el siguiente resultado:
SELECT * FROM users WHERE login="admin" and password="\" or ROWID=1 --"

De forma que siempre nos devolverá la fila=1. Este es un caso muy concreto y posible gracias a SQLlite.

Otras maneras que tenemos es convertir la clásica inyección ' or ''=' en:
%2527 or %2527%2527=%2527

También funciona:
%2527%20or%20%2527%2527=%2527

Si por defecto no tenemos las magic quotes activadas lo mejor es utilizar addslashes(). Según PHP v6 es lo que tendremos que utilizar y además mejora el rendimiento en vez de aplicar magic quotes en todos los valores. El problema es ir con cuidado de que no se nos escape ninguna variable sin aplicar addslashes(). Lo contrario de un addslashes() es un stripslashes().

También existe otra función para escapar los datos antes de construir la consulta SQL. Esta es mysql_real_escape_string() y es la que recomienda PHP.
mysql_real_escape_string($login);
mysql_real_escape_string($password);

He visto también que hay gente que escapea doblemente:
addcslashes(mysql_real_escape_string($login),'%_'));

Personalmente creo que lo mejor es crearte tú propia función para escapar o satanizar tus entradas:
   function sanitize($x, $quotes = true) {
      if (is_array($x))   
         foreach ($x as &$y)
            $y = sanitize($y);

      else if (is_string($x)) {
          $x = mysql_real_escape_string($x);
          if ($quotes)
             $x = "'". $x ."'";

        else if (is_null($x))
           $x = "NULL";

        else if (is_bool($x)) 
           $x = ($x) ? 1 : 0;

        return $x;
    }

Más cosas que he visto. Para los paranoicos totales, se podría modificar esta función para eliminar palabras prohibidas como "select", "insert", "password", etc. Aunque esto es fácilmente petable. Basta con poner "seLect", "inSert", o "pAssword". Otras modificaciones que podemos hacer es limitar la longitud de los datos que nos llegan. No a nivel de HTML o JavaScript, sino en el propio codigo de PHP. De esta forma si nuestro password es de longitud máxima de 10 caracteres será casi imposible inyectar con éxito un código de SQL.

Como he dicho antes, la inyección de SQL es muy amplia y no solo se aplica en las aplicaciones web en .php, .asp, .aspx, .py, sino también en cualquier servicio Web como puede ser SOAP.
Por ejemplo, imaginaros que tengo una aplicación que lee datos referentes al clima a través de llamadas SOAP a http://www.weather.gov/xml/. Teóricamente podemos pensar que www.weather.gov es una web confiable y que no nos inyectará código, ¿no?. Pero que pasaría si en vez de devolvernos la temperatura que hace en Barcelona capital, nos devuelve una inyección…. Malo malo.

Mas información:
+ SQL Injection cheat sheet
+ Securizar tú mysql para tus webs
+ PHP Filters