В прошлом году злоумышленники совершили на 30 % больше атак на российские банки, чем годом ранее. Пытались вывести около 6 млрд рублей. Часто атака становится возможной из-за недостаточной защищенности финансовых приложений.
По нашей статистике, более половины систем дистанционного банковского обслуживания (54 %) содержали XSS-уязвимости, которые позволяют осуществить MitM-атаку и перехватить доступ к интернет-банкингу. С мобильными банковскими приложениями ситуация выглядит не лучше: 70 % «кошельков» для Android и 50 % для iOS в 2014 году содержали уязвимости, достаточные для получения доступа к счету.
Выявлять уязвимости на ранней стадии гораздо дешевле, чем потом расхлебывать последствия их эксплуатации. В середине октября эксперты Positive Technologies Тимур Юнусов и Владимир Кочетков провели двухдневный мастер-класс по безопасной разработке банковских приложений. Сегодня мы представляем краткий пересказ.
Разговор о проблемах безопасности и их возможных решениях следует начать с типичных проблем защищенности банковских приложений.
Проблемы управления доступом
Такие проблемы возникают главным образом при реализации следующих механизмов управления доступом:
- идентификация, аутентификация, авторизация;
- двухфакторные методы аутентификации.
Аудиты безопасности постоянно выявляют такие ошибки, как недостаточное разграничение доступа, возможность получения доступа к различным backend-и администраторским системам. Самые распространенные из таких уязвимостей встречаются практически в каждом банке и банковском приложении.
Часто корень проблем кроется в неверном использовании криптопротоколов и реализаций криптопримитивов (средств криптографии, встроенных в стандартные библиотеки .NET, Java и т. п.). Здесь также важно отметить, что использование низкоуровневых криптопримитивов в принципе нежелательно, поскольку очень легко допустить ошибку в их конфигурации и тем самым свести на нет все усилия по внедрению криптографии в отдельно взятом приложении.
Одним из самых ярких последствий таких ошибок является уязвимость к атакам Padding Oracle, возникающая при использовании слабых режимов работы блочных шифров. Вместо использования низкоуровневых средств всегда нужно стремиться к использованию высокоуровневых библиотек типа KeyCzar, libsodium.
Еще один пласт проблем связан с подходом security through obscurity. Каждый банк использует криптографию (SSL, TLS и т. п.) и нередко шифрует данные на уровне приложений (L7). Это дает финансовым организациям иллюзию защищенности, и возникает мысль, что на серверной части теперь ничего защищать не надо: все ведь «обернуто» криптографией и атакующий попросту не сможет ничего злонамеренно послать на сервер.
Это, конечно же, не так. Криптография поддается обратной разработке, проверки в мобильных приложениях обходятся, если злоумышленник имеет физический доступ к устройству с установленным банковским приложением. Другими словами, осуществить MitM-атаку на SSL-трафик можно всегда. Более того, иногда уязвимости удается эксплуатировать даже «поверх» криптографии — например, из форм на сайте.
Проблемы управления потоками операций
Среди наиболее популярных и опасных ошибок управления потоками операций — и возможных атак на их основе — можно выделить:
- недостаточные проверки процесса;
- race condition и прочие атаки на атомарность;
- другие уязвимости бизнес-логики;
- атаки CSRF.
Данный тип проблем является вторым по частоте обнаружения в банковских приложениях. Для того чтобы свести вероятность их появления к минимуму и обеспечить защиту бизнес-логики, необходимо четко формализовать каждый бизнес-процесс. Вообще, бизнес-логика — это баззворд-синоним понятия «логика функциональной предметной области». Предметная область же — набор сущностей, их инвариантов и правил взаимодействия друг с другом.
Чтобы избежать возникновения уязвимостей в некоей абстрактной предметной области, достаточно: а) иметь формализованное и непротиворечивое описание инвариантов сущностей и правил их взаимодействия; б) реализовать строгий (принудительный, без разрешающих умолчаний) контроль соблюдения всех инвариантов и правил предметной области при прохождении сущностей через границы доверия.
Часто логику предметной области можно выразить в виде некоторого workflow (потока операций либо конечного автомата), состояниями которого являются наборы допустимых инвариантов сущностей предметной области, а переход между состояниями является единственным способом их взаимодействия друг с другом. В этом случае можно сформулировать несколько конкретных правил по обеспечению защищенности реализации предметной области:
- Следует избегать появления в потоке операций рекурсивных путей и циклов.
- Необходимо учитывать возможное нарушение целостности данных, разделяемых различными потоками.
- Текущее состояние потока необходимо хранить перед границей доверия, а не за ней (применительно к «двухзвенке» — на сервере, а не на клиенте).
- Необходимо реализовать строгий контроль аутентичности инициатора перехода между состояниями workflow (неэффективный контроль приводит, например в случае с Вебом, к уязвимости для атак CSRF);
- В случае если несколько потоков операций, разделяющих данные, могут работать одновременно, необходимо обеспечить гранулированный доступ ко всем таким данным из всех таких потоков.
Проблемы управления потоками данных
Ошибки в организации управления потоками данных могут приводить к возникновению следующих серьезных проблем:
- инъекции (SQL, XSS, XML, XXE, XPath, XQuery, Linq и т. п.),
- внедрение и выполнение произвольного кода на серверной стороне.
Третий по частоте обнаружения тип проблем банковских приложений, хотя и наиболее обширный. Главный недостаток здесь — неэффективная предварительная обработка данных. Он приводит к многочисленным атакам и уязвимостям: от XSS, которая в банковском приложении может свести на нет все механизмы защиты (одноразовые пароли и т. п.), до SQL-инъекций, наличие которых в финансовых приложениях позволяет получить абсолютный доступ к критически важной информации — счетам, паролям (в т. ч. одноразовым) — и осуществлять хищения средств.
Существует три подхода к организации предварительной обработки данных:
- типизация — приведение строковых данных к конкретным типам в терминах ООП и дальнейшее использование в коде уже этих типов (параметризация SQL-запросов, например, является неявной реализацией типизации SQL-литералов);
- санитизация — преобразование входных строковых данных в вид, безопасный для их использования в качестве выходных (примерами являются всевозможные HtmlEncode, UrlEncode, addslashes и т. п.);
- валидация — проверка данных на соответствие каким-либо критериям; возможна валидация двух видов: синтаксическая (например, проверка на соответствие регулярному выражению) и семантическая (например, проверка числа на вхождение в определенный диапазон).
Порядок предпочтения этих подходов именно таков. То есть, там, где невозможна типизация, следует рассмотреть возможность санитизации, а там, где невозможна и санитизация, — следует внедрять валидацию. Это необходимо для того, чтобы максимально дистанцироваться от изменения семантики кода. Кроме того, стоит по возможности придерживаться правила: «типизация / валидация на входе (как можно ближе к началу потока выполнения кода), санитизация — на выходе (как можно ближе к месту в коде, в котором данные уходят наружу)».
Рассмотрим несколько примеров применения описанных выше подходов.
Типизация
Предположим, у нас есть следующий код:
var parm = Request.Params["parm1"];
if (Request.Params["cond1"] == "true")
{
return;
}
if (Request.Params["cond2"] == "true")
{
parm = Request.Params["parm2"];
} else {
parm = "<div>Harmless value</div>";
}
Response.Write("<a href=\"" + parm + "\">");
Здесь в
parm
записывается опасное значение, что приводит к возникновению уязвимости для атак класса XSS, но контекст его использования позволяет осуществить типизацию.var typedParm = new Uri(Request.Params["parm2"]);
var parm = Request.Params["parm1"];
if (Request.Params["cond1"] == "true")
{
return;
}
if (Request.Params["cond2"] == "true")
{
parm = typedParm.GetComponents(
UriComponents.HttpRequestUrl, UriFormat.UriEscaped);
} else {
parm = "<div>Harmless value</div>";
}
Response.Write("<a href=\"" + parm + "\">");
Санитизация
У нас есть следующий код:
var parm = Request.Params["parm1"];
if (Request.Params["cond1"] == "true")
{
return;
}
if (Request.Params["cond2"] == "true")
{
parm = Request.Params["parm2"];
} else {
parm = "<div>Harmless value</div>";
}
Response.Write("Selected parameter: " + parm);
Нетрудно заметить, что в
parm
здесь также записывается опасное значение. В данном случае невозможна типизация, но возможно применение санитизации в контексте опасной операции.var parm = Request.Params["parm1"];
if (Request.Params["cond1"] == "true")
{
return;
}
if (Request.Params["cond2"] == "true")
{
parm = HttpUtility.HtmlEncode(Request.Params["parm2"]);
} else {
parm = "<div>Harmless value</div>";
}
Response.Write("Selected parameter: " + parm);
Валидация
В примере ниже (уязвимость для атак переполнения буфера) осуществить типизацию и санитизацию невозможно, а значит, нужно применить валидацию:
const int BufferSize = 16;
public unsafe struct Buffer
{
public fixed char Items [BufferSize];
}
static void Main(string[] args)
{
var buffer = new Buffer();
var argument = args[0].ToCharArray();
if (argument.Length < BufferSize) { return; }
for (var i = 0; i < argument.Length; i++)
{
unsafe
{
buffer.Items[i] = argument[i];
}
}
}
Код с валидацией будет выглядеть так:
const int BufferSize = 16;
public unsafe struct Buffer
{
public fixed char Items [BufferSize];
}
static void Main(string[] args)
{
Func<int, int> __ai_bkfoepld_validator = index =>
{
if (index >= BufferSize)
{
throw new IndexOutOfRangeException();
}
return index;
};
var buffer = new Buffer();
var argument = args[0].ToCharArray();
if (argument.Length < BufferSize) { return; }
for (var i = 0; i < argument.Length; i++)
{
unsafe
{
buffer.Items[__ai_bkfoepld_validator(i)] = argument[i];
}
}
}
Инфраструктурные проблемы и методы их решения
Существует и целый ряд инфраструктурных проблем, которые могут приводить к успешным атакам на банковские системы. Среди них:
- application DoS,
- проблемы окружения,
- стороннее ПО, модули и плагины.
Случаются и успешные атаки с помощью куда более тривиальных незакрытых FTP, или админок IBM/Tomcat и т. п.
И вот что следует делать, чтобы повысить безопасность банковских приложений на этапе их разработки и развертывания:
- Нужно рассматривать каждый компонент инфраструктуры как скомпрометированный.
- TLS (не SSL) должен применяться везде, даже внутри инфраструктуры.
- Каждый компонент инфраструктуры должен быть развернут и настроен в соответствии с официальным security guide (если есть) или лучшими практиками.
- Использование специализированных средств для анализа защищенности и соответствия стандартам (например, MaxPatrol) также позволяет серьезно повысить уровень безопасности.
- Весь код должен быть подписан, даже если инфраструктура этого не требует.
- Все плагины и сторонние недоверенные модули должны выполняться в выделенных песочницах.
Предметные области банковских приложений
Эксперты также рассказали слушателям о различных банковских приложениях, не относящихся к серверной части систем ДБО, и возможных проблемах с ними:
- Плагины и клиентские приложения — ошибки безопасности различных плагинов и клиентских приложений, изначально не связанных с банкингом, могут приводить к проблемам.
- Мобильные приложения и их типовые проблемы безопасности — как правило, мобильные приложения защищены хуже десктопных собратьев. Вообще говоря, серверная часть должна быть одинаково унифицированной для приложений любого типа, но часто это не так.
- Операторские станции и приложения — часто хакерам даже не приходится взламывать сложные системы безопасности, чтобы пробиться во внутреннюю сеть, достаточно лишь обмануть сотрудников предприятия — операторов этих систем.
- Развитие клиентских атак — украсть деньги у клиентов банка можно с помощью целенаправленных атак на самих клиентов.
Заключение
Несмотря на то, что банковские приложения вызывают все более пристальный интерес злоумышленников, а на пути обеспечения их безопасности есть целый ряд сложностей, часто возникновение проблем можно предотвратить еще на этапе разработке программного продукта.
И сделать это можно без лишних усилий, просто следуя лучшим практикам разработки защищенного софта.
Комментарии (13)
slonopotamus
19.11.2015 21:03__ai_bkfoepld_validator
Снимите кота с клавиатуры.VladimirKochetkov
20.11.2015 11:34На скриншотах представлены диффы по патчам, сгенерированным автоматически экспериментальной версией Application Inspector (рассказывал об этом на PHDays V: www.slideshare.net/kochetkov.vladimir/ss-48743308). Отсюда и странные имена у добавленных патчами идентификаторов.
Oxoron
20.11.2015 09:04У вас в примерах кода используется http (UriComponents.HttpRequestUrl). Это просто безразличный к http/https парсер, или в рассматриваемом куске кода применяются http запросы?
VladimirKochetkov
20.11.2015 11:37Первое. Но по-хорошему, пробрасываемая пользователем схема протокола также должна жестко контролироваться, чтобы у атакующего не было возможности перенаправить браузер жертвы по незащищенному каналу. Но это уже сильно зависит от семантики конкретного фрагмента кода. У кода из статьи семантика: «пример, демонстрирующий уязвимость к атакам XSS» :)
Scf
20.11.2015 11:19Интересно про Padding Oracle и нестойкий CBC. Получается, что если мы хотим хранить на клиенте зашифрованные данные, ключ от которых есть только на сервере, то имеет смысл всегда добавлять к ним HMAC?
VladimirKochetkov
20.11.2015 11:47Сначала стоит подумать об отказе от CBC (и ECB, если вдруг) в пользу более достойных режимов, позволяющих организовать аутентифицированное шифрование (CCM, GCM — предпочтительнее). Если по каким-либо причинам это невозможно, тогда стоит реализовать encrypt-then-autheticate, например добавлением к зашифрованным данным их HMAC, да.
Scf
20.11.2015 11:22И еще такой вопрос: В Википедии написано, что TLS требует всегда проверять, что при дешифровании padding заполнен правильными данными. Чем опасно отсутствие такой проверки?
VladimirKochetkov
20.11.2015 12:27TLS требует всегда проверять, что при дешифровании padding заполнен правильными данными
Именно в такой формулировке? Можно ссылку на статью?
Но в целом, если дополнение заполнено некорректными данными, то это значит, что целостность сообщения была нарушена, а т.к. в SSL и ранних версиях TLS использовался подход authenticate-then-encrypt, то в этом случае на этапе дешифровки проконтролировать целостность можно только проверяя корректность дополнения.Scf
20.11.2015 12:31en.wikipedia.org/wiki/POODLE
«Even though TLS specifications require servers to check the padding, some implementations fail to validate it properly, which makes some servers vulnerable to POODLE even if they disable SSL 3.0.»VladimirKochetkov
20.11.2015 12:35Насколько я помню, там речь шла о том, что не все реализации TLS в случае неправильного дополнения отправляли алерт bad_record_mac, что и позволяло атакующему отличить ошибки дополнения от ошибок контроля целостности.
lair
Что такое «Linq-инъекция»?
tbl
В Dynamic Linq можно повлиять на построение запроса, если не проводить параметризацию, а собирать из строк типа такого:
Атакующий может передать туда
В результате это все развернется в
Еще вариант — какая-нибудь уязвимость в стороннем LINQ-провайдере, позволяющая инжекцию.
VladimirKochetkov
Речь об инъекциях в выражения System.Linq.Dynamic (http://magazine.hitb.org/issues/HITB-Ezine-Issue-009.pdf, стр. 62)