mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-04-06 02:58:15 +00:00
refactor: remove waf and firewall services
Checks and logic were moved to zones service
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
from prowler.providers.cloudflare.services.firewall.firewall_service import Firewall
|
||||
from prowler.providers.common.provider import Provider
|
||||
|
||||
firewall_client = Firewall(Provider.get_global_provider())
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"Provider": "cloudflare",
|
||||
"CheckID": "firewall_has_blocking_rules",
|
||||
"CheckTitle": "Firewall rules use blocking actions to protect against threats",
|
||||
"CheckType": [],
|
||||
"ServiceName": "firewall",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "FirewallRule",
|
||||
"Description": "Verifies that Cloudflare firewall rules use blocking actions (block, challenge, js_challenge, managed_challenge) to actively protect against threats rather than only logging.",
|
||||
"Risk": "Firewall rules configured only for logging provide visibility but no protection. Malicious traffic reaches the origin server, enabling attacks such as credential stuffing, application exploits, and data exfiltration.",
|
||||
"RelatedUrl": "https://developers.cloudflare.com/waf/custom-rules/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": "resource \"cloudflare_ruleset\" \"example\" {\n zone_id = var.zone_id\n name = \"Block malicious requests\"\n kind = \"zone\"\n phase = \"http_request_firewall_custom\"\n rules {\n action = \"block\"\n expression = \"(ip.geoip.country eq \\\"XX\\\")\"\n description = \"Block traffic from country XX\"\n }\n}"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Configure firewall rules with blocking actions to enforce security policies. Use challenge actions for suspicious traffic and block actions for known malicious patterns following the principle of least privilege.",
|
||||
"Url": "https://hub.prowler.com/checks/firewall_has_blocking_rules"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Blocking actions include: block, challenge, js_challenge, managed_challenge."
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
from prowler.lib.check.models import Check, CheckReportCloudflare
|
||||
from prowler.providers.cloudflare.services.firewall.firewall_client import (
|
||||
firewall_client,
|
||||
)
|
||||
|
||||
BLOCKING_ACTIONS = {"block", "challenge", "js_challenge", "managed_challenge"}
|
||||
|
||||
|
||||
class firewall_has_blocking_rules(Check):
|
||||
def execute(self) -> list[CheckReportCloudflare]:
|
||||
findings = []
|
||||
|
||||
for rule in firewall_client.rules:
|
||||
report = CheckReportCloudflare(
|
||||
metadata=self.metadata(),
|
||||
resource=rule,
|
||||
zone=rule.zone,
|
||||
)
|
||||
|
||||
if rule.action in BLOCKING_ACTIONS:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Firewall rule '{rule.name}' uses blocking action '{rule.action}'."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Firewall rule '{rule.name}' uses non-blocking action '{rule.action}'."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"Provider": "cloudflare",
|
||||
"CheckID": "firewall_rate_limiting_configured",
|
||||
"CheckTitle": "Firewall rule is configured as a rate limiting rule",
|
||||
"CheckType": [],
|
||||
"ServiceName": "firewall",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "informational",
|
||||
"ResourceType": "FirewallRule",
|
||||
"Description": "Identifies firewall rules configured as rate limiting rules to protect against volumetric attacks, brute force attempts, and API abuse.",
|
||||
"Risk": "Without rate limiting rules, applications are vulnerable to DDoS attacks, credential brute forcing, and API abuse that can exhaust resources, compromise accounts, or cause service degradation.",
|
||||
"RelatedUrl": "https://developers.cloudflare.com/waf/rate-limiting-rules/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": "resource \"cloudflare_ruleset\" \"rate_limit\" {\n zone_id = var.zone_id\n name = \"Rate limiting\"\n kind = \"zone\"\n phase = \"http_ratelimit\"\n rules {\n action = \"block\"\n ratelimit {\n characteristics = [\"ip.src\"]\n period = 60\n requests_per_period = 100\n mitigation_timeout = 600\n }\n expression = \"true\"\n description = \"Rate limit all requests\"\n }\n}"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Implement rate limiting rules as part of a defense in depth strategy. Configure appropriate thresholds based on expected traffic patterns to protect authentication endpoints, APIs, and resource-intensive operations.",
|
||||
"Url": "https://hub.prowler.com/checks/firewall_rate_limiting_configured"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Rate limiting rules are in the http_ratelimit phase."
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
from prowler.lib.check.models import Check, CheckReportCloudflare
|
||||
from prowler.providers.cloudflare.services.firewall.firewall_client import (
|
||||
firewall_client,
|
||||
)
|
||||
|
||||
|
||||
class firewall_rate_limiting_configured(Check):
|
||||
def execute(self) -> list[CheckReportCloudflare]:
|
||||
findings = []
|
||||
|
||||
for rule in firewall_client.rules:
|
||||
# Only evaluate rate limit phase rules
|
||||
if rule.phase != "http_ratelimit":
|
||||
continue
|
||||
|
||||
report = CheckReportCloudflare(
|
||||
metadata=self.metadata(),
|
||||
resource=rule,
|
||||
zone=rule.zone,
|
||||
)
|
||||
|
||||
if rule.enabled:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Rate limiting rule '{rule.name}' is enabled."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Rate limiting rule '{rule.name}' is disabled."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -1,86 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic.v1 import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.cloudflare.lib.service.service import CloudflareService
|
||||
from prowler.providers.cloudflare.models import CloudflareZone
|
||||
|
||||
|
||||
class CloudflareFirewallRule(BaseModel):
|
||||
"""Represents a firewall rule from custom rulesets."""
|
||||
|
||||
id: str
|
||||
name: str = ""
|
||||
description: Optional[str] = None
|
||||
action: Optional[str] = None
|
||||
enabled: bool = True
|
||||
expression: Optional[str] = None
|
||||
phase: Optional[str] = None
|
||||
zone: CloudflareZone
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class Firewall(CloudflareService):
|
||||
"""Collect Cloudflare firewall rules for each zone using rulesets API."""
|
||||
|
||||
def __init__(self, provider):
|
||||
super().__init__(__class__.__name__, provider)
|
||||
self.rules: list[CloudflareFirewallRule] = []
|
||||
self.__threading_call__(self._list_firewall_rules)
|
||||
|
||||
def _list_firewall_rules(self, zone: CloudflareZone):
|
||||
"""List firewall rules from custom rulesets for a zone."""
|
||||
seen_ruleset_ids: set[str] = set()
|
||||
try:
|
||||
for ruleset in self.client.rulesets.list(zone_id=zone.id):
|
||||
ruleset_id = getattr(ruleset, "id", "")
|
||||
if ruleset_id in seen_ruleset_ids:
|
||||
break
|
||||
seen_ruleset_ids.add(ruleset_id)
|
||||
|
||||
ruleset_phase = getattr(ruleset, "phase", "")
|
||||
if ruleset_phase in [
|
||||
"http_request_firewall_custom",
|
||||
"http_ratelimit",
|
||||
"http_request_firewall_managed",
|
||||
]:
|
||||
try:
|
||||
ruleset_detail = self.client.rulesets.get(
|
||||
ruleset_id=ruleset_id, zone_id=zone.id
|
||||
)
|
||||
rules = getattr(ruleset_detail, "rules", []) or []
|
||||
seen_rule_ids: set[str] = set()
|
||||
for rule in rules:
|
||||
rule_id = getattr(rule, "id", "")
|
||||
if rule_id in seen_rule_ids:
|
||||
break
|
||||
seen_rule_ids.add(rule_id)
|
||||
try:
|
||||
self.rules.append(
|
||||
CloudflareFirewallRule(
|
||||
id=rule_id,
|
||||
name=getattr(rule, "description", "")
|
||||
or rule_id,
|
||||
description=getattr(rule, "description", None),
|
||||
action=getattr(rule, "action", None),
|
||||
enabled=getattr(rule, "enabled", True),
|
||||
expression=getattr(rule, "expression", None),
|
||||
phase=ruleset_phase,
|
||||
zone=zone,
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
from prowler.providers.cloudflare.services.waf.waf_service import WAF
|
||||
from prowler.providers.common.provider import Provider
|
||||
|
||||
waf_client = WAF(Provider.get_global_provider())
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"Provider": "cloudflare",
|
||||
"CheckID": "waf_owasp_enabled",
|
||||
"CheckTitle": "OWASP managed WAF rulesets are enabled for the zone",
|
||||
"CheckType": [],
|
||||
"ServiceName": "waf",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "WAFRuleset",
|
||||
"Description": "Verifies that OWASP managed WAF rulesets are enabled to protect against common web application vulnerabilities including SQL injection, XSS, and other OWASP Top 10 threats.",
|
||||
"Risk": "Without OWASP managed rulesets, web applications are exposed to well-known attack vectors including SQL injection, cross-site scripting (XSS), and remote code execution that could lead to data breaches or system compromise.",
|
||||
"RelatedUrl": "https://developers.cloudflare.com/waf/managed-rules/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable OWASP Core Ruleset managed rules as part of a defense in depth strategy. Regularly review and tune rule sensitivity based on application requirements while maintaining protection against known attack patterns.",
|
||||
"Url": "https://hub.prowler.com/checks/waf_owasp_enabled"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"vulnerabilities"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
from prowler.lib.check.models import Check, CheckReportCloudflare
|
||||
from prowler.providers.cloudflare.services.waf.waf_client import waf_client
|
||||
|
||||
|
||||
class waf_owasp_enabled(Check):
|
||||
def execute(self) -> list[CheckReportCloudflare]:
|
||||
findings = []
|
||||
|
||||
for ruleset in waf_client.rulesets:
|
||||
report = CheckReportCloudflare(
|
||||
metadata=self.metadata(),
|
||||
resource=ruleset,
|
||||
zone=ruleset.zone,
|
||||
)
|
||||
|
||||
# Check if this is an OWASP managed ruleset
|
||||
is_owasp = (
|
||||
"owasp" in (ruleset.name or "").lower()
|
||||
or ruleset.phase == "http_request_firewall_managed"
|
||||
)
|
||||
|
||||
if is_owasp:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"WAF ruleset '{ruleset.name}' is an OWASP managed ruleset."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"WAF ruleset '{ruleset.name}' is not an OWASP managed ruleset."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -1,59 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic.v1 import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.cloudflare.lib.service.service import CloudflareService
|
||||
from prowler.providers.cloudflare.models import CloudflareZone
|
||||
|
||||
|
||||
class CloudflareWAFRuleset(BaseModel):
|
||||
"""Represents a WAF ruleset (managed rules) for a zone."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
kind: Optional[str] = None
|
||||
phase: Optional[str] = None
|
||||
enabled: bool = True
|
||||
zone: CloudflareZone
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class WAF(CloudflareService):
|
||||
"""Collect WAF ruleset information for Cloudflare zones using rulesets API."""
|
||||
|
||||
def __init__(self, provider):
|
||||
super().__init__(__class__.__name__, provider)
|
||||
self.rulesets: list[CloudflareWAFRuleset] = []
|
||||
self.__threading_call__(self._list_waf_rulesets)
|
||||
|
||||
def _list_waf_rulesets(self, zone: CloudflareZone):
|
||||
"""List WAF rulesets for a zone using the new rulesets API."""
|
||||
seen_ids: set[str] = set()
|
||||
try:
|
||||
for ruleset in self.client.rulesets.list(zone_id=zone.id):
|
||||
ruleset_id = getattr(ruleset, "id", "")
|
||||
if ruleset_id in seen_ids:
|
||||
break
|
||||
seen_ids.add(ruleset_id)
|
||||
try:
|
||||
self.rulesets.append(
|
||||
CloudflareWAFRuleset(
|
||||
id=ruleset_id,
|
||||
name=getattr(ruleset, "name", ""),
|
||||
kind=getattr(ruleset, "kind", None),
|
||||
phase=getattr(ruleset, "phase", None),
|
||||
enabled=True,
|
||||
zone=zone,
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "cloudflare",
|
||||
"CheckID": "zones_firewall_blocking_rules_configured",
|
||||
"CheckTitle": "Firewall rules use blocking actions to protect against threats",
|
||||
"CheckType": [],
|
||||
"ServiceName": "zones",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "Zone",
|
||||
"Description": "**Cloudflare zones** are assessed for **firewall blocking rules** by checking if custom rules use block, challenge, js_challenge, or managed_challenge actions to actively protect against threats rather than only logging.",
|
||||
"Risk": "Firewall rules configured only for **logging** provide visibility but no protection.\n- **Confidentiality**: malicious traffic can access and exfiltrate sensitive data\n- **Integrity**: application exploits can modify data without being blocked\n- **Availability**: credential stuffing and abuse attacks reach the origin unimpeded",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://developers.cloudflare.com/waf/custom-rules/"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > WAF > Custom rules\n3. Review existing rules and their actions\n4. Update rules to use blocking actions (Block, Challenge, JS Challenge, Managed Challenge)\n5. Test rules in log mode first, then enable blocking actions",
|
||||
"Terraform": "```hcl\n# Configure firewall rule with blocking action\nresource \"cloudflare_ruleset\" \"blocking_rule\" {\n zone_id = \"<ZONE_ID>\"\n name = \"Block malicious requests\"\n kind = \"zone\"\n phase = \"http_request_firewall_custom\"\n rules {\n action = \"block\" # Actively blocks matching traffic\n expression = \"(ip.geoip.country eq \\\"XX\\\")\"\n description = \"Block traffic from high-risk country\"\n }\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Configure **firewall rules** with blocking actions to enforce security policies.\n- Use challenge actions for suspicious traffic to verify human visitors\n- Use block actions for known malicious patterns and high-risk sources\n- Test rules in log mode before enabling blocking to avoid false positives\n- Follow the principle of least privilege in rule configuration",
|
||||
"Url": "https://hub.prowler.com/checks/cloudflare/zones_firewall_blocking_rules_configured"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"internet-exposed"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Blocking actions include: block, challenge, js_challenge, managed_challenge. Log-only rules provide visibility but do not prevent attacks."
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
from prowler.lib.check.models import Check, CheckReportCloudflare
|
||||
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
|
||||
|
||||
BLOCKING_ACTIONS = {"block", "challenge", "js_challenge", "managed_challenge"}
|
||||
|
||||
|
||||
class zones_firewall_blocking_rules_configured(Check):
|
||||
def execute(self) -> list[CheckReportCloudflare]:
|
||||
findings = []
|
||||
|
||||
for zone in zones_client.zones.values():
|
||||
report = CheckReportCloudflare(
|
||||
metadata=self.metadata(),
|
||||
resource=zone,
|
||||
)
|
||||
|
||||
# Find blocking rules for this zone
|
||||
blocking_rules = [
|
||||
rule for rule in zone.firewall_rules if rule.action in BLOCKING_ACTIONS
|
||||
]
|
||||
|
||||
if blocking_rules:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Zone {zone.name} has firewall rules with blocking actions "
|
||||
f"({len(blocking_rules)} rule(s))."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Zone {zone.name} has no firewall rules with blocking actions."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -1,7 +1,4 @@
|
||||
from prowler.lib.check.models import Check, CheckReportCloudflare
|
||||
from prowler.providers.cloudflare.services.firewall.firewall_client import (
|
||||
firewall_client,
|
||||
)
|
||||
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
|
||||
|
||||
|
||||
@@ -9,7 +6,7 @@ class zones_rate_limiting_enabled(Check):
|
||||
def execute(self) -> list[CheckReportCloudflare]:
|
||||
findings = []
|
||||
|
||||
for zone in zones_client.zones:
|
||||
for zone in zones_client.zones.values():
|
||||
report = CheckReportCloudflare(
|
||||
metadata=self.metadata(),
|
||||
resource=zone,
|
||||
@@ -18,10 +15,8 @@ class zones_rate_limiting_enabled(Check):
|
||||
# Find rate limiting rules for this zone
|
||||
rate_limit_rules = [
|
||||
rule
|
||||
for rule in firewall_client.rules
|
||||
if rule.zone.id == zone.id
|
||||
and rule.phase == "http_ratelimit"
|
||||
and rule.enabled
|
||||
for rule in zone.firewall_rules
|
||||
if rule.phase == "http_ratelimit" and rule.enabled
|
||||
]
|
||||
|
||||
if rate_limit_rules:
|
||||
|
||||
@@ -7,6 +7,34 @@ from prowler.providers.cloudflare.lib.service.service import CloudflareService
|
||||
from prowler.providers.cloudflare.models import CloudflareAccount
|
||||
|
||||
|
||||
class CloudflareFirewallRule(BaseModel):
|
||||
"""Represents a firewall rule from custom rulesets."""
|
||||
|
||||
id: str
|
||||
name: str = ""
|
||||
description: Optional[str] = None
|
||||
action: Optional[str] = None
|
||||
enabled: bool = True
|
||||
expression: Optional[str] = None
|
||||
phase: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class CloudflareWAFRuleset(BaseModel):
|
||||
"""Represents a WAF ruleset (managed rules) for a zone."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
kind: Optional[str] = None
|
||||
phase: Optional[str] = None
|
||||
enabled: bool = True
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class Zones(CloudflareService):
|
||||
"""Retrieve Cloudflare zones with security-relevant settings."""
|
||||
|
||||
@@ -16,6 +44,8 @@ class Zones(CloudflareService):
|
||||
self._list_zones()
|
||||
self._get_zones_settings()
|
||||
self._get_zones_dnssec()
|
||||
self._get_zones_firewall_rules()
|
||||
self._get_zones_waf_rulesets()
|
||||
|
||||
def _list_zones(self) -> None:
|
||||
"""List all Cloudflare zones with their basic information."""
|
||||
@@ -107,6 +137,109 @@ class Zones(CloudflareService):
|
||||
f"{zone.id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _get_zones_firewall_rules(self) -> None:
|
||||
"""Get firewall rules for all zones."""
|
||||
logger.info("Zones - Getting firewall rules...")
|
||||
for zone in self.zones.values():
|
||||
try:
|
||||
self._get_zone_firewall_rules(zone)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{zone.id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _get_zone_firewall_rules(self, zone: "CloudflareZone") -> None:
|
||||
"""List firewall rules from custom rulesets for a zone."""
|
||||
seen_ruleset_ids: set[str] = set()
|
||||
try:
|
||||
for ruleset in self.client.rulesets.list(zone_id=zone.id):
|
||||
ruleset_id = getattr(ruleset, "id", "")
|
||||
if ruleset_id in seen_ruleset_ids:
|
||||
break
|
||||
seen_ruleset_ids.add(ruleset_id)
|
||||
|
||||
ruleset_phase = getattr(ruleset, "phase", "")
|
||||
if ruleset_phase in [
|
||||
"http_request_firewall_custom",
|
||||
"http_ratelimit",
|
||||
"http_request_firewall_managed",
|
||||
]:
|
||||
try:
|
||||
ruleset_detail = self.client.rulesets.get(
|
||||
ruleset_id=ruleset_id, zone_id=zone.id
|
||||
)
|
||||
rules = getattr(ruleset_detail, "rules", []) or []
|
||||
seen_rule_ids: set[str] = set()
|
||||
for rule in rules:
|
||||
rule_id = getattr(rule, "id", "")
|
||||
if rule_id in seen_rule_ids:
|
||||
break
|
||||
seen_rule_ids.add(rule_id)
|
||||
try:
|
||||
zone.firewall_rules.append(
|
||||
CloudflareFirewallRule(
|
||||
id=rule_id,
|
||||
name=getattr(rule, "description", "")
|
||||
or rule_id,
|
||||
description=getattr(rule, "description", None),
|
||||
action=getattr(rule, "action", None),
|
||||
enabled=getattr(rule, "enabled", True),
|
||||
expression=getattr(rule, "expression", None),
|
||||
phase=ruleset_phase,
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _get_zones_waf_rulesets(self) -> None:
|
||||
"""Get WAF rulesets for all zones."""
|
||||
logger.info("Zones - Getting WAF rulesets...")
|
||||
for zone in self.zones.values():
|
||||
try:
|
||||
self._get_zone_waf_rulesets(zone)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{zone.id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _get_zone_waf_rulesets(self, zone: "CloudflareZone") -> None:
|
||||
"""List WAF rulesets for a zone using the rulesets API."""
|
||||
seen_ids: set[str] = set()
|
||||
try:
|
||||
for ruleset in self.client.rulesets.list(zone_id=zone.id):
|
||||
ruleset_id = getattr(ruleset, "id", "")
|
||||
if ruleset_id in seen_ids:
|
||||
break
|
||||
seen_ids.add(ruleset_id)
|
||||
try:
|
||||
zone.waf_rulesets.append(
|
||||
CloudflareWAFRuleset(
|
||||
id=ruleset_id,
|
||||
name=getattr(ruleset, "name", ""),
|
||||
kind=getattr(ruleset, "kind", None),
|
||||
phase=getattr(ruleset, "phase", None),
|
||||
enabled=True,
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _get_zone_setting(self, zone_id: str, setting_id: str):
|
||||
"""Get a single zone setting by ID."""
|
||||
try:
|
||||
@@ -241,3 +374,5 @@ class CloudflareZone(BaseModel):
|
||||
plan: Optional[str] = None
|
||||
settings: CloudflareZoneSettings = Field(default_factory=CloudflareZoneSettings)
|
||||
dnssec_status: Optional[str] = None
|
||||
firewall_rules: list[CloudflareFirewallRule] = Field(default_factory=list)
|
||||
waf_rulesets: list[CloudflareWAFRuleset] = Field(default_factory=list)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "cloudflare",
|
||||
"CheckID": "zones_waf_owasp_ruleset_enabled",
|
||||
"CheckTitle": "OWASP managed WAF rulesets are enabled for the zone",
|
||||
"CheckType": [],
|
||||
"ServiceName": "zones",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "Zone",
|
||||
"Description": "**Cloudflare zones** are assessed for **OWASP managed rulesets** by checking if they are enabled to protect against common web application vulnerabilities including **SQL injection**, **XSS**, and other **OWASP Top 10** threats.",
|
||||
"Risk": "Without **OWASP managed rulesets**, web applications are exposed to well-known attack vectors.\n- **Confidentiality**: SQL injection attacks can exfiltrate sensitive database contents\n- **Integrity**: XSS attacks can modify page content and steal session tokens\n- **Availability**: remote code execution can compromise server availability",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://developers.cloudflare.com/waf/managed-rules/"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > WAF > Managed rules\n3. Enable the Cloudflare OWASP Core Ruleset\n4. Review and configure rule sensitivity based on your application\n5. Monitor WAF analytics to tune rules and reduce false positives",
|
||||
"Terraform": "```hcl\n# Enable OWASP managed WAF rulesets\nresource \"cloudflare_ruleset\" \"waf_owasp\" {\n zone_id = \"<ZONE_ID>\"\n name = \"OWASP Managed Rules\"\n kind = \"zone\"\n phase = \"http_request_firewall_managed\"\n rules {\n action = \"execute\"\n action_parameters {\n id = \"4814384a9e5d4991b9815dcfc25d2f1f\" # Cloudflare OWASP Core Ruleset\n }\n expression = \"true\"\n description = \"Execute Cloudflare OWASP Core Ruleset\"\n }\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable **OWASP Core Ruleset** managed rules as part of a defense in depth strategy.\n- Protects against OWASP Top 10 vulnerabilities including SQLi and XSS\n- Regularly review and tune rule sensitivity based on application requirements\n- Monitor WAF analytics to identify and address false positives\n- Combine with custom rules for application-specific protection",
|
||||
"Url": "https://hub.prowler.com/checks/cloudflare/zones_waf_owasp_ruleset_enabled"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"vulnerabilities"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "OWASP managed rulesets are available on Pro, Business, and Enterprise plans. The Cloudflare OWASP Core Ruleset provides protection against common web application vulnerabilities."
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
from prowler.lib.check.models import Check, CheckReportCloudflare
|
||||
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
|
||||
|
||||
|
||||
class zones_waf_owasp_ruleset_enabled(Check):
|
||||
def execute(self) -> list[CheckReportCloudflare]:
|
||||
findings = []
|
||||
|
||||
for zone in zones_client.zones.values():
|
||||
report = CheckReportCloudflare(
|
||||
metadata=self.metadata(),
|
||||
resource=zone,
|
||||
)
|
||||
|
||||
# Find OWASP managed rulesets for this zone
|
||||
owasp_rulesets = [
|
||||
ruleset
|
||||
for ruleset in zone.waf_rulesets
|
||||
if (
|
||||
"owasp" in (ruleset.name or "").lower()
|
||||
or ruleset.phase == "http_request_firewall_managed"
|
||||
)
|
||||
]
|
||||
|
||||
if owasp_rulesets:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Zone {zone.name} has OWASP managed WAF ruleset enabled "
|
||||
f"({len(owasp_rulesets)} ruleset(s))."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Zone {zone.name} does not have OWASP managed WAF ruleset enabled."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
Reference in New Issue
Block a user