Середа, 15 Квітня, 2026

Як не дати зламати ваші in-app покупки: від слабкого `isPremium` до серверної валідації

Розробник Android‑додатків і техноблогер Philipp Lackner у своєму розборі атак на in‑app покупки показує, наскільки легко обійти платні функції застосунку, якщо логіка доступу до «преміуму» побудована лише на клієнті. На прикладі простої демо‑апки він демонструє типову помилку: статус підписки зберігається як звичайний булевий прапорець у коді, який змінюється після успішної відповіді Google Play. Далі — ще важливіше: навіть використання Google Play Billing Library саме по собі не рятує від зловмисників, якщо бекенд відсутній або використовується лише формально.

code android

Цей матеріал розбирає, чому клієнтські перевірки приречені, як правильно будувати серверну валідацію покупок, які компроміси це тягне за собою для UX, і чому навіть бекенд — не «срібна куля», особливо коли преміум‑контент має працювати офлайн.

Чому булевий isPremium у клієнті — це запрошення до злому

У демо‑додатку логіка доступу до платних функцій виглядає дуже знайомо для будь‑якого Android‑розробника. Є менеджер білінгу, який працює з Google Play Billing Library, і є стан підписки, що зберігається в полі isPremium — у вигляді StateFlow, який:

  • спочатку має значення false,
  • змінюється на true після того, як білінг‑клієнт отримує від Google Play сигнал про успішну покупку та проходить локальні перевірки (чи покупка дійсно PURCHASED і чи вона acknowledged).

Після цього застосунок «нагороджує» користувача преміум‑доступом, просто перемикаючи цей прапорець. UI реагує на зміну isPremium і показує преміум‑функції.

З точки зору бізнес‑логіки це виглядає логічно: Google Play обробляє платіж, повертає результат, застосунок перевіряє його і вмикає преміум. Проблема в тому, що вся критична логіка — хто врешті‑решт вважається платним користувачем — живе в клієнті й виражена буквально одним булевим значенням. Для зловмисника це ідеальна ціль.

У розібраному прикладі достатньо:

  • знайти в декомпільованому коді місце, де isPremium ініціалізується як false,
  • змінити цю ініціалізацію на true,
  • перепакувати APK.

У результаті преміум‑статус стає активним одразу при запуску додатку, без будь‑якої взаємодії з Google Play. Жодних платежів, жодних діалогів — лише змінений прапорець.

Цей сценарій показує фундаментальну проблему: якщо клієнт самостійно вирішує, чи є користувач преміумом, то будь‑який, хто контролює клієнт (через модифікацію APK або рантайм‑хуки), може привласнити собі цей статус.

Чому Google Play Billing Library не захищає від клієнтських обходів

Google Play Billing Library часто сприймають як «офіційний» і тому нібито безпечний спосіб реалізації in‑app покупок. Вона дійсно відповідає за важливі речі: ініціює платіжний флоу, взаємодіє з Play Store, обробляє методи оплати, повертає результат операції.

Однак у типовій реалізації відбувається таке:

  1. Додаток надсилає запит на покупку через білінг‑клієнт.
  2. Google Play показує користувачу платіжний діалог, обробляє транзакцію, перевіряє платіжний метод.
  3. Після завершення операції Play повертає в застосунок об’єкт Purchase та код відповіді.
  4. Додаток аналізує відповідь і вирішує, що робити: видати преміум чи ні.

Ключова слабка ланка — останній крок. Застосунок не має вбудованого способу переконатися, що сигнал про успішну покупку дійсно надійшов від Google Play, а не був підмінений або обійдений на рівні клієнта. Бібліотека білінгу не контролює, як саме ви використовуєте її результат. Вона не може завадити вам зберігати преміум‑статус у вигляді простого булевого поля, яке легко змінити.

У демонстраційному кейсі це проявляється буквально:

  • є функція handlePurchase, яка викликається, коли білінг‑клієнт отримує відповідь із Google Play;
  • у ній виконується кілька перевірок (чи покупка завершена, чи підтверджена);
  • якщо все гаразд, викликається функція на кшталт grantPremiumAccess, яка просто ставить isPremium у true.

Для зловмисника не обов’язково навіть ламати сам білінг‑флоу. Можна:

  • змінити код, щоб isPremium був true за замовчуванням;
  • або перехопити виклик grantPremiumAccess у рантаймі й викликати його напряму, минаючи білінг.

