mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
feat(aws/codebuild): add check for CodeBreach webhook filter vulnerability (#9840)
Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
This commit is contained in:
@@ -7,6 +7,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
### Added
|
||||
|
||||
- `compute_instance_suspended_without_persistent_disks` check for GCP provider [(#9747)](https://github.com/prowler-cloud/prowler/pull/9747)
|
||||
- `codebuild_project_webhook_filters_use_anchored_patterns` check for AWS provider to detect CodeBreach vulnerability [(#9840)](https://github.com/prowler-cloud/prowler/pull/9840)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "codebuild_project_webhook_filters_use_anchored_patterns",
|
||||
"CheckTitle": "CodeBuild project webhook filters use anchored regex patterns",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices"
|
||||
],
|
||||
"ServiceName": "codebuild",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsCodeBuildProject",
|
||||
"ResourceGroup": "devops",
|
||||
"Description": "AWS CodeBuild webhook filters using `ACTOR_ACCOUNT_ID`, `HEAD_REF`, or `BASE_REF` have regex patterns anchored with `^` (start) and `$` (end) to enforce exact matching and prevent substring bypass attacks.",
|
||||
"Risk": "Unanchored patterns expose CI/CD pipelines to **CodeBreach** attacks. Attackers can bypass `ACTOR_ACCOUNT_ID` filters by creating GitHub accounts with IDs containing trusted values as substrings. **Confidentiality**: Credentials leaked via build logs. **Integrity**: Malicious code injected into builds. **Availability**: Resource exhaustion through unauthorized builds.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws codebuild update-webhook --project-name <PROJECT_NAME> --filter-groups '[[{\"type\":\"ACTOR_ACCOUNT_ID\",\"pattern\":\"^123456$|^234567$\"}]]'",
|
||||
"NativeIaC": "AWSTemplateFormatVersion: '2010-09-09'\nResources:\n CodeBuildWebhook:\n Type: AWS::CodeBuild::Project\n Properties:\n Triggers:\n Webhook: true\n FilterGroups:\n - - Type: ACTOR_ACCOUNT_ID\n Pattern: '^123456$|^234567$' # Anchored pattern",
|
||||
"Other": "1. Open AWS Console and navigate to CodeBuild. 2. Select the project with webhook filters. 3. Click Edit and go to Primary source webhook events. 4. For each filter using ACTOR_ACCOUNT_ID, HEAD_REF, or BASE_REF, update patterns to include ^ at start and $ at end (e.g., change '123456|234567' to '^123456$|^234567$'). 5. Save changes.",
|
||||
"Terraform": "resource \"aws_codebuild_webhook\" \"example\" {\n project_name = aws_codebuild_project.example.name\n filter_group {\n filter {\n type = \"ACTOR_ACCOUNT_ID\"\n pattern = \"^123456$|^234567$\" # Anchored pattern\n }\n }\n}"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Anchor all webhook filter patterns with `^` (start) and `$` (end) to enforce exact matching. For multiple values use: `^value1$|^value2$`. This prevents attackers from bypassing filters using substring matches.",
|
||||
"Url": "https://hub.prowler.com/check/codebuild_project_webhook_filters_use_anchored_patterns"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"software-supply-chain",
|
||||
"ci-cd"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "This check targets the CodeBreach vulnerability disclosed by Wiz Research. The vulnerability allows attackers to bypass ACTOR_ACCOUNT_ID filters by creating GitHub accounts with IDs that contain trusted IDs as substrings.",
|
||||
"AdditionalURLs": [
|
||||
"https://www.wiz.io/blog/wiz-research-codebreach-vulnerability-aws-codebuild",
|
||||
"https://docs.aws.amazon.com/codebuild/latest/userguide/github-webhook.html"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.codebuild.codebuild_client import codebuild_client
|
||||
|
||||
HIGH_RISK_FILTER_TYPES = {"ACTOR_ACCOUNT_ID", "HEAD_REF", "BASE_REF"}
|
||||
|
||||
|
||||
def is_pattern_anchored(pattern: str) -> bool:
|
||||
"""Check if each alternative in a pipe-separated pattern is anchored with ^ and $."""
|
||||
if not pattern:
|
||||
return True
|
||||
|
||||
for alt in pattern.split("|"):
|
||||
alt = alt.strip()
|
||||
if alt and not (alt.startswith("^") and alt.endswith("$")):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class codebuild_project_webhook_filters_use_anchored_patterns(Check):
|
||||
def execute(self) -> List[Check_Report_AWS]:
|
||||
findings = []
|
||||
|
||||
for project in codebuild_client.projects.values():
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=project)
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"CodeBuild project {project.name} has no webhook configured or all "
|
||||
"webhook filter patterns are properly anchored."
|
||||
)
|
||||
|
||||
if not project.webhook or not project.webhook.filter_groups:
|
||||
findings.append(report)
|
||||
continue
|
||||
|
||||
unanchored_filters = []
|
||||
for filter_group in project.webhook.filter_groups:
|
||||
for webhook_filter in filter_group.filters:
|
||||
if webhook_filter.type in HIGH_RISK_FILTER_TYPES:
|
||||
if not is_pattern_anchored(webhook_filter.pattern):
|
||||
unanchored_filters.append(
|
||||
f"{webhook_filter.type}: '{webhook_filter.pattern}'"
|
||||
)
|
||||
|
||||
if unanchored_filters:
|
||||
report.status = "FAIL"
|
||||
filters_str = ", ".join(unanchored_filters[:3])
|
||||
if len(unanchored_filters) > 3:
|
||||
filters_str += f" and {len(unanchored_filters) - 3} more"
|
||||
report.status_extended = (
|
||||
f"CodeBuild project {project.name} has webhook filters with "
|
||||
f"unanchored patterns that could allow bypass attacks: {filters_str}."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -122,6 +122,29 @@ class Codebuild(AWSService):
|
||||
project.tags = project_info.get("tags", [])
|
||||
project.service_role_arn = project_info.get("serviceRole", "")
|
||||
project.project_visibility = project_info.get("projectVisibility", "")
|
||||
|
||||
# Extract webhook configuration
|
||||
webhook_data = project_info.get("webhook")
|
||||
if webhook_data:
|
||||
filter_groups = []
|
||||
for fg in webhook_data.get("filterGroups", []):
|
||||
filters = []
|
||||
for f in fg:
|
||||
filters.append(
|
||||
WebhookFilter(
|
||||
type=f.get("type", ""),
|
||||
pattern=f.get("pattern", ""),
|
||||
exclude_matched_pattern=f.get(
|
||||
"excludeMatchedPattern", False
|
||||
),
|
||||
)
|
||||
)
|
||||
filter_groups.append(WebhookFilterGroup(filters=filters))
|
||||
|
||||
project.webhook = Webhook(
|
||||
filter_groups=filter_groups,
|
||||
branch_filter=webhook_data.get("branchFilter"),
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
@@ -209,6 +232,27 @@ class CloudWatchLogs(BaseModel):
|
||||
stream_name: str
|
||||
|
||||
|
||||
class WebhookFilter(BaseModel):
|
||||
"""Represents a single filter in a webhook filter group."""
|
||||
|
||||
type: str # ACTOR_ACCOUNT_ID, HEAD_REF, BASE_REF, EVENT, etc.
|
||||
pattern: str
|
||||
exclude_matched_pattern: bool = False
|
||||
|
||||
|
||||
class WebhookFilterGroup(BaseModel):
|
||||
"""Represents a group of filters (AND logic within group)."""
|
||||
|
||||
filters: List[WebhookFilter] = []
|
||||
|
||||
|
||||
class Webhook(BaseModel):
|
||||
"""Represents the webhook configuration for a CodeBuild project."""
|
||||
|
||||
filter_groups: List[WebhookFilterGroup] = []
|
||||
branch_filter: Optional[str] = None
|
||||
|
||||
|
||||
class Project(BaseModel):
|
||||
name: str
|
||||
arn: str
|
||||
@@ -224,6 +268,7 @@ class Project(BaseModel):
|
||||
cloudwatch_logs: Optional[CloudWatchLogs]
|
||||
tags: Optional[list]
|
||||
project_visibility: Optional[str] = None
|
||||
webhook: Optional[Webhook] = None
|
||||
|
||||
|
||||
class ExportConfig(BaseModel):
|
||||
|
||||
@@ -0,0 +1,667 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.aws.services.codebuild.codebuild_service import (
|
||||
Project,
|
||||
Webhook,
|
||||
WebhookFilter,
|
||||
WebhookFilterGroup,
|
||||
)
|
||||
|
||||
AWS_REGION = "eu-west-1"
|
||||
AWS_ACCOUNT_NUMBER = "123456789012"
|
||||
|
||||
|
||||
class Test_codebuild_project_webhook_filters_use_anchored_patterns:
|
||||
def test_no_projects(self):
|
||||
codebuild_client = mock.MagicMock
|
||||
codebuild_client.projects = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
def test_project_without_webhook(self):
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=None,
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
"no webhook configured or all webhook filter patterns are properly anchored"
|
||||
in result[0].status_extended
|
||||
)
|
||||
assert result[0].resource_id == project_name
|
||||
assert result[0].resource_arn == project_arn
|
||||
assert result[0].region == AWS_REGION
|
||||
|
||||
def test_project_webhook_empty_filter_groups(self):
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(filter_groups=[]),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
"no webhook configured or all webhook filter patterns are properly anchored"
|
||||
in result[0].status_extended
|
||||
)
|
||||
|
||||
def test_project_webhook_with_anchored_patterns(self):
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="^123456789$|^987654321$",
|
||||
),
|
||||
WebhookFilter(
|
||||
type="HEAD_REF",
|
||||
pattern="^refs/heads/main$",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
"no webhook configured or all webhook filter patterns are properly anchored"
|
||||
in result[0].status_extended
|
||||
)
|
||||
assert result[0].resource_id == project_name
|
||||
assert result[0].resource_arn == project_arn
|
||||
assert result[0].region == AWS_REGION
|
||||
|
||||
def test_project_webhook_with_unanchored_patterns(self):
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="123456|234567",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "unanchored patterns" in result[0].status_extended
|
||||
assert "ACTOR_ACCOUNT_ID" in result[0].status_extended
|
||||
assert result[0].resource_id == project_name
|
||||
assert result[0].resource_arn == project_arn
|
||||
assert result[0].region == AWS_REGION
|
||||
|
||||
def test_project_webhook_with_mixed_anchored_unanchored(self):
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="^123456$|234567",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "unanchored patterns" in result[0].status_extended
|
||||
|
||||
def test_project_multiple_filter_groups_one_bad(self):
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="^123456789$",
|
||||
),
|
||||
]
|
||||
),
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="BASE_REF",
|
||||
pattern="refs/heads/main",
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "BASE_REF" in result[0].status_extended
|
||||
assert "unanchored patterns" in result[0].status_extended
|
||||
|
||||
def test_project_non_high_risk_filter_unanchored(self):
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="EVENT",
|
||||
pattern="PUSH|PULL_REQUEST_MERGED",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
"no webhook configured or all webhook filter patterns are properly anchored"
|
||||
in result[0].status_extended
|
||||
)
|
||||
|
||||
def test_project_multiple_unanchored_filters_truncated(self):
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="123456",
|
||||
),
|
||||
WebhookFilter(
|
||||
type="HEAD_REF",
|
||||
pattern="refs/heads/main",
|
||||
),
|
||||
WebhookFilter(
|
||||
type="BASE_REF",
|
||||
pattern="refs/heads/develop",
|
||||
),
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="987654",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "and 1 more" in result[0].status_extended
|
||||
|
||||
def test_project_webhook_with_empty_pattern(self):
|
||||
"""Empty patterns should PASS as they don't pose a bypass risk."""
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
|
||||
def test_project_webhook_with_start_anchor_only(self):
|
||||
"""Pattern with only start anchor (^) should FAIL."""
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="^123456789",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "unanchored patterns" in result[0].status_extended
|
||||
|
||||
def test_project_webhook_with_end_anchor_only(self):
|
||||
"""Pattern with only end anchor ($) should FAIL."""
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="123456789$",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "unanchored patterns" in result[0].status_extended
|
||||
|
||||
def test_project_webhook_codebreach_research_vulnerable_pattern(self):
|
||||
"""Test with the exact vulnerable pattern from Wiz CodeBreach research - should FAIL."""
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="16024985|755743|48153483|191175973|47447266|213081198",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "unanchored patterns" in result[0].status_extended
|
||||
assert "ACTOR_ACCOUNT_ID" in result[0].status_extended
|
||||
|
||||
def test_project_webhook_codebreach_research_fixed_pattern(self):
|
||||
"""Test with the properly anchored version of the research pattern - should PASS."""
|
||||
codebuild_client = mock.MagicMock
|
||||
project_name = "test-project"
|
||||
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
|
||||
codebuild_client.projects = {
|
||||
project_arn: Project(
|
||||
name=project_name,
|
||||
arn=project_arn,
|
||||
region=AWS_REGION,
|
||||
webhook=Webhook(
|
||||
filter_groups=[
|
||||
WebhookFilterGroup(
|
||||
filters=[
|
||||
WebhookFilter(
|
||||
type="ACTOR_ACCOUNT_ID",
|
||||
pattern="^16024985$|^755743$|^48153483$|^191175973$|^47447266$|^213081198$",
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
tags=[],
|
||||
)
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
|
||||
codebuild_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_client",
|
||||
codebuild_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_webhook_filters_use_anchored_patterns.codebuild_project_webhook_filters_use_anchored_patterns import (
|
||||
codebuild_project_webhook_filters_use_anchored_patterns,
|
||||
)
|
||||
|
||||
check = codebuild_project_webhook_filters_use_anchored_patterns()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
"no webhook configured or all webhook filter patterns are properly anchored"
|
||||
in result[0].status_extended
|
||||
)
|
||||
@@ -11,6 +11,9 @@ from prowler.providers.aws.services.codebuild.codebuild_service import (
|
||||
ExportConfig,
|
||||
Project,
|
||||
ReportGroup,
|
||||
Webhook,
|
||||
WebhookFilter,
|
||||
WebhookFilterGroup,
|
||||
s3Logs,
|
||||
)
|
||||
from tests.providers.aws.utils import (
|
||||
@@ -73,6 +76,23 @@ def mock_make_api_call(self, operation_name, kwarg):
|
||||
},
|
||||
"tags": [{"key": "Name", "value": project_name}],
|
||||
"projectVisibility": project_visibility,
|
||||
"webhook": {
|
||||
"filterGroups": [
|
||||
[
|
||||
{
|
||||
"type": "ACTOR_ACCOUNT_ID",
|
||||
"pattern": "^123456789$",
|
||||
"excludeMatchedPattern": False,
|
||||
},
|
||||
{
|
||||
"type": "EVENT",
|
||||
"pattern": "PUSH",
|
||||
"excludeMatchedPattern": False,
|
||||
},
|
||||
]
|
||||
],
|
||||
"branchFilter": "main",
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -155,7 +175,37 @@ class Test_Codebuild_Service:
|
||||
assert codebuild.projects[project_arn].tags[0]["key"] == "Name"
|
||||
assert codebuild.projects[project_arn].tags[0]["value"] == project_name
|
||||
assert codebuild.projects[project_arn].project_visibility == project_visibility
|
||||
# Asserttions related with report groups
|
||||
# Assertions related with webhooks
|
||||
assert codebuild.projects[project_arn].webhook is not None
|
||||
assert isinstance(codebuild.projects[project_arn].webhook, Webhook)
|
||||
assert codebuild.projects[project_arn].webhook.branch_filter == "main"
|
||||
assert len(codebuild.projects[project_arn].webhook.filter_groups) == 1
|
||||
assert isinstance(
|
||||
codebuild.projects[project_arn].webhook.filter_groups[0], WebhookFilterGroup
|
||||
)
|
||||
assert (
|
||||
len(codebuild.projects[project_arn].webhook.filter_groups[0].filters) == 2
|
||||
)
|
||||
assert isinstance(
|
||||
codebuild.projects[project_arn].webhook.filter_groups[0].filters[0],
|
||||
WebhookFilter,
|
||||
)
|
||||
assert (
|
||||
codebuild.projects[project_arn].webhook.filter_groups[0].filters[0].type
|
||||
== "ACTOR_ACCOUNT_ID"
|
||||
)
|
||||
assert (
|
||||
codebuild.projects[project_arn].webhook.filter_groups[0].filters[0].pattern
|
||||
== "^123456789$"
|
||||
)
|
||||
assert (
|
||||
codebuild.projects[project_arn]
|
||||
.webhook.filter_groups[0]
|
||||
.filters[0]
|
||||
.exclude_matched_pattern
|
||||
is False
|
||||
)
|
||||
# Assertions related with report groups
|
||||
assert len(codebuild.report_groups) == 1
|
||||
assert isinstance(codebuild.report_groups, dict)
|
||||
assert isinstance(codebuild.report_groups[report_group_arn], ReportGroup)
|
||||
|
||||
Reference in New Issue
Block a user