[REDACTED] 4.5.6 — Security Audit Report
[REDACTED] 4.5.6 — Отчёт по аудиту безопасности
1. Application Overview
XAPK bundle contains:
- base.apk — all code (10 DEX files, 65,125 smali classes), manifest, resources, assets, 120 MB
- split_0.apk — native libraries (arm64-v8a): Sentry, DataStore, JNI dispatch, CEL uniffi, 3 MB
- split_1.apk — ExoPlayer drawable resources, ~100 KB
- split_2.apk — English translations (translation_en.zip, translation_en_tr.zip), 7.6 MB
Dynamic Feature Modules deliver translations for 8 languages: en, uk, ru, ja, ko, vi, id, th. Each has a _tr variant for traditional Chinese characters.
Embedded assets:
- [REDACTED]-hieroglyphs-db — character/hanzi database, 48 MB
- [REDACTED]-grammar-db — grammar database, 7 MB
- voices/ — audio samples (example_child/female/male.webm, practice_course symbols)
- entities/entities.zip — preset course/lesson/book structures, 912 KB
1. Обзор приложения
XAPK-бандл содержит:
- base.apk — весь код (10 DEX-файлов, 65 125 smali-классов), манифест, ресурсы, ассеты, 120 МБ
- split_0.apk — нативные библиотеки (arm64-v8a): Sentry, DataStore, JNI dispatch, CEL uniffi, 3 МБ
- split_1.apk — drawable-ресурсы ExoPlayer, ~100 КБ
- split_2.apk — английские переводы (translation_en.zip, translation_en_tr.zip), 7.6 МБ
Dynamic Feature Modules доставляют переводы для 8 языков: en, uk, ru, ja, ko, vi, id, th. У каждого есть вариант _tr для традиционных китайских иероглифов.
Встроенные ассеты:
- [REDACTED]-hieroglyphs-db, база иероглифов/ханьцзы, 48 МБ
- [REDACTED]-grammar-db, база грамматики, 7 МБ
- voices/, примеры аудио (example_child/female/male.webm, символы practice_course)
- entities/entities.zip, предустановленная структура курсов/уроков/книг, 912 КБ
2. SDK & Service Stack
- RevenueCat — subscription management, billing, entitlements
- Superwall — paywall display via WebView
- Google Play Billing v8.3.0 — payment processing (proxied via RevenueCat)
- Firebase Remote Config — kill switch
app_in_review - Firebase Crashlytics — crash reporting
- Firebase Analytics — usage analytics
- Supabase — auth (Google Sign-In) + file storage
- gRPC — primary backend data transport
- Algolia — search (folders + word lists)
- Amplitude — product analytics
- OneSignal — push notifications
- AppsFlyer — attribution
- Branch.io — deep linking
- Facebook SDK — analytics
- Sentry — error tracking
- com.pairip.licensecheck — Google Play license check
2. Стек SDK и сервисов
- RevenueCat — управление подписками, биллинг, entitlements
- Superwall — отображение paywall через WebView
- Google Play Billing v8.3.0 — обработка платежей (проксируется через RevenueCat)
- Firebase Remote Config — kill switch
app_in_review - Firebase Crashlytics — отчёты о крашах
- Firebase Analytics — аналитика использования
- Supabase — авторизация (Google Sign-In) + хранилище файлов
- gRPC — основной транспорт данных бэкенда
- Algolia — поиск (папки + списки слов)
- Amplitude — продуктовая аналитика
- OneSignal — push-уведомления
- AppsFlyer — атрибуция
- Branch.io — deep linking
- Facebook SDK — аналитика
- Sentry — трекинг ошибок
- com.pairip.licensecheck — проверка лицензии Google Play
3. Trial Implementation
3.1. Client-Side 7-Day Trial
File: SubscriptionUseCaseImpl.java, method checkAppInstalledDate()
The trial is NOT a Google Play free trial. It is entirely client-side:
- On first launch, app saves
appInstalledDateas Long timestamp in Android DataStore checkAppInstalledDate()reads the saved date- If installDate + 7 days > current date → returns true, full premium access
Logic: DateExtensionsKt.addDays(new Date(appInstalledDate), 7).after(new Date())
Vulnerability: Trivially bypassed by clearing app data — resets install date and grants a new 7-day trial infinitely. Timestamp stored in DataStore (SharedPreferences-based), modifiable with root access or via backup/restore.
Mitigation: Bind trial to user account. No account = no trial. Server-side tracking of trial activation date by user_id. Client only displays state; backend makes the decision.
3.2. Server-Side 2-Day Access
File: SubscriptionUseCaseImpl.java, method checkServerProductFlag()
- Reads
userSubscriptionFromServerUpdateDatefrom DataStore - If within
SERVER_PRODUCT_DAYS_DURATION = 2days → grants premium - Presumably for promos or support-granted temporary access
- Same client-side date-check vulnerability as the trial
Mitigation: Same as trial — server-side control. Client queries backend for current access state rather than storing date locally.
3.3. App Review Bypass
File: SubscriptionUseCaseImpl.java, method isAppInReview()
FirebaseRemoteConfig.getBoolean("app_in_review"), default false, refresh interval 3600 seconds.
Risk: When set to true via Firebase Remote Config, ALL users receive premium access unconditionally. Intended for App Store review periods. If Firebase project is compromised → universal premium bypass.
Mitigation: Scope the flag — combine with specific build number or version code. Or use a separate flavor/build type for review instead of Runtime Config that applies to everyone.
3.4. Daily Feature Limits (Free/Trial Users)
File: FeatureDayLimits.java
writtenWordsCountLimitation— daily written word limitexamplesShownCountLimitation— daily shown examples limitlessonPlusAvailabilityDate— premium lesson access restriction
3. Реализация триала
3.1. Клиентский 7-дневный триал
Файл: SubscriptionUseCaseImpl.java, метод checkAppInstalledDate()
Триал НЕ является бесплатным пробным периодом Google Play. Он полностью клиентский:
- При первом запуске приложение сохраняет
appInstalledDateкак Long timestamp в Android DataStore checkAppInstalledDate()читает сохранённую дату- Если installDate + 7 дней > текущая дата, возвращает true, полный премиум-доступ
Логика: DateExtensionsKt.addDays(new Date(appInstalledDate), 7).after(new Date())
Уязвимость: тривиально обходится очисткой данных приложения, сбрасывает дату установки и даёт новый 7-дневный триал бесконечно. Timestamp хранится в DataStore (на базе SharedPreferences), модифицируется с root-доступом или через backup/restore.
Митигация: привязать триал к аккаунту пользователя. Без аккаунта — нет триала. Серверсайд трекинг даты активации триала по user_id. Клиент только отображает состояние, решение принимает бэкенд.
3.2. Серверный 2-дневный доступ
Файл: SubscriptionUseCaseImpl.java, метод checkServerProductFlag()
- Читает
userSubscriptionFromServerUpdateDateиз DataStore - Если в пределах
SERVER_PRODUCT_DAYS_DURATION = 2дней, предоставляет премиум - Предположительно для промоакций или временного доступа от поддержки
- Та же клиентская уязвимость с проверкой даты, что и у триала
Митигация: аналогично триалу, серверсайд контроль. Клиент запрашивает у бэкенда текущий статус доступа, а не хранит дату локально.
3.3. Обход при ревью приложения
Файл: SubscriptionUseCaseImpl.java, метод isAppInReview()
FirebaseRemoteConfig.getBoolean("app_in_review"), по умолчанию false, интервал обновления 3600 секунд.
Риск: при значении true через Firebase Remote Config ВСЕ пользователи получают премиум-доступ безусловно. Предназначено для периодов ревью в App Store. При компрометации Firebase-проекта — универсальный обход премиума.
Митигация: ограничить область действия флага. Проверять комбинацию флага + конкретный build number или version code. Или использовать отдельный flavor/build type для ревью.
3.4. Дневные лимиты функций (бесплатные/триальные пользователи)
Файл: FeatureDayLimits.java
writtenWordsCountLimitation, дневной лимит написанных словexamplesShownCountLimitation, дневной лимит показанных примеровlessonPlusAvailabilityDate, ограничение доступа к премиум-урокам
4. Subscription Implementation
4.1. Architecture
Three-tier stack:
- Google Play Billing v8.3.0 — base payment processor
- RevenueCat — subscription state, entitlements, offerings
- Superwall — paywall display via WebView
Single entitlement: "[REDACTED]_plus" — all plans grant identical premium access.
4.2. Products
File: SubscriptionManager.java, method fetchProducts()
- WEEK — weekly RevenueCat package
- MONTH — monthly RevenueCat package
- YEAR — annual RevenueCat package, selected by default in paywall
- LIFETIME_FULL — lifetime RevenueCat package
- PRACTICE_COURSE — custom "Course" package, separate offering
- ONBOARD — custom "AnnualCourse" package, for onboarding
4.3. Premium Decision Chain
File: SubscriptionUseCaseImpl.java, method updateProducts()
MutableStateFlow<Boolean>, initialized false — single source of truth. Decision chain:
isLaoshiPlusEnabled()already true? → return, skip everythingcheckAppInstalledDate()true? → state = true (7-day trial)checkServerProductFlag()true? → state = true (2-day server grant)- Recheck enabled? → return
isAppInReview()true? → state = true, return (Firebase flag)isAppInstallerValid(context)false? → return (abort, don't check RevenueCat)- Query RevenueCat entitlements → if active: state = true
Mitigation: Move subscription check to backend. Client requests server with auth token; server checks subscription via RevenueCat S2S API and returns current state.
4.4. RevenueCat Configuration
File: [REDACTED]Application.java, method initializeRevenueCat()
- API key: [REDACTED]
- Proxy URL: [REDACTED] (non-standard RevenueCat endpoint)
- Log level: ERROR
- Collects: device identifiers, Facebook Anonymous ID, user email
4.5. Superwall Paywall
Files: SuperwallContainer.java, RevenueCatPurchaseController.java
- API key: [REDACTED]
- One registered placement:
"main_paywall" - Custom attributes for targeting: email, name, language, is_premium, registration_date, onboard_is_finished
4.6. Installer Validation
File: ContextExtensionsKt.java, method isAppInstallerValid()
public static final boolean isAppInstallerValid(Context context) {
return true; // HARDCODED — no validation
}
Mitigation: Enable real installer check. More robust: server-side verification of APK signing certificate hash per request.
4. Реализация подписок
4.1. Архитектура
Трёхуровневый стек:
- Google Play Billing v8.3.0, базовый платёжный процессор
- RevenueCat, состояние подписки, entitlements, offerings
- Superwall, отображение paywall через WebView
Единственный entitlement: "[REDACTED]_plus", все тарифы подписки дают идентичный премиум-доступ.
4.2. Продукты
Файл: SubscriptionManager.java, метод fetchProducts()
- WEEK — weekly пакет RevenueCat
- MONTH — monthly пакет RevenueCat
- YEAR — annual пакет RevenueCat, выбран по умолчанию в paywall
- LIFETIME_FULL — lifetime пакет RevenueCat
- PRACTICE_COURSE — кастомный пакет "Course", отдельный offering
- ONBOARD — кастомный пакет "AnnualCourse", для онбординга
4.3. Цепочка принятия решения о премиуме
Файл: SubscriptionUseCaseImpl.java, метод updateProducts()
MutableStateFlow<Boolean>, инициализирован false — единственный источник истины. Цепочка решений:
isLaoshiPlusEnabled()уже true? → return, пропустить всёcheckAppInstalledDate()true? → state = true (7-дневный триал)checkServerProductFlag()true? → state = true (2-дневный серверный грант)- Повторная проверка? → return
isAppInReview()true? → state = true, return (флаг Firebase)isAppInstallerValid(context)false? → return (прервать, не проверять RevenueCat)- Запросить entitlements RevenueCat → если активен: state = true
Митигация: вынести проверку подписки на бэкенд. Клиент запрашивает сервер с токеном авторизации, сервер проверяет статус через RevenueCat S2S API.
4.4. Конфигурация RevenueCat
Файл: [REDACTED]Application.java, метод initializeRevenueCat()
- API-ключ: [REDACTED]
- Proxy URL: [REDACTED] (не стандартный эндпоинт RevenueCat)
- Уровень логирования: ERROR
- Собирает: идентификаторы устройства, Facebook Anonymous ID, email пользователя
4.5. Paywall Superwall
Файлы: SuperwallContainer.java, RevenueCatPurchaseController.java
- API-ключ: [REDACTED]
- Зарегистрирован один placement:
"main_paywall" - Пользовательские атрибуты для таргетинга: email, name, language, is_premium, registration_date, onboard_is_finished
4.6. Валидация установщика
Файл: ContextExtensionsKt.java, метод isAppInstallerValid()
public static final boolean isAppInstallerValid(Context context) {
return true; // ЗАХАРДКОЖЕНО, никакой валидации
}
Митигация: включить реальную проверку установщика. Более надёжный вариант: серверсайд верификация хэша подписи APK при каждом запросе.
5. Dynamic Content Loading
5.1. Backend Endpoints
- [REDACTED]:1443 — gRPC, main data service (10 services)
- [REDACTED]/v2/json/ — REST, Content API v2
- [REDACTED]/v1/ — REST, Legacy API v1
- [REDACTED]/v3/ — REST, authentication
- [REDACTED] — REST, cloud storage/sync
- [REDACTED] — Supabase, auth + file storage
- [REDACTED] — CDN, voice audio files
- [REDACTED] — REST, RevenueCat proxy
- Algolia [REDACTED] — Search API
5.2. REST API Endpoints (Retrofit)
Main Service (base: [REDACTED]):
- GET entities — courses/lessons/books (pagination by date, page, language)
- GET words — word data (pagination by date, page, language, traditional flag)
- GET words/id — voice updates by date
- GET changelog — app news by language and platform
Auth Service (base: [REDACTED]): GET mail/register, POST otp
Cloud Service (base: [REDACTED]): POST simple-sharing-upload, GET simple-sharing-download, GET remove-user-data
Slack Service (base: [REDACTED]): POST [REDACTED] — teacher info to Slack webhook
5.3. gRPC Services
File: GrpcServiceFactory.java, connection: [REDACTED]:1443
All services authenticate via Supabase JWT token in gRPC metadata headers.
- UserService — account management, login time, settings
- WordStatisticsService — word learning statistics sync
- CustomListsService — user word list CRUD
- FoldersService — folder management
- FocusedService — focused learning content
- CollectionsService — content collections
- RecommendationsService — personalized recommendations
- FavouriteFoldersService — favorite folders sync
- FavouriteListsService — favorite lists sync
- BlacklistService — blocked content management
5.4. Supabase
File: Supabase.java
- URL: [REDACTED]
- Key: [REDACTED] (role: anon, expires 2033)
- Modules: Auth + Storage
5. Динамическая загрузка контента
5.1. Эндпоинты бэкенда
- [REDACTED]:1443 — gRPC, основной сервис данных (10 сервисов)
- [REDACTED]/v2/json/ — REST, Content API v2
- [REDACTED]/v1/ — REST, Legacy API v1
- [REDACTED]/v3/ — REST, аутентификация
- [REDACTED] — REST, облачное хранилище/синхронизация
- [REDACTED] — Supabase, авторизация + хранилище файлов
- [REDACTED] — CDN, аудиофайлы голосов
- [REDACTED] — REST, прокси RevenueCat
- Algolia [REDACTED] — Search API
5.2. REST API эндпоинты (Retrofit)
Основной сервис (база: [REDACTED]):
- GET entities — курсы/уроки/книги (пагинация по дате, странице, языку)
- GET words — данные слов (пагинация по дате, странице, языку, флаг традиционных)
- GET words/id — обновления голосов по дате
- GET changelog — новости приложения по языку и платформе
Auth-сервис (база: [REDACTED]): GET mail/register, POST otp
Cloud-сервис (база: [REDACTED]): POST simple-sharing-upload, GET simple-sharing-download, GET remove-user-data
Slack-сервис (база: [REDACTED]): POST [REDACTED] — информация об учителе в Slack webhook
5.3. gRPC-сервисы
Файл: GrpcServiceFactory.java, подключение: [REDACTED]:1443
Все сервисы аутентифицируются через Supabase JWT токен в metadata-заголовках gRPC.
- UserService — управление аккаунтом, время входа, настройки
- WordStatisticsService — синхронизация статистики изучения слов
- CustomListsService — CRUD пользовательских списков слов
- FoldersService — управление папками
- FocusedService — контент фокусированного обучения
- CollectionsService — коллекции контента
- RecommendationsService — персонализированные рекомендации
- FavouriteFoldersService — синхронизация избранных папок
- FavouriteListsService — синхронизация избранных списков
- BlacklistService — управление заблокированным контентом
5.4. Supabase
Файл: Supabase.java
- URL: [REDACTED]
- Ключ: [REDACTED] (роль: anon, истекает в 2033)
- Модули: Auth + Storage
6. License Check (com.pairip.licensecheck)
6.1. Mechanism
Files: LicenseActivity.java, LicenseClient.java, LicenseClient$1.java
Google Play license check runs on app start:
- LicenseClient starts license verification on initialization
- On failure, launches LicenseActivity with ActivityType:
- PAYWALL (ordinal 0) — shows Google Play paywall + kills app
- ERROR (ordinal 1) — shows error dialog + kills app
closeApp()callsfinishAndRemoveTask()thenSystem.exit(0)
6.2. Missing Anti-Tamper
- No SafetyNet / Play Integrity API calls detected
- No root detection
- No Frida detection
- No signature verification
- No debugger detection
Mitigation: Implement Play Integrity API for device and app integrity verification. Send verdict to backend for server-side verification.
6. Проверка лицензии (com.pairip.licensecheck)
6.1. Механизм
Файлы: LicenseActivity.java, LicenseClient.java, LicenseClient$1.java
Проверка лицензии Google Play запускается при старте приложения:
- LicenseClient запускает проверку лицензии при инициализации
- При неудаче запускает LicenseActivity с ActivityType:
- PAYWALL (ordinal 0), показывает paywall Google Play + убивает приложение
- ERROR (ordinal 1), показывает диалог ошибки + убивает приложение
closeApp()вызываетfinishAndRemoveTask()затемSystem.exit(0)
6.2. Отсутствие защиты от модификации
- Вызовы SafetyNet / Play Integrity API не обнаружены
- Нет детекции root
- Нет детекции Frida
- Нет проверки подписи
- Нет детекции отладчика
Митигация: внедрить Play Integrity API для проверки целостности устройства и приложения. Verdict отправлять на бэкенд для серверсайд верификации.
7. Hardcoded Secrets & API Keys
7.1. BuildConfig.java
- REVENUE_CAT_KEY: [REDACTED]
- SUPERWALL_API_KEY: [REDACTED]
- SUPABASE_URL: [REDACTED]
- SUPABASE_KEY: [REDACTED]
- ALGOLIA_API_KEY: [REDACTED]
- AMPLITUDE_KEY: [REDACTED]
- ONE_SIGNAL_APP_ID: [REDACTED]
- GOOGLE_SIGN_IN_KEY: [REDACTED]
- GRPC_HOST: [REDACTED]
- All backend URLs: [REDACTED]
7.2. Other Locations
- Basic Auth credentials: [REDACTED] / [REDACTED] in BasicAuthKt.java
- Slack Webhook: [REDACTED] in SlackService.java
- Google API Key: [REDACTED] in strings.xml
- Firebase project: [REDACTED] in strings.xml
Mitigation: Basic Auth credentials must be removed from code. Slack webhook should be revoked and moved server-side. Remaining keys are standard for mobile but combined with weak server validation simplify reverse engineering.
7. Захардкоженные секреты и API-ключи
7.1. BuildConfig.java
- REVENUE_CAT_KEY: [REDACTED]
- SUPERWALL_API_KEY: [REDACTED]
- SUPABASE_URL: [REDACTED]
- SUPABASE_KEY: [REDACTED]
- ALGOLIA_API_KEY: [REDACTED]
- AMPLITUDE_KEY: [REDACTED]
- ONE_SIGNAL_APP_ID: [REDACTED]
- GOOGLE_SIGN_IN_KEY: [REDACTED]
- GRPC_HOST: [REDACTED]
- Все URL бэкенда: [REDACTED]
7.2. Другие места
- Basic Auth креденшелы: [REDACTED] / [REDACTED] в BasicAuthKt.java
- Slack Webhook: [REDACTED] в SlackService.java
- Google API Key: [REDACTED] в strings.xml
- Firebase проект: [REDACTED] в strings.xml
Митигация: Basic Auth креденшелы убрать из кода. Slack webhook отозвать и вынести URL на серверсайд. Остальные ключи стандартны для мобилок, но в связке со слабой серверной валидацией упрощают реверс.
8. Applied & Verified Patches
8.1. Patch Summary
Four smali patches applied to the decoded APK:
SubscriptionUseCaseImpl.smali—isLaoshiPlusEnabled()always returns trueSubscriptionUseCaseImpl.smali—checkAppInstalledDate()always returns true (infinite trial)LicenseActivity.smali—onStart()callsfinish()immediately (skip license check)LicenseClient$1.smali—run()is no-op instead ofSystem.exit(0)
8.2. Runtime Verification (Emulator, Medium_Phone_API_35)
02-09 23:35:16.161 D [REDACTED]_PATCH: LicenseActivity.onStart() PATCHED -> finish() immediately 02-09 23:35:16.943 D [REDACTED]_PATCH: isLaoshiPlusEnabled() PATCHED -> always true 02-09 23:35:50.096 D [REDACTED]_PATCH: isLaoshiPlusEnabled() PATCHED -> always true 02-09 23:36:32.994 D [REDACTED]_PATCH: isLaoshiPlusEnabled() PATCHED -> always true
- License check bypassed — app not killed
- Premium access check fires on every check, all return true
- Premium features accessible (voice download initiated successfully)
8. Применённые и проверенные патчи
8.1. Сводка патчей
Четыре smali-патча применены к декодированному APK:
SubscriptionUseCaseImpl.smali—isLaoshiPlusEnabled()всегда возвращает trueSubscriptionUseCaseImpl.smali—checkAppInstalledDate()всегда возвращает true (бесконечный триал)LicenseActivity.smali—onStart()вызываетfinish()немедленно (пропуск проверки лицензии)LicenseClient$1.smali—run()no-op вместоSystem.exit(0)
8.2. Верификация (эмулятор, Medium_Phone_API_35)
02-09 23:35:16.161 D [REDACTED]_PATCH: LicenseActivity.onStart() PATCHED -> finish() immediately 02-09 23:35:16.943 D [REDACTED]_PATCH: isLaoshiPlusEnabled() PATCHED -> always true 02-09 23:35:50.096 D [REDACTED]_PATCH: isLaoshiPlusEnabled() PATCHED -> always true 02-09 23:36:32.994 D [REDACTED]_PATCH: isLaoshiPlusEnabled() PATCHED -> always true
- Проверка лицензии обойдена, приложение не убито
- Проверка премиум-доступа срабатывает при каждой проверке, все возвращают true
- Премиум-функции доступны (загрузка голосов инициирована успешно)
9. Security Findings Summary
Critical
- Client-side 7-day trial: Pure timestamp check in DataStore, no server validation. Unlimited trials via app data clearing.
isAppInstallerValid()hardcoded to true: Sideload protection completely absent.app_in_reviewFirebase flag grants universal premium: One Remote Config key = full premium for all users.
High
- Single MutableStateFlow<Boolean>: Trivial Frida hook target. One boolean controls all premium.
- No modification/root/debug detection: APK can be freely patched, debugged, and hooked.
- Supabase anon JWT hardcoded, expires 2033: Data security entirely depends on RLS policies.
Medium
- Basic Auth credentials in code: CloudService endpoints accessible.
- Slack webhook token exposed: Can post to team Slack channel.
- RevenueCat proxied through non-standard endpoint: Unclear security posture.
- Server product flag also client-side: Same DataStore timestamp vulnerability.
Low / Informational
- All API keys in BuildConfig — standard for mobile but enables enumeration.
- Subscription notification delay timings exposed (2d / 6d).
- Spaced repetition intervals exposed (2h, 23h, 5d, 19d, 120d, 180d).
- DB_UPDATE_DATE = 2023-06-09 — embedded database age revealed.
9. Сводка находок безопасности
Критические
- Клиентский 7-дневный триал: чистая проверка timestamp в DataStore, без серверной валидации. Неограниченные триалы через очистку данных.
isAppInstallerValid()захардкожен в true: защита от сайдлоада полностью отсутствует.- Флаг
app_in_reviewв Firebase даёт универсальный премиум: один ключ Remote Config = полный премиум для всех.
Высокие
- Один MutableStateFlow<Boolean>: тривиальная цель для хука Frida, один boolean контролирует весь премиум.
- Отсутствие детекции модификации/root/отладки: APK можно свободно патчить, отлаживать и хукать.
- Supabase anon JWT захардкожен, истекает в 2033: безопасность данных полностью зависит от политик RLS.
Средние
- Basic Auth креденшелы в коде: эндпоинты CloudService доступны.
- Токен Slack webhook раскрыт: можно постить в Slack-канал команды.
- RevenueCat проксируется через нестандартный эндпоинт: неясная безопасность.
- Серверный флаг продукта тоже клиентский: та же уязвимость с timestamp в DataStore.
Низкие / Информационные
- Все API-ключи в BuildConfig — стандартно для мобилок, но позволяет перечисление.
- Тайминги напоминаний о подписке раскрыты (2д / 6д).
- Интервалы интервального повторения раскрыты (2ч, 23ч, 5д, 19д, 120д, 180д).
- DB_UPDATE_DATE = 2023-06-09 — возраст встроенной базы раскрыт.
10. Prioritized Recommendations
Priority 1 — Quick Wins
- Bind trial to user account, remove client-side timestamp
- Enable real validation in
isAppInstallerValid() - Revoke and move Slack webhook server-side
- Rotate Basic Auth credentials, move server-side
Priority 2 — Architecture Changes
- Server-side subscription check via RevenueCat S2S API
- Play Integrity API for device and app integrity verification
- APK signature hash verification on backend per request
Priority 3 — Hardening
- Basic root/Frida/Xposed detection
- Obfuscation of critical classes (SubscriptionUseCaseImpl, etc.)
- Scope
app_in_reviewflag to specific builds
10. Рекомендации по приоритету
Приоритет 1 — Быстрые победы
- Привязать триал к аккаунту, убрать клиентский timestamp
- Включить реальную проверку в
isAppInstallerValid() - Отозвать и переместить Slack webhook на серверсайд
- Сменить Basic Auth креденшелы, вынести на серверсайд
Приоритет 2 — Изменения архитектуры
- Серверсайд проверка подписки через RevenueCat S2S API
- Play Integrity API для проверки целостности устройства и приложения
- Верификация хэша подписи APK на бэкенде при каждом запросе
Приоритет 3 — Hardening
- Базовая детекция root/Frida/Xposed
- Обфускация критических классов (SubscriptionUseCaseImpl и т.д.)
- Ограничить область действия флага
app_in_review
11. Tools Used
- apktool 2.12.1 — APK decoding/rebuilding, smali editing
- jadx — Java decompilation for code review
- Android SDK (adb, zipalign, apksigner) — installation, alignment, signing
- Android Emulator — runtime testing
- logcat — patch verification via [REDACTED]_PATCH tag
11. Использованные инструменты
- apktool 2.12.1 — декодирование/пересборка APK, редактирование smali
- jadx — Java-декомпиляция для ревью кода
- Android SDK (adb, zipalign, apksigner) — установка, выравнивание, подпись
- Android Emulator — тестирование в рантайме
- logcat — верификация патчей по тегу [REDACTED]_PATCH