Article by Adan Álvarez
AWS CodeBuild GitHub Runner Persistence
-
Original Research
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¶
- Backdoor an IAM role trust policy to allow
codebuild.amazonaws.com
. - Create a CodeBuild Runner project linked to an attacker‑controlled GitHub repository (PAT / OAuth / App).
- Configure the project to use the backdoored role as its service role.
- Commit a minimal GitHub Actions workflow that targets the dynamic runner label.
- 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"