Introduction

The Open Door

Difficulty:
Help Goose Lucas in the hotel parking lot find the dangerously misconfigured Network Security Group rule that's allowing unrestricted internet access to sensitive ports like RDP or SSH.

Lucas the Goose is in the hotel parking lot with a cranberry-pi:

image-20251107160551287
Lucas

Lucas

(Spanish mode) Hi… welcome to the Dosis Neighborhood! Nice to meet you!

Please make sure the towns Azure network is secured properly.

The Neighborhood HOA uses Azure for their IT infrastructure.

Audit their network security configuration to ensure production systems aren’t exposed to internet attacks.

They claim all systems are properly protected, but you need to verify there are no overly permissive NSG rules.

The The Open Door terminal opens up a terminal with two panes. The top pane has instructions, and the bottom a place to interact:

image-20251107161123355

Solution

Background

Network Security Groups (NSGs) are Azure’s virtual firewalls that filter network traffic to and from Azure resources. Each NSG contains security rules that allow or deny traffic based on:

  • Direction - Inbound or Outbound
  • Priority - Lower numbers are evaluated first (100-4096)
  • Source/Destination - IP addresses, service tags (like AzureLoadBalancer), or * for any
  • Port - Specific ports or ranges
  • Protocol - TCP, UDP, or * for any
  • Action - Allow or Deny

Rules are evaluated in priority order, and processing stops at the first match. A common misconfiguration is allowing management ports (RDP/3389, SSH/22) from 0.0.0.0/0 (the entire internet) instead of restricting to known admin IPs.

#1

Welcome back! Let’s start by exploring output formats. First, let’s see resource groups in JSON format (the default): $ az group list JSON format shows detailed structured data.

This command lists the groups in the current account:

neighbor@da116465c00c:~$ az group list
[
  {
    "id": "/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/theneighborhood-rg1",
    "location": "eastus",
    "managedBy": null,
    "name": "theneighborhood-rg1",
    "properties": {
      "provisioningState": "Succeeded"
    },
    "tags": {}
  },
  {
    "id": "/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/theneighborhood-rg2",
    "location": "westus",
    "managedBy": null,
    "name": "theneighborhood-rg2",
    "properties": {
      "provisioningState": "Succeeded"
    },
    "tags": {}
  }
]

There are two groups.

#2

Great! Now let’s see the same data in table format for better readability 👀 $ az group list -o table Notice how -o table changes the output format completely! Both commands show the same data, just formatted differently.

This does look better:

neighbor@da116465c00c:~$ az group list -o table
Name                 Location    ProvisioningState
-------------------  ----------  -------------------
theneighborhood-rg1  eastus      Succeeded
theneighborhood-rg2  westus      Succeeded

#3

Lets take a look at Network Security Groups (NSGs). To do this try: az network nsg list -o table This lists all NSGs across resource groups. For more information: https://learn.microsoft.com/en-us/cli/azure/network/nsg?view=azure-cli-latest

The command shows there are five network security groups:

neighbor@da116465c00c:~$ az network nsg list -o table
Location    Name                   ResourceGroup
----------  ---------------------  -------------------
eastus      nsg-web-eastus         theneighborhood-rg1
eastus      nsg-db-eastus          theneighborhood-rg1
eastus      nsg-dev-eastus         theneighborhood-rg2
eastus      nsg-mgmt-eastus        theneighborhood-rg2
eastus      nsg-production-eastus  theneighborhood-rg1

#4

Inspect the Network Security Group (web) 🕵️ Here is the NSG and its resource group: --name nsg-web-eastus --resource-group theneighborhood-rg1

Hint: We want to show the NSG details. Use | less to page through the output. Documentation: https://learn.microsoft.com/en-us/cli/azure/network/nsg?view=azure-cli-latest#az-network-nsg-show

It wants me to look at the theneighborhood-rg1 resource group network security group:

