
В прошлом году злоумышленники совершили на 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) также позволяет серьезно повысить уровень безопасности.
- Весь код должен быть подписан, даже если инфраструктура этого не требует.
- Все плагины и сторонние недоверенные модули должны выполняться в выделенных песочницах.
Предметные области банковских приложений
Эксперты также рассказали слушателям о различных банковских приложениях, не относящихся к серверной части систем ДБО, и возможных проблемах с ними:
- Плагины и клиентские приложения — ошибки безопасности различных плагинов и клиентских приложений, изначально не связанных с банкингом, могут приводить к проблемам.
- Мобильные приложения и их типовые проблемы безопасности — как правило, мобильные приложения защищены хуже десктопных собратьев. Вообще говоря, серверная часть должна быть одинаково унифицированной для приложений любого типа, но часто это не так.
- Операторские станции и приложения — часто хакерам даже не приходится взламывать сложные системы безопасности, чтобы пробиться во внутреннюю сеть, достаточно лишь обмануть сотрудников предприятия — операторов этих систем.
- Развитие клиентских атак — украсть деньги у клиентов банка можно с помощью целенаправленных атак на самих клиентов.
Заключение
Несмотря на то, что банковские приложения вызывают все более пристальный интерес злоумышленников, а на пути обеспечения их безопасности есть целый ряд сложностей, часто возникновение проблем можно предотвратить еще на этапе разработке программного продукта.
И сделать это можно без лишних усилий, просто следуя лучшим практикам разработки защищенного софта.