Gaining Long-Term AWS Access with CodeBuild and GitHub

As I wrote in other blogs, such as Gaining AWS Persistence by Updating a SAML Identity Provider, when an attacker compromises an AWS account, one of the first tactics they will attempt is to gain persistence. This is because access obtained through temporary credentials may expire quickly, or the attacker may want to ensure continued access even if their initial foothold is discovered and removed. That’s why I’m interested in exploring how legitimate AWS services can be abused for persistence. While creating IAM users and assigning them admin privileges is still a common and effective tactic (as we continue to observe in data from TrailDiscover), it’s noisy and easier to detect. In this article, I’ll explore how an attacker could use/abuse AWS CodeBuild for persistence. I haven’t seen this particular approach documented in other research, but let me know if it’s been covered elsewhere. ⚠️ Disclaimer: This is NOT an AWS vulnerability. The technique described assumes the attacker has already compromised the account and has sufficient permissions. The goal is to expose how CodeBuild can be used to establish long-term access. What is AWS CodeBuild? CodeBuild is a fully managed continuous integration service that compiles source code, runs tests, and produces software packages for deployment. It supports custom build environments and integrates with popular tools such as GitHub. In mid-2024, AWS introduced support for running self-hosted GitHub Actions runners using CodeBuild. This functionality allows you to configure a CodeBuild project to act as a runner for GitHub Actions. This GitHub Actions integration is the option we are going to explore to see how an attacker can abuse it to gain persistence. How an Attacker Could Abuse CodeBuild The technique is surprisingly straightforward. Because CodeBuild now supports self-hosted GitHub Actions runners, an attacker can: Configure a CodeBuild project to serve as a GitHub Actions runner. Link that project to a GitHub repository controlled by the attacker. Modify an IAM role to allow CodeBuild to assume it. Then this role will provide access to the AWS environment. Let’s see these steps in more detail. Step 1: Backdoor a Role The attacker first needs to choose an IAM role to abuse. Ideally, this is an existing role with broad/admin privileges. In most cases, backdooring an existing role is often stealthier than creating a new one. To “backdoor” the role, the attacker modifies its trust policy to allow the CodeBuild service to assume it: { "Effect": "Allow", "Principal": { "Service": "codebuild.amazonaws.com" }, "Action": "sts:AssumeRole" } This change allows CodeBuild builds to assume the role and inherit its permissions. Note: Backdooring IAM roles for persistence is a well-known technique. It has been documented in research and observed in real-world incidents. Step 2: Create a CodeBuild Project and Connect it to GitHub Once the attacker has selected and backdoored an IAM role, the next step is to go into AWS CodeBuild and create a new build project linked to a GitHub repository they control. 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 Selecting a Project name and type The first step when creating a project will be to select the name and the type. In this case, the type will be a “Runner project” Connecting to GitHub To link a GitHub repository to the build project, AWS provides three options: GitHub App OAuth App Personal Access Token (PAT) To minimize noise and complexity, the attacker can choose the simplest route: using a GitHub Personal Access Token (PAT). This PAT would grant access to a repository that the attacker controls (it can even be private). Once the GitHub connection is established, the attacker can select the Repository Using the 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. Note: The attacker will likely disable CloudWatch Logs to reduce visibility of their build runs. Step 3: Set Up the GitHub Action At this point, the attacker has: Created a CodeBuild project Linked it to a GitHub repository they control Configured it to assume a backdoored IAM rol

Apr 13, 2025 - 13:14
 0
Gaining Long-Term AWS Access with CodeBuild and GitHub

As I wrote in other blogs, such as Gaining AWS Persistence by Updating a SAML Identity Provider, when an attacker compromises an AWS account, one of the first tactics they will attempt is to gain persistence. This is because access obtained through temporary credentials may expire quickly, or the attacker may want to ensure continued access even if their initial foothold is discovered and removed.

That’s why I’m interested in exploring how legitimate AWS services can be abused for persistence. While creating IAM users and assigning them admin privileges is still a common and effective tactic (as we continue to observe in data from TrailDiscover), it’s noisy and easier to detect.

Figure 1. TrailDiscover most reported event names

In this article, I’ll explore how an attacker could use/abuse AWS CodeBuild for persistence. I haven’t seen this particular approach documented in other research, but let me know if it’s been covered elsewhere.

⚠️ Disclaimer: This is NOT an AWS vulnerability. The technique described assumes the attacker has already compromised the account and has sufficient permissions. The goal is to expose how CodeBuild can be used to establish long-term access.

What is AWS CodeBuild?

CodeBuild is a fully managed continuous integration service that compiles source code, runs tests, and produces software packages for deployment. It supports custom build environments and integrates with popular tools such as GitHub.

In mid-2024, AWS introduced support for running self-hosted GitHub Actions runners using CodeBuild. This functionality allows you to configure a CodeBuild project to act as a runner for GitHub Actions.

This GitHub Actions integration is the option we are going to explore to see how an attacker can abuse it to gain persistence.

How an Attacker Could Abuse CodeBuild

The technique is surprisingly straightforward.

