This commit is contained in:
Carlos Polop
2026-03-01 21:18:03 +01:00
parent d847f32cc5
commit 9ebb2d956e
2 changed files with 91 additions and 18 deletions

View File

@@ -139,7 +139,7 @@ az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?id=='
``` ```
<details> <details>
<summary>Find all applications with API permissions to non-Microsoft APIs (az cli)</summary> <summary>Find all applications API permissions and mark Microsoft-owned APIs</summary>
```bash ```bash
#!/usr/bin/env bash #!/usr/bin/env bash
@@ -162,6 +162,32 @@ is_microsoft_owner() {
return 1 return 1
} }
get_permission_value() {
local resource_app_id="$1"
local perm_type="$2"
local perm_id="$3"
local key value
key="${resource_app_id}|${perm_type}|${perm_id}"
value="$(awk -F '\t' -v k="$key" '$1==k {print $2; exit}' "$tmp_perm_cache")"
if [ -n "$value" ]; then
printf '%s\n' "$value"
return 0
fi
if [ "$perm_type" = "Scope" ]; then
value="$(az ad sp show --id "$resource_app_id" --query "oauth2PermissionScopes[?id=='$perm_id'].value | [0]" -o tsv 2>/dev/null || true)"
elif [ "$perm_type" = "Role" ]; then
value="$(az ad sp show --id "$resource_app_id" --query "appRoles[?id=='$perm_id'].value | [0]" -o tsv 2>/dev/null || true)"
else
value=""
fi
[ -n "$value" ] || value="UNKNOWN"
printf '%s\t%s\n' "$key" "$value" >> "$tmp_perm_cache"
printf '%s\n' "$value"
}
command -v az >/dev/null 2>&1 || { echo "az CLI not found" >&2; exit 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; } command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }
az account show >/dev/null az account show >/dev/null
@@ -170,7 +196,8 @@ apps_json="$(az ad app list --all --query '[?length(requiredResourceAccess) > `0
tmp_map="$(mktemp)" tmp_map="$(mktemp)"
tmp_ids="$(mktemp)" tmp_ids="$(mktemp)"
trap 'rm -f "$tmp_map" "$tmp_ids"' EXIT tmp_perm_cache="$(mktemp)"
trap 'rm -f "$tmp_map" "$tmp_ids" "$tmp_perm_cache"' EXIT
# Build unique resourceAppId values used by applications. # Build unique resourceAppId values used by applications.
jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids" jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids"
@@ -184,9 +211,9 @@ while IFS= read -r rid; do
printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map" printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map"
done < "$tmp_ids" done < "$tmp_ids"
echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tresourceOwnerOrgId\tpermissionType\tpermissionId" echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tisMicrosoft\tpermissions"
# Print only app permissions where the target API is NOT Microsoft-owned. # Print all app API permissions and mark if the target API is Microsoft-owned.
while IFS= read -r row; do while IFS= read -r row; do
app_name="$(jq -r '.[0]' <<<"$row")" app_name="$(jq -r '.[0]' <<<"$row")"
app_id="$(jq -r '.[1]' <<<"$row")" app_id="$(jq -r '.[1]' <<<"$row")"
@@ -201,14 +228,25 @@ while IFS= read -r row; do
[ -n "$resource_name" ] || resource_name="UNKNOWN" [ -n "$resource_name" ] || resource_name="UNKNOWN"
if is_microsoft_owner "$owner_org"; then if is_microsoft_owner "$owner_org"; then
continue is_ms="true"
else
is_ms="false"
fi fi
permissions_csv=""
while IFS= read -r access; do while IFS= read -r access; do
perm_type="$(jq -r '.type' <<<"$access")" perm_type="$(jq -r '.type' <<<"$access")"
perm_id="$(jq -r '.id' <<<"$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}" perm_value="$(get_permission_value "$resource_app_id" "$perm_type" "$perm_id")"
perm_label="${perm_type}:${perm_value}"
if [ -z "$permissions_csv" ]; then
permissions_csv="$perm_label"
else
permissions_csv="${permissions_csv},${perm_label}"
fi
done < <(jq -c '.resourceAccess[]' <<<"$rra") done < <(jq -c '.resourceAccess[]' <<<"$rra")
echo -e "${app_name}\t${app_id}\t${resource_name}\t${resource_app_id}\t${is_ms}\t${permissions_csv}"
done < <(jq -c '.[2][]' <<<"$row") done < <(jq -c '.[2][]' <<<"$row")
done < <(jq -c '.[]' <<<"$apps_json") done < <(jq -c '.[]' <<<"$apps_json")
``` ```
@@ -472,5 +510,3 @@ az rest --method GET \
- `microsoft.directory/applications.myOrganization/permissions/update` - `microsoft.directory/applications.myOrganization/permissions/update`
{{#include ../../../../banners/hacktricks-training.md}} {{#include ../../../../banners/hacktricks-training.md}}

View File

@@ -761,10 +761,11 @@ For more information about Applications check:
../az-basic-information/ ../az-basic-information/
{{#endref}} {{#endref}}
When an App is generated 2 types of permissions are given: When an App is generated 3 types of permissions are given:
- **Permissions** given to the **Service Principal** - **Permissions** given to the **Service Principal** (via roles).
- **Permissions** the **app** can have and use on **behalf of the user**. - **Permissions** the **app** can have and use on **behalf of the user**.
- **API Permissions** that gives the app permissions over EntraID withuot requiring other roles granting these permissions.
{{#tabs }} {{#tabs }}
{{#tab name="az cli" }} {{#tab name="az cli" }}
@@ -820,7 +821,7 @@ az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?id=='
``` ```
<details> <details>
<summary>Find all applications with API permissions to non-Microsoft APIs (az cli)</summary> <summary>Find all applications API permissions and mark Microsoft-owned APIs (az cli)</summary>
```bash ```bash
#!/usr/bin/env bash #!/usr/bin/env bash
@@ -843,6 +844,32 @@ is_microsoft_owner() {
return 1 return 1
} }
get_permission_value() {
local resource_app_id="$1"
local perm_type="$2"
local perm_id="$3"
local key value
key="${resource_app_id}|${perm_type}|${perm_id}"
value="$(awk -F '\t' -v k="$key" '$1==k {print $2; exit}' "$tmp_perm_cache")"
if [ -n "$value" ]; then
printf '%s\n' "$value"
return 0
fi
if [ "$perm_type" = "Scope" ]; then
value="$(az ad sp show --id "$resource_app_id" --query "oauth2PermissionScopes[?id=='$perm_id'].value | [0]" -o tsv 2>/dev/null || true)"
elif [ "$perm_type" = "Role" ]; then
value="$(az ad sp show --id "$resource_app_id" --query "appRoles[?id=='$perm_id'].value | [0]" -o tsv 2>/dev/null || true)"
else
value=""
fi
[ -n "$value" ] || value="UNKNOWN"
printf '%s\t%s\n' "$key" "$value" >> "$tmp_perm_cache"
printf '%s\n' "$value"
}
command -v az >/dev/null 2>&1 || { echo "az CLI not found" >&2; exit 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; } command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }
az account show >/dev/null az account show >/dev/null
@@ -851,7 +878,8 @@ apps_json="$(az ad app list --all --query '[?length(requiredResourceAccess) > `0
tmp_map="$(mktemp)" tmp_map="$(mktemp)"
tmp_ids="$(mktemp)" tmp_ids="$(mktemp)"
trap 'rm -f "$tmp_map" "$tmp_ids"' EXIT tmp_perm_cache="$(mktemp)"
trap 'rm -f "$tmp_map" "$tmp_ids" "$tmp_perm_cache"' EXIT
# Build unique resourceAppId values used by applications. # Build unique resourceAppId values used by applications.
jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids" jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids"
@@ -865,9 +893,9 @@ while IFS= read -r rid; do
printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map" printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map"
done < "$tmp_ids" done < "$tmp_ids"
echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tresourceOwnerOrgId\tpermissionType\tpermissionId" echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tisMicrosoft\tpermissions"
# Print only app permissions where the target API is NOT Microsoft-owned. # Print all app API permissions and mark if the target API is Microsoft-owned.
while IFS= read -r row; do while IFS= read -r row; do
app_name="$(jq -r '.[0]' <<<"$row")" app_name="$(jq -r '.[0]' <<<"$row")"
app_id="$(jq -r '.[1]' <<<"$row")" app_id="$(jq -r '.[1]' <<<"$row")"
@@ -882,14 +910,25 @@ while IFS= read -r row; do
[ -n "$resource_name" ] || resource_name="UNKNOWN" [ -n "$resource_name" ] || resource_name="UNKNOWN"
if is_microsoft_owner "$owner_org"; then if is_microsoft_owner "$owner_org"; then
continue is_ms="true"
else
is_ms="false"
fi fi
permissions_csv=""
while IFS= read -r access; do while IFS= read -r access; do
perm_type="$(jq -r '.type' <<<"$access")" perm_type="$(jq -r '.type' <<<"$access")"
perm_id="$(jq -r '.id' <<<"$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}" perm_value="$(get_permission_value "$resource_app_id" "$perm_type" "$perm_id")"
perm_label="${perm_type}:${perm_value}"
if [ -z "$permissions_csv" ]; then
permissions_csv="$perm_label"
else
permissions_csv="${permissions_csv},${perm_label}"
fi
done < <(jq -c '.resourceAccess[]' <<<"$rra") done < <(jq -c '.resourceAccess[]' <<<"$rra")
echo -e "${app_name}\t${app_id}\t${resource_name}\t${resource_app_id}\t${is_ms}\t${permissions_csv}"
done < <(jq -c '.[2][]' <<<"$row") done < <(jq -c '.[2][]' <<<"$row")
done < <(jq -c '.[]' <<<"$apps_json") done < <(jq -c '.[]' <<<"$apps_json")
``` ```
@@ -1383,5 +1422,3 @@ The default mode is **Audit**:
- [EntraTokenAid](https://github.com/zh54321/EntraTokenAid) - [EntraTokenAid](https://github.com/zh54321/EntraTokenAid)
{{#include ../../../banners/hacktricks-training.md}} {{#include ../../../banners/hacktricks-training.md}}