mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
fix: zones_security_level check
This commit is contained in:
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"Provider": "cloudflare",
|
|
||||||
"CheckID": "zones_security_level",
|
|
||||||
"CheckTitle": "Security level is set to medium or higher for adequate threat protection",
|
|
||||||
"CheckType": [],
|
|
||||||
"ServiceName": "zones",
|
|
||||||
"SubServiceName": "",
|
|
||||||
"ResourceIdTemplate": "",
|
|
||||||
"Severity": "medium",
|
|
||||||
"ResourceType": "Zone",
|
|
||||||
"Description": "**Cloudflare zones** are assessed for **Security Level** configuration by checking if it is set to `medium`, `high`, or `under_attack` to ensure adequate **threat protection** based on visitor reputation scores.",
|
|
||||||
"Risk": "A **low security level** allows more potentially malicious traffic to reach your origin.\n- **Confidentiality**: increased exposure to credential stuffing and data exfiltration attempts\n- **Integrity**: higher risk of successful web application attacks\n- **Availability**: greater attack surface for DDoS and resource exhaustion attacks",
|
|
||||||
"RelatedUrl": "",
|
|
||||||
"AdditionalURLs": [
|
|
||||||
"https://developers.cloudflare.com/waf/tools/security-level/"
|
|
||||||
],
|
|
||||||
"Remediation": {
|
|
||||||
"Code": {
|
|
||||||
"CLI": "",
|
|
||||||
"NativeIaC": "",
|
|
||||||
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > Settings\n3. Scroll to Security Level\n4. Select Medium or High from the dropdown based on your threat tolerance\n5. Use Under Attack mode only during active attacks (temporarily)",
|
|
||||||
"Terraform": "```hcl\n# Set security level to medium or higher\nresource \"cloudflare_zone_settings_override\" \"security_level\" {\n zone_id = \"<ZONE_ID>\"\n settings {\n security_level = \"medium\" # Options: off, essentially_off, low, medium, high, under_attack\n }\n}\n```"
|
|
||||||
},
|
|
||||||
"Recommendation": {
|
|
||||||
"Text": "Set **Security Level** to `Medium` or `High` based on your threat tolerance.\n- **Low**: minimal protection, may allow suspicious traffic\n- **Medium**: balanced protection for most sites (recommended baseline)\n- **High**: aggressive filtering, may challenge more visitors\n- **Under Attack**: use only during active attacks, impacts user experience",
|
|
||||||
"Url": "https://hub.prowler.com/checks/cloudflare/zones_security_level"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Categories": [
|
|
||||||
"internet-exposed"
|
|
||||||
],
|
|
||||||
"DependsOn": [],
|
|
||||||
"RelatedTo": [],
|
|
||||||
"Notes": "Avoid keeping Under Attack mode enabled permanently as it presents challenges to all visitors. Use Page Rules for path-specific security levels if needed."
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"Provider": "cloudflare",
|
||||||
|
"CheckID": "zones_security_under_attack_disabled",
|
||||||
|
"CheckTitle": "Under Attack Mode is disabled during normal operations",
|
||||||
|
"CheckType": [],
|
||||||
|
"ServiceName": "zones",
|
||||||
|
"SubServiceName": "",
|
||||||
|
"ResourceIdTemplate": "",
|
||||||
|
"Severity": "low",
|
||||||
|
"ResourceType": "Zone",
|
||||||
|
"Description": "**Cloudflare zones** are assessed for **Under Attack Mode** configuration by checking if it is disabled during normal operations, as this mode performs additional security checks including an **interstitial JavaScript challenge page** that significantly impacts user experience.",
|
||||||
|
"Risk": "Keeping **Under Attack Mode** permanently enabled causes operational issues.\n- **Availability**: all visitors face a 5-second interstitial challenge page before accessing the site\n- **Accessibility**: visitors without JavaScript support cannot access the site at all\n- **User Experience**: legitimate users experience unnecessary delays and third-party analytics show degraded performance",
|
||||||
|
"RelatedUrl": "",
|
||||||
|
"AdditionalURLs": [
|
||||||
|
"https://developers.cloudflare.com/fundamentals/reference/under-attack-mode/",
|
||||||
|
"https://developers.cloudflare.com/waf/tools/security-level/"
|
||||||
|
],
|
||||||
|
"Remediation": {
|
||||||
|
"Code": {
|
||||||
|
"CLI": "",
|
||||||
|
"NativeIaC": "",
|
||||||
|
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > Settings\n3. Under 'Under Attack Mode', toggle it OFF\n4. Consider setting Security Level to 'High' for continued protection without the interstitial",
|
||||||
|
"Terraform": "```hcl\n# Set security level to high instead of under_attack\nresource \"cloudflare_zone_settings_override\" \"security_settings\" {\n zone_id = \"<ZONE_ID>\"\n settings {\n security_level = \"high\" # Use high instead of under_attack for normal operations\n }\n}\n```"
|
||||||
|
},
|
||||||
|
"Recommendation": {
|
||||||
|
"Text": "Disable **Under Attack Mode** when not actively under a DDoS attack.\n- Use it only as a **last resort** during active layer 7 DDoS attacks\n- For ongoing protection, use **Security Level** settings (Low, Medium, High)\n- Configure specific **WAF rules** for targeted protection without impacting all users",
|
||||||
|
"Url": "https://hub.prowler.com/checks/cloudflare/zones_security_under_attack_disabled"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Categories": [
|
||||||
|
"internet-exposed"
|
||||||
|
],
|
||||||
|
"DependsOn": [],
|
||||||
|
"RelatedTo": [],
|
||||||
|
"Notes": "Under Attack Mode is designed as a temporary measure during active attacks. The interstitial challenge page validates visitors using JavaScript, blocking automated attacks while allowing legitimate users through after a brief delay."
|
||||||
|
}
|
||||||
@@ -2,10 +2,9 @@ from prowler.lib.check.models import Check, CheckReportCloudflare
|
|||||||
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
|
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
|
||||||
|
|
||||||
|
|
||||||
class zones_security_level(Check):
|
class zones_security_under_attack_disabled(Check):
|
||||||
def execute(self) -> list[CheckReportCloudflare]:
|
def execute(self) -> list[CheckReportCloudflare]:
|
||||||
findings = []
|
findings = []
|
||||||
acceptable_levels = ["medium", "high", "under_attack"]
|
|
||||||
|
|
||||||
for zone in zones_client.zones.values():
|
for zone in zones_client.zones.values():
|
||||||
report = CheckReportCloudflare(
|
report = CheckReportCloudflare(
|
||||||
@@ -13,15 +12,16 @@ class zones_security_level(Check):
|
|||||||
resource=zone,
|
resource=zone,
|
||||||
)
|
)
|
||||||
security_level = (zone.settings.security_level or "").lower()
|
security_level = (zone.settings.security_level or "").lower()
|
||||||
if security_level in acceptable_levels:
|
|
||||||
report.status = "PASS"
|
if security_level == "under_attack":
|
||||||
report.status_extended = (
|
|
||||||
f"Security level is set to '{security_level}' for zone {zone.name}."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
report.status = "FAIL"
|
report.status = "FAIL"
|
||||||
report.status_extended = (
|
report.status_extended = (
|
||||||
f"Security level is set to '{security_level}' for zone {zone.name}."
|
f"Zone {zone.name} has Under Attack Mode enabled."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
report.status = "PASS"
|
||||||
|
report.status_extended = (
|
||||||
|
f"Zone {zone.name} does not have Under Attack Mode enabled."
|
||||||
)
|
)
|
||||||
findings.append(report)
|
findings.append(report)
|
||||||
return findings
|
return findings
|
||||||
@@ -11,7 +11,7 @@ from tests.providers.cloudflare.cloudflare_fixtures import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Test_zones_security_level:
|
class Test_zones_security_under_attack_disabled:
|
||||||
def test_no_zones(self):
|
def test_no_zones(self):
|
||||||
zones_client = mock.MagicMock
|
zones_client = mock.MagicMock
|
||||||
zones_client.zones = {}
|
zones_client.zones = {}
|
||||||
@@ -22,18 +22,57 @@ class Test_zones_security_level:
|
|||||||
return_value=set_mocked_cloudflare_provider(),
|
return_value=set_mocked_cloudflare_provider(),
|
||||||
),
|
),
|
||||||
mock.patch(
|
mock.patch(
|
||||||
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
|
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
|
||||||
new=zones_client,
|
new=zones_client,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
|
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
|
||||||
zones_security_level,
|
zones_security_under_attack_disabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
check = zones_security_level()
|
check = zones_security_under_attack_disabled()
|
||||||
result = check.execute()
|
result = check.execute()
|
||||||
assert len(result) == 0
|
assert len(result) == 0
|
||||||
|
|
||||||
|
def test_zone_under_attack_mode_enabled(self):
|
||||||
|
zones_client = mock.MagicMock
|
||||||
|
zones_client.zones = {
|
||||||
|
ZONE_ID: CloudflareZone(
|
||||||
|
id=ZONE_ID,
|
||||||
|
name=ZONE_NAME,
|
||||||
|
status="active",
|
||||||
|
paused=False,
|
||||||
|
settings=CloudflareZoneSettings(
|
||||||
|
security_level="under_attack",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with (
|
||||||
|
mock.patch(
|
||||||
|
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||||
|
return_value=set_mocked_cloudflare_provider(),
|
||||||
|
),
|
||||||
|
mock.patch(
|
||||||
|
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
|
||||||
|
new=zones_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
|
||||||
|
zones_security_under_attack_disabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = zones_security_under_attack_disabled()
|
||||||
|
result = check.execute()
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].resource_id == ZONE_ID
|
||||||
|
assert result[0].resource_name == ZONE_NAME
|
||||||
|
assert result[0].status == "FAIL"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Zone {ZONE_NAME} has Under Attack Mode enabled."
|
||||||
|
)
|
||||||
|
|
||||||
def test_zone_security_level_high(self):
|
def test_zone_security_level_high(self):
|
||||||
zones_client = mock.MagicMock
|
zones_client = mock.MagicMock
|
||||||
zones_client.zones = {
|
zones_client.zones = {
|
||||||
@@ -54,21 +93,22 @@ class Test_zones_security_level:
|
|||||||
return_value=set_mocked_cloudflare_provider(),
|
return_value=set_mocked_cloudflare_provider(),
|
||||||
),
|
),
|
||||||
mock.patch(
|
mock.patch(
|
||||||
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
|
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
|
||||||
new=zones_client,
|
new=zones_client,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
|
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
|
||||||
zones_security_level,
|
zones_security_under_attack_disabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
check = zones_security_level()
|
check = zones_security_under_attack_disabled()
|
||||||
result = check.execute()
|
result = check.execute()
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result[0].resource_id == ZONE_ID
|
|
||||||
assert result[0].resource_name == ZONE_NAME
|
|
||||||
assert result[0].status == "PASS"
|
assert result[0].status == "PASS"
|
||||||
assert "high" in result[0].status_extended
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Zone {ZONE_NAME} does not have Under Attack Mode enabled."
|
||||||
|
)
|
||||||
|
|
||||||
def test_zone_security_level_medium(self):
|
def test_zone_security_level_medium(self):
|
||||||
zones_client = mock.MagicMock
|
zones_client = mock.MagicMock
|
||||||
@@ -90,53 +130,18 @@ class Test_zones_security_level:
|
|||||||
return_value=set_mocked_cloudflare_provider(),
|
return_value=set_mocked_cloudflare_provider(),
|
||||||
),
|
),
|
||||||
mock.patch(
|
mock.patch(
|
||||||
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
|
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
|
||||||
new=zones_client,
|
new=zones_client,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
|
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
|
||||||
zones_security_level,
|
zones_security_under_attack_disabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
check = zones_security_level()
|
check = zones_security_under_attack_disabled()
|
||||||
result = check.execute()
|
result = check.execute()
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result[0].status == "PASS"
|
assert result[0].status == "PASS"
|
||||||
assert "medium" in result[0].status_extended
|
|
||||||
|
|
||||||
def test_zone_security_level_under_attack(self):
|
|
||||||
zones_client = mock.MagicMock
|
|
||||||
zones_client.zones = {
|
|
||||||
ZONE_ID: CloudflareZone(
|
|
||||||
id=ZONE_ID,
|
|
||||||
name=ZONE_NAME,
|
|
||||||
status="active",
|
|
||||||
paused=False,
|
|
||||||
settings=CloudflareZoneSettings(
|
|
||||||
security_level="under_attack",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
with (
|
|
||||||
mock.patch(
|
|
||||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
|
||||||
return_value=set_mocked_cloudflare_provider(),
|
|
||||||
),
|
|
||||||
mock.patch(
|
|
||||||
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
|
|
||||||
new=zones_client,
|
|
||||||
),
|
|
||||||
):
|
|
||||||
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
|
|
||||||
zones_security_level,
|
|
||||||
)
|
|
||||||
|
|
||||||
check = zones_security_level()
|
|
||||||
result = check.execute()
|
|
||||||
assert len(result) == 1
|
|
||||||
assert result[0].status == "PASS"
|
|
||||||
assert "under_attack" in result[0].status_extended
|
|
||||||
|
|
||||||
def test_zone_security_level_low(self):
|
def test_zone_security_level_low(self):
|
||||||
zones_client = mock.MagicMock
|
zones_client = mock.MagicMock
|
||||||
@@ -158,53 +163,18 @@ class Test_zones_security_level:
|
|||||||
return_value=set_mocked_cloudflare_provider(),
|
return_value=set_mocked_cloudflare_provider(),
|
||||||
),
|
),
|
||||||
mock.patch(
|
mock.patch(
|
||||||
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
|
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
|
||||||
new=zones_client,
|
new=zones_client,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
|
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
|
||||||
zones_security_level,
|
zones_security_under_attack_disabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
check = zones_security_level()
|
check = zones_security_under_attack_disabled()
|
||||||
result = check.execute()
|
result = check.execute()
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result[0].status == "FAIL"
|
assert result[0].status == "PASS"
|
||||||
assert "low" in result[0].status_extended
|
|
||||||
|
|
||||||
def test_zone_security_level_essentially_off(self):
|
|
||||||
zones_client = mock.MagicMock
|
|
||||||
zones_client.zones = {
|
|
||||||
ZONE_ID: CloudflareZone(
|
|
||||||
id=ZONE_ID,
|
|
||||||
name=ZONE_NAME,
|
|
||||||
status="active",
|
|
||||||
paused=False,
|
|
||||||
settings=CloudflareZoneSettings(
|
|
||||||
security_level="essentially_off",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
with (
|
|
||||||
mock.patch(
|
|
||||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
|
||||||
return_value=set_mocked_cloudflare_provider(),
|
|
||||||
),
|
|
||||||
mock.patch(
|
|
||||||
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
|
|
||||||
new=zones_client,
|
|
||||||
),
|
|
||||||
):
|
|
||||||
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
|
|
||||||
zones_security_level,
|
|
||||||
)
|
|
||||||
|
|
||||||
check = zones_security_level()
|
|
||||||
result = check.execute()
|
|
||||||
assert len(result) == 1
|
|
||||||
assert result[0].status == "FAIL"
|
|
||||||
assert "essentially_off" in result[0].status_extended
|
|
||||||
|
|
||||||
def test_zone_security_level_none(self):
|
def test_zone_security_level_none(self):
|
||||||
zones_client = mock.MagicMock
|
zones_client = mock.MagicMock
|
||||||
@@ -226,15 +196,15 @@ class Test_zones_security_level:
|
|||||||
return_value=set_mocked_cloudflare_provider(),
|
return_value=set_mocked_cloudflare_provider(),
|
||||||
),
|
),
|
||||||
mock.patch(
|
mock.patch(
|
||||||
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
|
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
|
||||||
new=zones_client,
|
new=zones_client,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
|
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
|
||||||
zones_security_level,
|
zones_security_under_attack_disabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
check = zones_security_level()
|
check = zones_security_under_attack_disabled()
|
||||||
result = check.execute()
|
result = check.execute()
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result[0].status == "FAIL"
|
assert result[0].status == "PASS"
|
||||||
Reference in New Issue
Block a user