Безопасность доступа кода

83

Чем важна модель безопасности доступа кода (Code Access Security)? С помощью модели безопасности на основе ролей можно указывать, что разрешено делать пользователю, а с помощью модели безопасности доступа кода — что разрешено делать коду. В .NET 4 эта модель упростилась, благодаря удалению необходимости в настройке сложных политик безопасности и добавлению второго уровня прозрачной безопасности (Security Transparency Level 2). Один такой уровень существовал и ранее, а второй является нововведением .NET 4.

На этом уровне проводится различие между кодом, которому разрешено выполнять привилегированные вызовы (такие как вызовы собственного кода), и кодом, которому это делать не разрешается. Весь код делится на три категории:

Критичный для безопасности код (Security-Critical Code)

В рамках этого кода может выполняться любой код. Вызываться из прозрачного кода такой код не может.

Безопасный код (Safe-Critical Code)

В рамках этого кода могут выполняться различные проверки на предмет безопасности. Такой код может вызываться из прозрачного кода.

Прозрачный код

В рамках этого кода может выполняться очень ограниченное число операций. Этому коду разрешено выполняться только с определенным набором разрешений и только в песочнице (sandbox). В нем не может содержаться никакого небезопасного или непроверяемого кода и вызываться критичный для безопасности код. При написании Windows-приложений ограничение прав кода не применяется.

Приложения, выполняющиеся в настольной среде, обладают всеми привилегиями доверия и могут содержать любой код. Технология изоляции кода на время выполнения в называемую песочницей (sandbox) ограниченную среду применяется с приложениями SilverLight, а также приложениями ASP.NET, которые обслуживаются веб-провайдером или обладают специфической функциональностью, например, предусматривают запуск дополнительных надстроек за счет использования Managed Add-In Framework.

Второй уровень прозрачной безопасности

Сборку можно снабжать атрибутом SecurityRules и устанавливать для него значение SecurityRuleSet.Level2 для применения нового уровня прозрачности, который доступен в .NET 4. (По умолчанию в .NET 4 используется именно этот уровень.) Для обеспечения обратной совместимости для него следует установить значение Level1:

[assembly: SecurityRules(SecurityRuleSet.Level2)]

В случае применения атрибута SecurityTransparent вся сборка не будет делать ничего привилегированного или небезопасного. Она сможет только вызывать какой-то прозрачный или безопасный код. Применять этот атрибут допускается только на уровне всей сборки.

[assembly: SecurityTransparent()]

Атрибут AllowPartiallyTrustedCallers позволяет сделать код чем-то средним между прозрачным кодом и кодом остальных категорий. В случае применения этого атрибута код по умолчанию интерпретируется как прозрачный, но отдельные типы или члены внутри него могут иметь и другие атрибуты:

[assembly: AllowPartiallyTrustedCallers()]

Если не применен ни один из этих атрибутов, код считается критическим для безопасности. Однако при желании в нем можно применить атрибут SecuritySafeCritical к отдельным типам и членам и тем самым сделать их пригодными для вызова из прозрачного кода:

[assembly: SecurityCritical()]

Полномочия

В случае выполнения кода внутри песочницы указывать, что коду разрешено делать, можно в самой песочнице за счет определения полномочий .NET. Если приложениям, запущенным в настольной среде, предоставляется полный набор полномочий, который позволяет им предпринимать любые действия, то приложениям, выполняющимся в песочнице, предоставляется лишь набор полномочий, который главная среда (хост) передает песочнице и который позволяет выполнять лишь определенные действия. Можно также определять полномочия для домена приложений, запускаемого из настольных приложений. Для этого должен использоваться API-интерфейс Sandbox.

Под полномочиями, или разрешениями, понимаются действия, которые разрешается (или запрещается) выполнять каждой группе кода. Например, полномочия могут включать в себя "чтение файлов из файловой системы", "выполнение операций записи в Active Directory" и "использование сокетов для открытия сетевых соединений". Есть несколько предопределенных полномочий, и можно также создавать собственные.

