Технология Клиент-Сервер 2010'3 |
||||||||
|
Как, возможно, известно многим из вас, Code Access Security (или коротко CAS) – это технология обеспечения безопасности, разработанная, чтобы предоставить возможность защиты системных ресурсов при исполнении сборок .NET. Такими системными ресурсами могут быть: локально расположенные файлы, файлы на удаленных системах, ключи реестра, базы данных, принтеры и так далее. Неограниченный доступ к этим типам ресурсам влечет за собой потенциальный риск, так как вредоносный код может выполнить разрушительные операции, например, удалить критически важные файлы, изменить ключи реестра или удалить данные, хранящиеся в БД.
К счастью, CAS может снизить этот риск, давая мелкозернистый контроль над тем, какие ресурсы доступны, и кому именно. К сожалению, использовать возможности CAS в .NET Framework не всегда просто, и обычно для их надлежащей реализации требуется немало усилий. По этой причине Microsoft существенно изменила CAS в .NET Framework 4.0, чтобы упростить его реализацию и упрощение им.
Это первая статья из двух, в которых мы познакомимся с новой моделью Code Access Security в .NET Framework 4.0 и с тем, как она изменяет способ разработки ПО в случаях, когда безопасностб имеет высший приоритет. Мы рассмотрим новую модель Security Transparence в .NET Framework 4.0, а также то, как она была спроектирована, чтобы исключить потребность в модели CAS Policy, использовавшейся вплоть до .NET Framework 3.5. Мы увидим, как важен стал хост, на котором исполняется защищенный код, и как хост и сборки (ваши или сторонние) взаимодействуют друг с другом при управлении защищенными ресурсами. В следующей статье мы углубимся в структуру сборки, сосредотачиваясь на методах защищенных сборок и том, как реализовать для них более тонкую, но гранулярную, стратегию защиты.
Эту статью мы начнем с описания CAS до .NET 4.0 Framework, чтобы наиболее ясно показать изменения, появившиеся в новой модели. Затем мы опишем основы новой системы CAS и сообщим, по крайней мере, что нужно, чтобы начать работать с ней. Наконец, мы проанализируем как хосты изменяют способ применения CAS, и как помещать ненадежные сборки в песочницу, чтобы предотвратить исполнение возможно вредоносного кода. Для описания новой модели безопасности и ее влияния на наши сборки мы приведем несколько демонстрационных примеров. Эти примеры можно найти в документах, перечисленных в конце статьи.
Вот очень краткое описание того, как до .NET Framework 4.0 Code Access Security позволяло разработчикам и системным администраторам защищать ресурся, определяя:
Если Evidence назначается рантаймом при каждом исполнении сборки, то Code Group и связанные с ними PermissionSet хранятся на машине, они могут быть изменены или созданы заново системным администратором. Разработчики могут взаимодействовать с разрешениями, предоставленными их сборкам, одни из двух способов:
Например, чтобы управлять доступом к файловой системе можно использовать FileIOPermissionAttribute или экземпляр класса FileIOPermission. Первый используется декларативно, второй – императивно.
Для проверки разрешений, выданных их коду, разработчики могут вызывать такие методы как Demand(), LinkDemand() или InheritanceDemand() (последние два – только декларативно), или переопределить их, используя такие методы как Assert() , Deny() или PermitOnly().
При загрузке сборки рантайм .NET Framework проверяет ее Evidence и назанчает ее только те разрешения, что допустимы для ее Code Group. Если у сборки есть все разрешения,
Изменения в .NET 4.0 во многом являются реакцией на имеющиеся проблемы, а не реализацией новых возможностей. С годами модель в CAS, реализованной в предыдущих версиях .NET Framework, проявились проблемы, которые не так просто решить. В частности:
Вся работа, которую нужно выполнить, чтобы создать успешную CAS Policy, то есть вся работа, необходимая для определения правильных PermissionSet и Code Group на каждой отдельной машине. Это отвратило многих администраторов от применения этой технологии на их системах.
Поэтому Microsoft .NET Security Team решила переписать Code Access Security с нуля. Основные различия можно свести к следующему:
Security Transparent-модель появилась в .NET Framework 2.0, но до версии 4.0 ее можно было использовать только на уровне сборок. В основном она использовалась для предотвращения расширения привилегий кода, прозрачного для системы безопасности (Security transparent). На самом деле такой код не мог использоваться для форсирования, форсирование управлялось системой политик CAS; такое поведение теперь называется Level1 Security Transparency. В .NET Framework 4.0 эти ограничения убраны, и Security Transparent-модель стала стандартным способом защиты ресурсов. Новая модель получила название Level2 Security Transparency, и сейчас мы посмотрим, как она работает.
Level2 security transparent-модель делит весь код на три категории: SecurityCritical-код, SecurityTransparent-код и SecuritySafeCritical-код. Давайте рассмотрим в подробностях, что они могут, а чего не могут:
Обратите внимание, что, поскольку SecurityTransparent-код не может вызывать SecurityCritical-код, Level2 Security Transparence становится механизмом принуждения.
Начнем с нескольких примеров, демонстрирующих модель. Начнем с простого консольного приложения, которое поможет нам исследовать установки безопасности для сборки, которую мы пишем. Для этого мы будем использовать новые свойства .NET Framework 4.0:
Сперва напишем простую DLL-библиотеку, содержащую следующий класс:
/// <summary> /// Demo class /// </summary> public class AssemblyInfo { /// <summary> /// Write to the console the security settings of the assembly /// </summary> public string GetCasSecurityAttributes() { //gets the reference to the current assembly Assembly a = Assembly.GetExecutingAssembly(); StringBuilder sb = new StringBuilder(); //show the transparence level sb.AppendFormat("Security Rule Set: {0} \n\n", a.SecurityRuleSet); //show if it is full trusted sb.AppendFormat("Is Fully Trusted: {0} \n\n", a.IsFullyTrusted); //get the type for the main class of the assembly Type t = a.GetType("CasAssemblyInfo.AssemblyInfo"); //show if the class is Critical,Transparent or SafeCritical sb.AppendFormat("Class IsSecurityCritical: {0} \n", t.IsSecurityCritical); sb.AppendFormat("Class IsSecuritySafeCritical: {0} \n", t.IsSecuritySafeCritical); sb.AppendFormat("Class IsSecurityTransparent: {0} \n", t.IsSecurityTransparent); try { sb.AppendFormat("\nPermissions Count: {0} \n", a.PermissionSet.Count); } catch (Exception ex) { sb.AppendFormat("\nError while trying to get the Permission Count: {0} \n", ex.Message); } return sb.ToString(); } } |
Метод GetCasSecurityAttributes() внутри класса возвращает строку, которая содержит:
Затем создадим консольное приложение, использующее класс AssemblyInfo из предыдущей библиотеки:
class Program { /// <summary> /// Точка входа /// </summary> /// <param name="args"></param> static void Main(string[] args) { // получаем zone evidence сборки Zone z = Assembly.GetExecutingAssembly().Evidence.GetHostEvidence<Zone>(); Console.WriteLine("Zone Evidence: " + z.SecurityZone.ToString() + "\n"); Console.WriteLine(new AssemblyInfo().GetCasSecurityAttributes()); } |
Метод Main записывает значение Zone Evidence сборки в консоль, после чего вызывает метод GetCasSecurityAttributes() класса AssemblyInfo, который содержится в DLL. При запуске программы мы получим следующий результат:
Как можно видеть на рисунке 2:
[assembly: SecurityRules(SecurityRuleSet.Level1)] |
Обратите внимание на то, что при этом сборка становится прозрачной, но наша программа будет исполняться по-прежнему. Это потому, что Level1 Security Transparency не может применить принуждение к нашему коду, поскольку, как я уже говорил, принуждением в версиях .NET Framework до версии 4.0 занималась CAS Policy. Вскоре мы увидим, что такого отсутствия принуждения не бывает при Level2 Security Transparence, а если вы хотите сохранить совместимости с предыдущими версиями CAS, вы должны активировать CAS Policy, добавив следующие строки в файл конфигурации сборки:
<configuration> <runtime> <NetFx40_LegacySecurityPolicy enabled="true" /> </runtime> </configuration> |
На рисунке 3 показано, что Evidence для сборки изменилась “Internet”, но наша сборка опять полностью доверенная, и классы в ней - SecurityCritical; к сборке не применяется никаких разрешений.
В предыдущих версиях .NET Framework разные свидетельства для зон предполагали, что к сборке будут применяться разные Code Group и, в некоторых ситуациях, тот же самый код по неизвестным причинам перестанет работать. Если, например, .exe нужен доступ к какому-то файлу, он может делать это на локальной машине (зона MyComputer), но при перемещении в разделяемую папку (зона Internet), он выдаст исключение безопасности и остановит исполнение. Сперва может показаться, что эта новая система обеспечивает минимальную безопасность, но давайте разберемся, как сохранить наш код в более трудных условиях.
В предыдущем примере первое, что приходит на ум, это что с новой Level2 Security Transparence общая безопасность системы снижается. Игнорируя принцип наименьших привилегий, новая модель наверняка приведет к тому, что большее количество кода будет полностью доверенным для исполнения? На самом деле это не так; в целом модель защиты только изменилась и стала более простой в реализации, тем самым уменьшая шанс появления потенциально опасных ошибок. Вскоре мы увидим, как можно снизить число разрешений для кода, исполняемого в модели Level2. Однако если администраторы хотят контролировать какого типа код может исполняться на конкретной системе, они могут использовать такие средства как Software Restriction Policies или новый AppLocker, входящий в Windows 7 и Windows 2008 Server R2. Используя эти новые средства, можно контролировать не только управляемый (как при старой CAS Policy), но даже и неуправляемый код.
С точки зрения разработчика, если говорить о сокращении числа разрешений, теперь можно исполнять приложения как SecurityTransparent-код.Если приложению не нужен доступ к защищенным ресурсам, это хороший способ соблюсти основы принципа наименьших привилегий. Чтобы заставить сборку исполняться как SecurityTransparent, нужно всего-навсего вставить следующий атрибут сборки:
[assembly: SecurityTransparent()] |
Он говорит, что весь код, даже если сборка является полностью доверенной, будет иметь тип SecurityTransparent. Если добавить эту строку в нашу демонстрационную сборку, мы получим следующий результат:
Как видно, наша сборка теперь SecurityTransparent, несмотря на то, что она полностью доверенная, поскольку она также unhosted. В результате она может вызывать только SecurityTransparent-код, таким образом, ее нельзя использовать для дотупа к защищенным (SecurityCritical) ресурсам. На самом деле, как мы видим, когда она пытается получить Permission Count, возникает исключение, так как код, содержащийся в аксессоре get свойства PermissionSet – это SecurityCritical-код. То же происходит, если .exe исполняется из сетевой папки.
Приведенные в этом разделе примеры, казалось бы, показывают, что де-факто Level2 Security Transparence – это модель "все или ничего". Если сборка является полностью доверенной, она может делать все что угодно, а если ей назначить SecurityTransparent, она вообще не может использовать защищенные ресурсы. Однако возможен и более гранулированный подход, если нужно защитить отдельные ресурсы, и основывается он на Allow Partially Trusted Caller Attribute (APTCA), который можно задать для сборки. С его помощью мы можем определить код как SecuritySafeCritical, тем самым создавая мост между SecurityTransparent- и SecurityCritical-кодом. Это мы обсудим подробнее в следующей статье.
Все это здорово, если мы работаем только со своим кодом. Но что если нам приходится использовать чужую сборку, которой мы не можем полностью доверять? Мы знаем, что если запустить ее на нашей машине и не указать внутри атрибут SecurityTransparent, она сможет сделать с нашими ресурсам все, что угодно.
Решение – поместить сборку в песочницу (sandbox), ограничивающую использование ресурсов сборкой, что позволит нам защитить систему. Помещение в песочницу состоит в создании частично доверенного хоста и исполнении сборки внутри него. Как уже говорилось в начале статьи, Level2 Security Transparence пришла на смену CAS Policy, оставив хосту возможность создавать разрешения, так что это вполне элегантный метод создания песочницы, в чем мы сейчас и убедимся. Частично оверенный хост создается методом AppDomain.CreateDomain(), определенным в .NET Framework 4.0. Этот метод появился не в версии 4.0, он просто изменен для того, чтобы создавать песочницу.
В доменах приложений до версии 4.0 разрешения на доступ к ресурсам определялись CAS Policy, которая, для каждой сборки, загруженной в домен, применяла ограничения, основываясь на ее Code Group, которая, в свою очередь, определялась ее Evidence, и PermissionSet самой Code Group. Это приводило к гетерогенности домена, в котором конфигурации PermissionSet-ов могли смешиваться, что приводило к очень сложным ситуациям. В Level2 Security Transparence разрешения даются непосредственно домену, и все сборки внутри него вынужденно им следуют (исключение делается для тех, которые разработчик считает полностью доверенными). Это называется гомогенным доменом (Homogeneous Domain).
Давайте продолжим работу над демонстрационной DLL и попробуем запустить ее в домене-песочнице (то есть частично доверенном). Для этого нужно изменить класс AssemblyInfo, позволив ему стать наследником MarshalByRefObject, и использовать метод AppDomain.CreateDomain(). Прежде чем мы сможем использовать этот метод, нужно создать PermissionSet, который будет предоставлен создаваемому домену, и, благодаря тому, что это гомогенный домен Level2 Security Transparent-модели, сборкам, помещаемым в него.
Вместо того, чтобы указывать разрешения в PermissionSet по одному, мы можем использовать новый метод .NET Framework 4.0 SecurityManager.GetStandardSandbox(), который позволяет возвратить ассоциированный PermissionSet с evidence, передаваемым в качестве ввода. Следующий код показывает, как это делается:
/// <summary> /// create a permission set /// </summary> public static PermissionSet GetPermissionSet() { //create an evidence of type zone Evidence ev = new Evidence(); ev.AddHostEvidence(new Zone(SecurityZone.MyComputer)); //return the PermissionSets specific to the type of zone return SecurityManager.GetStandardSandbox(ev); } |
Строка кода, приведенная выше, возвращает объект PermissionSet, который содержит все разрешения, ассоциированные с MyComputer Zone Evidence. Затем мы создаем метод, который просматривает свойства безопасности домена, который мы создаем:
/// <summary> /// Get the Domain security info /// </summary> public static string GetDomainInfo(AppDomain domain) { StringBuilder sb = new StringBuilder(); //check the domain trust sb.AppendFormat("Domain Is Full Trusted: {0} \n", domain.IsFullyTrusted); //show the number of the permission granted to the assembly sb.AppendFormat("\nPermissions Count: {0} \n\n\n", domain.PermissionSet.Count); return sb.ToString(); } |
Теперь можно реализовать метод Main:
/// <summary> /// Entry point /// </summary> static void Main(string[] args) { //create the AppDomainSetup AppDomainSetup info = new AppDomainSetup(); //set the path to the assembly to load. info.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); //create the domain AppDomain domain = AppDomain.CreateDomain("CasHostDemo", null, info, GetPermissionSet()); //create an instance of the AssemblyInfo class Type t = typeof(AssemblyInfo); ObjectHandle handle = Activator.CreateInstanceFrom(domain, t.Assembly.ManifestModule.FullyQualifiedName, t.FullName); AssemblyInfo ai = (AssemblyInfo)handle.Unwrap(); Console.WriteLine("DOMAIN INFO:\n"); //get the domain info Console.WriteLine(GetDomainInfo(domain)); Console.WriteLine("ASSEMBLY INFO:\n"); //get the assembly info form the sandboxed assembly Console.WriteLine(ai.GetCasSecurityAttributes()); Console.ReadKey(); } |
Просто чтобы объяснить, что происходит – метод Main:
Заметьте, что мы не передаем методу AppDomain.CreateDomain никаких объектов Evidence, поскольку они ему больше не нужны. Поскольку Evidence больше не используется для назначения домену правильной Code Group с использованием CAS Policies, Evidence просто больше не нужен.
Теперь при запуске нашей программы мы получим следующее:
Как можно видеть, использование зоны MyComputer в качестве Evidence не влияет на наш код. На самом деле эта зона создает полностью доверенный домен без разрешений из PermissionSet. Но если мы изменим зону с MyComputer на, например, Internet, мы получим:
Теперь домен работает как частично доверенный домен, и у него есть 7 разрешений (7 разрешений, связанных с зоной Internet), что значит, что наша сборка теперь работает как частично доверенная. Все классы – SecurityTransparent, и аксессор свойства PermissionSet выдает то же исключение, что и на рисунке 4. Итак, мы создали песочницу для сборки.
Я закончу эту статью указанием, что домен в песочнице дает возможность исполнения сборок как полностью доверенных, даже если сам домен является только частично доверенным: сборки, хранящиеся в Global Assembly Cache (GAC), исполняются в полностью доверенном режиме по умолчанию. Если мы хотим добавить сборку не из GAC в список полностью доверенных сборок, нужно только информировать о ней метод Assembly.CreateDomain(), указав ее строгое имя. Очевидно, сборки должны быть подписаны файлом ключей строгого имени. Для этого изменим предыдущий метод следующим образом:
/// <summary> /// Entry point /// </summary> static void Main(string[] args) { //create the AppDomainSetup AppDomainSetup info = new AppDomainSetup(); //set the path to the assembly to load. info.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); Assembly a = Assembly.LoadFile(Path.Combine(info.ApplicationBase,"CasAssemblyInfo.dll")); StrongName sName = a.Evidence.GetHostEvidence<StrongName>(); //create the domain AppDomain domain = AppDomain.CreateDomain("CasHostDemo", null, info, GetPermissionSet() ,new StrongName[] {sName}); //create an instance of the AseemblyInfo class Type t = typeof(AssemblyInfo); ObjectHandle handle = Activator.CreateInstanceFrom(domain, t.Assembly.ManifestModule.FullyQualifiedName, t.FullName); AssemblyInfo ai = (AssemblyInfo)handle.Unwrap(); Console.WriteLine("DOMAIN INFO:\n"); //get the domain info Console.WriteLine(GetDomainInfo(domain)); Console.WriteLine("ASSEMBLY INFO:\n"); //get the assembly info form the sandboxed assembly Console.WriteLine(ai.GetCasSecurityAttributes()); Console.ReadKey(); } |
В новом методе main мы загружаем демонстрационную сборку из файловой системы и получаем ее StrongName:
Assembly a = Assembly.LoadFile(Path.Combine(info.ApplicationBase,"CasAssemblyInfo.dll"));
StrongName sName = a.Evidence.GetHostEvidence<StrongName>();
|
Затем мы используем другую перегрузку метода AppDomain.CreateDomain(), которая позволяет нам задать, какие сборки должны считаться полностью доверенными, и передаем ей StrongName демонстрационной сборки.
AppDomain domain = AppDomain.CreateDomain("CasHostDemo", null, info, GetPermissionSet() ,new StrongName[] {sName}); |
Запустив наш .exe, мы получаем:
Как видим, домен остается частично доверенным, а сборка работет в режиме полного доверия, и все ее классы – SecurityCritical.
В этой статье мы посмотрели, как в .NET Framework 4.0 работает новая модель Code Access Security, и увидели, что все сильно изменилось по сравнению с предыдущими версиями. Это в значительной степени произошло вследствие того, что у предыдущей модели имелся ряд серьезных ограничений (сложность, прежде всего), которые было непросто изменить без перестройки. С точки зрения разработчика миграция кода на Level2 Security Transparence-модель будет непростой задачей, и, в некоторых случаях, потребует частичной переработки приложений.
На данный момент мы показали общие концепции новой модели CAS, но наш анализ остановился на границе сборки. Мы рассматривали прозрачность и то, как использовать ее на уровне сборки и класса, а также взаимодействие сборок с хостами. Мы также рассмотрели несколько примеров поведения новой модели CAS по умолчанию, и из этих примеров поняли, что новая Level2 Security Transparence-модель выглядит как модель "все или ничего". Если сборка пользуется полным доверием, у нее есть доступ ко всем системным ресурсам, а если она является частично доверенной, она не может использовать никакие из них. Вопреки этой видимости, на самом деле это не так, и в следующей статье мы увидим, как Level2 Security Transparence-модель может быть применена более гранулированным образом при помощи атрибута Allow Partially Trusted Callers (APTCA) и SecuritySafeCritical-кода для применения CAS на уровне методов.
Ваши предложения и комментарии мы ожидаем по адресу: mag@rsdn.ru
Copyright ©
1994-2002 Оптим.ру