neighbor@da116465c00c:~$ az network nsg show --name nsg-web-eastus --resource-group theneighborhood-rg1
{                           
  "id": "/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/theneighborhood-rg1/providers/Microsoft.Network/networkSecurityGroups/n
sg-web-eastus",
  "location": "eastus",
  "name": "nsg-web-eastus",
  "properties": {                  
    "securityRules": [ 
      {                     
        "name": "Allow-HTTP-Inbound",         
        "properties": {          
          "access": "Allow",
          "destinationPortRange": "80",
          "direction": "Inbound",                                        
          "priority": 100,
          "protocol": "Tcp",
          "sourceAddressPrefix": "0.0.0.0/0"
        }                          
      },               
      {                    
        "name": "Allow-HTTPS-Inbound",
        "properties": {          
          "access": "Allow",
          "destinationPortRange": "443",
          "direction": "Inbound",                                        
          "priority": 110,
          "protocol": "Tcp",
          "sourceAddressPrefix": "0.0.0.0/0"
        }
      },                                                                 
      {    
        "name": "Allow-AppGateway-HealthProbes",
        "properties": {
          "access": "Allow",
          "destinationPortRange": "80,443",
          "direction": "Inbound",
          "priority": 130,
          "protocol": "Tcp",
          "sourceAddressPrefix": "AzureLoadBalancer"
        }
      },
      {
        "name": "Allow-Web-To-App",
        "properties": {
          "access": "Allow",
          "destinationPortRange": "8080,8443",
          "direction": "Inbound",
          "priority": 200,
          "protocol": "Tcp",
          "sourceAddressPrefix": "VirtualNetwork"
        }
      },
      {
        "name": "Deny-All-Inbound",
        "properties": {
          "access": "Deny",
          "destinationPortRange": "*",
          "direction": "Inbound",
          "priority": 4096,
          "protocol": "*",
          "sourceAddressPrefix": "*"
        }
      }
    ]
  },
  "resourceGroup": "theneighborhood-rg1",
  "tags": {
    "env": "web"
  }
}

The securityRules section shows firewall rules evaluated in priority order. This NSG is well-configured: it allows only HTTP (80) and HTTPS (443) from the internet (0.0.0.0/0), health probes from Azure’s load balancer, and internal app traffic from the virtual network. The final “Deny-All-Inbound” rule at priority 4096 blocks everything else.

#5

Inspect the Network Security Group (mgmt) 🕵️ Here is the NSG and its resource group: --nsg-name nsg-mgmt-eastus --resource-group theneighborhood-rg2

Hint: We want to list the NSG rules Documentation: https://learn.microsoft.com/en-us/cli/azure/network/nsg/rule?view=azure-cli-latest#az-network-nsg-rule-list

This command shows just the firewall rules:

neighbor@da116465c00c:~$ az network nsg rule list --name nsg-mgmt-eastus --resource-group theneighborhood-rg2                                      
[   
  {
    "name": "Allow-AzureBastion",                                        
    "nsg": "nsg-mgmt-eastus",
    "properties": {
      "access": "Allow",
      "destinationPortRange": "443",                                     
      "direction": "Inbound",                                            
      "priority": 100,        
      "protocol": "Tcp",
      "sourceAddressPrefix": "AzureBastion"                              
    }
  },
  {
    "name": "Allow-Monitoring-Inbound", 
    "nsg": "nsg-mgmt-eastus",
    "properties": {
      "access": "Allow",
      "destinationPortRange": "443",             
      "direction": "Inbound",                                            
      "priority": 110,        
      "protocol": "Tcp",
      "sourceAddressPrefix": "AzureMonitor"                              
    }
  },
  {
    "name": "Allow-DNS-From-VNet",                                       
    "nsg": "nsg-mgmt-eastus",
    "properties": {
      "access": "Allow",
      "destinationPortRange": "53",
      "direction": "Inbound",
      "priority": 115,
      "protocol": "Udp",
      "sourceAddressPrefix": "VirtualNetwork"
      }
  },
  {
    "name": "Deny-All-Inbound",
    "nsg": "nsg-mgmt-eastus",
    "properties": {
      "access": "Deny",
      "destinationPortRange": "*",
      "direction": "Inbound",
      "priority": 4096,
      "protocol": "*",
      "sourceAddressPrefix": "*"
    }
  },
  {
    "name": "Allow-Monitoring-Outbound",
    "nsg": "nsg-mgmt-eastus",
    "properties": {
      "access": "Allow",
      "destinationAddressPrefix": "AzureMonitor",
      "destinationPortRange": "443", 
      "direction": "Outbound",
      "priority": 200,
      "protocol": "Tcp"
    }
  },
  {
    "name": "Allow-AD-Identity-Outbound",
    "nsg": "nsg-mgmt-eastus",
    "properties": {
      "access": "Allow",
      "destinationAddressPrefix": "AzureActiveDirectory",
      "destinationPortRange": "443", 
      "direction": "Outbound",
      "priority": 210,
      "protocol": "Tcp"
    }
  },
  {
    "name": "Allow-Backup-Outbound", 
    "nsg": "nsg-mgmt-eastus",
    "properties": {
      "access": "Allow",
      "destinationAddressPrefix": "AzureBackup",
      "destinationPortRange": "443", 
      "direction": "Outbound",
      "priority": 220,
      "protocol": "Tcp"
    }
  }
]

Looking at this output, the sourceAddressPrefix values like AzureBastion and AzureMonitor are Azure service tags, which represent groups of IP addresses managed by Microsoft. This is more secure than hardcoding IPs that might change.

To survey rules more easily, I’ll use jq:

