Skip to content

Latest commit

 

History

History
407 lines (316 loc) · 25.5 KB

File metadata and controls

407 lines (316 loc) · 25.5 KB

Авторизация и состояние сессии

Документ описывает только то, что подтверждено сохранёнными HAR, JavaScript- бандлами, декомпилированным актуальным Android-клиентом и безопасными live- пробами. Значения cookies, финансовые ответы и другие секреты здесь не приводятся.

Короткий вывод

Для актуальных web-endpoints балансов и операций не найден OAuth-подобный access_token и не используется Authorization: Bearer ....

Клиентская схема — cookie-backed web session:

post-login cookie bundle
        |
        +--> UFS / PLSBOL: withCredentials + Cookie
        |
        +--> CBRO sync: POST /web/auth/api/v2/token + Cookie
        |                    |
        |                    +--> settings/session marker, не access_token
        |
        +--> warm-up: POST /api/warmUpSession + Cookie

UFS-TOKEN — наиболее очевидный token-like элемент, но это Secure/HttpOnly cookie, а не Bearer-токен. Код Android-клиента выделяет минимальный кандидат из трёх значений: UFS-SESSION, UFS-TOKEN, SWJSESSIONID. Live web-тест сузил этот набор до UFS-SESSION и UFS-TOKEN.

Не смешивать две разные схемы

Legacy mobile prelogin token

В старом мобильном клиенте из MEJIOMAH17/sberbank-api слово token означает промежуточный результат входа:

  1. POST /CSAMAPI/login.do возвращает response/loginData/token.
  2. Значение сразу передаётся form-полем token в POST /mobile9/postCSALogin.do.
  3. Результатом обмена становится JSESSIONID из Set-Cookie.
  4. Последующие legacy-запросы идут уже в cookie-сессии.

Доказательства:

  • work/MEJIOMAH17-sberbank-api/src/main/kotlin/ru/mekosichkin/sberbank/api/SberbankLogining.kt:18-21 — извлечение response/loginData/token и вызов postCSALogin;
  • тот же файл :57-87 — token передаётся в body, результат берётся из Set-Cookie: JSESSIONID;
  • work/Personal-Finance-App/src/main/java/financeapp/bankConnection/sberbank/api/calls/SberbankApi.java:35-47 — отдельные login, postCSALogin и private endpoints.

Это prelogin exchange token, а не долго живущий access token. Он не должен попадать в SessionBundle, не должен сохраняться после обмена и не доказан как часть актуального web-flow. Старый код использует мобильные endpoints, версии и порт другого поколения; их текущая работоспособность не проверялась.

Актуальная web session

Сохранённый web capture начинается уже после входа. Auth shell делает GET {ufsHost}/auth/web с credentials: include, а business-клиенты включают withCredentials=true. Ни shell, ни UFS-interceptor не извлекают access token и не добавляют Bearer header.

Доказательства:

  • work/har-js/pl-res.online.sberbank.ru/csa.startup/1.2.1/index.js:356credentials:"include";
  • тот же файл :2338/auth/web;
  • work/har-js/pl-res.online.sberbank.ru/webpage.provider.bootstrap/1.23.0/bootstrap-plugins.js:9083e.withCredentials=!0;
  • тот же файл :22474 — добавляется X-Requested-With, но не auth header;
  • products region.products.cards/3.4.1/index.js:1522263/main-screen/rest/v2/m1/web/section/meta;
  • operations region.operations/3.9.0/index.js:1072554/uoh-bh/v1/operations/list.

Полный скан 50 сохранённых .js/.map файлов (20 772 421 байт) дал ноль совпадений для access_token, refresh_token и Bearer. Встречи слова Authorization относятся к generic Axios Basic Auth, парсеру заголовков и статусу банковской операции AUTHORIZATION, а не к авторизации UFS.

Remembered-browser PIN login

Актуальный ESA bundle содержит отдельный PIN-flow, который теперь реализован в AsyncPinAuth. Он не выпускает access token и не работает только по одному PIN: стартовое состояние — полный cookie jar уже запомненного браузера.

