feat: add more dns checks

This commit is contained in:
HugoPBrito
2025-12-16 11:57:24 +01:00
parent c1140dfcc0
commit 557b5aa480
13 changed files with 283 additions and 33 deletions

View File

@@ -0,0 +1,35 @@
{
"Provider": "cloudflare",
"CheckID": "dns_record_cname_target_valid",
"CheckTitle": "CNAME records point to valid targets without subdomain takeover risk",
"CheckType": [],
"ServiceName": "dns",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "DNSRecord",
"Description": "**Cloudflare DNS CNAME records** are assessed for **dangling CNAME** vulnerabilities by checking if the target domain resolves to a valid address, preventing **subdomain takeover** attacks.",
"Risk": "Dangling **CNAME records** pointing to non-existent targets create subdomain takeover vulnerabilities.\n- **Confidentiality**: attackers can host malicious content on your subdomain to phish users\n- **Integrity**: attackers can impersonate your organization and damage brand reputation\n- **Availability**: legitimate services may be disrupted or redirected",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to DNS > Records\n3. Identify CNAME records with dangling targets\n4. Either update the CNAME to point to a valid target or delete the record\n5. If the target service was decommissioned, remove the DNS record",
"Terraform": ""
},
"Recommendation": {
"Text": "Remove or update **dangling CNAME records** to prevent subdomain takeover.\n- Regularly audit DNS records when decommissioning services\n- Remove CNAME records pointing to deprovisioned cloud resources\n- Monitor for unauthorized changes to DNS records\n- Consider using DNS monitoring tools to detect dangling records",
"Url": "https://hub.prowler.com/checks/cloudflare/dns_record_cname_target_valid"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Subdomain takeover occurs when a CNAME points to a service (like cloud hosting) that has been deprovisioned, allowing attackers to claim that service and control the subdomain."
}

View File

@@ -0,0 +1,54 @@
import socket
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.dns.dns_client import dns_client
class dns_record_cname_target_valid(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for record in dns_client.records:
# Only check CNAME records
if record.type != "CNAME":
continue
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=record,
zone=record.zone,
)
target = record.content
is_valid = self._check_cname_target(target)
if is_valid:
report.status = "PASS"
report.status_extended = (
f"CNAME record '{record.name}' points to valid target '{target}'."
)
else:
report.status = "FAIL"
report.status_extended = (
f"CNAME record '{record.name}' points to potentially dangling target '{target}' - "
f"subdomain takeover risk."
)
findings.append(report)
return findings
def _check_cname_target(self, target: str) -> bool:
"""Check if CNAME target resolves to a valid address."""
# Remove trailing dot if present
target = target.rstrip(".")
try:
# Attempt DNS resolution
socket.getaddrinfo(target, None, socket.AF_UNSPEC)
return True
except socket.gaierror:
# DNS resolution failed - potential dangling CNAME
return False
except Exception:
# On any other error, assume valid to avoid false positives
return True

View File

@@ -0,0 +1,35 @@
{
"Provider": "cloudflare",
"CheckID": "dns_record_no_internal_ip",
"CheckTitle": "DNS records do not expose internal IP addresses",
"CheckType": [],
"ServiceName": "dns",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "DNSRecord",
"Description": "**Cloudflare DNS records** are assessed for **internal IP exposure** by checking if A or AAAA records point to private, loopback, or reserved IP addresses which could **leak internal network structure**.",
"Risk": "DNS records exposing **internal IP addresses** leak sensitive network information.\n- **Confidentiality**: reveals internal network topology and addressing schemes to attackers\n- **Integrity**: provides reconnaissance data for targeted attacks on internal infrastructure\n- **Availability**: internal IPs in public DNS may indicate misconfiguration affecting service routing",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to DNS > Records\n3. Identify A/AAAA records pointing to internal IP addresses\n4. Update records to point to public IP addresses or remove if not needed\n5. Use split-horizon DNS if internal resolution is required",
"Terraform": ""
},
"Recommendation": {
"Text": "Remove **internal IP addresses** from public DNS records.\n- Use split-horizon DNS for internal service resolution\n- Ensure DNS records only contain publicly routable IP addresses\n- Review DNS records after network changes or migrations\n- Consider using Cloudflare Access for secure internal service access",
"Url": "https://hub.prowler.com/checks/cloudflare/dns_record_no_internal_ip"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Internal IP ranges include: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (IPv4), fc00::/7 (IPv6 ULA), and loopback addresses. These should not appear in public DNS records."
}

View File

@@ -0,0 +1,54 @@
import ipaddress
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.dns.dns_client import dns_client
class dns_record_no_internal_ip(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for record in dns_client.records:
# Only check A and AAAA records
if record.type not in ("A", "AAAA"):
continue
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=record,
zone=record.zone,
)
is_internal = self._is_internal_ip(record.content)
if not is_internal:
report.status = "PASS"
report.status_extended = (
f"DNS record '{record.name}' ({record.type}) points to "
f"public IP address '{record.content}'."
)
else:
report.status = "FAIL"
report.status_extended = (
f"DNS record '{record.name}' ({record.type}) exposes "
f"internal IP address '{record.content}' - information disclosure risk."
)
findings.append(report)
return findings
def _is_internal_ip(self, ip_str: str) -> bool:
"""Check if IP address is internal/private."""
try:
ip = ipaddress.ip_address(ip_str)
# Check for private, loopback, link-local, or reserved addresses
return (
ip.is_private
or ip.is_loopback
or ip.is_link_local
or ip.is_reserved
or ip.is_unspecified
)
except ValueError:
# Invalid IP format, assume not internal
return False

