Skip to content

Commit d847f32

Browse files
committed
f
1 parent 0e45e2e commit d847f32

File tree

3 files changed

+160
-5
lines changed

3 files changed

+160
-5
lines changed

src/pentesting-cloud/azure-security/az-basic-information/az-tokens-and-public-applications.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ These refresh tokens must be minted in that broker context (a regular refresh to
216216

217217
### Goal and purpose
218218

219-
The goal of BroCI is to reuse a valid user session from a broker-capable app chain and request tokens for another trusted app/resource pair without running a new full interactive flow each time.
219+
The goal of BroCI is to reuse a valid user session from a broker-capable app chain and request tokens for another trusted app/resource pair. Therefore, allowing to "escalate privileges" from the original token.
220220

221221
From an offensive perspective, this matters because:
222222

@@ -235,6 +235,9 @@ NAA/BroCI token exchanges are **not** the same as a regular OAuth refresh exchan
235235
- You generally cannot "convert" a normal refresh token into a BroCI-valid one in code.
236236
- You need a refresh token already issued by a compatible brokered flow.
237237

238+
Check the web **<https://entrascopes.com/>** to find BroCI configured apps an the trust relationships they have.
239+
240+
238241
### Mental model
239242

240243
Think of BroCI as:
@@ -245,7 +248,7 @@ If any part of that broker chain does not match, the exchange fails.
245248

246249
### Where to find a BroCI-valid refresh token
247250

248-
In authorized testing/lab scenarios, one practical way is browser portal traffic collection:
251+
One practical way is browser portal traffic collection:
249252

250253
1. Sign in to `https://entra.microsoft.com` (or Azure portal).
251254
2. Open DevTools -> Network.

src/pentesting-cloud/azure-security/az-privilege-escalation/az-entraid-privesc/README.md

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ az ad app update --id <app-id> --web-redirect-uris "https://original.com/callbac
100100

101101
### Applications Privilege Escalation
102102

103-
**As explained in [this post](https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/)** it was very common to find default applications that have **API permissions** of type **`Application`** assigned to them. An API Permission (as called in the Entra ID console) of type **`Application`** means that the application can access the API without a user context (without a user login into the app), and without needing Entra ID roles to allow it. Therefore, it's very common to find **high privileged applications in every Entra ID tenant**.
103+
**As explained in [this post](https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/)** it was very common to find default applications that have **API permissions** of type **`Application`** assigned to them. An API Permission (as called in the Entra ID console) of type **`Application`** means that the application can access the API and perform actions without a user context (without a user login into the app), and without needing Entra ID roles to allow it. Therefore, it's very common to find **high privileged applications in every Entra ID tenant**.
104104

105105
Then, if an attacker has any permission/role that allows to **update the credentials (secret o certificate) of the application**, the attacker can generate a new credential and then use it to **authenticate as the application**, gaining all the permissions that the application has.
106106

@@ -138,6 +138,83 @@ az ad sp show --id <ResourceAppId> --query "appRoles[?id=='<id>'].value" -o tsv
138138
az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?id=='d07a8cc0-3d51-4b77-b3b0-32704d1f69fa'].value" -o tsv
139139
```
140140

141+
<details>
142+
<summary>Find all applications with API permissions to non-Microsoft APIs (az cli)</summary>
143+
144+
```bash
145+
#!/usr/bin/env bash
146+
set -euo pipefail
147+
148+
# Known Microsoft first-party owner organization IDs.
149+
MICROSOFT_OWNER_ORG_IDS=(
150+
"f8cdef31-a31e-4b4a-93e4-5f571e91255a"
151+
"72f988bf-86f1-41af-91ab-2d7cd011db47"
152+
)
153+
154+
is_microsoft_owner() {
155+
local owner="$1"
156+
local id
157+
for id in "${MICROSOFT_OWNER_ORG_IDS[@]}"; do
158+
if [ "$owner" = "$id" ]; then
159+
return 0
160+
fi
161+
done
162+
return 1
163+
}
164+
165+
command -v az >/dev/null 2>&1 || { echo "az CLI not found" >&2; exit 1; }
166+
command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }
167+
az account show >/dev/null
168+
169+
apps_json="$(az ad app list --all --query '[?length(requiredResourceAccess) > `0`].[displayName,appId,requiredResourceAccess]' -o json)"
170+
171+
tmp_map="$(mktemp)"
172+
tmp_ids="$(mktemp)"
173+
trap 'rm -f "$tmp_map" "$tmp_ids"' EXIT
174+
175+
# Build unique resourceAppId values used by applications.
176+
jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids"
177+
178+
# Resolve resourceAppId -> owner organization + API display name.
179+
while IFS= read -r rid; do
180+
[ -n "$rid" ] || continue
181+
sp_json="$(az ad sp show --id "$rid" --query '{owner:appOwnerOrganizationId,name:displayName}' -o json 2>/dev/null || true)"
182+
owner="$(jq -r '.owner // "UNKNOWN"' <<<"$sp_json")"
183+
name="$(jq -r '.name // "UNKNOWN"' <<<"$sp_json")"
184+
printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map"
185+
done < "$tmp_ids"
186+
187+
echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tresourceOwnerOrgId\tpermissionType\tpermissionId"
188+
189+
# Print only app permissions where the target API is NOT Microsoft-owned.
190+
while IFS= read -r row; do
191+
app_name="$(jq -r '.[0]' <<<"$row")"
192+
app_id="$(jq -r '.[1]' <<<"$row")"
193+
194+
while IFS= read -r rra; do
195+
resource_app_id="$(jq -r '.resourceAppId' <<<"$rra")"
196+
map_line="$(awk -F '\t' -v id="$resource_app_id" '$1==id {print; exit}' "$tmp_map")"
197+
owner_org="$(awk -F'\t' '{print $2}' <<<"$map_line")"
198+
resource_name="$(awk -F'\t' '{print $3}' <<<"$map_line")"
199+
200+
[ -n "$owner_org" ] || owner_org="UNKNOWN"
201+
[ -n "$resource_name" ] || resource_name="UNKNOWN"
202+
203+
if is_microsoft_owner "$owner_org"; then
204+
continue
205+
fi
206+
207+
while IFS= read -r access; do
208+
perm_type="$(jq -r '.type' <<<"$access")"
209+
perm_id="$(jq -r '.id' <<<"$access")"
210+
echo -e "${app_name}\t${app_id}\t${resource_name}\t${resource_app_id}\t${owner_org}\t${perm_type}\t${perm_id}"
211+
done < <(jq -c '.resourceAccess[]' <<<"$rra")
212+
done < <(jq -c '.[2][]' <<<"$row")
213+
done < <(jq -c '.[]' <<<"$apps_json")
214+
```
215+
216+
</details>
217+
141218
## Service Principals
142219

143220
### `microsoft.directory/servicePrincipals/credentials/update`
@@ -397,4 +474,3 @@ az rest --method GET \
397474
{{#include ../../../../banners/hacktricks-training.md}}
398475

399476

400-

src/pentesting-cloud/azure-security/az-services/az-azuread.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,83 @@ az ad sp show --id <ResourceAppId> --query "appRoles[?id=='<id>'].value" -o tsv
819819
az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?id=='d07a8cc0-3d51-4b77-b3b0-32704d1f69fa'].value" -o tsv
820820
```
821821
822+
<details>
823+
<summary>Find all applications with API permissions to non-Microsoft APIs (az cli)</summary>
824+
825+
```bash
826+
#!/usr/bin/env bash
827+
set -euo pipefail
828+
829+
# Known Microsoft first-party owner organization IDs.
830+
MICROSOFT_OWNER_ORG_IDS=(
831+
"f8cdef31-a31e-4b4a-93e4-5f571e91255a"
832+
"72f988bf-86f1-41af-91ab-2d7cd011db47"
833+
)
834+
835+
is_microsoft_owner() {
836+
local owner="$1"
837+
local id
838+
for id in "${MICROSOFT_OWNER_ORG_IDS[@]}"; do
839+
if [ "$owner" = "$id" ]; then
840+
return 0
841+
fi
842+
done
843+
return 1
844+
}
845+
846+
command -v az >/dev/null 2>&1 || { echo "az CLI not found" >&2; exit 1; }
847+
command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }
848+
az account show >/dev/null
849+
850+
apps_json="$(az ad app list --all --query '[?length(requiredResourceAccess) > `0`].[displayName,appId,requiredResourceAccess]' -o json)"
851+
852+
tmp_map="$(mktemp)"
853+
tmp_ids="$(mktemp)"
854+
trap 'rm -f "$tmp_map" "$tmp_ids"' EXIT
855+
856+
# Build unique resourceAppId values used by applications.
857+
jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids"
858+
859+
# Resolve resourceAppId -> owner organization + API display name.
860+
while IFS= read -r rid; do
861+
[ -n "$rid" ] || continue
862+
sp_json="$(az ad sp show --id "$rid" --query '{owner:appOwnerOrganizationId,name:displayName}' -o json 2>/dev/null || true)"
863+
owner="$(jq -r '.owner // "UNKNOWN"' <<<"$sp_json")"
864+
name="$(jq -r '.name // "UNKNOWN"' <<<"$sp_json")"
865+
printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map"
866+
done < "$tmp_ids"
867+
868+
echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tresourceOwnerOrgId\tpermissionType\tpermissionId"
869+
870+
# Print only app permissions where the target API is NOT Microsoft-owned.
871+
while IFS= read -r row; do
872+
app_name="$(jq -r '.[0]' <<<"$row")"
873+
app_id="$(jq -r '.[1]' <<<"$row")"
874+
875+
while IFS= read -r rra; do
876+
resource_app_id="$(jq -r '.resourceAppId' <<<"$rra")"
877+
map_line="$(awk -F '\t' -v id="$resource_app_id" '$1==id {print; exit}' "$tmp_map")"
878+
owner_org="$(awk -F'\t' '{print $2}' <<<"$map_line")"
879+
resource_name="$(awk -F'\t' '{print $3}' <<<"$map_line")"
880+
881+
[ -n "$owner_org" ] || owner_org="UNKNOWN"
882+
[ -n "$resource_name" ] || resource_name="UNKNOWN"
883+
884+
if is_microsoft_owner "$owner_org"; then
885+
continue
886+
fi
887+
888+
while IFS= read -r access; do
889+
perm_type="$(jq -r '.type' <<<"$access")"
890+
perm_id="$(jq -r '.id' <<<"$access")"
891+
echo -e "${app_name}\t${app_id}\t${resource_name}\t${resource_app_id}\t${owner_org}\t${perm_type}\t${perm_id}"
892+
done < <(jq -c '.resourceAccess[]' <<<"$rra")
893+
done < <(jq -c '.[2][]' <<<"$row")
894+
done < <(jq -c '.[]' <<<"$apps_json")
895+
```
896+
897+
</details>
898+
822899
{{#endtab }}
823900
824901
{{#tab name="Az" }}
@@ -1308,4 +1385,3 @@ The default mode is **Audit**:
13081385
{{#include ../../../banners/hacktricks-training.md}}
13091386
13101387
1311-

0 commit comments

Comments
 (0)