Последовательность фронтенда:

  1. GET /CSAFront/index.do и безопасный разбор window.config без eval.
  2. Из config берутся динамические baseApiUrl, processId, pinConfig.srp_N, pinConfig.srp_g и длина PIN.
  3. POST {baseApiUrl}/api/v1/pin/begin передаёт новый srp_A и строку deviceprint, полученную фронтендом из window.bfd.getData().
  4. Клиент вычисляет нестандартный SRP/SHA-512 srp_M; PIN не включает login.
  5. POST .../pin/logon, затем обязательная constant-time проверка srp_R.
  6. Ответ либо требует /pin/otp/confirm, либо содержит redirect.
  7. При isUfsRedirectMethodPostEnabled=true redirect выполняется пустым POST с X-Seamless-Web: true; HTML /auth/web отдаёт динамический UFS API host.
  8. После /api/front/ready успех признаётся только при наличии UFS-SESSION и UFS-TOKEN; для старого config сохранён GET fallback.

Каждый POST получает новый frontend-совместимый Rq-Uid, постоянный Process-Id текущего процесса и вращаемый X-CSRF-Token. Captcha и OTP retry реализованы теми же endpoints. Автоповторов POST нет: network failure после приёма запроса сервером нельзя безопасно отличить от failure до отправки.

Полный HAR-цикл и live-вход 2026-07-14 подтвердили относительный auth base CSAFront, remembered PIN длиной 5, 2048-bit SRP group, seamless POST redirect и динамический web-node из HTML /auth/web.

Первичный вход, SMS и создание PIN

Полный capture нового браузера содержит отдельную cold-browser цепочку, теперь реализованную в AsyncPrimaryAuth:

  1. GET /CSAFront/index.do: authTypeByCookie=start, SRP N/g, новый processId, PIN ещё отсутствует.
  2. Первый form POST /CSAFront/authMainJson.do: operation=button.begin, login, storeLogin, deviceprint, srp_A.
  3. Ответ даёт token, srp_s, srp_B; второй form POST отправляет srp_M и оба token-поля Struts/front-end.
  4. После проверки srp_R сервер возвращает NEED_CONFIRM; третий form POST отправляет SMS-код и новый token.
  5. pinInfo.publicKey используется для RSA-OAEP/SHA-1/MGF1-SHA-1 шифрования нового PIN. Открытый PIN в запрос не попадает.
  6. JSON POST /api/v1/pin/create, затем /api/v1/auth с новым Rq-Uid, Process-Id и текущим CSRF.
  7. Пустой seamless POST на /auth/web выдаёт динамические UFS hosts и cookies; /api/front/ready завершает вход.

Три form-body, заголовки, token transition, CSRF/Rq-Uid и redirect проверяются точным синтетическим контрактным тестом. Реальный 2048-bit public key из HAR успешно разобран и даёт ожидаемый 256-byte RSA ciphertext. Это подтверждает воспроизведение capture, но не означает, что cold-browser вход был повторно запущен live после реализации.

Жизненный цикл сохранённого профиля

Короткая UFS-сессия и remembered-browser state — разные уровни. Версия 3 SessionBundle хранит полный cookie jar, browser headers, raw deviceprint, опциональный opaque antifraud_deviceprint для write-запросов и последние UFS hosts. В исследованном snapshot sb_user имел срок около 45 дней, а sso_private — около 24 часов; это наблюдаемые сроки JWT/cookie, не публичный refresh-token контракт.

AsyncSberClient.from_pin_profile() делает следующее:

  1. Использует текущие UFS-SESSION/UFS-TOKEN, пока сервер их принимает.
  2. На первом redirect/401/403 сериализует релогин одним async-lock.
  3. Запускает AsyncPinAuth на полном remembered jar и том же deviceprint.
  4. После успеха атомарно сохраняет весь вращённый профиль с mode 0600.
  5. Заменяет transport и повторяет исходный read-only запрос ровно один раз.