View File

@@ -0,0 +1,35 @@
{
"Provider": "cloudflare",
"CheckID": "dns_record_no_wildcard",
"CheckTitle": "DNS records do not use wildcard entries",
"CheckType": [],
"ServiceName": "dns",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "DNSRecord",
"Description": "**Cloudflare DNS records** are assessed for **wildcard usage** by checking if A, AAAA, or CNAME records use wildcard entries (*.example.com) which can **increase attack surface** and expose unintended services.",
"Risk": "**Wildcard DNS records** can expose unintended services and increase attack surface.\n- **Confidentiality**: any subdomain resolves, potentially exposing internal naming conventions\n- **Integrity**: attackers can access unintended services via arbitrary subdomains\n- **Availability**: wildcard records may route traffic to services not designed for public access",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to DNS > Records\n3. Identify wildcard DNS records (starting with *.)\n4. Evaluate if the wildcard is necessary for your use case\n5. Replace wildcard records with specific subdomain records where possible",
"Terraform": ""
},
"Recommendation": {
"Text": "Avoid using **wildcard DNS records** unless absolutely necessary.\n- Use specific subdomain records instead of wildcards\n- If wildcards are required, ensure the target service handles unknown subdomains securely\n- Document the business justification for any wildcard records\n- Combine with proper web server configuration to reject unknown hosts",
"Url": "https://hub.prowler.com/checks/cloudflare/dns_record_no_wildcard"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Wildcard DNS records (*.example.com) cause any subdomain query to resolve. While useful for some applications, they can expose services unintentionally and make subdomain enumeration easier for attackers."
}

View File

@@ -0,0 +1,34 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.dns.dns_client import dns_client
class dns_record_no_wildcard(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for record in dns_client.records:
# Only check A, AAAA, and CNAME records for wildcards
if record.type not in ("A", "AAAA", "CNAME"):
continue
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=record,
zone=record.zone,
)
# Check if record name starts with wildcard
is_wildcard = record.name.startswith("*.")
if not is_wildcard:
report.status = "PASS"
report.status_extended = f"DNS record '{record.name}' ({record.type}) is not a wildcard record."
else:
report.status = "FAIL"
report.status_extended = (
f"DNS record '{record.name}' ({record.type}) is a wildcard record - "
f"may expose unintended services."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,35 @@
{
"Provider": "cloudflare",
"CheckID": "dns_record_proxied",
"CheckTitle": "Cloudflare proxy is enabled for applicable DNS records",
"CheckType": [],
"ServiceName": "dns",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "DNSRecord",
"Description": "**Cloudflare DNS records** are assessed for **proxy configuration** by checking if A, AAAA, and CNAME records are proxied through Cloudflare to benefit from **DDoS protection**, **WAF**, and **caching** capabilities.",
"Risk": "Unproxied **DNS records** expose origin server IP addresses directly to the internet.\n- **Confidentiality**: origin IP exposure enables targeted reconnaissance and attacks\n- **Integrity**: direct access to origin bypasses WAF and security controls\n- **Availability**: origin is exposed to DDoS attacks without Cloudflare protection",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to DNS > Records\n3. For each A, AAAA, or CNAME record that should be protected\n4. Click Edit and toggle Proxy status to Proxied (orange cloud)\n5. Save the changes and verify traffic flows through Cloudflare",
"Terraform": "```hcl\n# Enable Cloudflare proxy for DNS records\nresource \"cloudflare_record\" \"proxied_record\" {\n zone_id = \"<ZONE_ID>\"\n name = \"www\"\n content = \"192.0.2.1\"\n type = \"A\"\n proxied = true # Critical: enables DDoS protection, WAF, and caching\n}\n```"
},
"Recommendation": {
"Text": "Enable the **Cloudflare proxy** (orange cloud) for DNS records that should be protected.\n- Proxied records benefit from DDoS protection, WAF, and caching\n- Origin server IP addresses are hidden from public DNS queries\n- Apply defense in depth by combining proxy protection with origin hardening\n- Some record types (MX, TXT) cannot be proxied by design",
"Url": "https://hub.prowler.com/checks/cloudflare/dns_record_proxied"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Only A, AAAA, and CNAME records can be proxied. MX, TXT, and other record types are always DNS-only. Some services may require DNS-only mode for specific use cases."
}

View File

@@ -4,7 +4,7 @@ from prowler.providers.cloudflare.services.dns.dns_client import dns_client
PROXYABLE_TYPES = {"A", "AAAA", "CNAME"}
class dns_records_proxied(Check):
class dns_record_proxied(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []

View File

@@ -1,32 +0,0 @@
{
"Provider": "cloudflare",
"CheckID": "dns_records_proxied",
"CheckTitle": "Cloudflare proxy is enabled for applicable DNS records",
"CheckType": [],
"ServiceName": "dns",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "DNSRecord",
"Description": "Verifies that A, AAAA, and CNAME DNS records are proxied through Cloudflare to benefit from DDoS protection, WAF, and caching capabilities.",
"Risk": "Unproxied DNS records expose origin server IP addresses directly to the internet, bypassing Cloudflare's security protections and increasing the attack surface for direct attacks against the origin infrastructure.",
"RelatedUrl": "https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable the Cloudflare proxy (orange cloud) for DNS records that should be protected. Apply defense in depth by combining proxy protection with origin server hardening and access controls.",
"Url": "https://hub.prowler.com/checks/dns_records_proxied"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}