Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions compliance/frameworks/cis_azure_benchmark.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@
"control_id": "6.4",
"control_name": "Ensure that Azure Firewall is enabled on Virtual Networks",
"description": "VNet peering connections with allowGatewayTransit or useRemoteGateways enabled allow traffic to route between network segments through shared gateways. This can break network segmentation and enable lateral movement between zones that should remain isolated. Peering connections should be reviewed and gateway transit disabled unless explicitly required and documented."
},
"AZ-NET-015": {
"control_id": "9.2",
"control_name": "Ensure that Network Security Groups have rules configured",
"description": "Public DNS zones expose all DNS records to internet enumeration, allowing attackers to map hostnames, IP addresses, and service endpoints. CIS 9.2 requires that network access controls are explicitly configured. Internal services should use Azure Private DNS zones linked to virtual networks rather than public zones."
}
}
}
6 changes: 6 additions & 0 deletions compliance/frameworks/iso27001.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@
"control_id": "A.13.1.1",
"control_name": "Network controls",
"description": "VNet peering connections with gateway transit enabled allow traffic to flow between network segments through shared gateways, potentially bypassing network controls. Networks should be managed and controlled to protect information in systems and applications. Gateway transit on peering connections should be disabled unless explicitly required."
},
"AZ-NET-015": {
"control_id": "A.13.1.1",
"control_name": "Network controls",
"description": "Public DNS zones expose internal hostnames, IP addresses, and service endpoints to enumeration by any internet user. Networks should be managed and controlled to protect information systems. Internal services should use Azure Private DNS zones to restrict resolution to authorised networks."
}
}
}

6 changes: 6 additions & 0 deletions compliance/frameworks/nist_csf.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@
"control_id": "PR.AC-5",
"control_name": "Network integrity is protected",
"description": "VNet peering with gateway transit enabled allows traffic to cross network boundaries through shared gateways, undermining network segmentation. PR.AC-5 requires that network integrity is protected. Disabling gateway transit on peering connections enforces boundary integrity between network zones."
},
"AZ-NET-015": {
"control_id": "PR.AC-5",
"control_name": "Network integrity is protected",
"description": "Public DNS zones allow unrestricted internet enumeration of DNS records, exposing internal hostnames and IP addresses. PR.AC-5 requires that network integrity is protected. Internal services should be resolved through Azure Private DNS zones linked to trusted virtual networks."
}
}
}

9 changes: 7 additions & 2 deletions compliance/frameworks/soc2.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@
"control_id": "CC6.6",
"control_name": "Restricts Access from Outside the Network Boundary",
"description": "VNet peering with allowGatewayTransit or useRemoteGateways enabled allows traffic to cross network boundaries through shared gateways, weakening the logical separation between network zones. CC6.6 requires that logical access from outside the network boundary is restricted and controlled. Gateway transit on peering connections should be disabled to enforce boundary separation."
},
"AZ-NET-015": {
"control_id": "CC6.6",
"control_name": "Restricts Access from Outside the Network Boundary",
"description": "A public DNS zone allows any external party to query and enumerate DNS records, exposing internal hostnames and service endpoints. CC6.6 requires that logical access from outside the network boundary is restricted and controlled. Internal services should use Azure Private DNS zones linked to virtual networks."
}
}
}
}
}
27 changes: 27 additions & 0 deletions playbooks/cli/fix_az_net_015.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash
set -euo pipefail

RESOURCE_GROUP=$1
ZONE_NAME=$2
VNET_ID=$3

if [ -z "$RESOURCE_GROUP" ] || [ -z "$ZONE_NAME" ] || [ -z "$VNET_ID" ]; then
echo "Usage: $0 <resource-group> <zone-name> <vnet-id>"
exit 1
fi

echo "Creating private DNS zone: $ZONE_NAME"

az network private-dns zone create \
--resource-group "$RESOURCE_GROUP" \
--name "$ZONE_NAME"

az network private-dns link vnet create \
--resource-group "$RESOURCE_GROUP" \
--zone-name "$ZONE_NAME" \
--name "${ZONE_NAME}-link" \
--virtual-network "$VNET_ID" \
--registration-enabled false

echo "Done. Private DNS zone created and linked to VNet."
echo "Note: Migrate records from the public zone to the private zone before deleting the public zone."
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ azure-mgmt-rdbms==10.1.0
azure-mgmt-authorization==4.0.0
azure-monitor-ingestion==1.0.3
azure-mgmt-monitor==6.0.0
azure-mgmt-dns==8.0.0
psycopg2-binary==2.9.9
python-dotenv==1.0.0
pyjwt==2.8.0
Expand Down
10 changes: 10 additions & 0 deletions scanner/azure_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,16 @@ def get_vnet_peerings(self, resource_group: str, vnet_name: str) -> List[Any]:
except Exception as exc:
logger.error("get_vnet_peerings(%s) failed: %s", vnet_name, exc)
return []
def get_dns_zones(self) -> List[Any]:
"""List all DNS zones in the subscription."""
try:
from azure.mgmt.dns import DnsManagementClient
client = DnsManagementClient(self.credential, self.subscription_id)
return list(client.zones.list())
except Exception as exc:
logger.error("get_dns_zones failed: %s", exc)
return []


# ------------------------------------------------------------------ #
# Compute #
Expand Down
54 changes: 54 additions & 0 deletions scanner/rules/az_net_015.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""AZ-NET-015: Public DNS zone exposes internal infrastructure to enumeration."""
from typing import Any, Dict, List

RULE_ID = "AZ-NET-015"
RULE_NAME = "Public DNS Zone Exposes Infrastructure to Enumeration"
SEVERITY = "MEDIUM"
CATEGORY = "Network"
FRAMEWORKS = {
"CIS": "9.2",
"NIST": "PR.AC-5",
"ISO27001": "A.13.1.1",
"SOC2": "CC6.6"
}
DESCRIPTION = (
"The DNS zone is configured as a Public zone, meaning its records "
"are queryable by anyone on the internet. Public DNS zones expose "
"internal hostnames, IP addresses, and service endpoints to "
"enumeration, which can assist attackers in mapping the organisation's "
"infrastructure and identifying targets for further attack."
)
REMEDIATION = (
"Review all DNS zones and migrate records for internal services to "
"Azure Private DNS zones linked to the appropriate virtual networks. "
"Retain public DNS zones only for resources that are intentionally "
"internet-facing and require public resolution."
)
PLAYBOOK = "playbooks/cli/fix_az_net_015.sh"


def scan(azure_client: Any, subscription_id: str) -> List[Dict[str, Any]]:
findings: List[Dict[str, Any]] = []
for zone in azure_client.get_dns_zones():
parsed = azure_client.parse_resource_id(zone.id)
resource_group = parsed["resource_group"]
zone_type = getattr(zone, "zone_type", "Public")
if zone_type == "Public":
findings.append({
"rule_id": RULE_ID,
"rule_name": RULE_NAME,
"severity": SEVERITY,
"category": CATEGORY,
"resource_id": zone.id,
"resource_name": zone.name,
"resource_type": "Microsoft.Network/dnsZones",
"description": DESCRIPTION,
"remediation": REMEDIATION,
"playbook": PLAYBOOK,
"frameworks": FRAMEWORKS,
"metadata": {
"resource_group": resource_group,
"zone_type": zone_type
}
})
return findings
Loading