Помните, как еще до пандемии компании стремились обеспечить безопасный доступ в периметр для своих сотрудников-удаленщиков? Особенно, если данные были сверхважными, — например, бухгалтерские сведения или корпоративные документы. Решения были сложные, громоздкие и дорогие. А представляете, насколько критичным это стало сейчас?!
Меня зовут Сергей Яковлев, я руковожу проектом Kaspersky Thin Client, построенным на базе нашей собственной операционной системы KasperskyOS. Thin Client — это один из основных компонентов Virtual Desktop Infrastructure, то есть системы доступа к удаленным рабочим столам. В этой статье я на примере такого клиента расскажу, как можно сделать продукт безопасным (и притом коммерчески рентабельным!). Какие были этапы, с чем столкнулись, через что прошли и как решали проблемы. Поехали!
Что такое Kaspersky Thin Client внутри (и причем здесь RDP-клиент)
Физически Thin Client (тонкий клиент) — это небольшая коробочка, мини-компьютер, в которой стоит наш программный продукт на базе KasperskyOS. Он позволяет подключиться к удаленным рабочим столам на сервере, где запущены Windows или Linux — привычные юзерам операционки. Внутри Kaspersky Thin Client находится микроядро KasperskyOS и модуль обеспечения безопасности KSS, к которым мы добавили необходимые драйверы, сервисы, красивый UI и приложения.
Важная конкурентная особенность нашего тонкого клиента заключается в том, что благодаря KasperskyOS он обладает свойством кибериммунности — встроенной защищенностью от подавляющего большинства типов кибератак. То есть для него не нужны дополнительные наложенные средства защиты, например антивирусы.
Кибериммунитет обеспечивается разделением IT-системы на изолированные части и контролем взаимодействий между ними. Одним из таких ключевых компонентов является приложение, о котором я расскажу, — RDP-клиент. Почему мы выбрали RDP? Он позволяет пользователям подключаться к удаленным рабочим столам по протоколу Remote Desktop от Microsoft. Этот протокол популярный и известный, кроме того, есть его OpenSource-реализация
FreeRDP
Чтобы сделать продукт, нужно соорудить прототип — понять, насколько решение востребовано, и взлетит ли оно на нашей операционке. Поэтому мы взяли опенсорсную реализацию протокола RDP — библиотеку FreeRDP. Там есть и клиентская часть, и серверная, нам потребовалась клиентская.
Библиотеку мы портировали на KasperskyOS, добавили необходимые компоненты, чтобы работало на железке, и Qt, чтобы рисовать UI. Получился небольшой прототип.
Как портировать FreeRDP на KasperskyOS
FreeRDP — достаточно объемная библиотека, около 400 тысяч строк кода. Казалось, портировать будет сложно.
Но во-первых, она уже сделана кроссплатформенной. А во-вторых, исходники доступны — опенсорс же.
Вот, что мы сделали.
Добавили новую платформу (KasperskyOS) там, где необходимо. Вписали определение нашей операционки, чтобы компилировалось правильно.
Добавили то, чего не хватало на нашей платформе: функции и некоторые типы данных. Получился porting layer.
Убрали то, что точно не поддержано KasperskyOS, например использование функции fork().
Нашли дефекты и пофиксили. Протестировали почти все функции, стремились к покрытию 75% по бранчам.
Получилось примерно 30 небольших коммитов. Я считаю, портировали легко и просто.
PCSC
С целью сделать решение более привлекательным для бизнеса мы добавили еще одну библиотеку — PCSC. Она позволяет пробросить смарт-карты и токены в удаленную рабочую сессию.
Это нужно для того, чтобы пользователь, вставив токен в железку, на которой запущен наш продукт, смог работать на удаленном столе с этим токеном, как будто он подключен прямо туда, и, например, подписывать документы.
Как портировать PCSC на KasperskyOS
Портировать PCSC оказалось еще проще, чем FreeRDP. Вообще говоря, PCSC — не совсем библиотека. Скорее это компонент, собирающийся в линуксовый демон и клиентскую библиотеку, которая встраивается в конкретное приложение.
Демон и библиотека общаются друг с другом по API на основе юникс-сокетов. Сам демон работает уже с USB-подсистемой — когда смарт-карта или токен подключаются к Kaspersky Thin Client, демон начинает работать, и приложение получает необходимую информацию.
Как мы портировали.
Взяли демона и сделали из него сущность в рамках KasperskyOS.
Оставили API на основе юникс-сокетов.
Получилось очень быстро, нам почти не пришлось модифицировать код. В основном обновляли скрипты сборки.
Архитектура протокола RDP-клиента
Центральный компонент — RDP-клиент, в который встроены библиотеки RDP и PCSC. Клиент общается с Wayland Compositor, который занимается выводом UI, окон приложений на монитор.
Общается клиент и с USB-подсистемой. Она, в свою очередь, взаимодействует с флешками, смарт-картами, мышью, клавиатурой и так далее.
PCSCD — это наш демон, связанный с USB и другими сущностями. И есть большая сущность VFS — virtual file system. Она обеспечивает работу с файлами и сетью.
Дополнительно обращу внимание, что наш клиент общается с PSCS через сокетный API, то есть через VFS (пунктирная стрелка).
Наше решение кибериммунное?
Пока еще нет.
Как так?
Чтобы создать кибериммунное решение, необходимо следовать специальной методологии разработки. Она происходит по определенному алгоритму и включает в себя несколько обязательных этапов.
Во-первых, необходимо сформулировать цели и предположения безопасности. Цели — это требования, выполнение которых должно обеспечиваться при любых сценариях работы кибериммунной системы с учетом предположений безопасности. В нашем тонком клиенте мы будем обеспечивать:
целостность данных, полученных от пользователя, непосредственно работающего с устройством;
конфиденциальность и целостность данных, передаваемых между RDP-клиентом и удаленным рабочим столом;
обновление прошивки тонкого клиента только через централизованную консоль управления Kaspersky Security Center.
Предположения безопасности — это дополнительные ограничения, накладываемые на условия эксплуатации, которые могут как усложнять, так и облегчать выполнение целей безопасности. Они вырабатываются по итогам исследований, общения с бизнесом, проектной и продуктовыми командами. В нашем случае мы выяснили, что защищать шлюз от атак с использованием физического доступа к устройству не требуется, поскольку клиент будет стоять в защищенном контуре — злоумышленника просто не пропустит охрана. Также мы считаем доверенной аппаратную платформу.
На основе целей безопасности разрабатывается архитектура решения: определяется перечень доменов безопасности их состав и взаимодействие между ними.
Далее строится модель угроз и определяются риски безопасности. После чего составляется план митигации угроз. После ресерча, определяем, что для наших целей безопасности и с учетом предположений безопасности наиболее актуальными являются следующие угрозы.
Man-in-the-middle. Злоумышленник встраивается между клиентом и сервером и получает полный доступ к тому, что делает пользователь.
Подмена RDP-сервера. Пользователь подключается вместо своего рабочего стола к столу злоумышленника. Например, так злоумышленник может «увести» учетную запись пользователя.
RCE — встраивание и выполнение кода. Широкий спектр действий: кража данных, первый шаг к осуществлению других атак и так далее.
Физический доступ к тонкому клиенту в обход ОС. Например, злоумышленник вставляет флешку с Linux, загружает железку и получает доступ к жесткому диску.
Secure by Design: безопасность начинается с архитектуры
Основная фишка кибериммунности — следование принципу Secure by Design. Наша цель не просто митигировать конкретные риски, применив наложенные средства безопасности (антивирусы, файрволы, DPI/DLP и так далее), а сделать так, чтобы архитектура решения в принципе не позволяла злоумышленнику нарушить цели безопасности.
Ведь не так важно, какая именно уязвимость позволит реализовать атаку: прогресс не стоит на месте и спустя некоторое время после релиза могут появиться новые эксплоиты.
Поэтому необходимо посмотреть на нашу архитектуру критическим взглядом и скорректировать ее. Где у нас есть небезопасные места? Что может пойти не так, если компонент будет взломан? Что нужно изменить в архитектуре, чтобы это не привело к взлому всего устройства?
Итак, у нас есть MVP, который мы получили, портировав FreeRDP и базовые компоненты в KasperskyOS. После первой итерации процесса кибериммунизации мы выявили следующие архитектурные проблемы.
В cущности VFS объединены и сетевой, и файловый стеки. Если все потоки данных будут обрабатываться одной сущностью, то при ее компрометации будут скомпрометированы и все данные. Фактически вся система окажется под ударом. Злоумышленник получит возможность развивать атаку в любом направлении, со всеми вытекающими последствиями. Сущность, которая обрабатывает поток данных от внешней сети, находится близко к периметру и вероятность атаки на нее высока; кроме того, там еще и сетевой стек, который достаточно сложный и вполне может содержать проблемы, которые не должны повлиять на цели безопасности.
Общение между RDP-клиентом и PCSC-сущностью также осуществляется через эту большую сущность. При компрометации VFS также будут скомпрометированы и проходящие через нее данные
TLS-шифрование канала интегрировано непосредственно в RDP-клиент. Это нехорошо, поскольку критически важные компоненты должны быть вынесены в отдельные домены безопасности и для обеспечения доверенности иметь минимальное количество кода. Кроме того вынесение TLS в отдельную сущность позволило нам использовать библиотеку mbetTLS — она намного более легковесная, чем OpenSSL и менее «дырявая».
Для того чтобы устранить данные проблемы, мы можем использовать два архитектурных паттерна, предлагаемых разработчикам на KasperskyOS:
Разделение VFS (разделение потоков данных);
TLS-терминатор.
Разделяем VFS
При разделении VFS у нас появляется новая сущность — Net VFS. И две сущности для работы с файлами — одна для RDP-клиента, одна для PSCSD (см. схему). Если с файлами работает кто-то еще — этот поток данных тоже нужно отделить и пропустить через дополнительный файловый VFS.
Однако все еще есть проблема: связь между PCSCD и RDP-клиентом проходит через сущность Net VFS. Та же сущность смотрит вовне. То есть смешиваются потоки данных внутреннего и внешнего сетевого взаимодействия
Оказалось, что у нас нет способа «из коробки» разделить сетевые потоки данных. Но мы же программисты! Мы умеем писать код (это правда). И мы придумали решение.
Сокеты, для работы с внешней сетью — обычные. А юниксовые сокеты, по которым идет взаимодействие с PCSCD-библиотекой, имеют определенный признак. Его мы и использовали.
Мы дописали небольшой слой, разделяющий сокеты и, соответственно, потоки данных. При создании сокета мы определяем, какого он типа, и перенаправляем все запросы на нужную сущность. Если сокет юниксовый, запросы отправляем в Internal Net VFS, а если обычный — в External Net VFS. Эти две сущности VFS реализуют сетевой стек и отличаются только тем, что в Internal Net VFS нет сетевого драйвера.
Теперь мы разделили файловый и сетевой стеки по разным сущностям. Данные не смешиваются, а значит, если более уязвимый и более подверженный атакам поток внешнего сетевого взаимодействия будет взломан, это не коснется внутреннего, и наоборот.
Применяем TLS-терминатор
Следующий паттерн, который мы будем использовать — TLS-терминатор. Фактически это небольшая сущность, которая инкапсулирует шифрование канала передачи данных по сети. Ее использование позволит нам защититься от атак типа man-in-the-middle и помешает распространению атак со стороны сети. А поскольку она маленькая и простая, ее доверенность легко доказать.
Почему не сразу получилось
Итак, есть RDP-клиент, есть сетевая сущность — между ними нужно встроить TLS-терминатор. Мы попробовали встроить — не заработало.
Для того чтобы понять почему, немного посмотрим на то, как работает RDP-протокол. Как видно на схеме, первый запрос-ответ между клиентом и сервером, Connection Request, — фактически обмен поддерживаемыми свойствами, то есть клиент и сервер договариваются, какие особенности протокола будут использоваться. Эта пара запрос-ответ не должна быть зашифрована. А TLS-терминатор, если его встроить как есть, начинает шифровать канал с самого начала. Вот и проблема.
Тут мы снова вспоминаем, что мы программисты, и модифицируем TLS-терминатор так, чтобы он пропускал первый запрос-ответ, не зашифровывая. Первый запрос содержит в себе информацию о том, в какие режимы поддерживает наш клиент, тут всегда сообщаем, что поддерживается только режим TLS. В ответ сервер сообщает, какие режимы он поддерживает. Соответственно, если сервер не поддерживает TLS, мы завершаем соединение с ошибкой. Дополнительно, так как TLS-слой отделен от RDP-клиента, любая попытка RDP-клиента работать по незащищенному каналу разобьется о TLS-терминатор, который попытается установить зашифрованное соединение.
Контроль передаваемых параметров означает также парсинг данных, и потенциально это могло бы использоваться для атаки. Поэтому для того, чтобы подтвердить безопасность такого кода, кроме юнит-тестов мы разрабатываем также фаззинг-тесты и усиленно тестируем такие места в нашем продукте.
После чего уже устанавливается зашифрованное соединение, мы получаем зашифрованный канал, и дальнейшее общение идет по нему.
Маленький недостаток такого решения: TLS-терминатор становится сложнее. Это придется компенсировать дополнительными тестами.
(Подробнее о TLS как сервисе KasperskyOS есть в докладе моей коллеги Дарьи Зимариной.)
Как выглядит финальная архитектура
В итоге мы получаем архитектуру, изображенную на схеме. Мы развели потоки данных, добавили TLS-терминатор. Также появилась загадочная сущность Cert Storage — она является хранилищем для доверенных сертификатов, которые TLS-терминатор использует для проверки сервера. Ее описание выходит за рамки данной статьи.
В KasperskyOS корректность всех IPC-сообщений и контроль взаимодействий между сущностями происходит в специальной подсистеме Kaspersky Security System. Поэтому дальше нам нужно, руководствуясь принципом наименьших привилегий, прописать разрешения на взаимодействия между нашими сущностями, так называемую «KSS-политику».
Добавляем KSS-политику
Мы покажем, как задается KSS-политика, на примере двух взаимодействий.
Между PCSCD и Internal Net VFS.
Между RDP-клиентом и тоже Internal Net VFS.
Напомню, что, с точки зрения сетевого взаимодействия, PCSCD — это сервер. А RDP-клиент — это (внезапно) клиент. Разница в их политике простая: сущности PCSCD мы разрешаем вызывать серверный API Internal Net VFS, наподобие Listen, Accept, Bind. А RDP-клиенту — не разрешаем. Также клиенту мы запрещаем использовать API Internal Net VFS для работы с файлами.
Описание KSS-политик
Подробнее об этих отличиях — в докладе моего коллеги Михаила Демченко на тему того, как разделить POSIX и написать политику KSS.
Проверка решения на кибериммунность
Мы доработали архитектуру, написали KSS-политику. Защищены ли мы от перечисленных в начале статьи угроз (кроме физического доступа)? Давайте проверим.
Man-in-the-middle. Защищены: мы используем только зашифрованные соединения и только доверенные сертификаты, к тому же есть TLS-терминатор.
Подмена RDP-сервера. Защищены: аналогично предыдущему пункту.
RCE — встраивание и выполнение кода. Условно защищены: архитектура стала более иммунной, мы прописали правила взаимодействия между сущностями, разделили потоки данных. RCE все еще возможен, например в External Net VFS. В ней находится сетевой стек и драйвер, и она обеспечивает сетевое взаимодействие с внешним миром. Но дальше External Net VFS распространить эту атаку почти невозможно: за ней стоит TLS-терминатор.
Шпаргалка: как мы добиваемся кибериммунности
Описываем цели и предположения безопасности.
Используем подход Secure by Design, то есть, начиная с проработки требований к архитектуре, выявляем то, что нужно защищать. Проще говоря, заранее строим систему защищенной.
Используем KasperskyOS как платформу, спроектированную для удобного создания кибериммунных решений.
Применяем SDL-практики: secure code review, статический и динамический анализ и другие.
Выполняем фаззинг-тестирование. Особый упор делаем на те компоненты, доверенность которых надо повысить, и те, где происходит парсинг данных, например External Net VFS. Так мы убеждаемся, что компоненты будут предсказуемо и стабильно реагировать на любые входные данные.
Проводим пентестирование: превентивно до выпуска продукта ищем уязвимости и закрываем их.
Какова цена кибериммунности
Кибериммунность не бесплатна, в нашем случае отмечу два момента.
Необходимо применять SDL-практики. Это больше процедур, больше внимания качеству кода и так далее, что в целом удорожает разработку. Но без них не получится защищенный продукт. Для «Лаборатории Касперского» это замечание не столь релевантно, так как SDL по умолчанию обязателен для применения во всех релизах. Но для наших менее зрелых партнеров это может стать неожиданностью. В целом я рекомендую применять SDL-практики при разработке любых продуктов — вместе мы сделаем мир безопаснее.
Примерно на 30% снизилась скорость доставки изображения удаленного рабочего стола до пользователя. Однако мы компенсировали это оптимизациями в других местах, не связанных с RDP-клиентом, дали ему больше ресурсов процессора, и потеря стала незаметной.
Выводы
Если использовать KasperskyOS, несложно взять опенсорсную библиотеку и построить на ней кибериммунное решение.
В KasperskyOS уже есть компоненты и паттерны для создания кибериммунных решений — и мы ими пользуемся. Если чего-то не хватает — легко расширить или кастомизировать функционал. А следующие продукты смогут в свою очередь использовать наши наработки.
Кибериммунность не бесплатна. В нашем случае это отразилось на скорости доставки изображения удаленного рабочего стола.
В итоге мы получаем безопасное решение, которое защищает пользователей и бизнес- от киберугроз.
Дополнительные материалы
● FreeRDP
● PCSC
● RDP
● SDL