Полномочия .NET не зависят от разрешений операционной системы. Полномочия .NET просто проверяются исполняющей средой. Сборка требует выдачи полномочия для выполнения определенной операции (например, класс File требует выдачи полномочия FileIOPermission), а среда CLR проверяет, чтобы сборке было выдано необходимое полномочие, и она могла продолжить свою работу.

Перечень полномочий, которые могут применяться к сборке и запрашиваться из кода, является очень длинным и многоуровневым. Ниже перечислены лишь некоторые из предоставляемых CLR классов полномочий, чтобы продемонстрировать, насколько высокую степень контроля над кодом с их помощью можно обеспечить:

DirectoryServicesPermission

Позволяет управлять возможностью получать доступ к Active Directory с помощью классов System.DirectoryServices.

DnsPermission

Позволяет управлять возможностью использования DNS (Domain Name System — служба имен доменов).

EnvironmentPermission

Позволяет управлять возможностью выполнять чтение и запись в переменных среды.

EventLogPermission

Позволяет управлять возможностью выполнять операции чтения и записи в журнале событий.

FileDialogPemission

Позволяет управлять возможностью получать доступ к файлам, которые пользователь выбирает в диалоговом окне Open (Открыть). Обычно такое полномочие применяется в случае невыдачи полномочия FileIOPermission для предоставления хотя бы ограниченного доступа к файлам.

FileIOPermission

Позволяет управлять возможностью работать с файлами (чтение, запись и добавление в файлы, а также создание, изменение и доступ к папкам).

IsolatedStorageFilePermission

Позволяет управлять возможностью доступа к приватным виртуальным файловым системам.

IsolatedStoragePermission

Позволяет управлять возможностью доступа к изолированным хранилищам, т.е. хранилищам, ассоциируемым с отдельными пользователями и имеющими какие-то идентификационные данные в коде.

MessageQueuePermission

Позволяет управлять возможностью использования очереди сообщений через службу Microsoft Message Queue.

PerformanceCounterPermission

Позволяет управлять возможностью использования счетчиков производительности.

PrintingPermission

Позволяет управлять возможностью выполнения печати.

ReflectionPermission

Позволяет управлять возможностью обнаружения информации о типах во время выполнения с использованием класса System.Reflection.

RegistryPermission

Позволяет управлять возможностью чтения, записи, создания и удаления разделов и параметров в системном реестре.

SecurityPermission

Позволяет управлять возможностью выполнения, подтверждения полномочий, вызова неуправляемого кода, пропуска процесса верификации, а также выдачи других прав.

ServiceControllerPermission

Позволяет управлять возможностью осуществлять управление службами Windows.

Socket Permission

Позволяет управлять возможностью создания или приема соединений TCP/IP по сетевому транспортному адресу.

SQLClientPermission

Позволяет управлять возможностью доступа к базам данных SQL Server с помощью предусмотренного в .NET поставщика данных для SQL Server.

С помощью каждого из этих классов полномочия часто удается задавать даже еще более точно. Например, класс DirectoryServicesPermission позволяет проводить разграничение между полномочиями на выполнение операций чтения и на выполнение операций записи, а также указывать, к каким именно записям в службах каталогов должен быть разрешен или запрещен доступ.

Наборы полномочий

Под наборами полномочий понимаются коллекции разрешений. Они избавляют от необходимости применять к коду каждое разрешение отдельно и позволяют группировать необходимые разрешения и применять их к коду сразу вместе. Например, сборка с набором полномочий FullTrust будет иметь полный доступ ко всем ресурсам, а сборка с набором полномочий Local Intranet — лишь ограниченный с возможностью выполнения операций записи только в изолированном хранилище и больше нигде в файловой системе. Допускается создавать собственные наборы полномочий, включающие только определенные разрешения.

Назначение полномочий группам кода избавляет от необходимости иметь дело с одиночными полномочиями и позволяет применять полномочия сразу целыми блоками, для чего в .NET и предусмотрена концепция наборов полномочий. Эти наборы, по сути, представляют собой списки предоставляемых коду прав, объединенные в именованный набор. Ниже приведено краткое описание семи именованных наборов полномочий, которые поставляются в .NET изначально:

