AWS CodeBuild - Untrusted PR Webhook Bypass (CodeBreach-style)

Tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

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:

AWS Codebuild - Token Leakage

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.

Script: detect CodeBuild URLs in a PR and test if they look public
#!/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 /tmp/check_pr_codebuild_urls.sh carlospolop codebuild-codebreach-ctf-lab 1

Quick audit checklist

# 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

Tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks