Security Shepherd — Broken Auth and Session Management Challenge 5
“Practice makes a man (and also women) perfect.” — This is something that we are all aware of at a conscious (or unconscious) level. However, practice would never be complete if we aren’t patient enough.
As part of our training at Appsecco, I was trying to understand and exploit security flaws in the session management mechanisms implemented by web applications. After solving challenge 1 till 4 (with lots of online references), in the OWASP Security Shepherd application, I got stuck at “Session Management Challenge 5”. It felt miserable to find no help available on the Internet. “Is there no one who has solved this challenge?”, I wondered. I tried harder to search for a solution, and, ran out of luck.
Even though it appeared hopeless at first, I felt an urge to be patient with myself. What felt like futile attempts in the beginning, ultimately led me to paths that made me hopeful and, eventually, to the solution.
If you had felt lost, just like I did, while trying to solve the current challenge, you are on the right track! As long as you don’t give up, you will succeed. But if you have already given up, I am sharing the solution below.
Step 1:
Try to login using some common usernames and passwords. In the current example, the server responds differently to valid and invalid usernames. This behavior helps in user enumeration.
Step 2:
Now that we have found a valid username, i.e., “admin”, we will try to find the corresponding password for this account.
Brute force method had failed for me. I could not guess the password for the “admin” user, so I decided to use the ‘Forgotten Password’ application feature. On clicking the ‘Forgotten Password?’ button, the corresponding form is displayed.
Step 3:
Enter “admin” in the ‘User Name’ field, and click on ‘Send Email’ button. A message is returned by the server informing that the password reset token was sent successfully to the user.
Step 4:
Start Burp Suite (or any other intercepting proxy tool), and intercept all requests and responses.
LOGIN FORM
GET CHANGE PASSWORD EMAIL
Step 5:
View the source code of web-page by doing a right click and selecting “View Page Source” option. You would find a JavaScript code that reveals the entire logic associated with forgot password functionality.
$("#leForm").submit(function(){
var theSubName = $("#subUserName").val();
var theSubPassword = $("#subUserPassword").val();
$("#loadingSign").show("slow");
$("#forgottenPassDiv").hide("slow");
$("#resultsDiv").hide("slow", function(){
var ajaxCall = $.ajax({
type: "POST",
url: "7aed58f3a00087d56c844ed9474c671f8999680556c127a19ee79fa5d7a132e1",
data: {
subUserName: theSubName,
subUserPassword: theSubPassword
},
async: false
});
if(ajaxCall.status == 200)
{
$("#resultsDiv").html(ajaxCall.responseText);
}
else
{
$("#resultsDiv").html("<p> An Error Occurred: " + ajaxCall.status + " " + ajaxCall.statusText + "</p>");
}
$("#loadingSign").hide("fast", function(){
$("#resultsDiv").show("slow");
});
});
});//Reset Password Form
$("#leForm2").submit(function(){
var theUserToReset = $("#userToReset").val();
$("#resetSubmit").hide("fast");
$("#resetLoadingSign").show("slow");
$("#resultsDiv2").hide("slow", function(){
var ajaxCall = $.ajax({
type: "POST",
url: "7aed58f3a00087d56c844ed9474c671f8999680556c127a19ee79fa5d7a132e1SendToken",
data: {
subUserName: theUserToReset
},
async: false
});
if(ajaxCall.status == 200)
{
$("#resultsDiv2").html(ajaxCall.responseText);
}
else
{
$("#resultsDiv2").html("<p> An Error Occurred: " + ajaxCall.status + " " + ajaxCall.statusText + "</p>");
}
$("#resultsDiv2").show("slow", function(){
$("#resetLoadingSign").hide("fast", function(){
$("#resetSubmit").show("slow");
});
});
});
});$("#showUserControl").click(function(){
$("#userControl").toggle("slow", function(){
//Animation Complete
;
});
});//Change Password Form (Requires Valid Token)
//Token life = 10 mins
$("#leForm3").submit(function(){
var theUserName = $("#subUserName").val();
var theNewPassword = $("#subNewPass").val();
var theToken = $("#updatePasswordToken").val();
$("#resetSubmit").hide("fast");
$("#resetLoadingSign").show("slow");
$("#resultsDiv2").hide("slow", function(){
var ajaxCall = $.ajax({
type: "POST",
url: "7aed58f3a00087d56c844ed9474c671f8999680556c127a19ee79fa5d7a132e1ChangePass",
data: {
userName: theUserName,
newPassword: theNewPassword,
resetPasswordToken: theToken
},
async: false
});
if(ajaxCall.status == 200)
{
$("#resultsDiv2").html(ajaxCall.responseText);
}
else
{
$("#resultsDiv2").html("<p> An Error Occurred: " + ajaxCall.status + " " + ajaxCall.statusText + "</p>");
}
$("#resultsDiv2").show("slow", function(){
$("#resetLoadingSign").hide("fast", function(){
$("#resetSubmit").show("slow");
});
});
});
});
By going through the above code snippet, one would discover about the existence of a third form, named ‘#leForm3’. This form is clearly meant for changing the password of a user. However, there is no direct way to submit this form. Look for ways to get access to the invisible form, or, understand the functionality invoked by the form and simulate the behavior manually.
I was unable to find a way to get direct access to the ‘Change Password Form’, but, I did understand the request that would have been triggered whenever this form was accessed. I simulated this request in Burp Suite’s Repeater tool:
The server returned an error message on sending the custom request. Nevertheless, the error message was informative and it confirmed that the request structure was valid, and that the server was able to parse the values from request body. The server was trying to read a timestamp value from the ‘resetPasswordToken’ parameter.
I tried passing timestamp values in different forms and formats to the ‘resetPasswordToken’ parameter, but nothing worked. I copied the error message returned by the server, and performed a Google search operation on it:
owasp security shepherd “Could not parse/manipulate date or time from” token:`
I accessed the only link that was displayed in the search result, and I reached a page that contained the source code for ‘SessionManagement5ChangePassword.java’ file.
On scrolling down the page, I came across a piece of code that revealed an important information.
The code revealed that a Base64 encoded value is expected in the ‘resetPasswordToken’ parameter. I repeated my fuzzing test on the ‘resetPasswordToken’ parameter, but this time all my values were Base64 encoded.
I did not get the expected results still. I had started to wonder if there was a specific format of timestamp that was expected. I went through the source code again with a hope to gain some insights. I came across this:
I wasn’t familiar with this date format. I did not know what EEE
and Z
stood for. I googled for ‘SimpleDateFormat(“EEE MMM d HH:mm:ss Z yyyy”)’ and accessed the first link in the search results page.
Now that the date format was clear, the next question that came to my mind was regarding the expected timezone. What was the server expecting in the request? PDT or IST or something else? I went back to the ‘Repeater’ tool in Burp Suite and analyzed the server response. I had found my answer.
I copied the timestamp value from the server response, modified it to match the format EEE MMM d HH:mm:ss z yyyy
, Base64 encoded the timestamp value and triggered the request with an anxious heart. I got following response:
I modified the timestamp value to ensure the 10-minute token expiry limit was not crossed, and re-triggered the server request.
At last, I got the much awaited server response:
I went back to access the intercepted user login request, modified the value for ‘subUserPassword’ parameter to the newly set password value “mynewpass123”, and triggered the seemingly perfect request: