Detect Azure VM with a Public IP associated

Last week a friend asked me if creating or updating a virtual machine where a public IP address was associated with was detectable. This is a very common requirement in cloud security monitoring. Having a workload (aka virtual machine) with Internet exposure is never recommended. Otherwise, that virtual machine plays a security perimeter role.

In this article, let’s see how we can trigger an alert when someone creates or updates a virtual machine that has a public IP address.

Use Case

A virtual machine with Internet exposure is always put at risk. Bad actors can try different vulnerabilities when that virtual machine is accessible from them. Once that virtual machine is compromised bad actors could scan and extract sensitive information on it and try to laterally move across your environment. It would become worse if that virtual machine had its system-assigned managed identity being granted more than just the Reader access in Azure subscription.

As a SecOps analyst you would need to monitor and get notification if someone creates or updates a virtual machine to associate a public IP Address. And this is what this article is about.

Approach

The approach is to use either Azure Monitor Alert or Microsoft Sentinel to monitor and trigger an alert. I would highly recommend you to read the following article to understand more

Detect NSG inbound rule updated to allow All

The query to use for the detection is as follows:

// Find Network Interface Card (NIC) where a Public Ip address is associated with
let NicResourceIds = 
AzureActivity
| where OperationNameValue =~ "Microsoft.Network/networkInterfaces/write"
| extend publicIpResourceId = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).properties)).ipConfigurations))[0].properties)).publicIpAddress)).id)
| where publicIpResourceId != ""
| project _ResourceId;
// Find virtual machines that have Nic associated with
AzureActivity
| where CategoryValue  == "Administrative"
| extend Action = tostring(parse_json(Authorization).action),
        NicResourceId = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).responseBody)).properties)).networkProfile)).networkInterfaces))[0].id)
| where Action =~ "Microsoft.Compute/virtualMachines/write" and
        ActivityStatusValue =~ 'Accepted' and
        NicResourceId in~ (NicResourceIds)
| project TimeGenerated,
          VmName=Resource,
          ResourceGroup, 
          SubscriptionId, 
          CallerIpAddress, 
          ActivityStatusValue, 
          NicResourceId,
          _ResourceId

Below are sample templates:

Resource Graph Explorer

The Resource Graph Explorer is another good way to monitor and track virtual machines with Public IP Address. However, it doesn’t give you who created or updated as well as information that Azure Activity Log provides.

If you wish to perform an on-demand assessment, below is the query to find those virtual machines:

Resources 
| where type =~ 'microsoft.compute/virtualmachines' 
| extend nics=array_length(properties.networkProfile.networkInterfaces)  
| mvexpand nic=properties.networkProfile.networkInterfaces  
| extend hostName = properties.osProfile.computerName 
| where nics == 1 or nic.properties.primary =~ 'true' or isempty(nic)  
| project vmId = id, 
          vmName = name, 
          nicId = tostring(nic.id), 
          hostName 
| join kind=leftouter ( 
    Resources 
    | where type =~ 'microsoft.network/networkinterfaces' 
    | extend ipConfigsCount=array_length(properties.ipConfigurations)  
    | mvexpand ipconfig=properties.ipConfigurations  
    | extend privateIp = ipconfig.properties.privateIPAddress,
             publicIpId = tostring(ipconfig.properties.publicIPAddress.id)
    | where ipConfigsCount == 1 or ipconfig.properties.primary =~ 'true' 
    | project nicId = id, 
              privateIp, 
              publicIpId
) 
on nicId 
| project-away nicId1 
| summarize by vmId, vmName, tostring(hostName), nicId, publicIpId, tostring(privateIp) 
| join kind=leftouter ( 
    Resources 
    | where type =~ 'microsoft.network/publicipaddresses' 
    | project publicIpId = id, publicIpAddress = properties.ipAddress) 
on publicIpId 
| where publicIpAddress != ""
| project-away publicIpId1

If you have any feedback, please feel free to leave a comment in the Comment box orĀ create an issue in GitHub.

This entry was posted in Monitoring & Detection, Security Automation and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.