Launchlet

Web medium

Launchlet — конструктор приложений с preview, панелью деплоя и API данных. На старте есть URL сервиса. В клиентских манифестах находятся токены, идентификаторы и описания трёх серверных операций с неполной проверкой прав. ## Решение Открываем DevTools и смотрим, что загружает интерфейс: | Ресурс | Что узнаём | |---|---| | `/api/app/bootstrap` | Параметры preview и API данных | | `/assets/build-manifest.json` | Токен получения build receipt | | `/assets/mock-supabase-client.js` | Ключ API и гостевую сессию | | `/assets/server-reference-manifest.json` | Идентификатор серверного действия | ### Токен receipt В `build-manifest.json` находим проект `launchlet-demo-board`, `previewReceipt = lrec_preview_demo_2026` и адрес `/api/receipts/redeem`. Пробуем предъявить найденный токен: ```bash curl -sS http://:31380/api/receipts/redeem \ -H 'Content-Type: application/json' \ --data '{"receipt":"lrec_preview_demo_2026","project":"launchlet-demo-board"}' ``` Сервер принимает его как право прочитать receipt и возвращает первый флаг. Build pipeline оставил в публичном артефакте повторно используемый токен доступа. ### Доступ к workspace Из bootstrap или клиентского JS берём `anonKey = ll_anon_mock_publishable` и сессию `preview_guest_session`. Интерфейс читает `workspace_notes` с фильтром `workspace_id=eq.ws-launchlet-public`. Проверяем, ограничивает ли сессия доступ к строкам. В имени workspace заменяем `public` на `internal`, сохраняя гостевой ключ и сессию: ```bash curl -sS 'http://:31380/api/db/rest/v1/workspace_notes?select=workspace_id,title,body&workspace_id=eq.ws-launchlet-internal' \ -H 'apikey: ll_anon_mock_publishable' \ -H 'Authorization: Bearer preview_guest_session' ``` Получаем внутреннюю заметку со вторым флагом. API проверяет ключ и наличие preview-сессии, но выбирает доступные строки по переданному query-параметру. Workspace сессии с ним не сверяется. ### Область серверного действия В `server-reference-manifest.json` открываем запись `serverActions.inspectDeploy`. Из неё берём идентификатор `act_6f4b9a2c1d0e8a71` и endpoint `/api/actions/invoke`. Манифест также описывает `intendedScope` — область действия. Проверяем, привязывает ли сервер переданные аргументы к этой области. Для вызова нужны оба идентификатора, `workspaceId` и `projectId`. Проверяем пару `ws-launchlet-internal` и `proj-enterprise-rollout`, сохраняя публичный проект в заголовке `X-Launchlet-Preview`. ```bash curl -sS http://:31380/api/actions/invoke \ -H 'Content-Type: application/json' \ -H 'X-Launchlet-Preview: launchlet-demo-board' \ --data '{ "actionId":"act_6f4b9a2c1d0e8a71", "args":{ "workspaceId":"ws-launchlet-internal", "projectId":"proj-enterprise-rollout" } }' ``` Dispatcher принимает чужие workspace и project с гостевым preview-контекстом и возвращает третий флаг. Проверка идентификатора действия есть, а привязки аргументов к разрешённой области нет. В этой ветке, как и при чтении заметок, параметры клиента позволяют выбрать чужие данные. [Солвер](solve/solve.py). ## Все этапы | Этап | Флаг | |---|---| | 1 | `caplag{preview_manifest_kept_the_receipt}` | | 2 | `caplag{anon_key_needs_real_rls}` | | 3 | `caplag{server_actions_need_scope_checks}` |