mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-07-28 22:51:09 -07:00
236 lines
14 KiB
Markdown
236 lines
14 KiB
Markdown
# AWS CodeBuild - Untrusted PR Webhook Bypass (CodeBreach-style)
|
||
|
||
{{#include ../../../../banners/hacktricks-training.md}}
|
||
|
||
Цей вектор атаки виникає, коли **публічний PR workflow** підключено до **привілейованого проекту CodeBuild** з ненадійними контролями webhook.
|
||
|
||
Якщо зовнішній атакуючий може змусити CodeBuild виконати їхній pull request, зазвичай це дає змогу отримати **довільне виконання коду всередині build** (скрипти збірки, hooks залежностей, тестові скрипти тощо), а далі перескочити на секрети, IAM credentials або credentials провайдера репозиторію.
|
||
|
||
## Чому це небезпечно
|
||
|
||
Фільтри webhook у CodeBuild оцінюються за допомогою regex-патернів (для фільтрів, що не `EVENT`). У фільтрі `ACTOR_ACCOUNT_ID` це означає, що слабкий патерн може співпадати з більшою кількістю користувачів, ніж передбачалося.
|
||
Якщо в проекті будуються ненадійні PR і цей проект має привілеї AWS role або credentials GitHub, це може перетворитися на повний compromise ланцюжка постачання.
|
||
|
||
Wiz продемонструвала практичний ланцюжок, де:
|
||
|
||
1. У allowlist для actor webhook використовувався **унанкорований regex**.
|
||
2. Атакуючий зареєстрував GitHub ID, який співпадав як **superstring** довіреного ID.
|
||
3. Зловмисний PR тригернув CodeBuild.
|
||
4. Виконання коду в build використали для дампу пам’яті та відновлення credentials/tokens провайдера сорсу.
|
||
|
||
## Misconfigurations that allow external PR code execution
|
||
|
||
Нижче наведені високоризикові помилки конфігурації та як їх зловживають:
|
||
|
||
1. **`EVENT` filters allow untrusted triggers**
|
||
- Поширені ризиковані events: `PULL_REQUEST_CREATED`, `PULL_REQUEST_UPDATED`, `PULL_REQUEST_REOPENED`.
|
||
- Інші events, які теж можуть стати небезпечними, якщо прив’язані до привілейованих збірок: `PUSH`, `PULL_REQUEST_CLOSED`, `PULL_REQUEST_MERGED`, `RELEASED`, `PRERELEASED`, `WORKFLOW_JOB_QUEUED`.
|
||
- Bad: `EVENT="PUSH, PULL_REQUEST_CREATED, PULL_REQUEST_UPDATED"` в привілейованому проекті.
|
||
- Better: використовувати approval через PR comment і мінімізувати trigger events для привілейованих проектів.
|
||
- Abuse: атакуючий відкриває/оновлює PR або пушить у гілку під своїм контролем, і їхній код виконується в CodeBuild.
|
||
|
||
2. **`ACTOR_ACCOUNT_ID` regex is weak**
|
||
- Bad: неанкоровані патерни типу `123456|7890123`.
|
||
- Better: точне анкорування, наприклад `^(123456|7890123)$`.
|
||
- Abuse: regex over-match дозволяє неавторизованим GitHub ID проходити allowlist.
|
||
|
||
3. **Other regex filters are weak or missing**
|
||
- `HEAD_REF`
|
||
- Bad: `refs/heads/.*`
|
||
- Better: `^refs/heads/main$` (або явний список trusted гілок)
|
||
- `BASE_REF`
|
||
- Bad: `.*`
|
||
- Better: `^refs/heads/main$`
|
||
- `FILE_PATH`
|
||
- Bad: відсутні обмеження по шляху
|
||
- Better: виключати ризикові файли, наприклад `^buildspec\\.yml$`, `^\\.github/workflows/.*`, `(^|/)package(-lock)?\\.json$`
|
||
- `COMMIT_MESSAGE`
|
||
- Bad: довіряти маркеру з вільним матчем на кшталт `trusted`
|
||
- Better: не використовувати commit message як межу довіри для виконання PR
|
||
- `REPOSITORY_NAME` / `ORGANIZATION_NAME`
|
||
- Bad: `.*` в org/global webhooks
|
||
- Better: лише точні збіги для repo/org
|
||
- `WORKFLOW_NAME`
|
||
- Bad: `.*`
|
||
- Better: лише точні збіги по імені workflow (або уникати цього як механізму довіри)
|
||
- Abuse: атакуючий формує ref/path/message/repo контекст, який задовольняє дозволяючі regex і тригерить збірки.
|
||
|
||
4. **`excludeMatchedPattern` is misused**
|
||
- Неправильне встановлення цього прапора може інвертувати бажану логіку.
|
||
- Bad: `FILE_PATH '^buildspec\\.yml$'` з `excludeMatchedPattern=false`, коли намір був блокувати редагування buildspec.
|
||
- Better: той самий патерн з `excludeMatchedPattern=true` щоб заборонити збірки, що торкаються `buildspec.yml`.
|
||
- Abuse: захисники думають, що вони забороняють ризикові події/шляхи/акторів, але насправді дозволяють їх.
|
||
|
||
5. **Multiple `filterGroups` create accidental bypasses**
|
||
- CodeBuild оцінює групи як OR (достатньо однієї успішної групи).
|
||
- Bad: одна сувора група + одна лояльна fallback-група (наприклад, тільки `EVENT=PULL_REQUEST_UPDATED`).
|
||
- Better: прибрати fallback-групи, які не примушують обмеження по actor/ref/path.
|
||
- Abuse: атакуючому потрібно задовольнити лише найслабшу групу.
|
||
|
||
6. **Comment approval gate disabled or too permissive**
|
||
- `pullRequestBuildPolicy.requiresCommentApproval=DISABLED` найменш безпечний.
|
||
- Надто широкі ролі approver-ів зменшують контроль.
|
||
- Bad: `requiresCommentApproval=DISABLED`.
|
||
- Better: `ALL_PULL_REQUESTS` або `FORK_PULL_REQUESTS` з мінімальними ролями approver-ів.
|
||
- Abuse: fork/drive-by PR-и запускаються автоматично без схвалення trusted maintainers.
|
||
|
||
7. **No restrictive branch/path strategy for PR builds**
|
||
- Відсутність defense-in-depth з `HEAD_REF` + `BASE_REF` + `FILE_PATH`.
|
||
- Bad: тільки `EVENT` + `ACTOR_ACCOUNT_ID`, без контролю ref/path.
|
||
- Better: комбінувати точні `ACTOR_ACCOUNT_ID` + `BASE_REF` + `HEAD_REF` + `FILE_PATH` обмеження.
|
||
- Abuse: атакуючий змінює вхідні дані збірки (buildspec/CI/залежності) і отримує довільне виконання команд.
|
||
|
||
8. **Public visibility + status URL exposure**
|
||
- Публічні build/check URL полегшують reconnaissance і ітеративне тестування для атакуючого.
|
||
- Bad: `projectVisibility=PUBLIC_READ` з чутливими логами/конфігурацією у публічних збірках.
|
||
- Better: тримати проекти приватними, якщо немає вагомої бізнес-потреби, і санітизувати логи/артефакти.
|
||
- Abuse: атакуючий виявляє патерни/поведінку проекту, після чого підлаштовує payload-и та спроби обходу.
|
||
|
||
## Token leakage from memory
|
||
|
||
У write-up від Wiz пояснюється, що credentials провайдера сорсу присутні в build runtime context і їх можна вкрасти після compromise збірки (наприклад, через дамп пам’яті), що дозволяє takeover репозиторія, якщо scope-и надто широкі.
|
||
|
||
AWS впровадила жорсткіші заходи після розкриття, але головний урок залишається: **ніколи не виконувати код з ненадійних PR у привілейованих build-контекстах** і припускати, що код під контролем атакуючого спробує викрасти credentials.
|
||
|
||
Для додаткових технік викрадення credentials у CodeBuild також перевірте:
|
||
|
||
{{#ref}}
|
||
aws-codebuild-token-leakage.md
|
||
{{#endref}}
|
||
|
||
## Finding CodeBuild URLs in GitHub PRs
|
||
|
||
Якщо CodeBuild повертає статус коміту в GitHub, URL збірки CodeBuild зазвичай видно в:
|
||
|
||
1. **PR page** -> **Checks** tab (або рядок статусу в Conversation/Commits).
|
||
2. **Commit page** -> секція status/checks -> **Details** link.
|
||
3. **PR commits list** -> клікнути по check context, прив’язаному до коміту.
|
||
|
||
Для публічних проектів це посилання може виставляти metadata/config збірки для неавторизованих користувачів.
|
||
|
||
<details>
|
||
<summary>Скрипт: виявити CodeBuild URL-и в PR і перевірити, чи виглядають вони публічними</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"
|
||
```
|
||
Перевірено на сумісність з:
|
||
```bash
|
||
bash /tmp/check_pr_codebuild_urls.sh carlospolop codebuild-codebreach-ctf-lab 1
|
||
```
|
||
</details>
|
||
|
||
## Швидкий чекліст аудиту
|
||
```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
|
||
```
|
||
Перевірте кожен проект на:
|
||
|
||
- `webhook.filterGroups`, що містять події PR.
|
||
- `ACTOR_ACCOUNT_ID` шаблони, які не заякорені за допомогою `^...$`.
|
||
- `pullRequestBuildPolicy.requiresCommentApproval`, рівне `DISABLED`.
|
||
- Відсутні обмеження гілок/шляхів.
|
||
- Високопривілейований `serviceRole`.
|
||
- Ризиковий обсяг прав та повторне використання облікових даних джерела.
|
||
|
||
## Рекомендації щодо підвищення захисту
|
||
|
||
1. Вимагати підтвердження коментарем для PR builds (`ALL_PULL_REQUESTS` або `FORK_PULL_REQUESTS`).
|
||
2. Якщо використовуєте actor allowlists, заякорюйте regex-и і робіть їх точними.
|
||
3. Додайте обмеження `FILE_PATH`, щоб уникнути небезпечних змін у `buildspec.yml` та CI scripts.
|
||
4. Відокремте довірені release builds від недовірених PR builds у різні проекти/ролі.
|
||
5. Використовуйте тонко-гранульні, найменш привілейовані source-provider tokens (надавайте перевагу виділеним низькопривілейованим ідентичностям).
|
||
6. Постійно перевіряйте webhook filters та використання source credentials.
|
||
|
||
## 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}}
|