Если ваше приложение или сервис работает с внутренней валютой, следует проверить его на уязвимости типа race condition («состояние гонки» или, если точнее, — «неопределенность параллелизма»). Race condition — это «плавающая ошибка», которую могут эксплуатировать злоумышленники. Суть в том, что благодаря параллельному выполнению кода можно получить доступ к внутренней валюте приложения, манипулировать ею и, при желании, нанести ощутимый финансовый ущерб владельцу сервиса. Недавно мы обнаружили такую проблему у одного из наших клиентов и помогли ее решить.
Так как разработчики часто забывают, что код может выполняться несколькими потоками одновременно, они не тестируют продукт на уязвимость типа race condition, хотя эта ошибка довольно распространена.
С точки зрения бэкэнда это выглядит так: несколько потоков одновременно обращаются к одному общему ресурсу: переменным или файлам, для которых не предусмотрена блокировка или синхронизация. Это приводит к несогласованности вывода данных.
Вот конкретный пример такой уязвимости. Допустим, у нас есть приложение, которое позволяет переводить бонусы между платежными кошельками. У злоумышленника есть два кошелька — A и B, и на каждом из них есть 1000 бонусов. На схеме показано, как манипулируя временем отправки запроса на транзакцию, злоумышленник может увеличивать сумму перевода на свой счет и сделать из 10 бонусов — 20.
Существуют автоматические инструменты для поиска таких уязвимостей. Например, RacePWN который за минимальное время отправляет множество HTTP-запросов на сервер и принимает на вход json-конфигурацию, облегчая процесс атаки для злоумышленников. Вручную же это делается с помощью отправки POST-запросов.
В США с июня 1985 года по январь 1987 года ошибка race condition в аппарате лучевой терапии Therac-25, созданном канадской государственной организацией Atomic Energy of Canada Limited (AECL) стала причиной шести передозировок радиацией. Жертвы получили дозы в десятки тысяч рад. Смертельным считается уровень в 1000 рад. После полученных ожогов пострадавшие умерли в течение нескольких недель. Выжить удалось только одной пациентке.
Предыдущие модели Therac имели аппаратные механизмы защиты: независимые цепи блокировки, контролирующие электронный луч; механические блокираторы; аппаратные автоматические выключатели; отключающие предохранители. В Therac-25 аппаратную защиту убрали. За безопасность отвечало программное обеспечение. Аппарат имел несколько режимов работы, и из-за ошибки race condition врач иногда не понимал, в каком режиме аппарат работает на самом деле. В ходе судебных разбирательств выяснилось, что ПО Therac-25 было разработано одним программистом, но у AECL не было данных, кем конкретно.
По итогам процесса правительство США серьезно ужесточило требования к проектированию и работе систем, чья безопасность критична для людей.
Проще и дешевле всего решить проблему race condition — правильно спроектировать архитектуру приложения. Вот что для этого следует предусмотреть.
Наш клиент — интернет-магазин доставки продуктов, который поддерживает функцию предоставления скидок с помощью купонов. В процессе тестирования мы обнаружили уязвимость — при отправке POST-запроса со значением купона. Отправляя запрос с различными временными задержками, удавалось получить скидку дважды. Судя по всему, разработчики допустили грубую ошибку, связанную с разделяемым доступом к объекту, который отождествлялся с покупкой.
Скорее всего имел место такой псевдокод без механизмов синхронизации:
…
1 If promo_flag is not set:
2 Price = get_price()
3 Price -= price * promo_percent;
4 set_price(price)
5 set_promo_flag()
…
Здесь, применение промокода и установка соответствующего флага не являются атомарной операцией. Вероятней всего, когда началось второе применение промокода, первое остановилось на 5-й строке (то есть еще не выполнилось). В этот момент функция get_price() во второй строке вернула уже новое значение цены, уже со скидкой.
Проблема решается просто:
…
1 acqure_mutex()
2 If promo_flag is not set:
3 Price = get_price()
4 Price -= price * promo_percent;
5 set_price(price)
6 set_promo_flag()
7 release_mutex()
…
Теперь, применение промокода будет выполняться целиком и полностью один раз. Даже когда возникнет ситуация, при которой второй поток попытается применить промокод в то время как первый процесс уже занят обработкой, он не сможет этого сделать. Мьютекс будет блокировать доступ в «критическую секцию», и второй процесс будет вынужден дождаться окончания работы первого.
Race condition не следует недооценивать. Лучше потратить время и ресурсы на поиск уязвимости, чтобы избежать непредвиденных последствий, в том числе для бюджета компании.
Блог ITGLOBAL.COM — Managed IT, частные облака, IaaS, услуги ИБ для бизнеса:
Что такое race condition
Так как разработчики часто забывают, что код может выполняться несколькими потоками одновременно, они не тестируют продукт на уязвимость типа race condition, хотя эта ошибка довольно распространена.
С точки зрения бэкэнда это выглядит так: несколько потоков одновременно обращаются к одному общему ресурсу: переменным или файлам, для которых не предусмотрена блокировка или синхронизация. Это приводит к несогласованности вывода данных.
Вот конкретный пример такой уязвимости. Допустим, у нас есть приложение, которое позволяет переводить бонусы между платежными кошельками. У злоумышленника есть два кошелька — A и B, и на каждом из них есть 1000 бонусов. На схеме показано, как манипулируя временем отправки запроса на транзакцию, злоумышленник может увеличивать сумму перевода на свой счет и сделать из 10 бонусов — 20.
Существуют автоматические инструменты для поиска таких уязвимостей. Например, RacePWN который за минимальное время отправляет множество HTTP-запросов на сервер и принимает на вход json-конфигурацию, облегчая процесс атаки для злоумышленников. Вручную же это делается с помощью отправки POST-запросов.
Смертельная race condition
В США с июня 1985 года по январь 1987 года ошибка race condition в аппарате лучевой терапии Therac-25, созданном канадской государственной организацией Atomic Energy of Canada Limited (AECL) стала причиной шести передозировок радиацией. Жертвы получили дозы в десятки тысяч рад. Смертельным считается уровень в 1000 рад. После полученных ожогов пострадавшие умерли в течение нескольких недель. Выжить удалось только одной пациентке.
Предыдущие модели Therac имели аппаратные механизмы защиты: независимые цепи блокировки, контролирующие электронный луч; механические блокираторы; аппаратные автоматические выключатели; отключающие предохранители. В Therac-25 аппаратную защиту убрали. За безопасность отвечало программное обеспечение. Аппарат имел несколько режимов работы, и из-за ошибки race condition врач иногда не понимал, в каком режиме аппарат работает на самом деле. В ходе судебных разбирательств выяснилось, что ПО Therac-25 было разработано одним программистом, но у AECL не было данных, кем конкретно.
По итогам процесса правительство США серьезно ужесточило требования к проектированию и работе систем, чья безопасность критична для людей.
Как защититься
Проще и дешевле всего решить проблему race condition — правильно спроектировать архитектуру приложения. Вот что для этого следует предусмотреть.
- Блокировку критически важных записей в базе данных. Есть разные способы обеспечить работу с записью одного потока в конкретный момент времени. Главное — не заблокировать ничего лишнего.
- Изоляцию транзакций в базе данных, которая гарантирует, что транзакции будут совершены последовательно. Самое важное здесь — соблюсти баланс между безопасностью и скоростью.
- Использование мьютекса. Эта функция защищает конкретный участок кода от одновременного доступа к нему нескольких потоков. Здесь также следует соблюсти баланс, иначе можно создать новые проблемы, например, взаимную блокировку кода или его двойной захват. Кстати, в кейсе, который мы сейчас расскажем, мы использовали именно этот способ защиты.
Как мы нашли уязвимость
Наш клиент — интернет-магазин доставки продуктов, который поддерживает функцию предоставления скидок с помощью купонов. В процессе тестирования мы обнаружили уязвимость — при отправке POST-запроса со значением купона. Отправляя запрос с различными временными задержками, удавалось получить скидку дважды. Судя по всему, разработчики допустили грубую ошибку, связанную с разделяемым доступом к объекту, который отождествлялся с покупкой.
Скорее всего имел место такой псевдокод без механизмов синхронизации:
…
1 If promo_flag is not set:
2 Price = get_price()
3 Price -= price * promo_percent;
4 set_price(price)
5 set_promo_flag()
…
Здесь, применение промокода и установка соответствующего флага не являются атомарной операцией. Вероятней всего, когда началось второе применение промокода, первое остановилось на 5-й строке (то есть еще не выполнилось). В этот момент функция get_price() во второй строке вернула уже новое значение цены, уже со скидкой.
Решение
Проблема решается просто:
…
1 acqure_mutex()
2 If promo_flag is not set:
3 Price = get_price()
4 Price -= price * promo_percent;
5 set_price(price)
6 set_promo_flag()
7 release_mutex()
…
Теперь, применение промокода будет выполняться целиком и полностью один раз. Даже когда возникнет ситуация, при которой второй поток попытается применить промокод в то время как первый процесс уже занят обработкой, он не сможет этого сделать. Мьютекс будет блокировать доступ в «критическую секцию», и второй процесс будет вынужден дождаться окончания работы первого.
Race condition не следует недооценивать. Лучше потратить время и ресурсы на поиск уязвимости, чтобы избежать непредвиденных последствий, в том числе для бюджета компании.
Блог ITGLOBAL.COM — Managed IT, частные облака, IaaS, услуги ИБ для бизнеса:
- Как мы нашли уязвимость в почтовом сервере банка и чем она грозила
- Информационная безопасность в 2021 году. Угрозы, отраслевые тренды
- Популярные сайты все еще уязвимы для массовой DDoS-атаки
- Почему этичным хакерам следует взламывать корпорации сообща. Интервью с баг-хантером Алексом Чапманом
- Страх перед автоматизацией работы и другие тренды в мировой и российской кибербезопасности