neighbor@da116465c00c:~$ az network nsg rule list --name nsg-mgmt-eastus --resource-group theneighborhood-rg2 | jq -c '.[] | [.name, "\(.properties.access):\(.properties.destinationPortRange):\(.properties.direction):\(.properties.direction):\(.properties.protocol)"]'
["Allow-AzureBastion","Allow:443:Inbound:Inbound:Tcp"]
["Allow-Monitoring-Inbound","Allow:443:Inbound:Inbound:Tcp"]
["Allow-DNS-From-VNet","Allow:53:Inbound:Inbound:Udp"]
["Deny-All-Inbound","Deny:*:Inbound:Inbound:*"]
["Allow-Monitoring-Outbound","Allow:443:Outbound:Outbound:Tcp"]
["Allow-AD-Identity-Outbound","Allow:443:Outbound:Outbound:Tcp"]
["Allow-Backup-Outbound","Allow:443:Outbound:Outbound:Tcp"]

#6

Take a look at the rest of the NSG rules and examine their properties. After enumerating the NSG rules, enter the command string to view the suspect rule and inspect its properties. Hint: Review fields such as direction, access, protocol, source, destination and port settings.

Documentation: https://learn.microsoft.com/en-us/cli/azure/network/nsg/rule?view=azure-cli-latest#az-network-nsg-rule-show

Now I’ll look at the other three that I haven’t looked at yet

neighbor@da116465c00c:~$ az network nsg rule list --name nsg-dev-eastus --resource-group theneighborhood-rg2 | jq -c '.[] | [.name, "\(.properties.access):\(.properties.destinationPortRange):\(.properties.direction):\(.properties.direction):\(.properties.protocol)"]'
["Allow-HTTP-Inbound","Allow:80:Inbound:Inbound:Tcp"]
["Allow-HTTPS-Inbound","Allow:443:Inbound:Inbound:Tcp"]
["Allow-DevOps-Agents","Allow:5986:Inbound:Inbound:Tcp"]
["Allow-Jumpbox-Remote-Access","Allow:3389,22:Inbound:Inbound:Tcp"]
["Deny-All-Inbound","Deny:*:Inbound:Inbound:*"]
neighbor@da116465c00c:~$ az network nsg rule list --name nsg-db-eastus --resource-group theneighborhood-rg1 | jq -c '.[] | [.name, "\(.properties.access):\(.properties.destinationPortRange):\(.properties.direction):\(.properties.direction):\(.properties.protocol)"]'
["Allow-App-To-DB","Allow:1433:Inbound:Inbound:Tcp"]
["Allow-AD-Trusted-Subnet","Allow:88,389,636,3268-3269,445:Inbound:Inbound:Tcp"]
["Deny-All-Inbound","Deny:*:Inbound:Inbound:*"]
neighbor@da116465c00c:~$ az network nsg rule list --name nsg-production-eastus --resource-group theneighborhood-rg1 | jq -c '.[] | [.name, "\(.properties.access):\(.properties.destinationPortRange):\(.properties.direction):\(.properties.direction):\(.properties.protocol)"]'
["Allow-HTTP-Inbound","Allow:80:Inbound:Inbound:Tcp"]
["Allow-HTTPS-Inbound","Allow:443:Inbound:Inbound:Tcp"]
["Allow-AppGateway-HealthProbes","Allow:80,443:Inbound:Inbound:Tcp"]
["Allow-RDP-From-Internet","Allow:3389:Inbound:Inbound:Tcp"]
["Deny-All-Inbound","Deny:*:Inbound:Inbound:*"]

nsg-production-eastus has a rule named “Allow-RDP-From-Internet”! That’s not a safe thing to do. I’ll show it:

neighbor@da116465c00c:~$ az network nsg rule show --name "Allow-RDP-From-Internet" --nsg-name nsg-production-eastus --resource-group theneighborhood-rg1
{
  "name": "Allow-RDP-From-Internet",
  "properties": {
    "access": "Allow",
    "destinationPortRange": "3389",
    "direction": "Inbound",
    "priority": 120,
    "protocol": "Tcp",
    "sourceAddressPrefix": "0.0.0.0/0"
  }
}

This rule allows RDP (port 3389) from 0.0.0.0/0, meaning anyone on the internet can attempt to connect. RDP exposed to the internet is a critical security risk because:

  • Attackers constantly scan for open RDP ports
  • Brute force attacks can compromise weak passwords
  • RDP vulnerabilities (like BlueKeep) can allow unauthenticated remote code execution

Production systems should only allow RDP from specific admin IPs or through Azure Bastion.

Outro

On completing question 6, the banner says:

Port 3389 is used by Remote Desktop Protocol — exposing it broadly allows attackers to brute-force credentials, exploit RDP vulnerabilities, and pivot within the network.

✨ To finish, type: finish

Running finish completes the challenge.

Forgotton IP

Congratulations! You have completed the Forgotton IP challenge!

Lucas can’t believe what I found:

Lucas

Lucas

Ha! ‘Properly protected’ they said. More like ‘properly exposed to the entire internet’! Good catch, amigo.