feat(aws/codebuild): add check for CodeBreach webhook filter vulnerability (#9840)

Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
This commit is contained in:
Andoni Alonso
2026-01-22 15:12:24 +01:00
committed by GitHub
parent 7dfafb9337
commit 6cb0edf3e1
7 changed files with 862 additions and 1 deletions

View File

@@ -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

View File

@@ -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"
]
}

View File

@@ -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

View File

@@ -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):

View File

@@ -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
)

View File

@@ -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)