Skip to content

Commit a5e792e

Browse files
committed
PRs public codebuild abuse
1 parent 6d17062 commit a5e792e

File tree

4 files changed

+255
-16
lines changed

4 files changed

+255
-16
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@
260260
- [AWS - CloudFront Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-cloudfront-post-exploitation/README.md)
261261
- [AWS - CodeBuild Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-codebuild-post-exploitation/README.md)
262262
- [AWS Codebuild - Token Leakage](pentesting-cloud/aws-security/aws-post-exploitation/aws-codebuild-post-exploitation/aws-codebuild-token-leakage.md)
263+
- [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)
263264
- [AWS - Control Tower Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-control-tower-post-exploitation/README.md)
264265
- [AWS - DLM Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-dlm-post-exploitation/README.md)
265266
- [AWS - DynamoDB Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-dynamodb-post-exploitation/README.md)

src/pentesting-cloud/aws-security/aws-post-exploitation/aws-codebuild-post-exploitation/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ aws codebuild list-source-credentials
5050
aws-codebuild-token-leakage.md
5151
{{#endref}}
5252

53+
### Untrusted PR execution via webhook filter misconfiguration
54+
55+
If webhook filters are weak, external attackers can get their PRs built in privileged CodeBuild projects and then execute arbitrary code in CI.
56+
57+
{{#ref}}
58+
aws-codebuild-untrusted-pr-webhook-bypass.md
59+
{{#endref}}
60+
5361
### `codebuild:DeleteProject`
5462

5563
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>
8593

8694

8795

88-

src/pentesting-cloud/aws-security/aws-post-exploitation/aws-codebuild-post-exploitation/aws-codebuild-token-leakage.md

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -185,25 +185,15 @@ aws codebuild start-build --project-name <proj-name>
185185
> [!WARNING]
186186
> 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.
187187
188-
## Webhook filter ACTOR_ID regex allowlist bypass (PR-triggered privileged builds)
188+
## Untrusted PR execution via webhook filter misconfiguration
189189

190-
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.
190+
For the PR-triggered webhook bypass chain (`ACTOR_ACCOUNT_ID` regex + untrusted PR execution), check:
191191

192-
**Exploit path**
193-
194-
1. Find public CodeBuild projects exposing webhook filters and extract an unanchored `ACTOR_ID` allowlist.
195-
2. Obtain an eclipsing GitHub ID:
196-
- Sample the global ID counter by creating/deleting GitHub orgs (org IDs share the pool).
197-
- 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.
198-
3. Open a PR from the eclipsing account; the regex matches the substring and the privileged build runs.
199-
4. Use build RCE (e.g., dependency install hooks) to dump process memory handling the GitHub credential and recover the PAT/OAuth token.
200-
5. With the token’s `repo` scope, invite your account as collaborator/admin and push/approve malicious commits or exfiltrate secrets.
201-
202-
## References
203-
- [Wiz: CodeBreach – AWS CodeBuild ACTOR_ID regex bypass and token theft](https://www.wiz.io/blog/wiz-research-codebreach-vulnerability-aws-codebuild)
192+
{{#ref}}
193+
aws-codebuild-untrusted-pr-webhook-bypass.md
194+
{{#endref}}
204195

205196
{{#include ../../../../banners/hacktricks-training.md}}
206197

207198

208199

209-
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# AWS CodeBuild - Untrusted PR Webhook Bypass (CodeBreach-style)
2+
3+
{{#include ../../../../banners/hacktricks-training.md}}
4+
5+
This attack vector appears when a **public-facing PR workflow** is wired to a **privileged CodeBuild project** with weak webhook controls.
6+
7+
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.
8+
9+
## Why this is dangerous
10+
11+
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.
12+
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.
13+
14+
Wiz showed a practical chain where:
15+
16+
1. A webhook actor allowlist used an **unanchored regex**.
17+
2. An attacker registered a GitHub ID that matched as a **superstring** of a trusted ID.
18+
3. A malicious PR triggered CodeBuild.
19+
4. Build code execution was used to dump memory and recover source-provider credentials/tokens.
20+
21+
## Misconfigurations that allow external PR code execution
22+
23+
The following are high-risk mistakes and how attackers abuse each one:
24+
25+
1. **`EVENT` filters allow untrusted triggers**
26+
- Common risky events: `PULL_REQUEST_CREATED`, `PULL_REQUEST_UPDATED`, `PULL_REQUEST_REOPENED`.
27+
- Other events that can also become dangerous if tied to privileged builds: `PUSH`, `PULL_REQUEST_CLOSED`, `PULL_REQUEST_MERGED`, `RELEASED`, `PRERELEASED`, `WORKFLOW_JOB_QUEUED`.
28+
- Bad: `EVENT="PUSH, PULL_REQUEST_CREATED, PULL_REQUEST_UPDATED"` in a privileged project.
29+
- Better: use PR comment approval and minimize trigger events for privileged projects.
30+
- Abuse: attacker opens/updates PR or pushes to a branch they control, and their code executes in CodeBuild.
31+
32+
2. **`ACTOR_ACCOUNT_ID` regex is weak**
33+
- Bad: unanchored patterns like `123456|7890123`.
34+
- Better: exact-match anchoring `^(123456|7890123)$`.
35+
- Abuse: regex over-match allows unauthorized GitHub IDs to pass allowlists.
36+
37+
3. **Other regex filters are weak or missing**
38+
- `HEAD_REF`
39+
- Bad: `refs/heads/.*`
40+
- Better: `^refs/heads/main$` (or an explicit trusted list)
41+
- `BASE_REF`
42+
- Bad: `.*`
43+
- Better: `^refs/heads/main$`
44+
- `FILE_PATH`
45+
- Bad: no path restrictions
46+
- Better: exclude risky files like `^buildspec\\.yml$`, `^\\.github/workflows/.*`, `(^|/)package(-lock)?\\.json$`
47+
- `COMMIT_MESSAGE`
48+
- Bad: trust marker with loose match like `trusted`
49+
- Better: do not use commit message as a trust boundary for PR execution
50+
- `REPOSITORY_NAME` / `ORGANIZATION_NAME`
51+
- Bad: `.*` in org/global webhooks
52+
- Better: exact repo/org matches only
53+
- `WORKFLOW_NAME`
54+
- Bad: `.*`
55+
- Better: exact workflow name matches only (or avoid this as trust control)
56+
- Abuse: attacker crafts ref/path/message/repo context to satisfy permissive regex and trigger builds.
57+
58+
4. **`excludeMatchedPattern` is misused**
59+
- Setting this flag incorrectly can invert intended logic.
60+
- Bad: `FILE_PATH '^buildspec\\.yml$'` with `excludeMatchedPattern=false` when intent was to block buildspec edits.
61+
- Better: same pattern with `excludeMatchedPattern=true` to deny builds touching `buildspec.yml`.
62+
- Abuse: defenders think they deny risky events/paths/actors, but actually allow them.
63+
64+
5. **Multiple `filterGroups` create accidental bypasses**
65+
- CodeBuild evaluates groups as OR (one passing group is enough).
66+
- Bad: one strict group + one permissive fallback group (e.g., only `EVENT=PULL_REQUEST_UPDATED`).
67+
- Better: remove fallback groups that do not enforce actor/ref/path constraints.
68+
- Abuse: attacker only needs to satisfy the weakest group.
69+
70+
6. **Comment approval gate disabled or too permissive**
71+
- `pullRequestBuildPolicy.requiresCommentApproval=DISABLED` is least safe.
72+
- Overly broad approver roles reduce the control.
73+
- Bad: `requiresCommentApproval=DISABLED`.
74+
- Better: `ALL_PULL_REQUESTS` or `FORK_PULL_REQUESTS` with minimal approver roles.
75+
- Abuse: fork/drive-by PRs auto-run without trusted maintainer approval.
76+
77+
7. **No restrictive branch/path strategy for PR builds**
78+
- Missing defense-in-depth with `HEAD_REF` + `BASE_REF` + `FILE_PATH`.
79+
- Bad: only `EVENT` + `ACTOR_ACCOUNT_ID`, no ref/path controls.
80+
- Better: combine exact `ACTOR_ACCOUNT_ID` + `BASE_REF` + `HEAD_REF` + `FILE_PATH` restrictions.
81+
- Abuse: attacker modifies build inputs (buildspec/CI/dependencies) and gets arbitrary command execution.
82+
83+
8. **Public visibility + status URL exposure**
84+
- Public build/check URLs improve attacker recon and iterative testing.
85+
- Bad: `projectVisibility=PUBLIC_READ` with sensitive logs/config in public builds.
86+
- Better: keep projects private unless there is a strong business need, and sanitize logs/artifacts.
87+
- Abuse: attacker discovers project patterns/behavior, then tunes payloads and bypass attempts.
88+
89+
## Token leakage from memory
90+
91+
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.
92+
93+
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.
94+
95+
For additional credential theft techniques in CodeBuild, also check:
96+
97+
{{#ref}}
98+
aws-codebuild-token-leakage.md
99+
{{#endref}}
100+
101+
## Finding CodeBuild URLs in GitHub PRs
102+
103+
If CodeBuild reports commit status back to GitHub, the CodeBuild build URL usually appears in:
104+
105+
1. **PR page** -> **Checks** tab (or the status line in Conversation/Commits).
106+
2. **Commit page** -> status/checks section -> **Details** link.
107+
3. **PR commits list** -> click the check context attached to a commit.
108+
109+
For public projects, this link can expose build metadata/configuration to unauthenticated users.
110+
111+
<details>
112+
<summary>Script: detect CodeBuild URLs in a PR and test if they look public</summary>
113+
114+
```bash
115+
#!/usr/bin/env bash
116+
set -euo pipefail
117+
118+
# Usage:
119+
# ./check_pr_codebuild_urls.sh <owner> <repo> <pr_number>
120+
#
121+
# Requirements: gh, jq, curl
122+
123+
OWNER="${1:?owner}"
124+
REPO="${2:?repo}"
125+
PR="${3:?pr_number}"
126+
127+
for bin in gh jq curl timeout; do
128+
command -v "$bin" >/dev/null || { echo "[!] Missing dependency: $bin" >&2; exit 1; }
129+
done
130+
131+
tmp_commits="$(mktemp)"
132+
tmp_urls="$(mktemp)"
133+
trap 'rm -f "$tmp_commits" "$tmp_urls"' EXIT
134+
135+
gh_api() {
136+
timeout 20s gh api "$@" 2>/dev/null || true
137+
}
138+
139+
# Get all commit SHAs in the PR (bounded call to avoid hangs)
140+
gh_api "repos/${OWNER}/${REPO}/pulls/${PR}/commits" --paginate --jq '.[].sha' > "$tmp_commits"
141+
if [ ! -s "$tmp_commits" ]; then
142+
echo "[!] No commits found (or API call timed out/failed)." >&2
143+
exit 1
144+
fi
145+
146+
echo "[*] PR commits:"
147+
cat "$tmp_commits"
148+
echo
149+
150+
echo "[*] Searching commit statuses/check-runs for CodeBuild URLs..."
151+
152+
while IFS= read -r sha; do
153+
[ -z "$sha" ] && continue
154+
155+
# Classic commit statuses (target_url)
156+
gh_api "repos/${OWNER}/${REPO}/commits/${sha}/status" \
157+
--jq '.statuses[]? | .target_url // empty' 2>/dev/null || true
158+
159+
# GitHub Checks API (details_url)
160+
gh_api "repos/${OWNER}/${REPO}/commits/${sha}/check-runs" \
161+
--jq '.check_runs[]? | .details_url // empty' 2>/dev/null || true
162+
done < "$tmp_commits" | sort -u > "$tmp_urls"
163+
164+
grep -Ei 'codebuild|codebuild\.aws\.amazon\.com|console\.aws\.amazon\.com/.*/codebuild' "$tmp_urls" || true
165+
166+
echo
167+
echo "[*] Public-access heuristic:"
168+
echo " - If URL redirects to signin.aws.amazon.com -> likely not public"
169+
echo " - If URL is directly reachable (HTTP 200) without auth redirect -> potentially public"
170+
echo
171+
172+
cb_urls="$(grep -Ei 'codebuild|codebuild\.aws\.amazon\.com|console\.aws\.amazon\.com/.*/codebuild' "$tmp_urls" || true)"
173+
if [ -z "$cb_urls" ]; then
174+
echo "[*] No CodeBuild URLs found in PR statuses/check-runs."
175+
exit 0
176+
fi
177+
178+
while IFS= read -r url; do
179+
[ -z "$url" ] && continue
180+
final_url="$(timeout 20s curl -4 -sS -L --connect-timeout 5 --max-time 20 -o /dev/null -w '%{url_effective}' "$url" || true)"
181+
code="$(timeout 20s curl -4 -sS -L --connect-timeout 5 --max-time 20 -o /dev/null -w '%{http_code}' "$url" || true)"
182+
183+
if echo "$final_url" | grep -qi 'signin\.aws\.amazon\.com'; then
184+
verdict="NOT_PUBLIC_OR_AUTH_REQUIRED"
185+
elif [ "$code" = "200" ]; then
186+
verdict="POTENTIALLY_PUBLIC"
187+
else
188+
verdict="UNKNOWN_CHECK_MANUALLY"
189+
fi
190+
191+
printf '%s\t%s\t%s\n' "$verdict" "$code" "$url"
192+
done <<< "$cb_urls"
193+
```
194+
195+
Tested working with:
196+
197+
```bash
198+
bash /tmp/check_pr_codebuild_urls.sh carlospolop codebuild-codebreach-ctf-lab 1
199+
```
200+
201+
</details>
202+
203+
## Quick audit checklist
204+
205+
```bash
206+
# Enumerate projects
207+
aws codebuild list-projects
208+
209+
# Inspect source/webhook configuration
210+
aws codebuild batch-get-projects --names <project-name>
211+
212+
# Inspect global source credentials configured in account
213+
aws codebuild list-source-credentials
214+
```
215+
216+
Review each project for:
217+
218+
- `webhook.filterGroups` containing PR events.
219+
- `ACTOR_ACCOUNT_ID` patterns that are not anchored with `^...$`.
220+
- `pullRequestBuildPolicy.requiresCommentApproval` equal to `DISABLED`.
221+
- Missing branch/path restrictions.
222+
- High-privilege `serviceRole`.
223+
- Risky source credentials scope and reuse.
224+
225+
## Hardening guidance
226+
227+
1. Require comment approval for PR builds (`ALL_PULL_REQUESTS` or `FORK_PULL_REQUESTS`).
228+
2. If using actor allowlists, anchor regexes and keep them exact.
229+
3. Add `FILE_PATH` restrictions to avoid untrusted edits to `buildspec.yml` and CI scripts.
230+
4. Separate trusted release builds from untrusted PR builds into different projects/roles.
231+
5. Use fine-grained, least-privileged source-provider tokens (prefer dedicated low-privilege identities).
232+
6. Continuously audit webhook filters and source credential usage.
233+
234+
## References
235+
236+
- [Wiz: CodeBreach - AWS CodeBuild ACTOR_ID regex bypass and token theft](https://www.wiz.io/blog/wiz-research-codebreach-vulnerability-aws-codebuild)
237+
- [AWS CodeBuild API - WebhookFilter](https://docs.aws.amazon.com/codebuild/latest/APIReference/API_WebhookFilter.html)
238+
- [AWS CLI - codebuild create-webhook](https://docs.aws.amazon.com/cli/latest/reference/codebuild/create-webhook.html)
239+
- [AWS CodeBuild User Guide - Best practices for webhooks](https://docs.aws.amazon.com/codebuild/latest/userguide/webhooks.html)
240+
241+
{{#include ../../../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)