46 lines
3.5 KiB
Markdown
46 lines
3.5 KiB
Markdown
<h1 align="center">Grant Residue</h1>
|
||
|
||
<p align="center">
|
||
<img src="https://img.shields.io/badge/category-Forensic-blueviolet" alt="Forensic"/>
|
||
<img src="https://img.shields.io/badge/difficulty-medium-orange" alt="medium"/>
|
||
</p>
|
||
|
||
В архиве `grant_residue_case.zip` находится выгрузка SOC. В материалах инцидента описаны отправка кода финансовому отделу под предлогом передачи встречи и последующий экспорт Microsoft Graph. Пароль не менялся, MFA прошла штатно. По журналам определяем, какой поток авторизации использовался для доступа.
|
||
|
||
## Решение
|
||
|
||
Начинаем с рабочей станции, затем идём по облачным журналам:
|
||
|
||
| Файл | Что ищем |
|
||
|---|---|
|
||
| `endpoint/powershell_transcript.txt` | Код, который видела жертва |
|
||
| `endpoint/defender_device_events.jsonl` | Подтверждение того же кода |
|
||
| `entra/signin_logs.jsonl` | Успешный вход и `correlationId` |
|
||
| `entra/audit_logs.jsonl` | Выданный OAuth-грант |
|
||
| `m365/unified_audit_log.jsonl` | Экспорт с этим грантом |
|
||
|
||
В транскрипте PowerShell находим `FJQ9-L2RM` — строку формата `[A-Z0-9]{4}-[A-Z0-9]{4}`. Тот же код есть в телеметрии Defender, в `AdditionalFields.UserCodeObserved`.
|
||
|
||
Теперь фильтруем входы Entra: `authenticationProtocol == "DeviceCode"`, `userCodeEvidence == "FJQ9-L2RM"`, `status.errorCode == 0`. Получаем успешную авторизацию через device code flow и её `correlationId`.
|
||
|
||
Пароль не менялся, поскольку жертва прошла штатный вход, но подтвердила код чужого запроса. Теперь выясняем, какие права получила эта авторизация.
|
||
|
||
В `entra/audit_logs.jsonl` находим `Consent to application` с тем же `correlationId` и `result == "success"`. Среди `targetResources[].modifiedProperties[]` читаем `newValue` свойства `OAuth2PermissionGrant.Id`. Сохраняем полный идентификатор для сопоставления событий, а его хвост после последнего дефиса, `grant72`, используем в ответе.
|
||
|
||
Проверяем последствия в M365. Нужна запись `GraphExportJobCreated`, где одновременно совпадают `CorrelationId` и `OAuth2PermissionGrantId`, а `ResultStatus == "Succeeded"`. Из `ExportId` берём хвост `export18`.
|
||
|
||
Формат ответа собирается так:
|
||
|
||
```python
|
||
parts = ["dc", "FJQ9-L2RM".replace("-", "").lower(), "grant72", "export18"]
|
||
flag = "caplag{" + "_".join(parts) + "}"
|
||
```
|
||
|
||
`dc` обозначает найденный поток. Остальные части связывают наблюдение кода на рабочей станции с успешным входом, выдачей согласия и последующим экспортом. Совпадение идентификаторов на каждом переходе подтверждает эту связь.
|
||
|
||
[Солвер](solve/solve.py).
|
||
|
||
## Флаг
|
||
|
||
`caplag{dc_fjq9l2rm_grant72_export18}`
|