mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-04-06 02:58:15 +00:00
feat: add more dns checks
This commit is contained in:
@@ -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."
|
||||
}
|
||||
@@ -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
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
Reference in New Issue
Block a user