Содержание статьи
Есть много способов борьбы со всякой малварью - начиная от самых простых
антивирусов, проактивных защит и файрволов и заканчивая сложными способами вроде
эмуляции оборудования и операционных систем. На программном рынке можно легко
отыскать способы защиты своей системы. В этой же статье мы поговорим о том как,
имея под рукой только Visual Studio, можно составить неплохую конкуренцию
имеющимся решениям безопасности.
Песочница
Что такое "песочница" для айтишника? В первую очередь, это слово должно быть
до боли знакомо SEO-оптимизаторам. Например, одну из значительных проблем для
оптимизаторов представляет Google Sandbox. Это одно из самых серьезных
препятствий, которое необходимо преодолеть новому сайту, прежде чем попасть в
результаты поиска Google. Песочница Google не позволяет новым сайтам
искусственным путем улучшать свои позиции. Далеко не каждая поисковая система
имеет подобную фильтрацию.
Сайты, оказавшиеся в песочнице, могут пробыть там от двух недель до года. В
течение этого периода новые сайты успеют занять высокие позиции в других
поисковых системах, но не в Google. Однако в статье мы поговорим о другой
"песочнице" - программной.
Существующая концепция создания программной "песочницы" включает в себя такие
"несложные" пункты: эмуляция железа и операционной системы, запуск вероятно
"зловредного приложения", отслеживание его действий и их последующий анализ.
Имеющиеся на данный момент решения по большей части сводятся к перехвату
системных функций и анализу выполнения подозрительного кода. И надо сказать, что
эта система очень даже неплохо работает.
Но повышенная безопасность исполнения кода в "песочнице" зачастую связана с
большой нагрузкой на систему — именно поэтому некоторые виды "песочниц"
используют только для неотлаженного или подозрительного кода.
Википедия утверждает, что "песочницы" в природе часто встречаются в виде
апплетов, которые исполняются в виртуальной машине или интерпретаторе, позволяяя
запускать Java-код с любых веб-сайтов без угрозы операционной системе, а также
так называемых "тюрем", которые позволяют вводить ограничения ресурсов для
пользователей и процессов некоторых ОС. Наиболее популярны виртуальные машины,
полностью эмулирующие "стандартный" компьютер (например, VirtualBox или VMvare и
др).
Помимо ограничения вредоносного и непроверенного ПО, "песочницы" также
используются в процессе разработки для запуска "сырого" кода, который может
случайно повредить систему или испортить сложную конфигурацию. Такие
"тестировочные песочницы" копируют основные элементы среды, для которой пишется
код, и позволяют разработчикам быстро и безболезненно экспериментировать с
неотлаженным кодом.
Единственный минус всего сказанного в том, что своими силами соорудить
подобную "песочницу" довольно проблематично, потому что она потребует знания
архитектуры системы, прямого вмешательства в ядро системы и перехвата основных
системных вызовов. Мы же поговорим о том, как можно, немного поднапрягшись,
соорудить средствами C# вполне приемлемую реализацию "сандбокса", которая
позволит существенным образом повысить безопасность твоей системы. И для этого
не понадобятся специфичные знания системного программиста!
С чем работаем?
Уверен, что ты знаешь: система безопасности в .NET Framework представлена
пространством имен System.Security. Она содержит все необходимые нам программные
инструменты. При разработке систем, подобных "песочнице", нужно иметь в виду,
что имеющиеся на вооружении методы можно разбить на две подгруппы - методы
обеспечения безопасности на основе ролей Windows и методы, ограничивающие
поведение управляемого кода.
В чем разница между ними? Если говорить просто и примитивно - безопасность на
основе ролей на порядок ниже, чем прямое ограничение вмешательства кода в
систему. Хотя бы потому, что в Windows, несмотря на имеющиеся роли
пользователей, все сидят под правами администратора (правильно, а чего
мелочиться?). Этот фактор очень часто приводит к запуску малвари и руткитов на
компе. С учетом того, что код будет запускаться на машине именно с правами
админа, на мой взгляд, гораздо эффективнее разграничивать способы воздействия
кода на систему. Об этом и поговорим.
Основой для создания "песочницы" должны являться так называемые зоны
безопасности. CLR определяет пять таких зон: "Мой компьютер", Интранет,
доверенная зона, Интернет, недоверенная зона и "отсутствие зоны". Это
распределение является наиболее важным для обеспечения безопасности - зоны
определяют ее основой уровень. Программная реализация зональной безопасности
представлена в классе ZoneIdentityPermission. Включи его в код и можешь смело
ограничивать его исполнение:
public class Secured
{
[ZoneIdentityPermission(SecurityAction.LinkDemand,
Zone=SecurityZone.MyComputer)]
public static void SaySomething(String Input)
{
MessageBox.Show(Input);
}
}
Нет необходимости перечислять и рассказывать о многочисленных атрибутах и
методах, предоставленных программисту в .NET. Позволю себе рассказать только о
нескольких наиболее интересных.
Основным атрибутом, используемым в программной реализации "песочницы", может
служить [SecurityPermissionAttribute] – он позволяет назначить практически любую
логику поведения кода. Очень важный атрибут - [PermissionSetAttribute]. К
примеру, его можно использовать для постановки запрета открытия/чтения
какого-либо файла:
[System.Security.Permissions.FileIOPermission(SecurityAction.Deny,All="C:\\Windows\\file.dll")]
Пространство имен System.Security включает также атрибут
AllowPartiallyTrustedCallersAttribute, который позволяет "частично доверенному
коду" вызывать ваши "строгие" сборки ("strongly named assembly"). Вместе с тем,
его использование имеет две стороны медали - если ты добавишь этот атрибут к
"строгой" сборке, то это даст шанс недовереному коду поставить под угрозу всю
концепцию безопасности. А если ты не добавишь этот атрибут, код, вероятно, и
вовсе не сможет вызывать "строгие" сборки.
Очень удобен для использования в проектируемой "песочнице" класс
SecurityManager; нижеследующий код наглядно показывает его использование:
CodeAccessPermission cap = new FileIOPermission
(FileIOPermissionAccess.AllAccess, @"C:\");
PrincipalPermission PP = new
PrincipalPermission(SystemInformation.UserName,
"Administrator");
if (SecurityManager.SecurityEnabled)
{
if (SecurityManager.CheckExecutionRights) (...)
if (SecurityManager.IsGranted(PP)) (...)
if (SecurityManager.IsGranted(CAP)) (...)
Policies = SecurityManager.PolicyHierarchy();
while (Policies.MoveNext())
{
Policy = (PolicyLevel)Policies.Current;
}
}
Он вполне может служить основой для разработки "песочницы" и показывает
использование двух типов разрешений: класс CodeAccessPermission определяет, что
может делать или не делать код. К примеру, даже если у юзера будут права на
доступ к жесткому диску, сам код может и не получить доступа. Забавно, не правда
ли? Класс PrincipalPermission фокусирует взгляд на правах пользователя. Он
поможет проверить, какими правами обладает тот или иной пользователь.
Существует, конечно, возможность определить необходимые разрешения для
выполнения приложения, однако как быть, если их нужно объединить в одно
логическое целое? Для этого и существует такой класс, как PermissionSet. В нем
есть несколько ключевых методов, которые позволяют легко реализовать нужную нам
функциональность "песочницы": метод AddPermission() добавит выбранное тобой
разрешение для приложения, а методы Demand(), Assert() и др. реализуют
программную логику по контролю за кодом:
class Program
{
static void Main()
{
string pluginFolder = AppDomain.CurrentDomain.
BaseDirectory;
string plugInPath = Path.Combine (pluginFolder,
"plugin.exe");
PermissionSet ps = new PermissionSet
(PermissionState.None);
ps.AddPermission
(new SecurityPermission
(SecurityPermissionFlag.Execution));
ps.AddPermission
(new FileIOPermission
(FileIOPermissionAccess.PathDiscovery |
FileIOPermissionAccess.Read, plugInPath));
AppDomainSetup setup =
AppDomain.CurrentDomain.SetupInformation;
AppDomain sandbox = AppDomain.CreateDomain
("Sandbox", null, setup, ps);
sandbox.ExecuteAssembly (plugInPath);
AppDomain.Unload (sandbox);
}
}
AppDomain - этим все сказано
В С#, в основном пространстве имен System, есть очень интересный, но
малоиспользуемый в повседневных прикладных целях класс – AppDomain. Это
своеобразный логический контейнер набора сборок.
Смысл моей статьи в том, чтобы показать, что домен приложения, в котором
исполняется твоя программа, определяет доступ к ресурсам и сервисам машины. И
этот доступ можно жестко задать самому. Домен приложения является своего рода
контейнером, который определяет набор атрибутов безопасности, предоставленных
программе.
При загрузке CLR (общеязыковая среда исполнения платформы .NET) создает домен
приложения или AppDomain. Основная задача домена приложения - обеспечить
изоляцию выполнения кода. Как результат - объекты, созданные одним AppDomain, не
видят другие домены приложения, то есть код одного AppDomain не может напрямую
ссылаться на объект, созданный в другом AppDomain. Домены приложения можно
защищать по отдельности - при создании домену приложения можно назначить набор
разрешений, определяющий максимальные права сборок, работающих в AppDomain. Это
позволяет загружать код и быть уверенным, что он не испортит важные структуры
данных, используемые самим доменом. Кроме того, домены приложения можно
конфигурировать по отдельности. Короче говоря, C# и .NET предоставляет нам
возможность контролировать не только само приложение, но и доступ кода с
разграничениями безопасности на основе ролей (столь любимой Microsoft). А
скомбинировав вместе изложенные выше способы, можно легко создать вполне сильную
реализацию "песочницы", которая добром послужит тебе в укреплении безопасности
системы.
Что делать с неуправляемым кодом?
С управляемым кодом, написанным на языках .NET-платформы, мы вроде
разобрались. Напоследок определимся с неуправляемым кодом - можно ли его
использовать в разработке "песочниц" наподобие нашей? Можно, но неуправляемый
код требует особого подхода. Поэтому ответ на вопрос: "Можно ли да на 100
процентов контролировать…" скорее будет отрицательным. Все, что ты сможешь
сделать - позволить или запретить выполнение неуправляемого кода в программе. И
это можно считать большим, но вполне объяснимым недостатком .NET Framework - уж
больно разные подсистемы Windows. По факту архитектура .NET Framework дает
программисту два варианта обеспечения безопасности в системах, использующих как
управляемый, так и неуправляемый код. Первый - использовать функции Win32 API
для обеспечения безопасности самого приложения. Второй - размещать управляемый
код в отдельном домене, и Microsoft предпочитает его. Впрочем, выбор как всегда,
за тобой.
Запрет на использование неуправляемого кода
SecurityPermission Perm;
Perm = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);
Perm.Deny();
Заключение
В рамках одной статьи затруднительно описать весь огромный набор средств,
предоставленных для реализации "песочницы", но, уверен, что основное
представление о поставленной задаче получено. Говорят, что технологии .NET
развращают программиста. Часто приходится слышать: "все уже было сделано до
нас". Такая точка зрения абсолютно обоснована, однако всегда есть области, где
для ума программиста остается свобода маневра. Удачного компилирования и да
пребудет с тобой Сила!
WWW
Чтобы лучше ориентироваться в вопросах безопасности .NET Framework, милости
просим на
http://msdn.microsoft.com/security/,
winguides.com/security,
а также www.codeproject.com.
INFO
Для работы непосредственно с наборами разрешений в .NET есть утилита CasPOL,
а вручную можно настроить набор политик безопасности через Администрирование -
Microsoft .NET Framework Configuration.
WARNING
Помни, если приложение состоит из управляемого кода (который гарантировано
безопасен) и не вызывает неуправляемого кода, - нет никаких проблем с
выполнением нескольких управляемых приложений в одном процессе!