Skip to content

Article by Adan Álvarez

AWS CodeBuild GitHub Runner Persistence

The AWS CodeBuild managed GitHub Actions runner feature can be abused to obtain recurring temporary credentials for an IAM role. By backdooring a role's trust policy to include the CodeBuild service principal, an attacker can trigger GitHub workflows that execute inside the victim account.

Overview

  1. Backdoor an IAM role trust policy to allow codebuild.amazonaws.com.
  2. Create a CodeBuild Runner project linked to an attacker‑controlled GitHub repository (PAT / OAuth / App).
  3. Configure the project to use the backdoored role as its service role.
  4. Commit a minimal GitHub Actions workflow that targets the dynamic runner label.
  5. Push (or manually trigger) to execute AWS API calls under the role via temporary credentials.

Step 1: Backdoor Role Trust Policy

Add a statement permitting CodeBuild to assume the role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "codebuild.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}

Step 2: Create Runner Project

Create a CodeBuild project of type Runner and connect it to a private GitHub repo controlled by the attacker.

While CodeBuild projects can be created via API or CLI, connecting a GitHub repository often requires going through the AWS Console. Fortunately for the attacker, it’s relatively easy to move from IAM credentials to a web console session, as described here: Create a Console Session from IAM Credentials

Step 3: Attach Backdoored Role

In the project settings, under the “Environment” section, the attacker can choose to use an existing service role; this is where they select the backdoored IAM role from Step 1.

There is also a checkbox: “Allow AWS CodeBuild to modify this service role so it can be used with this build project.”

If this box is checked, CodeBuild will automatically add extra permissions to the role (for things like writing logs to CloudWatch or interacting with CodeBuild resources). If the role already has admin-level permissions, the attacker won’t need to enable this.

Step 4: GitHub Workflow

The final step is to create a GitHub Action workflow that executes commands in the AWS environment using the assumed role.

The workflow can be extremely simple. Here’s an example that will run on every push and call the sts:GetCallerIdentity API:

name: persistence
on: [push]
jobs:
  access:
    runs-on:
      - codebuild-MyRunner-${{ github.run_id }}-${{ github.run_attempt }}
    steps:
      - name: Caller
        run: aws sts get-caller-identity

Note

Key CloudTrail Events:UpdateAssumeRolePolicy, ImportSourceCredentials, CreateProject, CreateWebhook and ProcessWebhook. Also, Prowler has a check to "Ensure AWS CodeBuild projects using GitHub connect only to allowed organizations"