Because CodeBuild now supports self-hosted GitHub Actions runners, an attacker can:

  1. Configure a CodeBuild project to serve as a GitHub Actions runner.
  2. Link that project to a GitHub repository controlled by the attacker.
  3. Modify an IAM role to allow CodeBuild to assume it. Then this role will provide access to the AWS environment.

Let’s see these steps in more detail.

Step 1: Backdoor a Role

The attacker first needs to choose an IAM role to abuse. Ideally, this is an existing role with broad/admin privileges. In most cases, backdooring an existing role is often stealthier than creating a new one.

To “backdoor” the role, the attacker modifies its trust policy to allow the CodeBuild service to assume it:

{
  "Effect": "Allow",
  "Principal": {
    "Service": "codebuild.amazonaws.com"
  },
  "Action": "sts:AssumeRole"
}

Figure 2. Adding CodeBuild to a trust policy

This change allows CodeBuild builds to assume the role and inherit its permissions.

Note: Backdooring IAM roles for persistence is a well-known technique. It has been documented in research and observed in real-world incidents.

Step 2: Create a CodeBuild Project and Connect it to GitHub

Once the attacker has selected and backdoored an IAM role, the next step is to go into AWS CodeBuild and create a new build project linked to a GitHub repository they control.

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

Selecting a Project name and type
The first step when creating a project will be to select the name and the type. In this case, the type will be a “Runner project”

Figure 3. Create a CodeBuild project with “Runner project” type

Connecting to GitHub
To link a GitHub repository to the build project, AWS provides three options:

  • GitHub App
  • OAuth App
  • Personal Access Token (PAT)

To minimize noise and complexity, the attacker can choose the simplest route: using a GitHub Personal Access Token (PAT). This PAT would grant access to a repository that the attacker controls (it can even be private).

Figure 4. CodeBuild runner configuration

Figure 5. Adding credentials to connect with GitHub

Once the GitHub connection is established, the attacker can select the Repository

Figure 6. Selecting a repository after successfully connected to GitHub

Using the 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.

Figure 7. Configuring the backdoored role as the service role

Note: The attacker will likely disable CloudWatch Logs to reduce visibility of their build runs.

Step 3: Set Up the GitHub Action

At this point, the attacker has:

  • Created a CodeBuild project
  • Linked it to a GitHub repository they control
  • Configured it to assume a backdoored IAM role

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: Backdoor AWS Access
on: [push]
jobs:
  persistence-access:
    runs-on:
      - codebuild-MyGitHubRunner-${{ github.run_id }}-${{ github.run_attempt }}
    steps:
      - name: Verify AWS Access
        run: |
          aws sts get-caller-identity

Here’s an example of the execution of this action:

Figure 8. Logs from the GitHub action that has access to the AWS environment

This confirms that the workflow is successfully running in the AWS environment with permission from the backdoored role. From here, the attacker can execute any AWS commands their role allows.

With these three simple steps, the attacker now has persistent access to the AWS environment. Even if their initial credentials are revoked or the original attack path is closed.

Defending Against CodeBuild-Based Persistence

Let’s walk through what defenders can observe (and what they can’t):

CloudTrail Logs
During the setup phase, several key events will appear in CloudTrail, such as:

  • UpdateAssumeRolePolicy: This event happens when the role is backdoored. We’ll see in the parameters the policyDocument containing codebuild.amazonaws.com
  • ImportSourceCredentials: This event happens when the connection with GitHub is established, but it will depend on the configuration. In the case of a PAT in codebuild we’ll see a requesParameters that will look like:

Figure 9. requestParameters from the ImportSourceCredentials event<br>

  • CreateProject: This event happens when the project is created.
  • CreateWebhook: This event happens when the project is created and is meant to create the webhook that will trigger the build from GitHub.
  • ProcessWebhook: This event happens when CodeBuild processes an event from GitHub

IAM Role Abuse Visibility
Once the GitHub Action runs, all actions will be performed under the backdoored IAM role.

  • AssumeRole will happen every time the CodeBuild assumes the backdoored role.
  • Source IP will appear as AWS infrastructure, or even an IP from your own VPC if the attacker configures CodeBuild to run in VPC mode.
  • PrincipalID and arn will contain AWSCodeBuild

Access Analyzer Blind Spot

One crucial point: AWS Access Analyzer does not detect the GitHub connection as external access.

This is a major blind spot. Many security teams rely on Access Analyzer to detect unexpected trust relationships, but in this case, no alerts are triggered, even though the GitHub repo is controlling a role externally.

I confirmed this with the AWS security team, and “it is expected behavior”. But it certainly complicates detection.

Conclusion

This is just another example of how attackers can abuse legitimate AWS services for persistence. With over 200 AWS services available, attackers with highly privileged access have plenty of options to carry out their attacks. As defenders, we should be prepared for the day an attacker gains access to our environment, because a compromised account shouldn’t be game over.

For this reason, it is essential to understand diverse abuse paths, monitor CloudTrail logs, audit IAM trust relationships, and stay aware of service limitations (like the one described here with Access Analyzer). These are key actions that will allow us to detect and stop an attack before it’s too late.