Второй отказ, CAPTCHA/SMS challenge или отклонённый remembered state наружу передаются как typed error. Бесконечных повторов и попыток обхода серверных ограничений нет.

Роли элементов web-сессии

UFS-TOKEN

По метаданным экспортированной сессии это cookie для .online.sberbank.ru, path /, Secure и HttpOnly.

Что доказано:

  • браузер отправляет его автоматически как часть подходящего cookie jar;
  • JavaScript не может прочитать его из-за HttpOnly;
  • UFS business layer использует cookie credentials.

Что не доказано:

  • внутренний формат и семантика значения;
  • возможность использовать значение в Authorization;
  • переносимость значения между независимыми входами и устройствами.

Поэтому SDK может принимать поле ufs_token для удобства, но обязан установить его как cookie с правильными domain/path/flags. Отправлять его как Bearer нельзя.

UFS-SESSION, SWJSESSIONID и APK evidence

UFS-SESSION также имеет domain .online.sberbank.ru, path /, Secure и HttpOnly. В browser session одновременно присутствуют SWJSESSIONID, JSESSIONID, SSO и служебные cookies. Frontend не выбирает из них «главную»: withCredentials отправляет весь подходящий набор, а решение принимает сервер.

Android-клиент даёт более сильную границу, чем web JavaScript:

  • eo0/C3288a.java:22-28,46 — модель SbolAuthData состоит из ufsBlockUrl, ufsSession, ufsToken, swjSessionId;
  • Mo0/C7151a.java:24-37 — значения берутся из token source как UFS-SESSION, UFS-TOKEN, SWJSESSIONID;
  • JP0/C26069e.java:39-44 — auth-фильтр содержит ровно регулярное выражение UFS-SESSION|UFS-TOKEN|SWJSESSIONID;
  • Ly6/h.java:189-224 — ровно эти три значения устанавливаются как Secure и HttpOnly cookies на UFS host.

Это Android-модель, а не обязательный web minimum. Контролируемый web cookie-elimination показал, что SWJSESSIONID, SSO, timestamp, TS/WAF и telemetry cookies для products/operations не нужны.

Live status 2026-07-14

После свежего входа текущий browser API host оказался web-standin2.online.sberbank.ru; старый HAR содержал уже неактуальные web-node2/web2 и мобильный UA. С текущим host и штатным curl_cffi Chrome-профилем полный control вернул 200/success=true для products и operations. Сохранённый User-Agent не потребовался.

После реализации profile lifecycle отдельная live-проверка 2026-07-14 прошла через curl_cffi без браузера: remembered PIN flow обновил полный профиль, products вернул карты/счета, а scoped operations — пять последних операций. Значения финансовых данных в репозиторий не записывались.

Результат последовательного leave-one-out с control после каждой пробы:

  • UFS-SESSION + UFS-TOKEN + SWJSESSIONID — оба API 200;
  • без UFS-SESSION — products 403, следующий полный control 200;
  • без UFS-TOKEN — products 403, следующий полный control 200;
  • без SWJSESSIONID — products и operations 200, следующий control 200.

Наблюдаемый web minimum для этой сессии: UFS-SESSION и UFS-TOKEN. Оба обязательны, вместе достаточны. Автоматический warm-up удалён из business-flow: на старом web2 он дал 403, на текущем UFS host — 404, тогда как оба business endpoint стабильно отвечали 200 без warm-up.

Business host назначается сервером динамически. Первый HTML https://online.sberbank.ru/app/main передаёт startup-loader значение ufsHost; loader с credentials: include получает {ufsHost}/main. Уже этот HTML содержит options.config["ufs.block.root.url"], а UFS interceptor читает ключ при сборке абсолютного URL каждого business-запроса. В обоих сохранённых HAR значение было web-node2.online.sberbank.ru; текущий live runtime выдал web-standin2.online.sberbank.ru. В статическом bootstrap JS и Android APK актуальный web host не зафиксирован.