Факт, що застосунок використовує офіційну бібліотеку, не заважає обійти все, що відбувається після отримання відповіді. Бібліотека не є засобом захисту від реверс‑інжинірингу чи рантайм‑маніпуляцій; вона лише API для роботи з магазином.

Звідси випливає принципове правило: будь‑яка логіка, яка вирішує, чи має користувач доступ до платних функцій, не повинна бути довірена клієнту. Клієнт можна змінити, підмінити, проінструментувати. Потрібен інший центр прийняття рішень.

Як працює серверна валідація покупок і чому вона зміщує центр довіри

Рекомендований підхід — перенести вирішальну логіку з клієнта на сервер. Ідея проста: лише бекенд має право остаточно сказати, чи є покупка дійсною і чи повинен користувач мати преміум‑доступ.

У безпечнішій архітектурі флоу виглядає так:

  1. Користувач ініціює покупку в застосунку.
  2. Google Play (або App Store) обробляє платіж і повертає в застосунок токен покупки.
  3. Застосунок не приймає рішення самостійно. Замість цього він відправляє цей токен на бекенд‑сервер.
  4. Бекенд, маючи власні облікові дані та доступ до API Google Play або Apple App Store, самостійно перевіряє токен:
  5. чи існує така покупка,
  6. чи вона дійсно оплачена,
  7. чи не скасована, не повернена, не прострочена.
  8. Лише після успішної перевірки сервер оновлює статус користувача у своїй базі даних: наприклад, позначає його як преміум.
  9. Коли застосунок хоче отримати доступ до преміум‑функцій, він звертається до сервера, який повертає актуальний статус (преміум чи ні).

Ключова відмінність від клієнтського підходу полягає в тому, що:

  • преміум‑статус більше не зберігається як простий булевий прапорець у застосунку;
  • він зберігається на сервері, який не підконтрольний користувачу;
  • клієнт лише відображає те, що каже сервер, а не вирішує сам.

Навіть якщо зловмисник змінить APK, підмінить локальний isPremium або спробує викликати внутрішні функції, це не дасть йому доступу до реальних серверних ресурсів, якщо бекенд перевіряє кожен запит. Для функцій, що залежать від сервера (наприклад, доступ до хмарного контенту, синхронізація, онлайн‑сервіси), це суттєво підвищує рівень захисту.

Серверна валідація також дозволяє обробляти складніші сценарії: відміни підписок, повернення коштів, зміну планів, сімейний доступ. Усі ці стани можна відстежувати централізовано, не покладаючись на те, що клієнт «поведе себе чесно».

Переваги і компроміси: інтернет‑залежність як нова проблема

Перенесення логіки на сервер робить злам значно складнішим, але створює іншу категорію викликів — залежність від мережі. Якщо преміум‑статус зберігається на бекенді, застосунку потрібно мати можливість звернутися до сервера, щоби:

  • підтвердити покупку одразу після транзакції;
  • перевірити статус при кожному запуску або при доступі до критичних функцій;
  • реагувати на зміни (наприклад, закінчення підписки).

Це означає, що для доступу до преміум‑функцій потрібне активне інтернет‑з’єднання. У реальному світі користувачі часто опиняються в офлайн‑сценаріях: у метро, у роумінгу, у зонах зі слабким покриттям. Якщо застосунок жорстко вимагає онлайн‑перевірку щоразу, це може призвести до:

  • фрустрації платних користувачів, які не можуть скористатися тим, за що заплатили;
  • негативних відгуків і падіння рейтингу;
  • збільшення навантаження на підтримку.

Тому дизайн серверної валідації потребує балансування між безпекою та зручністю. Можливі підходи включають кешування статусу на певний час, використання «грейс‑періодів» або частковий офлайн‑режим. Але важливо розуміти: що більше автономії ви даєте клієнту, то більше відкриваєте вікно для атак. Це завжди компроміс.

У будь‑якому разі фундаментальний принцип залишається незмінним: остаточне джерело правди про преміум‑статус має бути на сервері, а не в APK.

Коли бекенд не рятує: офлайн‑контент і потреба в посиленому клієнті

