diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 2188797585..cc0df18d0c 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -22,6 +22,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - CIS Google Cloud Platform Foundation Benchmark v5.0.0 compliance framework for the GCP provider [(#11714)](https://github.com/prowler-cloud/prowler/pull/11714) - CIS Kubernetes Benchmark v2.0.1 compliance framework for the Kubernetes provider [(#11722)](https://github.com/prowler-cloud/prowler/pull/11722) - CIS GitHub Benchmark v1.2.0 compliance framework for the GitHub provider [(#11719)](https://github.com/prowler-cloud/prowler/pull/11719) +- AWS Bedrock AgentCore privilege escalation paths in the IAM privilege escalation checks, covering Runtime, Harness, Code Interpreter and Custom Browser [(#11726)](https://github.com/prowler-cloud/prowler/pull/11726) - `--scan-secrets-validate` flag and `aws.secrets_validate` configuration option to optionally validate the secrets discovered by the secret-scanning checks against the provider APIs; secrets confirmed to be live are reported as critical [(#11694)](https://github.com/prowler-cloud/prowler/pull/11694) ### 🔄 Changed diff --git a/prowler/providers/aws/services/iam/lib/privilege_escalation.py b/prowler/providers/aws/services/iam/lib/privilege_escalation.py index 9fded2d5c9..d7f16895d5 100644 --- a/prowler/providers/aws/services/iam/lib/privilege_escalation.py +++ b/prowler/providers/aws/services/iam/lib/privilege_escalation.py @@ -19,6 +19,7 @@ from prowler.providers.aws.services.iam.lib.policy import get_effective_actions # - https://github.com/RhinoSecurityLabs/Security-Research/blob/master/tools/aws-pentest-tools/aws_escalate.py # - https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/ # - https://github.com/DataDog/pathfinding.cloud (AWS IAM Privilege Escalation Path Library) +# - https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation (AWS Bedrock AgentCore) privilege_escalation_policies_combination = { # IAM self-escalation and policy manipulation @@ -299,6 +300,7 @@ privilege_escalation_policies_combination = { "PassRole+AgentCoreCreateInterpreter+InvokeInterpreter": { "iam:PassRole", "bedrock-agentcore:CreateCodeInterpreter", + "bedrock-agentcore:StartCodeInterpreterSession", "bedrock-agentcore:InvokeCodeInterpreter", }, # Prerequisite: Existing Bedrock code interpreter with admin role @@ -306,6 +308,40 @@ privilege_escalation_policies_combination = { "bedrock-agentcore:StartCodeInterpreterSession", "bedrock-agentcore:InvokeCodeInterpreter", }, + # Prerequisite: Existing AgentCore Runtime or Harness with admin execution role. + # InvokeAgentRuntimeCommand runs shell commands as root inside the microVM and + # reads the execution role credentials from MMDS, bypassing the agent and guardrails. + "AgentCoreInvokeRuntimeCommand": { + "bedrock-agentcore:InvokeAgentRuntimeCommand", + }, + "PassRole+AgentCoreCreateRuntime+InvokeRuntimeCommand": { + "iam:PassRole", + "bedrock-agentcore:CreateAgentRuntime", + "bedrock-agentcore:CreateAgentRuntimeEndpoint", + "bedrock-agentcore:CreateWorkloadIdentity", + "bedrock-agentcore:InvokeAgentRuntimeCommand", + }, + "PassRole+AgentCoreCreateHarness+InvokeRuntimeCommand": { + "iam:PassRole", + "bedrock-agentcore:CreateHarness", + "bedrock-agentcore:CreateAgentRuntime", + "bedrock-agentcore:CreateAgentRuntimeEndpoint", + "bedrock-agentcore:CreateWorkloadIdentity", + "bedrock-agentcore:GetAgentRuntime", + "bedrock-agentcore:InvokeAgentRuntimeCommand", + }, + # Prerequisite: Existing AgentCore Custom Browser with admin execution role. + # A remote CDP driver on the browser session reads the role credentials from MMDS. + "AgentCoreBrowserSessionConnect": { + "bedrock-agentcore:StartBrowserSession", + "bedrock-agentcore:ConnectBrowserAutomationStream", + }, + "PassRole+AgentCoreCreateBrowser+ConnectBrowser": { + "iam:PassRole", + "bedrock-agentcore:CreateBrowser", + "bedrock-agentcore:StartBrowserSession", + "bedrock-agentcore:ConnectBrowserAutomationStream", + }, # TO-DO: We have to handle AssumeRole just if the resource is * and without conditions # "sts:AssumeRole": {"sts:AssumeRole"}, } diff --git a/tests/providers/aws/services/iam/iam_inline_policy_allows_privilege_escalation/iam_inline_policy_allows_privilege_escalation_test.py b/tests/providers/aws/services/iam/iam_inline_policy_allows_privilege_escalation/iam_inline_policy_allows_privilege_escalation_test.py index 176942ea0b..eda9588aef 100644 --- a/tests/providers/aws/services/iam/iam_inline_policy_allows_privilege_escalation/iam_inline_policy_allows_privilege_escalation_test.py +++ b/tests/providers/aws/services/iam/iam_inline_policy_allows_privilege_escalation/iam_inline_policy_allows_privilege_escalation_test.py @@ -1239,6 +1239,7 @@ class Test_iam_inline_policy_allows_privilege_escalation: "Action": [ "iam:PassRole", "bedrock-agentcore:CreateCodeInterpreter", + "bedrock-agentcore:StartCodeInterpreterSession", "bedrock-agentcore:InvokeCodeInterpreter", ], "Resource": "*", @@ -1286,6 +1287,10 @@ class Test_iam_inline_policy_allows_privilege_escalation: assert search( "bedrock-agentcore:CreateCodeInterpreter", result[0].status_extended ) + assert search( + "bedrock-agentcore:StartCodeInterpreterSession", + result[0].status_extended, + ) assert search( "bedrock-agentcore:InvokeCodeInterpreter", result[0].status_extended ) diff --git a/tests/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation_test.py b/tests/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation_test.py index 850a5ba2cb..35f0fd1f0e 100644 --- a/tests/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation_test.py +++ b/tests/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation_test.py @@ -928,6 +928,7 @@ class Test_iam_policy_allows_privilege_escalation: "Action": [ "iam:PassRole", "bedrock-agentcore:CreateCodeInterpreter", + "bedrock-agentcore:StartCodeInterpreterSession", "bedrock-agentcore:InvokeCodeInterpreter", ], "Resource": "*", @@ -973,10 +974,391 @@ class Test_iam_policy_allows_privilege_escalation: assert search( "bedrock-agentcore:CreateCodeInterpreter", result[0].status_extended ) + assert search( + "bedrock-agentcore:StartCodeInterpreterSession", + result[0].status_extended, + ) assert search( "bedrock-agentcore:InvokeCodeInterpreter", result[0].status_extended ) + @mock_aws + def test_iam_policy_allows_privilege_escalation_agentcore_invoke_runtime_command( + self, + ): + """Test detection of AgentCore Runtime/Harness privilege escalation via InvokeAgentRuntimeCommand on an existing resource.""" + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + policy_name = "agentcore_invoke_runtime_command_policy" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:InvokeAgentRuntimeCommand", + ], + "Resource": "*", + } + ], + } + + policy_arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client", + new=IAM(aws_provider), + ), + ): + from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import ( + iam_policy_allows_privilege_escalation, + ) + + check = iam_policy_allows_privilege_escalation() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == policy_name + assert result[0].resource_arn == policy_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + assert search( + f"Custom Policy {policy_arn} allows privilege escalation using the following actions: ", + result[0].status_extended, + ) + assert search( + "bedrock-agentcore:InvokeAgentRuntimeCommand", + result[0].status_extended, + ) + + @mock_aws + def test_iam_policy_allows_privilege_escalation_agentcore_passrole_create_runtime( + self, + ): + """Test detection of AgentCore Runtime privilege escalation by creating a new runtime with a passed role.""" + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + policy_name = "agentcore_create_runtime_policy" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:PassRole", + "bedrock-agentcore:CreateAgentRuntime", + "bedrock-agentcore:CreateAgentRuntimeEndpoint", + "bedrock-agentcore:CreateWorkloadIdentity", + "bedrock-agentcore:InvokeAgentRuntimeCommand", + ], + "Resource": "*", + } + ], + } + + policy_arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client", + new=IAM(aws_provider), + ), + ): + from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import ( + iam_policy_allows_privilege_escalation, + ) + + check = iam_policy_allows_privilege_escalation() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == policy_name + assert result[0].resource_arn == policy_arn + assert search("iam:PassRole", result[0].status_extended) + assert search( + "bedrock-agentcore:CreateAgentRuntime", + result[0].status_extended, + ) + assert search( + "bedrock-agentcore:CreateAgentRuntimeEndpoint", + result[0].status_extended, + ) + assert search( + "bedrock-agentcore:CreateWorkloadIdentity", + result[0].status_extended, + ) + assert search( + "bedrock-agentcore:InvokeAgentRuntimeCommand", + result[0].status_extended, + ) + + @mock_aws + def test_iam_policy_allows_privilege_escalation_agentcore_passrole_create_harness( + self, + ): + """Test detection of AgentCore Harness privilege escalation by creating a new harness with a passed role.""" + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + policy_name = "agentcore_create_harness_policy" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:PassRole", + "bedrock-agentcore:CreateHarness", + "bedrock-agentcore:CreateAgentRuntime", + "bedrock-agentcore:CreateAgentRuntimeEndpoint", + "bedrock-agentcore:CreateWorkloadIdentity", + "bedrock-agentcore:GetAgentRuntime", + "bedrock-agentcore:InvokeAgentRuntimeCommand", + ], + "Resource": "*", + } + ], + } + + policy_arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client", + new=IAM(aws_provider), + ), + ): + from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import ( + iam_policy_allows_privilege_escalation, + ) + + check = iam_policy_allows_privilege_escalation() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == policy_name + assert result[0].resource_arn == policy_arn + assert search("iam:PassRole", result[0].status_extended) + assert search("bedrock-agentcore:CreateHarness", result[0].status_extended) + assert search( + "bedrock-agentcore:CreateAgentRuntime", + result[0].status_extended, + ) + assert search( + "bedrock-agentcore:CreateAgentRuntimeEndpoint", + result[0].status_extended, + ) + assert search( + "bedrock-agentcore:CreateWorkloadIdentity", + result[0].status_extended, + ) + assert search( + "bedrock-agentcore:GetAgentRuntime", result[0].status_extended + ) + assert search( + "bedrock-agentcore:InvokeAgentRuntimeCommand", + result[0].status_extended, + ) + + @mock_aws + def test_iam_policy_allows_privilege_escalation_agentcore_browser_session_connect( + self, + ): + """Test detection of AgentCore Custom Browser privilege escalation via an existing browser session.""" + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + policy_name = "agentcore_browser_session_connect_policy" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:StartBrowserSession", + "bedrock-agentcore:ConnectBrowserAutomationStream", + ], + "Resource": "*", + } + ], + } + + policy_arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client", + new=IAM(aws_provider), + ), + ): + from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import ( + iam_policy_allows_privilege_escalation, + ) + + check = iam_policy_allows_privilege_escalation() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == policy_name + assert result[0].resource_arn == policy_arn + assert search( + "bedrock-agentcore:StartBrowserSession", result[0].status_extended + ) + assert search( + "bedrock-agentcore:ConnectBrowserAutomationStream", + result[0].status_extended, + ) + + @mock_aws + def test_iam_policy_allows_privilege_escalation_agentcore_passrole_create_browser( + self, + ): + """Test detection of AgentCore Custom Browser privilege escalation by creating a new browser with a passed role.""" + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + policy_name = "agentcore_create_browser_policy" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:PassRole", + "bedrock-agentcore:CreateBrowser", + "bedrock-agentcore:StartBrowserSession", + "bedrock-agentcore:ConnectBrowserAutomationStream", + ], + "Resource": "*", + } + ], + } + + policy_arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client", + new=IAM(aws_provider), + ), + ): + from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import ( + iam_policy_allows_privilege_escalation, + ) + + check = iam_policy_allows_privilege_escalation() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == policy_name + assert result[0].resource_arn == policy_arn + assert search("iam:PassRole", result[0].status_extended) + assert search("bedrock-agentcore:CreateBrowser", result[0].status_extended) + assert search( + "bedrock-agentcore:StartBrowserSession", result[0].status_extended + ) + assert search( + "bedrock-agentcore:ConnectBrowserAutomationStream", + result[0].status_extended, + ) + + @mock_aws + def test_iam_policy_allows_privilege_escalation_agentcore_wildcard( + self, + ): + """Test detection of AgentCore privilege escalation when the policy grants the bedrock-agentcore:* namespace wildcard.""" + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + policy_name = "agentcore_wildcard_policy" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:*", + ], + "Resource": "*", + } + ], + } + + policy_arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client", + new=IAM(aws_provider), + ), + ): + from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import ( + iam_policy_allows_privilege_escalation, + ) + + check = iam_policy_allows_privilege_escalation() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == policy_name + assert result[0].resource_arn == policy_arn + assert search( + "bedrock-agentcore:InvokeAgentRuntimeCommand", + result[0].status_extended, + ) + assert search( + "bedrock-agentcore:StartCodeInterpreterSession", + result[0].status_extended, + ) + assert search( + "bedrock-agentcore:StartBrowserSession", result[0].status_extended + ) + @mock_aws def test_iam_policy_allows_privilege_escalation_iam_put( self,