Поэтому browserless SDK не может вычислить host из двух UFS cookies офлайн. HAR-import извлекает request host и ufs.block.root.url. Конструктор из credentials теперь при первом business-запросе повторяет frontend bootstrap: GET /app/main -> ufsHost -> GET {ufsHost}/main -> ufs.block.root.url. Оба GET используют один cookie jar; discovery защищён lock и выполняется один раз на экземпляр клиента. Redirect/401/403 означают истёкшую сессию, а missing/conflicting/external host отклоняется без fallback. Явные api_base и web_base остаются только как override для диагностики и тестов.

/web/auth/api/v2/token для CBRO

Название endpoint не означает выдачу OAuth access token.

Фактический flow:

  1. CBRO сравнивает cookie session timestamp с marker в sessionStorage.
  2. При mismatch выполняет POST {UfsCBroHost}/web/auth/api/v2/token с body {"platform":"web"} и cookie credentials.
  3. Код читает только optional data.settings[].
  4. Затем сохраняет timestamp marker и продолжает CBRO-запрос.
  5. HTTP 404 этого sync-вызова явно допускается.

Доказательства в bootstrap-plugins.js:

  • byte 9179data:{platform:"web"};
  • byte 10280/web/auth/api/v2/token;
  • byte 10484session.cookie.name;
  • byte 10650 — чтение только .data.settings;
  • byte 11427 — recovery через /sm-uko/v3/web/session/notification.

В vendor constants webpage.provider.bootstrap/1.23.0/index.js:12960 заданы статусы 400, 401, 404; wrapper трактует 404 token-sync как допустимый ответ. Значение response header ufs-session клиентским JS не читается: такой literal отсутствует во всех сохранённых assets.

Следовательно, CBRO sync нельзя экспортировать как access token и нельзя использовать вместо UFS cookie session.

В текущем Chrome также присутствовала HttpOnly cookie JWT-Authorization, но её domain — .mc-cbro.online.sberbank.ru. По RFC scope она не отправляется на web-node2.online.sberbank.ru или web2.online.sberbank.ru, поэтому не входит в credentials для products/operations/warm-up. Значение не декодировалось и не логировалось; для отдельного CBRO SDK потребовался бы другой контракт.

Session timestamp

Runtime session.cookie.name указывает на SBTSBOL_SESSION_TIMESTAMP. Это доступный JavaScript marker поколения сессии, а не секрет авторизации.

Основной shell сравнивает его с sessionStorage["FRONT_MONITORING"]. При смене вызывает /front/ready, затем сохраняет новый marker. CBRO использует тот же принцип для cbroSessionTimestampName.

Доказательства в work/har-js/pl-res.online.sberbank.ru/index/3.8.1/index.js:

  • byte 209559/front/ready;
  • byte 254783FRONT_MONITORING;
  • byte 254813session.cookie.name;
  • byte 262002SBTSBOL_SESSION_TIMESTAMP.

Timestamp полезен для определения необходимости синхронизации, но сам по себе не авторизует products/operations.

Warm-up

Session renew plugin отправляет POST /api/warmUpSession через PLSBOL с cookie credentials. В исследованном runtime activity debounce равен 60 секундам, а frontend lifetime — 15 минут. После успеха frontend переставляет локальный таймер окончания сессии.

Доказательства в bootstrap-plugins.js:

  • byte 29274/api/warmUpSession;
  • byte 29322session.front.lifetime;
  • byte 29568session.autorenew.debounce;
  • byte 29635session.autorenew.events.

Warm-up продлевает уже существующую cookie-сессию. Это не refresh_token и не источник нового SDK-visible token. Любые Set-Cookie обновления ответа должны атомарно применяться к тому же jar.

Рекомендуемый контракт SDK

Публичная модель называется SberCredentials, а не AccessToken:

credentials = SberCredentials(
    ufs_session="...",
    ufs_token="...",
)

