PRs public codebuild abuse

This commit is contained in:
Carlos Polop
2026-02-03 13:42:01 +01:00
parent 6d17062d44
commit a5e792e60a
4 changed files with 255 additions and 16 deletions
+1
View File
@@ -260,6 +260,7 @@
- [AWS - CloudFront Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-cloudfront-post-exploitation/README.md)
- [AWS - CodeBuild Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-codebuild-post-exploitation/README.md)
- [AWS Codebuild - Token Leakage](pentesting-cloud/aws-security/aws-post-exploitation/aws-codebuild-post-exploitation/aws-codebuild-token-leakage.md)
- [AWS CodeBuild - Untrusted PR Webhook Bypass (CodeBreach-style)](pentesting-cloud/aws-security/aws-post-exploitation/aws-codebuild-post-exploitation/aws-codebuild-untrusted-pr-webhook-bypass.md)
- [AWS - Control Tower Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-control-tower-post-exploitation/README.md)
- [AWS - DLM Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-dlm-post-exploitation/README.md)
- [AWS - DynamoDB Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-dynamodb-post-exploitation/README.md)
@@ -50,6 +50,14 @@ aws codebuild list-source-credentials
aws-codebuild-token-leakage.md
{{#endref}}
### Untrusted PR execution via webhook filter misconfiguration
If webhook filters are weak, external attackers can get their PRs built in privileged CodeBuild projects and then execute arbitrary code in CI.
{{#ref}}
aws-codebuild-untrusted-pr-webhook-bypass.md
{{#endref}}
### `codebuild:DeleteProject`
An attacker could delete an entire CodeBuild project, causing loss of project configuration and impacting applications relying on the project.
@@ -85,4 +93,3 @@ aws codebuild delete-source-credentials --arn <value>
@@ -185,25 +185,15 @@ aws codebuild start-build --project-name <proj-name>
> [!WARNING]
> Now an attacker will be able to use the token from his machine, list all the privileges it has and (ab)use easier than using the CodeBuild service directly.
## Webhook filter ACTOR_ID regex allowlist bypass (PR-triggered privileged builds)
## Untrusted PR execution via webhook filter misconfiguration
Misconfigured CodeBuild GitHub webhooks that use unanchored `ACTOR_ID` regexes let *untrusted* PRs start privileged builds. If the allowlist is like `123456|7890123` without `^`/`$`, any ID containing one of those substrings matches. Because GitHub user IDs are sequential, an attacker can race to register an “eclipsing” ID (a superstring of a trusted ID) and trigger the build.
For the PR-triggered webhook bypass chain (`ACTOR_ACCOUNT_ID` regex + untrusted PR execution), check:
**Exploit path**
1. Find public CodeBuild projects exposing webhook filters and extract an unanchored `ACTOR_ID` allowlist.
2. Obtain an eclipsing GitHub ID:
- Sample the global ID counter by creating/deleting GitHub orgs (org IDs share the pool).
- Pre-stage many GitHub App manifest creations and fire the confirmation URLs when the counter is within ~100 IDs of the target to burst-register a bot ID containing the trusted substring.
3. Open a PR from the eclipsing account; the regex matches the substring and the privileged build runs.
4. Use build RCE (e.g., dependency install hooks) to dump process memory handling the GitHub credential and recover the PAT/OAuth token.
5. With the tokens `repo` scope, invite your account as collaborator/admin and push/approve malicious commits or exfiltrate secrets.
## References
- [Wiz: CodeBreach AWS CodeBuild ACTOR_ID regex bypass and token theft](https://www.wiz.io/blog/wiz-research-codebreach-vulnerability-aws-codebuild)
{{#ref}}
aws-codebuild-untrusted-pr-webhook-bypass.md
{{#endref}}
{{#include ../../../../banners/hacktricks-training.md}}
@@ -0,0 +1,241 @@
# AWS CodeBuild - Untrusted PR Webhook Bypass (CodeBreach-style)
{{#include ../../../../banners/hacktricks-training.md}}
This attack vector appears when a **public-facing PR workflow** is wired to a **privileged CodeBuild project** with weak webhook controls.
If an external attacker can make CodeBuild execute their pull request, they can usually get **arbitrary code execution inside the build** (build scripts, dependency hooks, test scripts, etc.), and then pivot to secrets, IAM credentials, or source-provider credentials.
## Why this is dangerous
CodeBuild webhook filters are evaluated with regex patterns (for non-`EVENT` filters). In the `ACTOR_ACCOUNT_ID` filter, this means a weak pattern can match more users than intended.
If untrusted PRs are built in a project that has privileged AWS role permissions or GitHub credentials, this can become a full supply-chain compromise.
Wiz showed a practical chain where:
1. A webhook actor allowlist used an **unanchored regex**.
2. An attacker registered a GitHub ID that matched as a **superstring** of a trusted ID.
3. A malicious PR triggered CodeBuild.
4. Build code execution was used to dump memory and recover source-provider credentials/tokens.
## Misconfigurations that allow external PR code execution
The following are high-risk mistakes and how attackers abuse each one:
1. **`EVENT` filters allow untrusted triggers**
- Common risky events: `PULL_REQUEST_CREATED`, `PULL_REQUEST_UPDATED`, `PULL_REQUEST_REOPENED`.
- Other events that can also become dangerous if tied to privileged builds: `PUSH`, `PULL_REQUEST_CLOSED`, `PULL_REQUEST_MERGED`, `RELEASED`, `PRERELEASED`, `WORKFLOW_JOB_QUEUED`.
- Bad: `EVENT="PUSH, PULL_REQUEST_CREATED, PULL_REQUEST_UPDATED"` in a privileged project.
- Better: use PR comment approval and minimize trigger events for privileged projects.
- Abuse: attacker opens/updates PR or pushes to a branch they control, and their code executes in CodeBuild.
2. **`ACTOR_ACCOUNT_ID` regex is weak**
- Bad: unanchored patterns like `123456|7890123`.
- Better: exact-match anchoring `^(123456|7890123)$`.
- Abuse: regex over-match allows unauthorized GitHub IDs to pass allowlists.
3. **Other regex filters are weak or missing**
- `HEAD_REF`
- Bad: `refs/heads/.*`
- Better: `^refs/heads/main$` (or an explicit trusted list)
- `BASE_REF`
- Bad: `.*`
- Better: `^refs/heads/main$`
- `FILE_PATH`
- Bad: no path restrictions
- Better: exclude risky files like `^buildspec\\.yml$`, `^\\.github/workflows/.*`, `(^|/)package(-lock)?\\.json$`
- `COMMIT_MESSAGE`
- Bad: trust marker with loose match like `trusted`
- Better: do not use commit message as a trust boundary for PR execution
- `REPOSITORY_NAME` / `ORGANIZATION_NAME`
- Bad: `.*` in org/global webhooks
- Better: exact repo/org matches only
- `WORKFLOW_NAME`
- Bad: `.*`
- Better: exact workflow name matches only (or avoid this as trust control)
- Abuse: attacker crafts ref/path/message/repo context to satisfy permissive regex and trigger builds.
4. **`excludeMatchedPattern` is misused**
- Setting this flag incorrectly can invert intended logic.
- Bad: `FILE_PATH '^buildspec\\.yml$'` with `excludeMatchedPattern=false` when intent was to block buildspec edits.
- Better: same pattern with `excludeMatchedPattern=true` to deny builds touching `buildspec.yml`.
- Abuse: defenders think they deny risky events/paths/actors, but actually allow them.
5. **Multiple `filterGroups` create accidental bypasses**
- CodeBuild evaluates groups as OR (one passing group is enough).
- Bad: one strict group + one permissive fallback group (e.g., only `EVENT=PULL_REQUEST_UPDATED`).
- Better: remove fallback groups that do not enforce actor/ref/path constraints.
- Abuse: attacker only needs to satisfy the weakest group.
6. **Comment approval gate disabled or too permissive**
- `pullRequestBuildPolicy.requiresCommentApproval=DISABLED` is least safe.
- Overly broad approver roles reduce the control.
- Bad: `requiresCommentApproval=DISABLED`.
- Better: `ALL_PULL_REQUESTS` or `FORK_PULL_REQUESTS` with minimal approver roles.
- Abuse: fork/drive-by PRs auto-run without trusted maintainer approval.
7. **No restrictive branch/path strategy for PR builds**
- Missing defense-in-depth with `HEAD_REF` + `BASE_REF` + `FILE_PATH`.
- Bad: only `EVENT` + `ACTOR_ACCOUNT_ID`, no ref/path controls.
- Better: combine exact `ACTOR_ACCOUNT_ID` + `BASE_REF` + `HEAD_REF` + `FILE_PATH` restrictions.
- Abuse: attacker modifies build inputs (buildspec/CI/dependencies) and gets arbitrary command execution.
8. **Public visibility + status URL exposure**
- Public build/check URLs improve attacker recon and iterative testing.
- Bad: `projectVisibility=PUBLIC_READ` with sensitive logs/config in public builds.
- Better: keep projects private unless there is a strong business need, and sanitize logs/artifacts.
- Abuse: attacker discovers project patterns/behavior, then tunes payloads and bypass attempts.
## Token leakage from memory
Wiz's write-up explains that source-provider credentials are present in build runtime context and can be stolen after build compromise (for example, via memory dumping), enabling repository takeover if scopes are broad.
AWS introduced hardening after the disclosure, but the core lesson remains: **never execute untrusted PR code in privileged build contexts** and assume attacker-controlled build code will attempt credential theft.
For additional credential theft techniques in CodeBuild, also check:
{{#ref}}
aws-codebuild-token-leakage.md
{{#endref}}
## Finding CodeBuild URLs in GitHub PRs
If CodeBuild reports commit status back to GitHub, the CodeBuild build URL usually appears in:
1. **PR page** -> **Checks** tab (or the status line in Conversation/Commits).
2. **Commit page** -> status/checks section -> **Details** link.
3. **PR commits list** -> click the check context attached to a commit.
For public projects, this link can expose build metadata/configuration to unauthenticated users.
<details>
<summary>Script: detect CodeBuild URLs in a PR and test if they look public</summary>
```bash
#!/usr/bin/env bash
set -euo pipefail
# Usage:
# ./check_pr_codebuild_urls.sh <owner> <repo> <pr_number>
#
# Requirements: gh, jq, curl
OWNER="${1:?owner}"
REPO="${2:?repo}"
PR="${3:?pr_number}"
for bin in gh jq curl timeout; do
command -v "$bin" >/dev/null || { echo "[!] Missing dependency: $bin" >&2; exit 1; }
done
tmp_commits="$(mktemp)"
tmp_urls="$(mktemp)"
trap 'rm -f "$tmp_commits" "$tmp_urls"' EXIT
gh_api() {
timeout 20s gh api "$@" 2>/dev/null || true
}
# Get all commit SHAs in the PR (bounded call to avoid hangs)
gh_api "repos/${OWNER}/${REPO}/pulls/${PR}/commits" --paginate --jq '.[].sha' > "$tmp_commits"
if [ ! -s "$tmp_commits" ]; then
echo "[!] No commits found (or API call timed out/failed)." >&2
exit 1
fi
echo "[*] PR commits:"
cat "$tmp_commits"
echo
echo "[*] Searching commit statuses/check-runs for CodeBuild URLs..."
while IFS= read -r sha; do
[ -z "$sha" ] && continue
# Classic commit statuses (target_url)
gh_api "repos/${OWNER}/${REPO}/commits/${sha}/status" \
--jq '.statuses[]? | .target_url // empty' 2>/dev/null || true
# GitHub Checks API (details_url)
gh_api "repos/${OWNER}/${REPO}/commits/${sha}/check-runs" \
--jq '.check_runs[]? | .details_url // empty' 2>/dev/null || true
done < "$tmp_commits" | sort -u > "$tmp_urls"
grep -Ei 'codebuild|codebuild\.aws\.amazon\.com|console\.aws\.amazon\.com/.*/codebuild' "$tmp_urls" || true
echo
echo "[*] Public-access heuristic:"
echo " - If URL redirects to signin.aws.amazon.com -> likely not public"
echo " - If URL is directly reachable (HTTP 200) without auth redirect -> potentially public"
echo
cb_urls="$(grep -Ei 'codebuild|codebuild\.aws\.amazon\.com|console\.aws\.amazon\.com/.*/codebuild' "$tmp_urls" || true)"
if [ -z "$cb_urls" ]; then
echo "[*] No CodeBuild URLs found in PR statuses/check-runs."
exit 0
fi
while IFS= read -r url; do
[ -z "$url" ] && continue
final_url="$(timeout 20s curl -4 -sS -L --connect-timeout 5 --max-time 20 -o /dev/null -w '%{url_effective}' "$url" || true)"
code="$(timeout 20s curl -4 -sS -L --connect-timeout 5 --max-time 20 -o /dev/null -w '%{http_code}' "$url" || true)"
if echo "$final_url" | grep -qi 'signin\.aws\.amazon\.com'; then
verdict="NOT_PUBLIC_OR_AUTH_REQUIRED"
elif [ "$code" = "200" ]; then
verdict="POTENTIALLY_PUBLIC"
else
verdict="UNKNOWN_CHECK_MANUALLY"
fi
printf '%s\t%s\t%s\n' "$verdict" "$code" "$url"
done <<< "$cb_urls"
```
Tested working with:
```bash
bash /tmp/check_pr_codebuild_urls.sh carlospolop codebuild-codebreach-ctf-lab 1
```
</details>
## Quick audit checklist
```bash
# Enumerate projects
aws codebuild list-projects
# Inspect source/webhook configuration
aws codebuild batch-get-projects --names <project-name>
# Inspect global source credentials configured in account
aws codebuild list-source-credentials
```
Review each project for:
- `webhook.filterGroups` containing PR events.
- `ACTOR_ACCOUNT_ID` patterns that are not anchored with `^...$`.
- `pullRequestBuildPolicy.requiresCommentApproval` equal to `DISABLED`.
- Missing branch/path restrictions.
- High-privilege `serviceRole`.
- Risky source credentials scope and reuse.
## Hardening guidance
1. Require comment approval for PR builds (`ALL_PULL_REQUESTS` or `FORK_PULL_REQUESTS`).
2. If using actor allowlists, anchor regexes and keep them exact.
3. Add `FILE_PATH` restrictions to avoid untrusted edits to `buildspec.yml` and CI scripts.
4. Separate trusted release builds from untrusted PR builds into different projects/roles.
5. Use fine-grained, least-privileged source-provider tokens (prefer dedicated low-privilege identities).
6. Continuously audit webhook filters and source credential usage.
## References
- [Wiz: CodeBreach - AWS CodeBuild ACTOR_ID regex bypass and token theft](https://www.wiz.io/blog/wiz-research-codebreach-vulnerability-aws-codebuild)
- [AWS CodeBuild API - WebhookFilter](https://docs.aws.amazon.com/codebuild/latest/APIReference/API_WebhookFilter.html)
- [AWS CLI - codebuild create-webhook](https://docs.aws.amazon.com/cli/latest/reference/codebuild/create-webhook.html)
- [AWS CodeBuild User Guide - Best practices for webhooks](https://docs.aws.amazon.com/codebuild/latest/userguide/webhooks.html)
{{#include ../../../../banners/hacktricks-training.md}}