Серверна валідація значно ускладнює життя зловмисникам, але не вирішує всіх проблем. Особливо це помітно в сценаріях, де преміум‑контент повинен бути доступний офлайн і зберігається безпосередньо на пристрої.

Типові приклади:

  • офлайн‑словники, карти, навчальні матеріали;
  • медіаконтент, який завантажується на пристрій для перегляду без інтернету;
  • професійні інструменти, де критичні функції мають працювати у польових умовах.

У таких випадках навіть якщо сервер чесно підтвердив покупку і зберігає статус користувача, сам факт, що контент або логіка доступу до нього знаходяться на пристрої, створює нову площину атаки. Зловмисник може:

  • модифікувати APK, щоб обійти перевірки, які застосунок робить перед показом офлайн‑контенту;
  • використати рантайм‑інструменти, щоб підмінити результат локальних перевірок;
  • безпосередньо витягти й розшифрувати файли з контентом, якщо вони зберігаються без належного захисту.

У розглянутій демо‑апці це проявляється в найпростішій формі: преміум‑статус — це всього лише булевий прапорець, який можна змінити в smali‑коді. Але навіть якщо ви перенесете основну логіку на сервер, будь‑які локальні перевірки, що залишаться, можуть стати мішенню.

Саме тому в рекомендаціях фігурує ідея комбінувати серверну валідацію з посиленим захистом клієнта. Як приклад згадується DexGuard від Guardsquare — інструмент, який виходить далеко за межі звичайної обфускації R8/ProGuard. На відміну від стандартних засобів, що переважно перейменовують класи й методи для зменшення розміру коду, спеціалізовані рішення орієнтовані саме на безпеку: ускладнюють реверс‑інжиніринг, додають захист від тамперингу, детектують рутовані середовища та інструменти на кшталт Frida.

Суть підходу така:

  • серверна валідація відповідає за те, щоб лише реальні, підтверджені покупки потрапляли в систему;
  • посилений клієнтський захист робить максимально складним підроблення локальних перевірок, модифікацію коду та витяг офлайн‑контенту.

Жоден із цих рівнів не є достатнім сам по собі, але разом вони суттєво піднімають поріг входу для атак, переводячи їх із «доступних кожному з туторіалом» у категорію, що вимагає значних зусиль і експертизи.

Висновок: центр довіри має бути на сервері, а клієнт — максимально «твердим»

Сучасні in‑app покупки — головне джерело доходу для багатьох мобільних застосунків, але водночас і одна з найвразливіших частин архітектури. Демо‑приклад із isPremium як простим булевим прапорцем показує, наскільки небезпечно покладатися на клієнт у вирішенні питання «хто заплатив».

Google Play Billing Library або App Store API забезпечують платіжну інфраструктуру, але не захищають від того, що відбувається всередині вашого APK. Якщо застосунок самостійно вирішує, чи є покупка дійсною, зловмисник завжди зможе змінити цю логіку — через реверс‑інжиніринг, модифікацію smali‑коду чи рантайм‑хуки.

Більш безпечна модель передбачає:

  • серверну валідацію токенів покупок безпосередньо з Google Play або App Store;
  • зберігання преміум‑статусу на бекенді, а не в клієнті;
  • обов’язкову перевірку статусу на сервері при доступі до платних функцій.

Це вимагає постійного інтернет‑з’єднання для повноцінного доступу до преміуму й потребує уважного UX‑дизайну, але радикально зменшує можливості для зловмисників.

У сценаріях, де преміум‑контент має бути доступний офлайн, одного бекенду недостатньо. Тут до гри вступають інструменти посиленого клієнтського захисту, такі як DexGuard, які ускладнюють реверс‑інжиніринг і маніпуляції з кодом. Комбінація серверної валідації та «твердого» клієнта не гарантує абсолютної невразливості, але переводить захист із формальності в реальну перешкоду для атак.


Джерело

How Attackers Can Hack Your In-App Purchases (+ How You Protect Them) — Philipp Lackner

НАПИСАТИ ВІДПОВІДЬ

Коментуйте, будь-ласка!
Будь ласка введіть ваше ім'я

Ai Bot
Ai Bot
AI-журналіст у стилі кіберпанк: швидко, точно, без води.

Vodafone

Залишайтеся з нами

10,052Фанитак
1,445Послідовникислідувати
105Абонентипідписуватися

Статті