Quick and Dirty Azure VM Attack Surface Enumeration for Defenders

Quick and Dirty Azure VM Attack Surface Enumeration for Defenders

Once upon a time I was sat in departures at DBX and needed to to quickly enumerate all virtual machines in an Azure tenant, to find out how many had a particular vulnerable service exposed cough omi cough.

It needed to be quick... 80/20 rule was good enough to get us going, so I quickly decided:

  1. I knew there were under 1000 virtual machines with public IP addresses in the tenant.
  2. I knew Azure Resource Graph (ARG) query was blindingly quick.
  3. Scanning the ports with nmap would be fine, but it needed to be quicker, and target the systems already known to attackers.

Below is the silly script I wrote to quickly dump all virtual machines with a public IP address using ARG, and then query Shodan to find out if those ports were known to be open.

Since Shodan would contain the same information attackers would have quick access to, this would prioritize known exposed services first while we could scan deeper afterwards.

🤯
For those that don't know, Shodan is the search engine for internet connected devices, with weekly updated data including open ports, service headers, a great API, browser integrations, etc.

Shodan publish a utility called nrich, a command line tool to quickly analyse IP addresses in a file and see which have open ports and vulnerabilities. Perfect for my quick and dirty use case. Check out the Shodan blog post about nrich here.

All I had to do is query the data from Azure, extract just the IP addresses into a file, and then process that with nrich. I could then manually correlate any vulnerable IP addresses back to the Azure machines and send them for remediation.

#!/bin/bash

# query the azure resource graph
# fast method but missing pagination
# if you have more than 1000 VM with publicip, you can skip to paginate
# eg --skip 1000 --first 1000 would give you records 1001 to 2000
argIP(){
az graph query --first 1000 -q "Resources
    | where type =~ 'microsoft.compute/virtualmachines'
    | project vmId = tolower(tostring(id)), vmName = name
    | join (Resources
        | where type =~ 'microsoft.network/networkinterfaces'
        | mv-expand ipconfig=properties.ipConfigurations
        | project vmId = tolower(tostring(properties.virtualMachine.id)), privateIp = ipconfig.properties.privateIPAddress, publicIpId = tostring(ipconfig.properties.publicIPAddress.id)
        | join kind=leftouter (Resources
            | where type =~ 'microsoft.network/publicipaddresses'
            | project publicIpId = id, publicIp = properties.ipAddress
        ) on publicIpId
        | project-away publicIpId, publicIpId1
        | summarize privateIps = make_list(privateIp), publicIps = make_list(publicIp) by vmId
    ) on vmId
    | project-away vmId1
    | sort by vmName asc
| where array_length(publicIps)>0"
}

# calls the function to query azure, and puts the json data in a file
argIP > public_az

# does funky stuff to extract just the IP addresses ready for nrich
cat public_az | jq '.data[] | { publicIps }' | grep -Eo '[0-9]+.[0-9]+.[0-9]+.[0-9]+' > public_ip

# calls nrich, and puts the data into another file for review
nrich public_ip > public_nrich

Running this script identified the vulnerable hosts and allowed me to get on my flight while they were being remediated by the team.

Of course, while remediation was happening, a slower and more deliberate scan of the environment was happening, I'll share that script another day.