Вступ
Днями мене запросили в приватну програму платформи для дорослих на основі підписки, де сновними типами користувачів є шанувальники та контент-мейкери. Там шанувальники можуть підписуватися на авторів, надсилати повідомлення, відправляти донати тощо. У цій статті я поділюся своїм звітом про цей сервіс. Оскільки це приватна програма, я не можу повідомити назву компанії, тому я видалю всю інформацію, пов’язану з ризиками ідентифікації клієнта.
Баг #1: Збережений DOM XSS на сторінці підписки дозволяє викрасти дані кредитної картки користувача
Сторінку підписки, згенеровану на https://example.com/<NICKNAME>
, можна відкрити за адресою https://secure.example.com/signup/?signup_key=<HASH>
в окремому вікні. Під час створення сторінки я помітив, що параметри successRedirectUrl
і declinedRedirectUrl
можуть приймати схему javascript:
POST /graphql HTTP/1.1
Host: ***.appsync-api.us-east-1.amazonaws.com
Authorization: ***
Content-Type: application/json
{"operationName":"getPaymentSignUpUrl","variables":{"paymentId":"1de202c3-d0d8-4a3e-b57a-1b2c301ba4cc","successRedirectUrl":"javascript:alert();//","declinedRedirectUrl":"javascript:alert();//","paymentMethod":"creditcard","beneficiary":"011fe32e-c3b3-4a27-a849-b65855da7b15"},"query":"query getPaymentSignUpUrl($paymentId: String, $successRedirectUrl: String, $declinedRedirectUrl: String, $paymentMethod: String, $beneficiary: String!) {\n getPaymentSignUpUrl(\n paymentId: $paymentId\n successRedirectUrl: $successRedirectUrl\n declinedRedirectUrl: $declinedRedirectUrl\n paymentMethod: $paymentMethod\n beneficiary: $beneficiary\n )\n}\n"}
Після переходу за URL-адресою, зазначеною у відповіді на запит введення даних кредитної картки або активування кнопки «Скасувати» Javascript буде виконано.
Сценарій атаки:
-
Зловмисник створює інфіковане платіжне посилання.
-
На сторінці зловмисника в соціальних мережах або безпосередньо на платформі для дорослих згадується сторінка платежу
https://secure.example.com/signup/?signup_key=
. -
Користувачі вводять свою CC або натискають «Скасувати», що запускає виконання шкідливого javascript зловмисника, в результаті чого дані кредитних карток (у DOM document.body.innerHTML) надсилаються на контрольований зловмисником сервер через fetch запит. Припустимо, користувач натискає кнопку «Скасувати» без вводу платіжних даних. У цьому випадку зловмисник може заволодіти обліковим записом жертви, змінивши електронну адресу за допомогою POST-запиту того самого джерела, або витратити гроші на кредитній картці жертви, підписавшись на облікові записи автора-зловмисника. secure.example.com (платіжний шлюз аппки для дорослих) має високий рівень автентичності через свою приналежність до веб-сайту example.com, тому є досить висока ймовірність того, що користувачі справді довіряться йому та використають свої дані кредитних карток.
-
У першому сценарії (введення картки&виконання JS коду) зловмисник збирає всі кредитні картки, щоб продати їх по оптовій ціні організованим злочинним групам або відмити гроші, купуючи коштовні предмети в мережі та перепродаючи їх. У другому сценарії (лише виконання JS за допомогою кнопки «Скасувати») зловмисник отримує гроші від жертви, підписавшись на облікові записи автора-зловмисника та знімаючи гроші.
Після відправки звіту в програму багу оцінили в 1,000$:
До цього моменту я тестував програму на облікових записах шанувальників, лише розмірковуючи про сценарії за участю облікових записів контент-мейкерів, так як процес перевірки особи творця був досить суровим і мені не вдалось її пройти. Враховуючи, що я отримав прямий місток зв’язку з програмою та повідомив про доступну вразливість, я ввічливо попросив дати зелене світло на одобрення мого облікового запису творця, і команда люб’язно надала його. Тоді у мене зʼявився додатковий тип користувача, який важко отримати, що значно розширило поверхню атаки!
Що стосується фіксу, команда не займалася чорними/білими списками, а замість цього видалила вразливі параметри із запитів і додала /payment-result?result=approved
і /payment-result?result=declined
як незмінні значення redirect_uri
та error_uri
за замовчуванням. Я оцінив ці методи як надійні, про що і повідомив команду після повторного тестування за 50$.
Баг #2: Race condition в запиті на зняття коштів і відсутність ручного затвердження виплат призводять до дублювання платежів і фінансових втрат для компанії
Відразу після перевірки я переслав 1 долар від шанувальника на обліковий запис творця, використовуючи місячну підписку. Під час переходу до /my-payouts як творця я виявив, що маю право на зняття грошей, яке не відбувається автоматично(!). Все, що мені потрібно зробити, щоб зняти свої кошти, це заповнити платіжну інформацію та натиснути кнопку «ВИПЛАТИТИ ЗАРАЗ». Я ввів дані свого стороннього гаманця, перехопив останній платіжний запит, надіслав його в Burp Turbo Intruder і… застосував свій звичний race condition флоу. Я надіслав 5 одночасних запитів за допомогою наступного сценарію Turbo Intruder:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=2,
requestsPerConnection=2,
pipeline=False
)
a = open("/Users/max/intruder1.txt", "r")
b = open("/Users/max/intruder2.txt", "r")
for i in range(1):
engine.queue(target.req, a.read(), gate='race1')
for i in range(1):
engine.queue(target.req, b.read(), gate='race1')
engine.openGate('race1')
engine.complete(timeout=60)
def handleResponse(req, interesting):
table.add(req)
На мій подив, всі 5 запитів зарахувалися як унікальні запити на виплату, і я помножив свою виплату в 5 разів на сторонній гаманець!
Результатом цього могло б бути зняття коштів компанії на сторонні гаманці шляхом множення балансу зловмисника, що приводить до значних грошових втрат для компанії. Після відправки звіту програмі я отримав максимальну винагороду та деякий бонус за винятковий ризик
Помилка була негайно усунена шляхом додавання блокування механізму синхронізації: оброблявся лише найраніший запит на зняття коштів, тоді як новіші повертали значення «Баланс має бути більше 0».
Баг #3: Неверифіковані користувачі-шанувальники можуть підписуватися на будь-яких творців, що призводить до масової накрутки підписників
Це досить простий недолік, який можна описати одним реченням. Під час реєстрації облікового запису шанувальника програма не вимагає верифікації профілю, що, у разі автоматизації реєстрації (без капчі чи інших перешкод), дозволяє будь-якому автору створювати необмежену кількість підписників.
Основним показником для визначення привабливості контент-мейкера є кількість підписників і ця помилка дозволяє легко підробити цю статистику. Після відправлення репорту помилку було виправлено, але баунті не було нараховано.
Баг #4: Можливість стягувати з користувачів плату за підписку шляхом їх блокування дозволяє продовжити підписку користувача, незважаючи на її припинення
Граючись з механізмами блокування користувачів, я виявив, що якщо творець блокує шанувальника, який підписаний на нього, з підписника все одно стягуватиметься плата за підписку необмежений час, незважаючи на те, що в розділі «Керування підписками» відображається нуль підписок. Варто зазначити, що навіть якщо фанат видалить кредитну картку зі свого облікового запису, підписка все одно залишиться активною. Єдине, що підписник може зробити в цій ситуації не звертаючись до служби підтримки (це не гарантує видалення підписки), щоб запобігти повторному стягненню додаткової плати,- це зробити недійсною свою кредитну картку на стороні банку.
Наслідком цього буде стягнення плати з користувачів на невизначений термін, і вони не зможуть скасувати підписку. Додатковим сценарієм прихованої атаки була б заманлива пропозиція купити підписку на контент творця за, скажімо, 1$. Після успішного збору підписників зловминик міг би збільшити ціну на підписку на 200%, при цьому утримуючи покупців у бан-списку. Кожен новий платіжний цикл стягував би вже нову суму.
Помилка була пофікшена шляхом анулювання існуючих підписок юзера після того, як автор додав шанувальника до бан-списку.
Баг #5: Контент-мейкер може надсилати повідомлення шанувальникам, які не є його підписниками
Додаток забороняє творцю надсилати повідомлення шанувальникам, які не є творцями, не підписані на творця чи не слідкують за ним. Якщо шанувальник не підписаний на контент-мейкера, під час надсилання повідомлення творець стикається з помилкою UI. Однак перевірка існує лише в UI, але не на серверній частині для graphql операцій CREATE_CONVERSATION
і sendMessage
. Механізм обмеження обміну повідомленнями між непідписаними шанувальниками та творцями є типовим для подібних аплікух, щоб уникнути масового спаму та небажаної розсилки. Коли виявляється проблема з подібним впливом, вона зазвичай розглядається як дійсна вразливість через очевидні причини. Після відправки, репорт було прийнято та нагороджено:
Баг #6: Обхід фіксу
Команда додала фікс для попереднього багу, який перевіряє, чи шанувальники та автори ділять підписку. Просто кажучи, автору дозволено надсилати повідомлення конкретному шанувальнику якщо шанувальник на нього підписаний. Однак жодних обмежень не реалізовано на сервері для graphql операції FOLLOW_USER
щоб підписатись на шанувальника як автор. Таким чином, можна задовільнити логіку аплікухи прийнятності для обміну повідомленнями, зафоловивши шанувальника з аккаунту контент-мейкера, дозволяючи після цього використовувати graphql операції CREATE_CONVERSATION
і sendMessage
. Вразливість була оцінена з тим же самим рівнем ризику, що і попередня.
Заключні думки
-
Визначте та охопіть якомога більше поверхонь атаки. У вищезгаданому прикладі схвалення типу користувача-автора виявився досить суворим і не дав мені ні шансу, але я знайшов спосіб отримати його, що дало мені доступ до додаткових багів та свіжого погляду на алікуху.
-
Якщо існує ймовірність дублювання поведінки, об’єкта чи елемента через створення повторних запитів, завжди перевіряйте race conditions! Все, що потрібно, це швидко перекинути запит у Turbo Intruder і запустити скрипт. Якщо це спрацює, ви вийдете переможцем!
-
Це дослідження в застосунку з контентом для дорослих принесло мені $7,750 разом із оплатою повторного тестування.