diff --git a/cloudsplaining/output/policy_finding.py b/cloudsplaining/output/policy_finding.py index 62db6c39..3cf46c12 100644 --- a/cloudsplaining/output/policy_finding.py +++ b/cloudsplaining/output/policy_finding.py @@ -12,6 +12,7 @@ from cloudsplaining.shared.constants import ( ACTIONS_THAT_RETURN_CREDENTIALS, + BYPASSES_NETWORK_CONTROLS_ACTIONS, ISSUE_SEVERITY, READ_ONLY_DATA_EXFILTRATION_ACTIONS, RISK_DEFINITION, @@ -77,6 +78,10 @@ def services_affected(self) -> list[str]: for action in self.data_exfiltration: service = action.partition(":")[0] services_affected.add(service) + # Bypasses network controls; may be used for control-plane/API access paths + for action in self.bypasses_network_controls: + service = action.partition(":")[0] + services_affected.add(service) return sorted(services_affected) @property @@ -108,6 +113,17 @@ def data_exfiltration(self) -> list[str]: if action.lower() not in self.exclusions.exclude_actions ] + @property + def bypasses_network_controls(self) -> list[str]: + """Returns actions that can bypass network layer controls in the policy, if present""" + return [ + action + for action in self.policy_document.allows_specific_actions_without_constraints( + BYPASSES_NETWORK_CONTROLS_ACTIONS + ) + if action.lower() not in self.exclusions.exclude_actions + ] + @property def service_wildcard(self) -> list[str]: """Determine if the policy gives access to all actions within a service - simple grepping""" @@ -157,6 +173,16 @@ def results(self) -> dict[str, Any]: else [] ), }, + "BypassesNetworkControls": { + "severity": ISSUE_SEVERITY["BypassesNetworkControls"], + "description": RISK_DEFINITION["BypassesNetworkControls"], + "findings": ( + self.bypasses_network_controls + if ISSUE_SEVERITY["BypassesNetworkControls"] in [x.lower() for x in self.severity] + or not self.severity + else [] + ), + }, "ResourceExposure": { "severity": ISSUE_SEVERITY["ResourceExposure"], "description": RISK_DEFINITION["ResourceExposure"], diff --git a/cloudsplaining/shared/constants.py b/cloudsplaining/shared/constants.py index 1df55d7b..ed54d20a 100644 --- a/cloudsplaining/shared/constants.py +++ b/cloudsplaining/shared/constants.py @@ -81,6 +81,10 @@ "secretsmanager:GetSecretValue", ] +BYPASSES_NETWORK_CONTROLS_ACTIONS = [ + "redshift:GetClusterCredentials", +] + ISSUE_SEVERITY = { "PrivilegeEscalation": "high", "DataExfiltration": "medium", @@ -92,6 +96,7 @@ "AssumableByCrossAccountPrincipal": "medium", "AssumableByAnyPrincipal": "critical", "AssumableByAnyPrincipalWithConditions": "medium", + "BypassesNetworkControls": "medium", } RISK_DEFINITION = { @@ -105,6 +110,11 @@ "AssumableByCrossAccountPrincipal": "
IAM Roles that can be assumed from other AWS accounts can present a greater risk than roles that can only be assumed within the same AWS account. This is especially true if the trusting account is not owned by your organization.
", "AssumableByAnyPrincipal": "IAM Roles that can be assumed by any principal (i.e. Principal: '*') present a very high risk and should be remediated immediately.
", "AssumableByAnyPrincipalWithConditions": "IAM Roles that can be assumed by any principal (i.e. Principal: '*') but have conditions present can lead to unexpected outcomes. The conditions should be carefully reviewed to ensure they are not overly permissive.
", + "BypassesNetworkControls": ( + "These policies allow IAM actions that can access resources via AWS-managed control planes or service APIs " + "without requiring direct network access to the target (for example, bypassing VPC Security Groups and " + "NACLs). This can make traditional network-layer protections ineffective for these access paths.
" + ), } PRIVILEGE_ESCALATION_METHODS = { diff --git a/test/command/test_scan_policy_file.py b/test/command/test_scan_policy_file.py index db775e05..3988c7ab 100644 --- a/test/command/test_scan_policy_file.py +++ b/test/command/test_scan_policy_file.py @@ -1,9 +1,12 @@ import unittest -import os -import json + from cloudsplaining.command.scan_policy_file import scan_policy -from cloudsplaining.shared.constants import DEFAULT_EXCLUSIONS_CONFIG -from cloudsplaining.shared.exclusions import DEFAULT_EXCLUSIONS, Exclusions + +BYPASSES_NETWORK_CONTROLS_DESCRIPTION = ( + "These policies allow IAM actions that can access resources via AWS-managed control planes or service APIs " + "without requiring direct network access to the target (for example, bypassing VPC Security Groups and NACLs). " + "This can make traditional network-layer protections ineffective for these access paths.
" +) class PolicyFileTestCase(unittest.TestCase): @@ -59,6 +62,11 @@ def test_policy_file(self): "description": 'Policies with Data Exfiltration potential allow certain read-only IAM actions without resource constraints, such as s3:GetObject, ssm:GetParameter*, or secretsmanager:GetSecretValue.
s3:GetObject permissions has a long history of customer data leaks.ssm:GetParameter* and secretsmanager:GetSecretValue are both used to access secrets.rds:CopyDBSnapshot and rds:CreateDBSnapshot can be used to exfiltrate RDS database contents."Service Wildcard" is the unofficial way of referring to IAM policy statements that grant access to ALL actions under a service - like s3:*. Prioritizing the remediation of policies with this characteristic can help to efficiently reduce the total count of issues in the Cloudsplaining report.
', @@ -111,6 +119,11 @@ def test_excluded_actions_scan_policy_file(self): "description": 'Policies with Data Exfiltration potential allow certain read-only IAM actions without resource constraints, such as s3:GetObject, ssm:GetParameter*, or secretsmanager:GetSecretValue.
s3:GetObject permissions has a long history of customer data leaks.ssm:GetParameter* and secretsmanager:GetSecretValue are both used to access secrets.rds:CopyDBSnapshot and rds:CreateDBSnapshot can be used to exfiltrate RDS database contents."Service Wildcard" is the unofficial way of referring to IAM policy statements that grant access to ALL actions under a service - like s3:*. Prioritizing the remediation of policies with this characteristic can help to efficiently reduce the total count of issues in the Cloudsplaining report.
', @@ -161,6 +174,11 @@ def test_excluded_actions_scan_policy_file_v2(self): "description": 'Policies with Data Exfiltration potential allow certain read-only IAM actions without resource constraints, such as s3:GetObject, ssm:GetParameter*, or secretsmanager:GetSecretValue.
s3:GetObject permissions has a long history of customer data leaks.ssm:GetParameter* and secretsmanager:GetSecretValue are both used to access secrets.rds:CopyDBSnapshot and rds:CreateDBSnapshot can be used to exfiltrate RDS database contents.Credentials Exposure actions return credentials as part of the API response , such as ecr:GetAuthorizationToken, iam:UpdateAccessKey, and others. The full list is maintained here: https://gist.github.com/kmcquade/33860a617e651104d243c324ddf7992a
", @@ -215,7 +233,7 @@ def test_checkov_gh_990_condition_restricted_action(self): } exclusions_cfg_custom = {} results = scan_policy(test_policy, exclusions_cfg_custom) - print(json.dumps(results, indent=4)) + # print(json.dumps(results, indent=4)) self.assertListEqual(results.get("InfrastructureModification")["findings"], []) self.assertListEqual(results.get("DataExfiltration")["findings"], []) self.assertListEqual(results.get("ServicesAffected"), []) @@ -232,7 +250,7 @@ def test_checkov_gh_990_condition_restricted_action(self): ], } results = scan_policy(test_policy_without_condition, exclusions_cfg_custom) - print(json.dumps(results, indent=4)) + # print(json.dumps(results, indent=4)) self.assertListEqual(results.get("InfrastructureModification")["findings"], []) self.assertListEqual(results.get("DataExfiltration")["findings"], ["s3:GetObject"]) self.assertListEqual(results.get("ServicesAffected"), ["s3"]) @@ -308,6 +326,11 @@ def test_gh_254_all_risky_actions_scan_policy(self): "description": 'Policies with Data Exfiltration potential allow certain read-only IAM actions without resource constraints, such as s3:GetObject, ssm:GetParameter*, or secretsmanager:GetSecretValue.
s3:GetObject permissions has a long history of customer data leaks.ssm:GetParameter* and secretsmanager:GetSecretValue are both used to access secrets.rds:CopyDBSnapshot and rds:CreateDBSnapshot can be used to exfiltrate RDS database contents.Credentials Exposure actions return credentials as part of the API response , such as ecr:GetAuthorizationToken, iam:UpdateAccessKey, and others. The full list is maintained here: https://gist.github.com/kmcquade/33860a617e651104d243c324ddf7992a
", diff --git a/test/output/test_policy_finding.py b/test/output/test_policy_finding.py index 4199397a..56ba725f 100644 --- a/test/output/test_policy_finding.py +++ b/test/output/test_policy_finding.py @@ -1,9 +1,15 @@ import unittest -import json + from cloudsplaining.output.policy_finding import PolicyFinding from cloudsplaining.scan.policy_document import PolicyDocument from cloudsplaining.shared.exclusions import Exclusions +BYPASSES_NETWORK_CONTROLS_DESCRIPTION = ( + "These policies allow IAM actions that can access resources via AWS-managed control planes or service APIs " + "without requiring direct network access to the target (for example, bypassing VPC Security Groups and NACLs). " + "This can make traditional network-layer protections ineffective for these access paths.
" +) + class TestPolicyFinding(unittest.TestCase): def test_policy_finding_for_data_exfiltration(self): @@ -12,8 +18,6 @@ def test_policy_finding_for_data_exfiltration(self): "Statement": [{"Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*"}], } policy_document = PolicyDocument(test_policy) - # (1) If the user is a member of an excluded group, return True - exclusions_cfg = dict(users=["obama"], groups=["exclude-group"], roles=["MyRole"], policies=["exclude-policy"]) exclusions = Exclusions(exclusions_cfg) policy_finding = PolicyFinding(policy_document, exclusions) @@ -35,6 +39,11 @@ def test_policy_finding_for_data_exfiltration(self): "description": 'Policies with Data Exfiltration potential allow certain read-only IAM actions without resource constraints, such as s3:GetObject, ssm:GetParameter*, or secretsmanager:GetSecretValue.
s3:GetObject permissions has a long history of customer data leaks.ssm:GetParameter* and secretsmanager:GetSecretValue are both used to access secrets.rds:CopyDBSnapshot and rds:CreateDBSnapshot can be used to exfiltrate RDS database contents."Service Wildcard" is the unofficial way of referring to IAM policy statements that grant access to ALL actions under a service - like s3:*. Prioritizing the remediation of policies with this characteristic can help to efficiently reduce the total count of issues in the Cloudsplaining report.
', @@ -79,6 +88,11 @@ def test_policy_finding_for_resource_exposure(self): "description": 'Policies with Data Exfiltration potential allow certain read-only IAM actions without resource constraints, such as s3:GetObject, ssm:GetParameter*, or secretsmanager:GetSecretValue.
s3:GetObject permissions has a long history of customer data leaks.ssm:GetParameter* and secretsmanager:GetSecretValue are both used to access secrets.rds:CopyDBSnapshot and rds:CreateDBSnapshot can be used to exfiltrate RDS database contents."Service Wildcard" is the unofficial way of referring to IAM policy statements that grant access to ALL actions under a service - like s3:*. Prioritizing the remediation of policies with this characteristic can help to efficiently reduce the total count of issues in the Cloudsplaining report.
', @@ -128,6 +142,11 @@ def test_policy_finding_for_privilege_escalation(self): "description": 'Policies with Data Exfiltration potential allow certain read-only IAM actions without resource constraints, such as s3:GetObject, ssm:GetParameter*, or secretsmanager:GetSecretValue.
s3:GetObject permissions has a long history of customer data leaks.ssm:GetParameter* and secretsmanager:GetSecretValue are both used to access secrets.rds:CopyDBSnapshot and rds:CreateDBSnapshot can be used to exfiltrate RDS database contents."Service Wildcard" is the unofficial way of referring to IAM policy statements that grant access to ALL actions under a service - like s3:*. Prioritizing the remediation of policies with this characteristic can help to efficiently reduce the total count of issues in the Cloudsplaining report.
', @@ -186,6 +205,11 @@ def test_finding_actions_excluded(self): "description": 'Policies with Data Exfiltration potential allow certain read-only IAM actions without resource constraints, such as s3:GetObject, ssm:GetParameter*, or secretsmanager:GetSecretValue.
s3:GetObject permissions has a long history of customer data leaks.ssm:GetParameter* and secretsmanager:GetSecretValue are both used to access secrets.rds:CopyDBSnapshot and rds:CreateDBSnapshot can be used to exfiltrate RDS database contents."Service Wildcard" is the unofficial way of referring to IAM policy statements that grant access to ALL actions under a service - like s3:*. Prioritizing the remediation of policies with this characteristic can help to efficiently reduce the total count of issues in the Cloudsplaining report.
', @@ -224,6 +248,11 @@ def test_finding_actions_excluded(self): "description": 'Policies with Data Exfiltration potential allow certain read-only IAM actions without resource constraints, such as s3:GetObject, ssm:GetParameter*, or secretsmanager:GetSecretValue.
s3:GetObject permissions has a long history of customer data leaks.ssm:GetParameter* and secretsmanager:GetSecretValue are both used to access secrets.rds:CopyDBSnapshot and rds:CreateDBSnapshot can be used to exfiltrate RDS database contents."Service Wildcard" is the unofficial way of referring to IAM policy statements that grant access to ALL actions under a service - like s3:*. Prioritizing the remediation of policies with this characteristic can help to efficiently reduce the total count of issues in the Cloudsplaining report.
', @@ -242,3 +271,18 @@ def test_finding_actions_excluded(self): } # print(json.dumps(results, indent=4)) self.assertDictEqual(results, expected_results) + + def test_policy_finding_for_bypasses_network_controls(self): + test_policy = { + "Version": "2012-10-17", + "Statement": [{"Effect": "Allow", "Action": ["redshift:GetClusterCredentials"], "Resource": "*"}], + } + policy_document = PolicyDocument(test_policy) + exclusions = Exclusions({}) + policy_finding = PolicyFinding(policy_document, exclusions) + results = policy_finding.results + + self.assertListEqual(results["BypassesNetworkControls"]["findings"], ["redshift:GetClusterCredentials"]) + self.assertIn("redshift", results["ServicesAffected"]) + self.assertEqual(results["BypassesNetworkControls"]["severity"], "medium") + self.assertEqual(results["BypassesNetworkControls"]["description"], BYPASSES_NETWORK_CONTROLS_DESCRIPTION)