Тип полномочия Описание
FullTrust Подразумевает отсутствие всяких ограничений в полномочиях.
SkipVerification Подразумевает обход верификации.
Execution Позволяет коду выполняться, но не получать доступ ни к каким защищенным ресурсам.
Nothing Не предоставляет коду никаких полномочий и не позволяет ему выполняться.
Local Intranet Предоставляет лишь подмножество из полного набора полномочий. Например, операции файлового ввода-вывода ограничиваются возможностью доступа только для чтения к общему ресурсу, который является источником сборки. В .NET 3.5 и более ранних выпусках (до появления .NET 3.5 с пакетом обновлений SP1) этот набор полномочий применялся в случае выполнения приложения из сетевого общего ресурса.
Internet Предусматривает использование стандартной политики безопасности для кода неизвестного происхождения. Из всех перечисленных наборов полномочий этот является самым ограниченным. Например, коду, выполняющемуся в рамках этого набора полномочий, не разрешены ни файловые операции ввода-вывода, ни операций чтения и записи журнала событий, ни операции чтения и записи переменных среды.
Everything Предусматривает выдачу всех перечисленных выше полномочий за исключением SkipVerification. Администратор может изменять любое из полномочий в этом наборе. Это удобно, если необходимо ужесточить стандартную политику безопасности.

Важно обратить внимание на то, что изменять определения полномочий можно только в наборе Everything (Все); остальные наборы являются фиксированными и изменяться не могут. Разумеется, можно создавать и собственные наборы полномочий.

Запрос полномочий программным образом

Сборка может запрашивать полномочия как декларативным, так и программным образом. В следующем фрагменте кода показано, как запрашивать полномочия с помощью метода DemandFileIOPermissions(). После импорта пространства имен System.Security.Permisions можно выполнять проверку на предмет наличия необходимых полномочий, создавая объект FileIOPermission и вызывая его метод Demand().

Этот метод позволяет проверить, есть ли у кода, вызывающего метод, в данном случае — DemandFileIOPermissions, необходимые полномочия. Если метод Demand() завершается неудачей, генерируется исключение типа SecurityException. Это исключение можно не перехватывать, и предоставлять его обработку вызывающему коду.

using System;
using System.Security;
using System.Security.Permissions;
using System.IO;

[assembly: AllowPartiallyTrustedCallers()]


namespace Wrox.ProCSharp.Security
{
    [SecuritySafeCritical]
    public class DemandPermissions
    {
        public void DemandFileIOPermissions(string path)
        {
            var fileIOPermission = new FileIOPermission(PermissionState.Unrestricted);
            fileIOPermission.Demand();

            //...
        }
    }
}

Класс FileIOPermission содержится внутри пространства имен System.Security.Permisions, в котором определен полный набор полномочий, а также классы для декларативных атрибутов полномочий и перечисления для параметров, применяемых для создания объектов полномочий (например, создания объекта FileIOPermission, указывающего, требуется доступ только для чтения или же полный доступ).

Для перехвата исключений, генерируемых исполняющей средой CLR, когда код пытается выполнить какие-то противоречащие выданным ему полномочиям действия, можно организовать перехват исключения типа SecurityException. Это исключение предоставляет доступ к набору полезных фрагментов информации, в том числе читабельной трассировке стека (SecurityException.StackTrace) и ссылке на метод, который привел к выдаче исключения (SecurityException.TargetSite). Вдобавок SecurityException предоставляет свойство SecurityException.PermissionType, которое возвращает информацию о типе объекта Permission, вызвавшего генерацию исключения.

В случае применения только классов .NET для операций файлового ввода и вывода самостоятельно запрашивать FileIOPermission не обязательно, поскольку классы .NET, позволяющие производить такие операции, умеют делать это сами. Однако при создании оболочек для вызова собственных методов API-интерфейса, таких как CreateFileTransacted(), его нужно запрашивать самостоятельно. Этот механизм также можно применять для запроса специальных полномочий у вызывающего кода.

Пройди тесты
Лучший чат для C# программистов