async with AsyncSberClient.from_credentials(credentials) as client:
    products = await client.products.get()
    rotated = client.session.credentials()

Для файлового хранения передайте session_path: SDK будет атомарно сохранять ротированные credentials после каждого успешного business/warm-up запроса.

Требования к контракту:

  • принимать только opaque auth values, без Netscape/Cookie строки;
  • назначать им domain/path/Secure/HttpOnly внутри SDK;
  • считать значения opaque: не декодировать и не пытаться определить JWT;
  • не смешивать transport identity (UA/Client Hints/TLS) с auth secrets;
  • применять server Set-Cookie к живому jar после каждого ответа;
  • возвращать ротированные credentials в память и, при заданном session_path, автоматически сохранять их;
  • не включать в bundle legacy prelogin token, Investor authCode, chat token или response header ufs-session.

SessionBundle остаётся внутренней/миграционной полной моделью cookie jar. SberCredentials.from_bundle() позволяет перейти со старого экспорта на два поля. Результат следует повторить после следующего независимого входа, прежде чем считать его универсальной гарантией для всех устройств.

Границы доказательств

Подтверждено:

  • cookie-based transport текущего web read-only flow;
  • отсутствие Bearer/access-token логики в сохранённых assets;
  • назначение CBRO sync, timestamp marker и warm-up на уровне frontend-кода;
  • наличие token-like HttpOnly cookies в post-login browser session;
  • тройка UFS-SESSION/UFS-TOKEN/SWJSESSIONID как отдельная auth-модель в Android-клиенте;
  • web minimum из UFS-SESSION и UFS-TOKEN в свежей сессии;
  • успешная работа без сохранённого User-Agent и без warm-up.
  • точные PIN/SRP формулы, endpoints, body builders, CSRF/Rq-Uid interceptors, captcha, SMS и redirect flow актуального ESA bundle;
  • безопасный live bootstrap текущего window.config без отправки PIN.
  • успешный end-to-end PIN-вход и совпадение его сетевой формы с полным HAR.
  • успешный автоматический PIN-релогин, атомарное сохранение полного профиля и последующий live products/scoped-operations read-only запрос;
  • точная cold-browser state machine и RSA public-key shape из полного HAR.

Не подтверждено:

  • повторный live-запуск cold-browser login/password и создание нового PIN после реализации (сама цепочка подтверждена полным HAR и контрактным тестом);
  • какой сервер выпускает и обновляет UFS-TOKEN/UFS-SESSION;
  • повторяемость двух-cookie minimum после независимого нового входа;
  • переносимость bundle между IP, TLS fingerprint, device identity или browser profile;
  • текущая работоспособность legacy mobile prelogin flow;
  • требования write/payment endpoints и antifraud.

SDK не заявляет выпуск access token: успешный PIN-flow возвращает полный ротированный cookie SessionBundle. Истёкшая UFS-сессия восстанавливается по PIN только пока сервер принимает remembered-browser jar; SMS/captcha всё равно могут потребовать участие пользователя.

Безопасное обращение с секретами

  • Не коммитить HAR, cookie jar, SberCredentials или экспортированный SessionBundle.
  • На диске использовать Keychain/secret store; временный файл — только 0600.
  • Не передавать секреты через CLI arguments, URL query или environment dump.
  • Редактировать repr, exceptions, traces и HTTP debug logs: показывать только имя cookie, никогда значение.
  • Запретить сериализацию SecretStr обычным json.dumps без явного opt-in.
  • Не копировать bundle между пользователями и окружениями.
  • При 401, 403 или redirect один PIN-релогин и один retry разрешены только для read-запроса. Mutation POST не переигрывается: сначала выполняется безопасный read-preflight, а неопределённый результат проверяется по продуктам/истории.
  • После logout или замены сессии удалять старый bundle и очищать jar в памяти.
  • Не пытаться продлевать сессию бесконечно и не обходить серверные ограничения.

Полный индекс доказательств и byte offsets находится в work/auth_token_deep_report.md.