Files
2026-09-17 00:50:07 +03:00

92 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<h1 align="center">Runner Ashfall</h1>
<p align="center">
<img src="https://img.shields.io/badge/category-Forensic-blueviolet" alt="Forensic"/>
<img src="https://img.shields.io/badge/difficulty-hard-critical" alt="hard"/>
</p>
В `runner_ashfall_case.zip` собраны следы инцидента в CI/CD: комментарий к issue, действия агента, установка npm-пакета, облачная авторизация и деплой. Первые девять ответов восстанавливаем по журналам. Из них получаем ключ к финальной заметке с десятым флагом.
## Решение
Распаковываем архив. Полезные артефакты удобно разделить по участкам цепочки:
| Каталог | Содержимое |
|---|---|
| `github/`, `agent/` | Issue, вызовы инструментов, CI run и attestation |
| `repo/`, `npm/` | Lockfile до и после, индекс кеша и копия `preinstall.js` |
| `runner/` | События процессов и файлов |
| `cloud/` | OIDC-токены, CloudTrail и закрывающая заметка |
| `registry/`, `kubernetes/` | События образов и фактическое обновление deployment |
### Комментарий и запуск CI (Q01–Q03)
В `github/audit_log.jsonl` ищем `issue_comment.created` с меткой `release-triage` и объектом `agent_route`. Его `source_key` — `issue-1842-comment-7f31`, первый ответ. Похожий комментарий с меткой `docs` дальше в эту цепочку не попадает.
В `agent/triage_assistant.jsonl` ищем событие `tool_call`, у которого `input.from_issue` совпадает с найденным `source_key`. У вызова `deploy_plan.render` читаем `tool_call_id`, равный `tool.deploy_plan.render:6d91c0`. Это второй ответ.
В `github/actions/` ищем запуск, у которого `source.tool_call_id` совпадает с найденным вызовом. Подходит `run_8716443.json`, из которого берём `tuple = run-8716443/check-314159265`. С найденными идентификаторами запуска сверяем остальные события.
### Пакет, кеш и раннер (Q04–Q06)
Сравниваем `repo/package-lock.before.json` и `repo/package-lock.after.json`. В новом lockfile выделяется зависимость с `hasInstallScript = true`: `@northstar/ledger-seal@2.8.4-ash.3`. Флаг `hasInstallScript` указывает на скрипт установки. Его имя проверяем по событиям раннера.
В `npm/_cacache/index-v5/ledger-seal-entry.json` сверяем имя и версию пакета, затем читаем `cache_path`. Получаем путь к содержимому кеша — ответ Q05.
По этому пути ищем процесс в `runner/process_events.jsonl`. У него должны совпасть `run_id=8716443` и `check_run_id=314159265`, а команда содержит `node ... preinstall.js`. Поле `host` даёт `ghr-prod-x64-ephemeral-0c8a7d`, ответ Q06. Другой ephemeral-раннер относится к старому запуску и сюда не подходит.
### Облачные права и деплой (Q07–Q09)
Из `jobs[0].job_id` в JSON запуска берём `9317`. В `cloud/oidc_tokens.jsonl` выбираем токен по тройке run/check/job и сохраняем его `sub` целиком. Так получаем Q07 и связываем облачную авторизацию с конкретной работой CI.
В `cloud/cloudtrail.jsonl` находим `PutObject`, у которого `x-amz-meta-run-id` и `x-amz-meta-check-run-id` совпадают с исследуемым запуском. `responseElements.object_locator` содержит bucket, key и версию объекта — это Q08.
Остаётся выяснить, что действительно развернули. В `kubernetes/audit.jsonl` ищем `patch` deployment `staging/recon-ledger`. Аннотация `northstar.io/source-object` должна точно совпасть с Q08. Из `requestObject.spec.template.spec.containers[0].image` берём полный адрес образа с digest, ответ Q09.
> Digest в attestation описывает результат сборки до подмены артефакта. Для ответа о деплое нужен образ из Kubernetes patch, связанный с конкретной версией объекта.
### Финальная заметка (Q10)
`cloud/final_closeout.note` содержит `nonce`, `ciphertext` и `mac`. `nonce` и `ciphertext` переводим из hex в байты, `mac` оставляем hex-строкой. Ответы Q01–Q09 записываем строками в список `raw_answers` **без** оболочки `caplag{...}`, сохраняя порядок. Для ключа соединяем их разделителем `|`.
```python
import hashlib
key = hashlib.sha256("|".join(raw_answers).encode()).digest()
expected_mac = hashlib.sha256(key + nonce + ciphertext).hexdigest()
assert expected_mac == mac
```
Сначала сверяем `expected_mac` с `mac`: ошибка хотя бы в одном ответе даст несовпадение. Затем получаем seed потока и его блоки:
```python
seed = hashlib.sha256(key + nonce).digest()
stream = bytearray()
counter = 0
while len(stream) < len(ciphertext):
stream.extend(hashlib.sha256(seed + counter.to_bytes(4, "big")).digest())
counter += 1
plain = bytes(a ^ b for a, b in zip(ciphertext, stream))
```
Счётчик начинается с нуля. `zip` ограничивает XOR длиной шифротекста. Декодируем `plain` как UTF-8 и разбираем JSON, поле `final_flag` содержит последний ответ.
Получилась сквозная цепочка событий. Текст issue запустил инструмент агента, тот создал CI run, установка зависимости привела к действиям на раннере, а связанные облачные события — к подменённому деплою. Точные идентификаторы на каждом переходе позволяют отделить её от ложных следов.
[Солвер](solve/solve.py).
## Все этапы
| Этап | Флаг |
|---|---|
| 1 | `caplag{issue-1842-comment-7f31}` |
| 2 | `caplag{tool.deploy_plan.render:6d91c0}` |
| 3 | `caplag{run-8716443/check-314159265}` |
| 4 | `caplag{@northstar/ledger-seal@2.8.4-ash.3}` |
| 5 | `caplag{sha512/6a/2d/6a2dc1e9b4f0d776a8e2b8fd47025703e617b1d9a61b88d5ad7b2c9f2e6a10f4}` |
| 6 | `caplag{ghr-prod-x64-ephemeral-0c8a7d}` |
| 7 | `caplag{repo:northstar-clearing/recon-ledger:ref:refs/heads/staging:workflow:release.yml:job_id:9317}` |
| 8 | `caplag{s3://nstar-stage-artifacts/releases/recon-ledger/2026.05.14/recon-ledger.tgz#v7d4b19e2}` |
| 9 | `caplag{registry.local/northstar/recon-ledger@sha256:9c2fd7f0d2b541e0b44267cc8d59a7cf508b1e3a7e422bcf1ac8a2d1377f1cc4}` |
| 10 | `caplag{runner_ashfall_ci_blast_radius}` |