WWW.BOOK.LIB-I.RU
БЕСПЛАТНАЯ  ИНТЕРНЕТ  БИБЛИОТЕКА - Электронные ресурсы
 
s

Pages:     | 1 || 3 | 4 |   ...   | 5 |

«том 4 альманах програ ста Тематический сборник материалов MSDN Library и IN/ISDN' Magazine Microsoft Безопасность в.NET Шифрование Защита кода и данных Н.РШШР Е альманах программиста ...»

-- [ Страница 2 ] --

Одно из преимуществ виртуализированных исполняющих сред (virtualized execution environments), например CLR, заключается в том, что они позволяют создавать новые модели защиты, выходящие за рамки модели защиты нижележащей операционной системы. С этой целью CLR реализует собственную модель безопасного выполнения кода, независимую Публиковалось в MSDN Magazine/Русская Редакция. 2002. № 3 (сентябрь). — Прим. изд.

84 Защита кода и данных от хост-платформы (т. е. платформы, на которой работает CLR). Помимо таких преимуществ, как создание защиты на платформах, никогда ее не имевших (например в Windows 98), это еще и возможность введения модели защиты, в большей мере ориентированной на компоненты (more component-centric security model) и учитывающей природу динамически формируемых систем (dynamically composed systems). Именно этой компонентно-центрической модели, называемой защитой по правам доступа кода (code access security), и посвящена моя статья.

Компоненты и защита

Системы, динамически формируемые из компонентов, предъявляют уникальные требования к защите. Поскольку индивидуальные компоненты приложения зачастую приходят от разных организаций, к его частям скорее всего нельзя относиться с одинаковым доверием. Так, компонентам от доверяемых организаций может понадобиться доступ к закрытой информации или к критически важным для предприятия ресурсам, которые должны быть защищены от опасного кода. К сожалению, классическая для Windows NT и Unix модель защиты на основе участников безопасности игнорирует источник кода и фокусируется лишь на том, кто именно запускает этот код. Для программ 80-х годов, когда концепции компонентов еще не было, такая модель имела смысл. Однако в современном мире, где все крутится вокруг компонентов и код приложения может поступать из любых уголков земного шара, эта модель слишком груба особой пользы от нее уже нет. Отсюда и появилась защита по правам доступа кода.

CLR реализует модель защиты по правам доступа кода, в которой привилегии выдаются коду, а не пользователям. При загрузке новой сборки (assembly) CLR определяет так называемый признак (evidence) — информацию об источнике кода. Признак сопоставляется CLR с представлением сборки в памяти и используется системой защиты для определения того, какие привилегии следует выдать только что загруженному коду. Определение осуществляется прогоном признака через политику безопасности (security policy). На входе она принимает признак, а на выходе дает набор разрешений. Чтобы избежать потери производительности, политика обычно применяется только при явном запросе проверки на безопасность.

Разрешение (permission) — это право (right) на выполнение какой-либо доверяемой операции (trusted operation). Для защиты целостности систеСервисы признаков, политик, разрешений и применения политик 35 мы и персональной информации пользователя CLR поставляется с набором встроенных типов разрешений. Однако эта модель защиты расширяема, и в нее можно интегрировать пользовательские типы разрешений (user-defined permission types).

Какие разрешения назначаются тому или иному коду, определяется политикой. Применение политики (enforcement) осуществляется отдельным набором механизмов. Перед выполнением привилегированной операции доверяемый код должен применять политику безопасности, явно проверяя у вызывающих (callers), достаточно ли у них привилегий на такую операцию. Обратите внимание: «у вызывающих*. По умолчанию применение политики требует, чтобы у всех вызывающих (явно или косвенно) были разрешения, достаточные для выполнения привилегированной операции.





Благодаря этому недоверяемые компоненты не могут запускать «доверчивый» (хотя и доверяемый) кусок кода для выполнения какой-либо операции в своих интересах, полагаясь на его привилегии.

Как и сбор мусора (garbage collection), защита по правам доступа кода требует всеведущей (omniscient) и всемогущей (omnipotent) исполняющей среды. Это, в частности, означает, что вызывающий код, не соответствующий строгому формату, может помешать системе защиты. В связи с этим Common Language Infrastructure (CLI) разбивает весь код на две большие категории: верифицируемый код (verifiable code) и неверифицируемый (поп-verifiable code). Соответствие верифицируемого кода модели выполнения с контролем типов (type-safe execution model), предпочтительной в CLI, можно проверить математическими методами. По умолчанию Visual Basic.NET и С# генерируют верифицируемый код.

Однако некоторые языковые средства не поддаются такой проверке, и каноническим примером тому служит reinterpret_cast в C++. По этой причине код, генерируемый компилятором C++, считается неверифицируемым, а его применение может поставить защиту под угрозу. С точки зрения сохранения целостности системы, возможность загрузки неверифицируемого кода сама является неким разрешением, которое должно быть явно предоставлено коду через политику. Политика безопасности, по умолчанию действующая в CLR, выдает такое разрешение лишь коду, установленному в локальной файловой системе. Аналогичным образом вызов классической DLL, написанной на С или с использованием СОМ (по определению не являющихся верифицируемыми), также рассматривается как доверяемая операция, разрешение на которую по умолчанию предоставляется только коду, установленному в локальной файловой системе.

Защита кода и данных В целом, защита по правам доступа кода слабо отражается на быстродействии. Загрузчику сборок приходится выполнять дополнительную работу для получения признаков. Однако это происходит лишь на этапе загрузки. Главный фактор — применение политики. Он может приводить к потенциально дорогостоящему проходу по всему стеку вызова. К счастью, издержки можно свести к минимуму, избегая лишних применений политики (о соответствующих приемах программирования я расскажу позже).

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

CLR поставляется с семью типами признаков. Четыре из них (Site, Url, Zone и ApplicationDirectory) относятся к тому, откуда загружается код.

Другие два (StrongName и Publisher) — к тому, кем написан код. И, наконец, седьмой тип, Hash, базируется на общем содержимом сборки и позволяет распознавать конкретную компиляцию куска кода независимо от номера версии.

Собирательное название этих семи типов — признаки хоста (host evidence), так как они реализуются хост-средой. Вы можете определять собственные типы признаков, которые обобщенно называются признаками сборки (assembly evidence), поскольку они явно предоставляются самой сборкой. Определение новых типов признаков сборки требует и расширения механизма политики, чтобы он мог распознавать их, но эта тематика выходит за рамки моей статьи. В оставшейся ее части я сосредоточусь на

-• рассмотрении встроенных типов признаков хоста.

Загрузчик сборок в конечном счете имеет дело с URL кодовых баз, и некоторые из этих URL могут быть основаны на файлах. URL кодовой базы (codebase URL) используется для определения трех из четырех типов признаков, основанных на адресах (location-based evidence types): Url, Zone и Site. Понять, что такое признак Url, легче всего, поскольку это не более чем URL кодовой базы в исходной форме. Site и Zone производны от URL кодовой базы на основе ее содержимого.

Тип признака Site — просто часть хост-имени в HTTP URL. Например, если кодовая база сборки хранится в http://www.example.com/foo.dll, ее Site — это www.example.com. Но будь у кодовой базы URL на основе файСервисы признаков, политик, разрешений и применения политик ла (скажем, f i le :/// С :/usr/bin/f oo.dll), в признаке сборки не было бы Site.

Тип признака Site обычно используется для того, чтобы выдать полный карт-бланш коду, загруженному из доверяемого хранилища.

Тип признака Zone также является производным от URL кодовой базы.

CLR делит весь мир на пять зон безопасности, представленных значениями System. Security.SecurityZone перечислимого типа:

namespace System. Security { public enum SecurityZone { MyComputer, Intranet, Trusted, Internet, Untrusted, NoZone = OxFFFFFFFF Зона MyCoraputer применяется ко всему коду, загружаемому из локальной файловой системы. Код, источником которого является удаленная файловая система, делится на категории в зависимости от параметров, установленных в диалоговом окне Internet Options браузера Microsoft Internet Explorer.

В Internet Explorer определено три категории особых случаев URL. Категория Local Intranet (представленная Security Zone. Intranet) охватывает весь код, который загружается из удаленной файловой системы, подключенной как сетевой диск или доступной по сетевым путям в стиле UNC (например \\сервер\общий_ресурс\сод,е.[1\). Эта зона также охватывает все HTTP URL, в которых используются WINS-имена (Windows Internet Name Service), а не DNS-имена или IP-адреса (скажем, http:// 'сервер/ vroot/code.dll).

В прочие две категории, определенные в Internet Explorer, включаются заслуживающие доверия и злонамеренные сайты. По умолчанию эти категории пусты; однако пользователи или системные администраторы могут добавить в них любое число URL (в том числе с символами подстановки).

Эти категории представлены соответственно Security Zone. Trusted и SecurityZone. Untrusted. URL кодовых баз, не подпадающие ни под одну из этих трех категорий, включаются в базовую зону SecurityZone.Internet.

–  –  –

Zone — в том смысле, что тоже делит URL кодовых баз на категории.

ApplicationDirectory, обычно используемый в сочетании с типом признака Url, выдает специальные разрешения DLL, загружаемым из каталога APPEASE приложения.

CLR предусматривает программные эквиваленты каждого типа признака, которые размещаются в пространстве имен System.Security.Policy (сборка mscorlib). Код на рис. 1 создает объекты Url, Site и Zone для заданного URL кодовой базы. Заметьте, тип Site является особым случаем. Дело в том, что с URL на основе файлов Site не сопоставляется, и, если вы передадите такой URL, Site.CreateFromUrl вызовет исключение System.ArgumentException.

Рис. 1, Создание объектов-признаков using System;

ysing System,Security;

using Systeffl-Seeurity.Policy;

class App { static void Malfl(string[] argv) { // Принять URL кодовой базы как аргумент командной строки string codebase = argv[03;

// Создать три обьекта-признака Url url = new UrHcodebase);

Zone zone - Zone.CreateFromUrKcodebase);

Site site - mill;

try { site и Site.CreateFromUrltcodebase}; } catcft (ArgumentExeeptioFi) { Л ignore */, // Показать интересующие нас биты Console.Writelinef"url; Ш", yrl.Value);

Console.WriteLine("zone: 0}", zone,SecurityZone);

if (sits != null) Console.Writeltne("site: {&}", site.Name);

До сих пор я говорил о признаках на основе адресов. CLR поддерживает и два типа признаков, по которым можно определять автора кода независимо от того, откуда загружается данный код. Наиболее прост для понимания тип StrongNtime.

Сервисы признаков, политик, разрешений и применения политик 39 Сборкам, в имена которых входят открытые ключи (public keys), тип признака StrongName присваивается при загрузке. Три свойства StrongName соответствуют трем из четырех свойств имени сборки. Свойства Name, Version и PublicKey инициализируются загрузчиком на основе метаданных загружаемой сборки. Как и в случае с Site, UrI и Zone, вы можете сконструировать объект-признак StrongName программным способом (рис. 2).

Но вы должны понимать, что признак StrongName разрешается создавать только для сборок с открытыми ключами. Кроме того, обратите внимание, что в коде на рис. 2 мне понадобился объект-обол очка типа System.Security.Permissions.StrongNamePublicKeyBlob для байтового массива, в котором содержится открытый ключ. Этот объект принимает либо открытые ключи, либо маркеры открытых ключей (public key tokens).

Рис. 2. Создание объекта-оболочки System;

using using System.Reflection = using System.Security;

using System.Security.Permissions;

Systeat. Security. Policy;

using class App { static StrongName CreateFromAssefflblyAssembly assm) { // Получить имя и открытый ключ Assembly Name name = assm.GetNameO;

bytftO Pk = naiae.GetPublicKeyO;

// Сконструировать новый объект-признак StrongName s StrongNamePublieKeyBlob blob = new StrorigSamePublicKeyBloHjilO;

return new StrongHame(blou, name.Майе, паве.Version);

I static void Hain(stri,ngt] argv) { // Принять имя сборки как аргумент командной строки string name - argv[Q];

// Загрузить оборку и получить StrongName Assembly assm = Assembly.Load(name);

StrongName SR = GreateFroraAsserably(assm);

–  –  –

Признак, основанный на строгом имени (strong name), предполагает, что все стороны распознают открытый ключ как идентификацию конкретной компании, разрабатывающей программное обеспечение. Нераспознаваемые открытые ключи бесполезны, так как установить владельца открытого ключа по какому-либо алгоритму нельзя. Но такая возможность предоставляется сертификатами Х.509, которые используются пятым типом признаков — Publisher.

Тип признака Publisher добавляется загрузчиком сборки к коду, подписанному сертификатом Х.

509. В отличие от пар ключей «открытый-закрытый», которые можно генерировать автономно на компьютере разработчика, сертификаты нужно получать в доверенном центре сертификации (certificate authority, CA), например VeriSign, Entrust или Thawte. Эти центры выдают сертификаты только известным компаниям, способным подтвердить свою идентификацию. Чтобы разработчики могли начать работу с сертификатами, Microsoft поставляет две утилиты, которые генерируют неподтвержденные сертификаты в целях тестирования: makecert.exe и cert2spc.exe. Эти утилиты создают соответственно сертификаты Х.509 и Software Publisher Certificates (SPC), однако они не годятся для реального кода. О том, как приобрести настоящие сертификаты, вы узнаете на сайте своего любимого центра сертификации.

Сборку можно подписать сертификатом с помощью утилиты signcode.exe.

Этот инструмент помещает сертификат в DLL по общеизвестному адресу и вычисляет цифровую подпись, препятствующую попыткам модификации кода. Загрузчик обнаруживает этот сертификат при загрузке сборки и присоединяет к ней объект-признак Publisher. Код на рис. 3 демонстрирует, как сконструировать объект-признак Publisher, основанный на сертификате Х.509, который загружается с диска. Первые три свойства сертификата (Name, IssuerName и ExpirationDate) несут большую часть информации, интересующей разработчиков и системных администраторов.

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

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

Сервисы признаков, политик, разрешений и применения политик 91.

Рис. 3. Создание объекта-признака Publisher using Bystew;

using System.Reflection;

using Systeai. Security;

using System.Securlty.Peneissicns;

using Systeffl.Security.Policy;

using System.Security.Cryptography.XSQSCertificates;

class App { static void KaiR(stringl3 argv) { // Принять «к» файла сертификата как аргумент командной строки string паде = argv[0];

// Загрузить сертификат и получить признак Publisher X5G9Certificate cert * X509Certificate.CreateFronCertFilename);

Publisher pub * new Publishercert);

// Показать интересующие нас биты XSQSCertificate value = pub.Certificate;

Console. Writeline( value. GetNameO);

Console. ttriteLinevalue.GetIssuerNanie());

Bonsole.WriteLine{value.GetExpirationDateStrln8());

Console. WriteLine(value.GetEffectiveOateStringO);

Console. WriteLine(value.SeteertHasfcStringO);

Console.WriteLineCvalue.GetSe ri alNumberSt ri ng(});

Console. Writetine(valoe.6etPtibUcKeyString());

CoRsole.Wri teUne(value.6etKeyAloorith»(»;

–  –  –

В CLR определен встроенный тип (System.Security.Policy.Evidence) для хранения элементов, составляющих признак и используемых политикой безопасности. Сам по себе тип Evidence является простым набором (collection) и реализует интерфейс System.Collections.ICollection. Тип Evidence отличается от универсального набора (generic collection) в том, как он хранит два внутренних набора: один — для встроенных объектов-признаков хоста, другой — для пользовательских объектов-признаков сборки.

Код на рис. 4 иллюстрирует, как создать новый объект Evidence, содержащий признаки Url, Zone и Site.

При запуске применительно к URL кодовой базы http://www.microsoft.com/foo.dll эта программа генерирует вот что:

Защита кода и данных System.Security.Policy.Url version="1" Urlhttp://www. microsoft.com/foo.dlK/Url /System. Security,, Policy. Url System.Security.Policy.Zone version="1" ZoneInternet/Zone /System. Security,, Policy. Zone System.Security.Policy.Site version="1" Namewww.microsoft.com/Name /System. Security,, Policy. Slte Заметьте, что каждый объект-признак генерирует собственное представление в XML-формате. Использование этого синтаксиса я поясню потом.

Рис. 4. Создание экземпляра типа признана, основанного на адресе using System;

using System. Security. Policy;

class App { static void Main(stringn argv) \ // Принять URL кодовой базы как аргумент командной строки string codebase = argvEO];

// Создать и заполнить объект Evidence evidence * new EvidenceC);

evidence. AddHost( new Url( codebase));

evidence. AddHost (Zone. GreateFromU rl ( codebase) ) ;

try { evidence. AddHost(Sits.CreateFroBUrlC(XKlebase)); } catch (ArguitientException) { /* ignore */ }• // Доказать интересующие. нас биты forsach (object part in evideisce) { Console. Writeline{ part);

Программное создание объектов Evidence иногда полезно. Однако главный пользователь этого механизма — загрузчик сборок. Он открывает доступ к признакам сборки политике безопасности и программистам через свойство System. Reflection. Assembly.Evidence.

Взгляните на следующий фрагмент кода и вы поймете, как это делается:

Сервисы признаков, политик, разрешений и применения политик 93 using System. Reflection;

using System. Security. Policy ;

public sealed class Util { public static Zone WhichZone(object obj) { Evidence ev = obj. GetTypeC). Module. Assembly. Evidence;

Enumerator i = ev.GetHostEnumerator();

while (l.NoveNextO) { Zone zone = i. Current as Zone;

if {zone != null) return zone;

return null; // зоны нет Вы еще неоднократно увидите в этой статье, что признаки используются в основном политикой безопасности и что программисты крайне редко явным образом обращаются к ним.

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

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

Security.PolicyLevelType перечислимого типа:

namespace System. Security { public enum PolicyLevelType { User, Machine, Enterprise, AppDomain Политика уровня User специфична для конкретного пользователя, Machine охватывает всех пользователей конкретной машины (где установлена CLR), a Enterprise — семейство машин, включенных в Active Directory. Наконец, политика уровня AppDomain специфична для конкретного приложения, выполняемого в процессе операционной системы.

94 Защита кода и данных Политика безопасности всех уровней, кроме AppDomain, загружается автоматически из конфигурационных XML-файлов, содержимое которых можно редактировать как исходный XML-код или через утилиту caspol.exe либо оснастку mscorcfg.msc консоли ММС. Политики Machine и Enterprise считываются соответственно из файлов security.config и enterprisesec.config. Эти файлы размещаются в подкаталоге CONFIG специфичного для конкретной версии каталога установки CLR. Политика User считывается из файла Application Data\Microsoft\CLR Security Config\vl.0.n«727Z\security.config, который находится в каталоге профиля, специфичного для конкретного пользователя. Политика AppDomain должна быть задана программно — вызовом метода System.AppDomain. Set AppDomainPolicy.

Комбинация четырех уровней политики называется иерархией политики безопасности в системе. Каждый уровень в иерархии выдает набор разрешений на основе представленных признаков. Конечный набор разрешений образуется на пересечении наборов разрешений от каждого уровня (рис. 5).

Рис. 5. Формирование конечного набора разрешений

Такой принцип формирования конечного набора разрешений применяется потому, что модель защиты в CLR основана на выдаче, а не на отклонении привилегий. Читателям, знакомым с системой защиты в Windows NT, эта модель напомнит концепцию привилегий в Win32 или ролей в СОМ+; иначе говоря, чтобы отклонить доступ к ресурсу, соответствующие разрешения просто не выдаются. В отличие от DACL-списков Win32 способа явно отклонить доступ к защищенному ресурсу или к выполнению защищенной операции нет. В этом плане политики по умолчанию уровней Enterprise, Сервисы признаков, политик, разрешений и применения политик 95 User и AppDomain выдают разрешение полного доверия (full-trust permission) независимо от представленных признаков. Но политика по умолчанию уровня Machine выдает такое разрешение только коду, загружаемому из зоны MyComputer. Код, который создан компаниями, отличными от Microsoft или ЕСМА, загружается из других зон безопасности и получает гораздо меньше разрешений.

Как и признаки, иерархия политик безопасности используется в инфраструктуре защиты неявно, но доступна для программного доступа. Каждый уровень иерархии предоставляется через тип System.Security.Policy.PolicyLevel, а набор уровней политик — через метод System.Security.SecurityManager. Policy Hierarchy. Код на рис. 6 вызывает этот метод для перечисления уровней политик безопасности, используемых текущей программой, и для отображения имен файлов, откуда были загружены эти политики.

После запуска программа выводит:

Enterprise:

C:\WINNT\Hicrosoft.NET\Framework\v1.0.3512\config\enterprisesec.config

Machine:

C:\HINNT\Hicroeoft.HET\Freiework\v1.0.3512\config\securlty.conflg User: C:\Documents and Settings\dbox\Application Data\ Hicrosoft\CLR Security Config\v1.0.3512\security.config Заметьте, что политика, специфичная для AppDomain, отсутствует.

Рис. 6. Перечисление уровней политик using Systea;

using Systen.Collections;

using System. Security;

using System.Security.Policy;

–  –  –

Каждый уровень политики состоит из трех элементов: списка именованных наборов разрешений (видимых через свойство PolicyLevel.NamedPermissionSets), каждый из которых выдает 0 или более привилегий; иерархии групп кода (видимых через свойство PolicyLevel.RootCodeGroup), с помощью которой определяется, какие наборы разрешений следует применить на основе данного признака; и списка полностью доверяемых сборок (видимых через свойство Policy Level. FullTrustAssemblies), где перечисляются сборки с типами, нужными для применения политики безопасности.

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

Группы кода используются для выдачи разрешений на основе признаков.

В связи с этим у группы кода два основных свойства: условие членства (membership condition) и имя набора разрешений (name of a permission set) (рис. 7). Условие членства на основе представленного признака определяет, может ли данная сборка стать членом данной группы кода. Если признак удовлетворяет условию, политика безопасности выдает права, содержащиеся в данном именованном наборе разрешений.

Рис. 7. Условия членства в группе кода Условия членства сильно типизированы (strongly typed) и представлены

CLR-типами, реализующими интерфейс System.Security.Policy.IMembershipCondition, в котором есть один интересный метод, Check:

namespace System.Security.Policy { public interface IMembershipCondition { Сервисы признаков, политик, разрешений и применения политик bool Check(Evidence evidence);

// Остальные методы опущены !

} Следующий метод определяет (на любом уровне политики), является ли данная сборка членом корневой группы кода (root code group):

static bool IsInRootGroup(Assembly assm, PolicyLevel level) { // Получить признак для сборки Evidence evidence = assm.Evidence;

// Получить условие для корневой групп кода CodeGroup group = level.RootCodeGroup;

IMembershipCondition cond = group.MembershipCondition;

// Проверить на возможность членства return cond.Check(evidence);

I Поскольку для всех встроенных политик условие членства в корневой группе кода соответствует всем сборкам, этот метод должен всегда возвращать true.

В систему встроено восемь типов условий членства. Метод Check для AllMembershipCondition всегда возвращает true — независимо от признаков (и используется корневой группы кода на каждом уровне политики).

Метод Check для ApplicationDirectoryMembershipCondition проверяет, загружается ли данная сборка из каталога приложения. По признаку Url это условие определяет кодовую базу сборки и полагается на объект-признак ApplicationDirectory в признаке, предоставленном хост-приложением, например ASP.NET. Остальные шесть типов условий членства (UrlMembershipCondition, ZoneMembershipCondition и др.) прямо соответствуют шести типам признаков хоста (Url, Zone и т. д.) и позволяют принимать решение о членстве по своей части признака.

Чтобы к одной сборке можно было применить более одного набора разрешений, группы кода имеют иерархическую структуру и могут содержать дочерние группы. Последние доступны через свойство CodeGroup.Children.

Большинство групп кода в политике безопасности являются экземплярами типа System.Security.Policy.UnionCodeGroup. Если условие членства выполняется, UnionCodeGroup перечисляет все дочерние группы кода и принимает объединение (union) наборов разрешений каждой дочерней группы, 4-138 Защита кода и данных условие членства в которой также выполнено. Поскольку и у дочерней группы кода бывают свои дочерние группы, этот процесс может потребовать оценки большого количества наборов разрешений. Однако, если условие членства для данной группы кода не удовлетворяется, все его дочерние группы игнорируются. Взгляните, например, на иерархию групп кода, показанную на рис. 8. Если условие членства для группы А не выполняется, остальные группы просто не рассматриваются (и конечный набор разрешений окажется пустым). Если же условие членства для группы А удовлетворяется, принимаются группы В, С, G, Н, I и N. А также группы D и Е — если проверка условия членства для группы С дает положительный результат.

ЧП4ШРис. 8. Иерархия

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

Свойство PolicyStatementAttribute.Exclusive указывает, что с данной группой кода нельзя комбинировать остальные группы того же уровня (sibling code groups). Будь этот атрибут установлен для группы В в предыдущем примере, то при выполнении условия членства в группе В группы С, G, H, I и N были бы проигнорированы. Когда представленный признак подходит более чем одной эксклюзивной группе на данном уровне иерархии, считается, что в политике безопасности допущена ошибка.

Второе свойство, изменяющее интерпретацию группы кода, — PolicyStatementAttribute. LevelFinal, Оно информирует диспетчер защиты (security manager) о необходимости игнорировать более низкие уровни политики.

Например, если бы такой атрибут был задан для отвечающей условию группы кода на уровне SecurityLevel.Machine, уровни политик SecurityLevel.User и SecurityLevel.AppDoraain были бы проигнорированы. Это лишает менее привилегированных пользователей и администраторов возможности отключать критически важные компоненты, блокируя выдачу им требуемых разрешений.

Сервисы признаков, политик, разрешений и применения политик 99 Программное преобразование содержимого признака в набор подходящих групп кода выполняется методом CodeGroup.ResolveMatchingCodeGroups.

Этот метод проверяет условие членства применительно к текущей группе кода и всем его дочерним группам, чтобы отобрать подходящие группы.

Для большего удобства тип PolicyLevel включает метод ResolveMatchingCodeGroups, который просто переадресует вызов корневой группе кода данного уровня политики (предварительно убедившись, что этот уровень действительно загружен):

namespace System. Security. Policy { public class Policylevel { CodeGroup ResolveMatchingCodeGroups(Evidence ev) { this. CheckLoadedf true);

if (ev == null) throw new ArgiunentException( "evidence");

return this.RootCodeGroup. ResolveMatchingCodeGroups(ev);

} Ц Остальные члены опущены Чтобы отобрать подходящие группы кода на всех имеющихся уровнях политики, можно применить статический метод SecurityManager.ResolvePolicyGroups. Он просто вызывает PolicyLevel. ResolveMatchingCodeGroups на каждом уровне в иерархии политики (например Machine или Enterprise). Так, программа, написанная на С# (рис. 9), перечисляет с помощью SecurityManager.ResolvePolicyGroups все группы, членом которых является данная сборка.

Метод SecurityManager.ResolvePolicyGroups несколько ограничен, поскольку имена наборов разрешений в отобранных группах кода имеют смысл, только если вы знаете, к какому уровню политики относится та или иная группа. К счастью, есть несколько более высокоуровневых методов, которые проясняют и этот вопрос. (Я расскажу о них позже.) Файлы политик по умолчанию, поставляемые с CLR, включают встроенный набор групп кода (рис. 10). Заметьте, что для каждой из пяти зон безопасности (значения SecurityZone) предусмотрена отдельная группа кода. Также имеется отдельная группа для сборок с открытыми ключами Microsoft и ЕСМА. Группы кода, определяемые пользователями, обычно добавляются к группе All_Code как дочерние, если только условие членства не определяет конкретную зону безопасности.

100 Защита кода и данных Рис. 9. Поиск подходящих групп using System;

using System. Collections;

using System. Reflection;

using System. Security;

using System. Security, Policy;

–  –  –

static void HaSn{stringt] argv) { string assrariame = argv[0j;

Evidence ev = Assembly. Load(assiBnarne). Evidence;

lEnunrerator 1 = SecurityHanaer.ResalvepQlicySroups(ev};

while (i.KoveNextO) DumpGroup((CodeGroup)i. Current, " ");

Запустив программу, представленную на рис. 9, применительно к сборке

mscorlib, вы получили бы вот что:

UnionCodeGroup['All_Code']:FullTrust UnionCodeGroup['All_Code']:Nothing UnionCodeGroup[' ECMA_Strong_NamE!' ]: FullTruat UnionCodeGroup['My_Computer_Zone']:FullTrust UnionCodeGroup['All_Code']:FullTrust Заметьте, что на первом и третьем уровнях политики (строки 1 и 5 в предыдущем примере кода) единственной группой кода, удовлетворяющей условию членства, является All_Code. Также обратите внимание, что All_Code выдает коду набор разрешений FullTrust. Это связано с тем, что первый и третий уровни относятся к политикам Enterprise и User соответственно, а они по умолчанию просто выдают FullTrust любой сборке независимо от ее признаков. Второй уровень (строки 2-4) соответствует политике Machine. На этом уровне группа All_Code не дает никаких разрешений. Однако mscorlib также удовлетворяет условиям членства в группах кода Му_Compul;er_Zone и ECMA_Strong_Name, которые предоставляют коду разрешения FullTrust.

Сервисы признаков, политик, разрешений и применения политик 101

–  –  –

Примечание 1:

FileCodeGroup выдает каталогу, содержащему сборку, разрешение FilelOPermission с доступом только для чтения

Примечание 2:

NetCodeGroup выдает сайту - источнику сборки разрешение WebPermission Рис. 10. Встроенные группы кода Я уже упоминал о нескольких встроенных именованных наборах разрешений. В каждом файле политики по умолчанию, поставляемом с CLR, содержится семь предопределенных именованных наборов разрешений (табл. 1).

Набор Nothing не дает никаких разрешений и используется корневой группой кода на уровне политики Machine. Это позволяет безопасно включать в корневую группу кода любую сборку, не выдавая ей какие-либо разрешения. Наборы разрешений FullTrust и Everything применяются только для полностью доверяемых компонентов. Группа Everything явно задает все известные разрешения, а набор разрешений FullTrust просто служит сигналом к тому, чтобы неявно предоставить все возможные разрешения, в том числе заранее не известные. Как показано на рис. 10, FullTrust выдается любому коду, загружаемому из локальной файловой системы.

Наборы разрешений Internet и Locallntranet предоставляют лишь ограниченное подмножество разрешений и применяются к коду, загружаемому из Защита кода и данных удаленных файловых систем или Интернета. В обоих случаях разрешается запускать только верифицируемый код. а доступ к неуправляемому коду блокируется, Кроме того, доступ к локальной файловой системе ограничивается на основе диалогов, требующих от пользователя явно выбрать нужный файл.

Группа кода Internet ограничивает еще больше, не позволяя генерировать код, счигывать буфер обмена и разрешать DNS-имена в IP-адреса.

Табл. 1. Встроенные наборы разрешений

–  –  –

Помимо предопределенных наборов разрешений, в CLR имеется и два особых типа групп кода, которые динамически формируют набор разрешений на основе признака: NetCodeGroup и File Code Group. Первая создает набор разрешений, содержащий динамически вычисляемое WebPermission. Оно предоставляет доступ к соединению с сайтом, с которого был получен данный код. FileCodeGroup создает набор разрешений, содержащий динамически вычисляемое FilelOPermission. Оно предоставляет доступ только для чтения к файлам каталога, из которого был загружен данный код. Как показано на рис. 10, NetCodeGroup используется для выдачи доступа к Web Сервисы признаков, политик, разрешений и применения политик 103 коду из зон безопасности Internet, Intranet и Trusted, a FileCodeGroup — для выдачи доступа к файлам коду из зоны безопасности Intranet.

Важно помнить, что в результате прогона содержимого признака через политику безопасности вы получаете некий набор разрешений. Эта функциональность предоставляется через метод Resolve типов CodeGroup и PoIicyLevel. Данный метод принимает содержимое (тело) признака и возвращает соответствующий набор разрешений на основе того, какие группы кода были отобраны как удовлетворяющие условиям членства. Методы Resolve возвращают объект System.Security.Policy.PolicyStatement, который определяет не только конечный набор разрешений, но и атрибут PolicyStatementAttribute, указывающий, является ли выражение политики (policy statement) эксклюзивным (exclusive) и/или завершающим уровень (level final) (рис. 11).

Рис. 1. Получение уровней политик using System;

using System.Collections;

using System.Security;

using System.Security.Policy;

class App { static void Main(string[3 argv) { string codebase - argv(0];

// Сконструировать признак Evidence evidence - new EvideneeQ;

evidence.AddHost(new Uri(codebase));

evidence.AddHost(Zone.С reateFroiMJ rl(codebase});

try { evidence.AddHost(Site.CreateFromUrKcodebase)}; } catch (ArguaentException) { /* ignore «/ }

–  –  –

Метод Resolve сначала находит подходящие группы кода, а затем принимает объединение разрешений, выданных набором разрешений каждой группы. Чтобы найти разрешения, используемые в совокупности, вы должны взять те из них, которые находятся на пересечении всех задействованных наборов разрешений, и при этом учесть значение свойства PermissionStatement Attribute. Level Final. К счастью, CLR предоставляет более высокоуровневый статический метод Security Manage r. Resolve Pol icy, который избавляет вас от таких операций. Я переписал пример кода, показанный на рис. 11, под метод ResolvePolicy (рис. 12).

Рис. 12. Применение ResolvePoficy using System;

using System. Security;

using System. Security. Policy;

class App { static void Hain{string[] argv) { string codebase = argv[0];

// Сконструировать признак Evidence ev = new Evidence^);

evidence. AddHost (new UrKcodebase));

evidence, AdttHqst (Zone. CreateFrofflUrlXcodebase));

try { evidence. AddHost(Site,Creat«FrfflnUrl{codebase)};

catch EArgutnentException) { /* igrore */ 3Получить совокупные разрешения от всех уровней PermissionSet ps ~ SecurityManager.ResolvePoUcyCev);

Console, Writ eLine( ps) ;

Каким же образом метод ResolvePolicy возвращает только набор разрешений, а не выражение политики, как метод Pol icy Level. Resolve? Дело в том, что ResolvePolicy учитывает все имеющиеся уровни политик и ему нет нужды возвращать что-либо, кроме конечного набора разрешений.

Кроме того, метод SecurityManager.ResolvePolicy добавляет разрешения идентификации (identity permissions) на основе представленного признака. Каждому типу признака хоста (например Url, Zone или StrongNarae) соответствует свой тип разрешения. SecurityManager.ResolvePolicy просматривает список элементов признака хоста и получает дополнительное разрешение от каждого элемента признака, поддерживающего интерфейс Сервисы признаков, политик, разрешений и применения политик IldentityPermissionFactory. Этот интерфейс поддерживается всеми встроенными типами признаков- Разрешение идентификации всегда добавляется политикой, и с его помощью можно потребовать, чтобы вызывающий или вызывающие были включены в определенную зону безопасности, либо обращались с конкретного сайта.

Разрешения Набор разрешений — это фактически не более чем группа отдельных разрешений, которая может быть пустой. Такие наборы доступны программно через пространство имен Systeift. Security. Permission Set. Поскольку PermissionSet реализует интерфейс System.Collections.I Collection, вы можете интерпретировать набор разрешений как стандартный набор объектов (collection). Элементы набора гарантированно реализуют хотя бы интерфейс System.Security.IPermission.

IPermission является основным интерфейсом для работы с разрешениями.

Интерфейс IPermission предусматривает следующие методы для поддержки операций над объектами разрешений:

namespace System.Security { public interface IPermission { IPermission Union(IPermission rhs);

IPermission Intersect(IPermission rhs);

bool IsSubsetOf(IPermission rhs};

IPermission Copy();

void DemandO;

Первые три метода позволяют обрабатывать однотипные объекты разрешений как наборы (рис. 13).

–  –  –

Рис. 14. Использование [Permission static void Dolt() j SeeyrityPermisslonFlag f1 = SecurltyPerrcissionFlag.Execution;

SecurityPermisssionFlag f2 = SecurityPermisslonFlag.SkipVerlfication;

IPermisslon pf = new SecurityPeriaission(f1};

IPeraission p = new SecurityPenission(f2);

// Суммировать разрешения р1 и р2 IPerisission p3 * p1.Union(p2);

// Принять разрешение, которое имеется в pi и рЗ IPermission р4 = p3.Intarsect(p1);

Debug.Assert(И.IsSubsetQf(рЗ));

Debug.Assert(pl.IsSubsetGf(p3));

Программирование с использованием интерфейса IPermission в общем-то понятно на интуитивном уровне, что иллюстрирует С#-метод на рис. 14.

В этом примере тип SecurityPermission поддерживает битовую маску, указывающую, какие операции разрешены. Вызов метода Union возвращает новый объект SecurityPermission с установленными битами Execution и Skip Verification (будто выполнена побитовая операция OR). Когда в этом примере вызывается метод Intersect, вы получаете новый объект SecurityPermission, у которого установлен только один бит, Execution (словно выполнена побитовая операция AND). Метод IsSubsetOf просто проверяет, поддерживает ли рЗ операции, поддерживаемые pi, для чего достаточно сравнить битовую маску р! с результатом пересечения множеств р! и рЗ.

Методы IPermission предполагают, что предоставляемые разрешения однотипны. Например, передача объекта FilelOPerniission в метод Intersect объекта Web Permission будет считаться ошибкой. Хотя большинство типов разрешений поддерживает битовую маску, указывающую, какие операции разрешены, многие из них несут и дополнительную информацию вроде пути к файлу или хост-имени. А значит, соответствующая реализация IPermission-метода ожидает и эту специфическую для типа информацию.

Вот вам пример, где используется тип FilelOPermission:

Сервисы признаков, политик, разрешений н применения политик 1Q7 static void DoItO { FilelOPermissionAccess all = FilelOPermissionAccess. AllAccess;

IPermission p1 = new FileJOPermission(all,@"C:\etc");

IPermission p2 = new FileIOPermission(all,&"C:\etc\bin");

IPermission p3 = p1.Union(p2); // C:\etc разрешен IPermission p4 = p1.Intersect(p2); // C:\etc\bin разрешен Debug. Asse rt (p2. IsSubsetOf ( p1 ) ) ;

Debug. Assert(p1.IsSubsetOf(p2) == false);

I Точная семантика Union, Intersect и IsSubsetOf специфична для конкретного типа. Нужные детали вы найдете в документации.

Типы разрешений, как правило, поддерживают конструктор, принимающий System.Security.Permissions.PermissionState перечислимого типа, на основе значения которого новый объект разрешения приводится в одно из общеизвестных состояний.

Содержимое PermissionState проще некуда:

namespace System. Security. Permissions { public enurn PermissionState { None, Unrestricted } I Объекты разрешений, инициализированные с помощью Permission State.None, представляют самый ограниченный набор разрешений для данного типа.

PermissionState.Unrestricted отражает наименее ограниченный набор разрешений для данного типа. Для большей универсальности проверки объектов разрешений на наименее ограниченное состояние типы разрешений, поддерживающие неограниченный доступ, должны поддерживать и интерфейс

System.Security. Permissions Л Unrestricted Permission:

namespace System. Security. Permissions { public interface lUnrestrictedPermission { bool IsUnrestrictedC);

–  –  –

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

Permission State и I Unrestricted Permission позволяют унифицировать обработку объектов разрешений независимо от того, как именно в них указывается неограниченный доступ. А значит, следующий обобщенный код будет давать true для всех типов разрешений Т.

lUnrestrictecfPermisslon perm = new T(PermissionState. Unrestricted);

Debug. Assert (pe rm.lsUnrestrictedO);

Но учтите, что объект разрешения может быть неограниченным, даже если он не был явно инициализировал таким образом. Например, передача параметра SecurityPermissionFlag.AHAccess в конструктор для SecurityPerniission эквивалентна передаче PermissionState. Unrestricted.

Кроме того, выполнение операций объединения (union operations) над объектами разрешений может дать в результате неограниченный объект разрешения.

Хотя вы можете определять собственные типы разрешений, реализуя интерфейс IPermission (и несколько его близких родственников, необходимых для кодирования объекта разрешения в формат XML), CLR предоставляет целое семейство встроенных типов разрешений, позволяющих защищать самые разнообразные ресурсы системного уровня. Наиболее часто используемые типы разрешений перечислены в табл. 2. Заметьте, что все они, кроме типов разрешений идентификации, поддерживают lUnrestrictedPermission. Многие типы допускают задание разрешений на основе совпадения шаблонов (pattern matching). Это в полной мере относится к типам, предназначенным для защиты сетевых ресурсов и ресурсов файловой системы.

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

Добавление объекта разрешения, который является экземпляром типа, уже хранящегося в наборе разрешений, приводит к объединению этого объекта с имеющимся. Пример можно посмотреть на рис. 15, где только что созданный набор разрешений содержит два объекта разрешений (один — типа FilelOPermission, другой — типа SecurityPermission) с установленными битами Execution и SkipVerification.

Сервисы признаков, политик, разрешений и применения политик 109 Табл. 2. Встроенные типы разрешений

–  –  –

Рис. 15. Комбинирование разрешений static void DoItO * FiielGPeraissioriAcoess all = FilelOPermissiQnAccess.AllAoceas;

SecurityPermiissionFlag f1 = SecurityPernissionFlag.Execution;

SecurityPermissionFlag f2

- SecurityPerffiissionFlafl.SkipVerificaticm;

PennissionSet ps

- new PermissionSet(PerBilsslonState.None);

ps.AddPerjBissiorKne* SecwrityPeriBission(fl));

ps.AddPemitssion(new SecurityPermission(f2));

ps.AddPermisslonnew FileIOPermissii3R(all, &"C:

\ Тип PermissionSet поддерживает три операции IPermission (Union, Intersect и IsSubsetOf). Реализации этих методов в PermissionSet просматривают индивидуальные объекты разрешений и выполняют операции присвоения (set operations) над подходящим объектом во втором наборе разрешений (рис. 16).

<

–  –  –

Рис. 16. Операции присвоения разрешения Выполнение операций присвоения над объектами PermissionSet — процесс тривиальный. Так, на рис. 17 С#-код дает тот же набор разрешений, что и код на рис. 15 (но делает это не столь явно).

Объекты PermissionSet могут предоставлять и неограниченные разрешения. Для этого в конструктор набора разрешений передается PermissionState. Unrestricted. Если набор разрешений предоставляет неограниченные права, это подразумевает неявную поддержку всех допустимых операций во всех возможных типах разрешений — при условии, что тип поддерживает lUnrestrictedPermission, Таким образом, это не означает, что он неявно выдает все права тем типам, которые не поддерживают lUnrestrictedPermission. Но вы вправе вызывать AddPermission применительно к неограниченному PermissionSet, чтобы явно выдавать специфические разрешения.

Сервисы признаков, политик, разрешений и применения политик 111 Рис, 17. Тот же набор разрешений Static void Dolt 0 { FiielQPerinissionAccess all

- FilelO'Pe mission Access. AllAcoess;

SecurityPenBissionflag f1 » SecurityPermissiorvFlag.Execution;

SecyrityPermissiQRFlag f2 * SecurityPermissiQnFlag.SkipVeriflcation;

PermissionSet ps1 = new PermisaionSet(niill);

PeraiasionSet ps2 = new PermissionSet(null);

PerwissionSet рэЗ = new PerffliS9ionSet(null);

ps1.AddPermission(new SecurityPerffilssion(fl));

ps2.AddferHiisslon(new SeeurityPermission(f2»;

ps2.AddPeriBissiennew FllelOPer«ission(all, 9"C:\etc"));

ps3 = ps1,Union(pa2);

Введение политики безопасности в действие Как ни важна политика безопасности, но по большей части она просто дремлет, пока не настает пора ее применить. Политика безопасности вводится в действие неявно самой CLR. Однако доверяемые библиотеки могут применять ее явным образом для защиты какого-либо охраняемого ресурса. Вы вводите политику в действие, требуя от всех вызывающих получить конкретное разрешение или набор разрешений. Для этого и интерфейс IPermission, и класс PermissionSet поддерживают метод Demand, который позволяет явно вводить политику в действие.

Метод Demand вызывает проход по стеку, при котором проверяются разрешения всех методов. CLR вычисляет разрешения каждого метода, пропуская признак, полученный из сборки для данного метода, через политику безопасности. Если хотя бы один метод в стеке не имеет затребованного разрешения, Demand генерирует исключение System.Security.SecurityException. А если у всех методов в стеке есть нужное разрешение, Demand не генерирует это исключение.

Чтобы вызвать метод Demand, вам нужно сначала получить объект разрешения или набора разрешений, указывающий, какие права следует требовать от вызывающих. Взгляните на Си-метод Dns.GetHostByName:

using System.Net;

–  –  –

public static IPAddress LookupHost(string host) { return Dns.GetHostByNarneC host). Add ressList[Oj;

) Вызывая Demand, он требует, чтобы у всех вызывающих было разрешение System.Net.DnsPermission;

using System. Security. Permissions;

namespace System. Net { public sealed class Dns { public static IPHostEntry GetHostByName(string host) { // Ввести в действие политику безопасности DnsPermission perm = new DnsPennission(PennissionState. Unrestricted);

perfn.DemandO;

// Если мы попали сюда, политика разрешила просмотр DNS, // поэтому занимаемся своей работой Заметьте, что метод GetHostByName делает проверку на безопасность в самом начале своего кода. Если политика запрещает просмотр DNS (DNS lookups), остальной код этого метода не выполняется. Конечно, если бы эту проверку делал метод Utils.LookupHost, он должен был бы явно обрабатывать соответствующее исключение:

using System. Net;

using System. Security;

public sealed class Utils { public static IPAddress LookupHost(string host) { try { return Dns.GetHostByName(host).AddressList[0];

} catch (SecurltyException) { return IPAddress. Loopback;

Тип разрешения, не удовлетворяющий данному уровню защиты, доступен как свойство объекта исключения защиты (security exception object) Security Exception. PermissionType.

Метод Demand требует, чтобы у каждого метода в стеке были разрешения, минимально удовлетворяющие запрашиваемым (смысл понятия «минимально удовлетворяющие» зависит от реализации метода Is Subset Of Сервисы признаков, политик, разрешений и применения политик 13 в конкретном типе разрешения). Конечный набор разрешений устанавливается, когда поток начинает использовать CLR. В случае потока, явно создаваемого CLR-совместимой программой, такой набор разрешений формируется простым пересечением множеств разрешений для каждого метода в стеке вызовов родительского потока. Аналогичным образом, когда поток из пула начинает обслуживать рабочий запрос, его набор разрешений создается с использованием «моментального снимка» родительского потока. В обоих случаях это не дает вторичному потоку выполнять операции, которые не были разрешены и родительскому. Применительно ко всем остальным потокам (в том числе к основному потоку CLR-совместимого приложения) конечный набор разрешений вычисляется на основе признака, который был предоставлен при создании AppDomain. Именно с этой целью метод AppDomain.CreateDomain принимает параметр типа System. Security. Policy. Evidence.

Ранее показанный класс System.Net.Dns демонстрировал пример категорического требования к правам доступа (imperative security demand). Категорическим оно называется потому, что в коде явно вызывается соответствующая программная конструкция. Но CLR поддерживает и декларативные требования к правам доступа (declarative security demands) на основе атрибутов. Для каждого типа разрешения CLR определяет специфичный атрибут, производный от System.Security.Permissions.CodeAccessSecurityAttribute. Обязательный конструктор (mandatory constructor) всех таких атрибутов принимает параметр типа System.Security.Permissions. Security Action;

namespace System.Security.Permissions { public enum SecurityAction { Demand = 1, Assert, Deny, PermitOnly, LinkDemand, InhsrltanceDemand, RequestMinimum, RequestOptional, RequestRefuse, I В SecurityAction нас сейчас интересует только Demand. Эта операция защиты (security action) позволила бы вместо категорического запроса;

–  –  –

public sealed class Dns { public static IPHostEntry GetHostByNaine( string host) { DnsPermlssion perm = new DnsPermisslon(Perm±ssionState. Unrestricted);

perm.DemandOi // Если мы попали сюда, запрос удовлетворен!

!

указать декларативные требования к правам доступа:

using System. Security. Permissions;

namespace System. Net { public sealed class Dns { [DnsPe mission (SecurityAction. Demand, Un rest ricted=t rue)] public static IPHostEntry GetHostByName(string host) { // Если мы попали сюда, запрос удовлетворен!

Преимущество последнего примера в том, что вы можете определить требования кода к защите, просто проверив метаданные для интересующего вас типа. А недостаток декларативных требований — отсутствие поддержки разрешений, которым нужна динамически меняющаяся информация (например пути к файлам), не известная на этапе компиляции. Такие атрибуты защиты, как DnsPermission, могут быть применены либо к отдельным методам, либо к типу (последнее означает, что атрибут применяется ко всем методам этого типа).

Часто возникает необходимость в подстройке разрешений, предоставляемых какому-либо методу, — обычно для ограничения прав, под которыми будет работать данный блок кода. CLR позволяет добиться этого двумя способами. Вы можете ограничить конечные разрешения их подмножеством, выдаваемым в соответствии с какой-либо политикой безопасности, или явно отказаться от одного или нескольких разрешений. Оба варианта можно реализовать либо как декларативные требования, либо как категорические. В первом случае можно использовать Security Action. PermitOnly и Security Action.

Deny, например:

–  –  –

public static IPAddress LookupHost(string host) { return Dns.GetHostByName(host),AddressList[0];

} ;

To же самое, но с предъявлением категорических требований:

using System. Net;

using System. Security. Permissions;

public sealed class Utils { public static IPAddress LookupHost(string host) { DnsPermission perm = new DnsPennission(PermissionState. Unrestricted);

perm.PermitQnlyO;

return Dns.GetHostByName(host).AddressList[0];

В любом из этих примеров, потребуй метод Dns Get Host ByName какое-нибудь разрешение, выходящее за рамки DnsPermission, его запрос был бы отклонен — даже если бы политика безопасности выдавала такое разрешение методу LookupHost (и всем, кто его вызывает).

Если в LookupHost нужно запретить использование лишь какого-то одного разрешения, достаточно следующего кода:

using System. Net;

using System. Security. Permissions;

–  –  –

public sealed class Utils { public static If'Address LookupHost(string host) { FilelOPermission perm = new FilelOPermissiorKPermlssionState.Unrestricted);

perm,Deny();

return Ons.GetHostByName(host).AddressList[0];

В этом случае отклоняются все запросы на разрешение FilelOPermission в Dns.GetHostByName; однако могут быть выданы любые другие типы разрешений (в зависимости от политики безопасности).

Важно отметить, что в одном фрейме стека может быть только один набор разрешений, использующий Deny или PermitOnly.

Возьмем для примера следующий метод:

using System.Net;

using System.Security.Permissions;

public sealed class Utils { public static IPAddress LookupHost(string host) { FilelOPermission p1 = new FileIOPermission(PermissionState.Unrestricted);

RegistryPermisKion p2 = new RegistryPermission(PennissionState.Unrestricted);

p1.Deny();

p2.Deny();

return Dns.GetHostByName(host).AddressList[0];

Здесь второй вызов Deny приведет к исключению. Если вы хотите запретить оба разрешения, нужно сделать примерно так, как показано на рис. 18.

Тогда оба разрешения будут отклонены для Dns.GetHostByName.

Доверяемым компонентам часто нужны разрешения, которые могут не предоставляться теми, кто их вызывает. Например, тип System.Net.Dns должен обращаться к нижележащей API-функции gethostbyname, вызов которой, как и любые другие API-вызовы, заставляет CLR требовать разрешение SecurityPermissionFIags.UnmanagedCode. Однако, если бы у всех вызывающих было такое разрешение, тип Dns стал бы бесполезен всем, кроме самых доверенных компонентов. CLR выходит из подобных ситуаций за счет поддержки операции защиты Assert (добавления разрешения).

Сервисы признаков, политик, разрешений и применения политик 117 Рис. 18. Отклонение обоих разрешений using System.Net;

using System.Security.Permissions;

public sealed class Uttis { public static IPAddress LookupHoststrinfl host) { FilelOPeraissioR p1 = new FileIQPennissiQn(PerrBissionState.Unrestricted);

RegistryPermission p2 = new RegistryPermission(PermisslonState.Unrestricted);

PermissionSet ps - new PeriiissionSet(null);

ps.AddPerffiission(pl);

ps.AddPermis8ion(p2);

ps.DenyQ;

return Dns.QetttostByNanie(fiGst).AddressListCQ];

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

добавление разрешения — тоже защищенная операция, которая требует от метода, выполняющего это действие, разрешения SecurityPermissionFlag.Assertion. Методы, добавляющие разрешение, обычно делают это в связи с запросом меньшего уровня прав, чем тот, который скорее всего имеется у вызывающих. Например, метод Dns.GetHostByName мог бы нести такие атрибуты защиты:

\ using System.Security.Permissions;

namespace System,Net { public sealed class Dns { [ DnsPermission(SecurityAction.Demand, Unrestricted=true),.

SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode) ] public static IPHostEntry GetHostByName(string host) { // Если мы попали сюда, запрос (demand) // и добавление (assertion) прошли успешно!

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

Действие Security Action. RequestMinmium указывает разрешения, без которых успешная загрузка сборки невозможна. Если политика не выдает эти разрешения, загрузчик не сумеет загрузить сборку. Кстати, реальные разрешения, предоставляемые методам сборки, являются подмножеством тех, которые выдаются политикой.

Итоговые разрешения метода — все разрешения с атрибутом SecurityAction. Request Minimum, объединенные с любыми разрешениями, которые помечены атрибутом SecurityAction.RequestOptional (допускаемые политикой), за вычетом разрешений с атрибутом SecurityAction.RequestRefuse.

Взгляните, например, на С#-код на рис. 19. Здесь у всех методов в сборке будут как минимум разрешения DnsPermission и EnvtronmentPermission.

Если политика отклонит выдачу любого из этих разрешений, загрузить сборку не удастся. Кроме того, сборке будут выданы любые разрешения FilelOPermissiori, допускаемые политикой. Однако возможность записи в файл C:\autoexec.bat будет запрещена явным образом. Рис. 20 иллюстрирует, как вычисляются конечные разрешения в данном фрейме стека.

Рис. 19. DnsPermission и EnvironmentPermission using System. (*e±;

usirtg Systesi. Security. Permissions;

[assembly;

DnsPer»ission(SecurityAcce38.ReqiiestMlfliau8i, Un rest ri ctecP=t rue ),

–  –  –

public sealed class Utils { public static IPAddress LookupHoststring name) { return Bns.GetHQstByNa»e(na!B9),AddressList[0];

Сервисы признаков, политик, разрешений и применения политик 119 Есть еще две операции защиты, о которых я пока ничего не сказал, SecurityAction.InheritanceDemand и Security Action. LinkDemand. Первый атрибут позволяет базовому типу требовать, чтобы производному типу было выдано заданное разрешение.

Для примера возьмем следующий абстрактный базовый С#-класс:

using System.Security.Permissions;

[ UIPermissionCSecurityAction.InheritanceDemand, Unrestricted=true) ] public

Abstract

class MyWindow { public abstract void Show(4e();

I У любых типов, производных от MyWindow, должно быть разрешение UlPermission. Это требование будет удовлетворено неявно, в процессе загрузки производного типа.

Получить Evidence для сборки (период загрузки)

–  –  –

Рис. 20. Вычисление разрешений по фрейму стека Атрибут Security Action.LinkDemand аналогичен Security Action.Demand.

Однако LinkDemand позволяет предъявлять требования по правам доступа только к вызывающему, а не ко всему стеку. Когда JIT-компилятор пытается транслировать какой-либо метод, любые его вызовы к методам, помеченным атрибутом LinkDemand, заставят выполнять проверку на 120 Защита кода и данных безопасность (security check). В отличие от нормального Demand-вызова при этом будет проверен только вызывающий (в данном случае — компилируемый метод). Кроме того, в отличие от обычного атрибута SecurityAction.Demand, который оценивается при каждом вызове метода, атрибут Security Action. LinkDemand оценивается лишь в период JIT-компиляции.

LinkDemand и InheritanceDemand идеальны для использования разрешений идентификации. Допустим, у вас есть набор сборок от одного поставщика. К сожалению, обычные модификаторы прав доступа (вроде public или internal) не обеспечивают тонкой настройки и не позволяют выдавать разрешение на доступ к какому-либо методу отдельным сборкам. Но применив атрибут LinkDemand к методу, который требует от вызывающего определенного разрешения идентификации (например StrongNameldentityPermission), вы могли бы потребовать, чтобы у всех вызывающих был конкретный открытый ключ в именах их сборок. Возьмем простой С#класс, показанный на рис. 21. Несмотря на тот факт, что метод Dolt помечен как public, его могут вызывать лишь методы, в имени сборки которых имеется открытый ключ, идентичный указанному в атрибуте LinkDemand.

Попытки вызова этого метода из сборок, в чьих именах нет заданного открытого ключа, закончатся неудачей в период JIT-компиляции. В том же духе вы могли бы пометить какой-нибудь абстрактный базовый класс атрибутом InheritanceDemand (рис. 22).

Рис. 21. Требование к открытому ключу using System;

using System,Security.Permissions;

using System.Reflection;

(assembly; AssefnblyKeyFlle("pubpriv.snk") 3 public sealed class Utils { С St rongNaineldentltyPeriaission( SecurityAction. LinkDemand, PublicKey»

"GQ24QQEIOQ48QQQQ0940QOQ0006G20QOOQQ240QQ" 4 "Q525341l31QQ04000QQ1QQQ100cb3cec5f4)ac3e5" + "3Qb73fEi823ee6Q84be139df6119fdcQd4ff3a65" + "680da9Cif2819a10ef6a1fbOeb5e5e6ea3822456" + "92at5Q5f88ad8f6716d366fb2d9d553eOf680b3" + "09f7e78dca447a23ec892d13f1&0e7c7b7997e8" + "50dc64273860e752clffb76ed244522d293b46f" + "74d51Ie17f76b2874ee80cb&2babea3b624b745" + "baca48B7") ] public static void Dottf) { Console.WriteLine("Hello, world");

Сервисы признаков, политик, разрешений и применения политик 121

Рис. 22. Еще одно требование к открытому ключу

StrongNameIdentityPeri4i3sion( SecyrltyAction. InheritanceOemand, РиЬИсКеу= "OQ2400000480000094000000060200000024000" "052534131Q00400000100G100cb3eec5fQac3e5" "30b73fe823ce6B84be13Sdf SI 19fdc(Jd4f f 3a65" "68Qda9Qf2819al0ef6a1dbGeb5c5e6ea3822456" "92a1505f88ad8f6716d366fb2d9d553eOf680b3" "09f7e78dca447a23ec892d13f150e7c7b7997e8" "5QdC6427386Qe752c1ffb75ed244522d293b46f" "74d511e17f76b2874ee80cb82babea3t624b745" "Baca48b7") public abstract class Personlmpl { // Остальные члены опущены При таком определении любые типы, производные от Personlmpl, должны входить в сборки, несущие указанный открытый ключ. Все попытки загрузки и инициализации производного от Personlmpl типа из сборок, в которых нет этого открытого ключа, закончатся неудачей в период инициализации типа.

Заключение CLR поддерживает компонентно-центрическую модель защиты, известную под названием «защита по правам доступа кода». Эта модель предполагает, что каждая сборка может предоставить информацию о своем происхождении (признак): кто писал этот код и откуда он скачан. Защита по правам доступа кода использует конфигурируемую политику безопасности, которая выдает разрешения коду на основе признаков.

Дон Бокс (Don Box) — архитектор в Microsoft. Работает над протоколами и инфраструктурой Web-сервисов следующего поколения. В сферу его интересов входят системы типов для XML и Web-сервисов, метаданные и обнаружение Web-сервисов, а также интеграция ПО на основе CLR.

Его работа над Web-сервисами началась в 1998 году в качестве одного из авторов спецификации SOAP. Эта статья публикуется в адаптированном виде по материалам его книги «Essential.NET Volume 1: The Common Language Runtime», которая вскоре выйдет в издательстве Addison-Wesley.

Принципы безопасного кодирования для.NET Framework Общеязыковая исполняющая среда (common language runtime, CLR) и Microsoft.NET Framework предоставляет всем приложениям с управляемым кодом защиту на основе признаков (evidence-based security). В большинстве случаев при написании кода обеспечивать защиту явным образом не требуется. В этом документе кратко описывается система защиты, рассматриваются вопросы безопасности, которые вам, возможно, понадобится учитывать при написании кода, и излагаются принципы классификации компонентов, позволяющие определять, что нужно предпринять для гарантированной защиты кода.

Защита на основе признаков и по правам доступа кода

Для защиты управляемого кода используются две технологии:

• защита на основе признаков (evidence-based security) — позволяет определять, какие разрешения следует предоставлять коду;

• защита по правам доступа кода (code access security) — позволяет проверять, весь ли код в стеке имеет необходимые разрешения на выполнение каких-либо действий.

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

Разрешение (permission) — это право на выполнение определенной операции, подлежащей защите. Например, «читать из c:\temp» относится к файловому разрешению, а «подключаться к www.msn.com» — к сетевому.

Защита на основе признаков определяет разрешения, выдаваемые коду.

Признак (evidence) — это информация, присущая любой сборке (разрешения предоставляются на уровне сборок) и используемая в качестве входных * Secure Coding Guidelines for the.NET Framework//MSDK Library. Microsoft Corporation.

2002. January. — Прим. изд.

Принципы безопасного кодирования для.NET Framework 123 данных для политики безопасности (security policy). По признакам и политике безопасности, устанавливаемой администратором, система защиты определяет, какие разрешения могут быть выданы коду. Программа сама может запрашивать какое-либо разрешение, влияя на состав окончательного набора разрешений. Запрос разрешения выражается в виде объявления на уровне сборки с синтаксисом пользовательских (custom) атрибутов. Однако в любом случае код не может получить более широкие или ограниченные разрешения, чем это предписано политикой безопасности. Разрешение предоставляется только раз и определяет права всего кода в сборке. Для просмотра и изменения политики безопасности используется инструмент настройки.NET Framework (Mscorcfg.msc).

В следующей таблице показаны некоторые общие типы признаков, используемые политиками для предоставления разрешений коду. К указанным в ней стандартным типам признаков, выдаваемым системой защиты, можно добавлять новые типы, определяемые пользователями.

Характеристика Описание

–  –  –

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

Проверки безопасности позволяют предотвращать атаки с подменой (luring attack), при которых неавторизованный код вызывает ваш код и заставляет его выполнять какие-либо действия. Допустим, у вас есть приложение, считывающее файл, и политика безопасности, которая разрешает это действие.

Так как весь код вашего приложения обладает соответствующим разрешением, он пройдет проверку безопасности доступа. Однако, если злонамеренный код, не имеющий доступа к этому файлу, попытается каким-то образом вызвать ваш код, проверка безопасности закончится неудачей, так как в стеке будет.обнаружен менее доверяемый код вследствие попытки вызова вашего кода.

124 Защита кода и данных Важно заметить, что вся эта защита основана на указании операций, разрешенных коду, а авторизация пользователей — на информации, введенной при входе в систему, и является совершенно независимым механизмом нижележащей операционной системы. Рассматривайте эти две системы как многоуровневую защиту. Например, для доступа к файлу авторизацию должны пройти и код, и пользователь. Авторизация пользователей также играет важную роль во многих приложениях, которые полагаются на регистрационную информацию или другие удостоверения защиты (credentials) и используют эти данные для контроля за тем, что могут и чего не могут делать определенные пользователи. Однако этот тип защиты в данном документе не рассматривается.

Цели безопасного кодирования В данном документе предполагается, что политика безопасности настроена правильно и что у потенциально злонамеренного кода нет разрешений, предоставляемых доверяемому коду и позволяющих ему безопасно выполнять свои операции. (Если же исходить из иного, один тип кода станет неотличим от другого, что сведет все усилия на нет.) Используя разрешения.NET Framework и налагая на код другие ограничения, вы должны возвести барьеры, запрещающие злонамеренному коду несанкционированный доступ к информации или выполнение нежелательных действий. Кроме того, во всех предполагаемых ситуациях использования доверяемого кода необходимо соблюдать баланс между безопасностью кода и удобством в работе с ним.

Защита на основе признаков и защита по правам доступа кода предоставляют очень мощные, явные механизмы обеспечения безопасности. Коду большинства приложений достаточно задействовать инфраструктуру, предлагаемую.NET Framework. В некоторых случаях нужна дополнительная защита, специфичная для приложения и реализуемая либо расширением системы защиты, либо применением новых специализированных методов (ad hoc methods).

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

Код, нейтральный к защите Код, нейтральный к защите (security-neutral code), не делает ничего явного с системой безопасности. Он выполняется с любыми разрешениями, которые ему выдаются. Хотя такой подход не позволяет перехватывать связанные с защитой исключения при выполнении защищенных операций (например, при сетевых операциях, использовании файлов и т. д.) и может вызвать неприятные впечатления (из-за генерации исключений со многими деталями, совершенно непонятными большинству пользователей), зато он дает возможность задействовать преимущества технологий защиты, так как даже высоко доверяемый код не сумеет обнаружить дыры в системе защиты. В худшем случае вызывающему коду потребуются многочисленные разрешения, или система защиты остановит его выполнение.

Библиотека, нейтральная к защите, обладает особыми характеристиками, которые вы должны понимать. Допустим, ваша библиотека предоставляет API-элементы, использующие файлы или вызывающие неуправляемый код; если у вашего кода нет соответствующего разрешения, он не будет работать как задумано. Но, даже если у кода есть все разрешения, для его нормальной работы нужно, чтобы и у кода приложения, вызывающего ваш код, были те же разрешения. Если у вызывающего кода нет необходимых разрешений, при проверке стека защитой по правам доступа кода генерируется исключение, связанное с нарушением безопасности. Если от вызывающего кода можно требовать разрешений на все действия, выполняемые вашей библиотекой, то это легкий и надежный путь обеспечения безопасности, поскольку рискованного переопределения параметров защиты не происходит. Однако, если вы хотите оградить код приложения, вызывающего вашу библиотеку, от необходимости запрашивать разрешения (возможно, предоставляющих очень широкие права), изучите модель библиотеки, работающей с защищенными ресурсами (см. раздел «Библиотечный код, предоставляющий защищенные ресурсы» этого документа).

Код приложения, который не являетсяповторно используемым компонентом

Если ваш код — часть приложения, не вызываемого другим кодом, защита проста и специального программирования не требует. Но учтите, что он может быть вызван злонамеренным кодом. Хотя защита по правам доступа кода препятствует доступу злонамеренного кода к ресурсам, он все равно способен считать значения полей или свойств, которые, возможно, содержат ценную информацию.

Защита кода и данных Кроме того, если код получает пользовательский ввод из Интернета или из других ненадежных источников, остерегайтесь ввода вредоносных данных.

Дополнительную информацию см. в разделах этого документа «Защита данных о состоянии» и «Пользовательский ввод», Управляемая оболочка для машинного кода Обычно в этом сценарии некая полезная функциональность реализована в виде машинного кода, и вы хотите сделать ее доступной управляемому коду, не переписывая машинный код. Управляемые оболочки (managed wrappers) легко пишутся с использованием механизма либо Platform Invoke (P/Invoke), либо COM Interop. Однако в таком случае вызывающие вашу оболочку программы должны иметь те же права, что и неуправляемый код. И если в системе действует политика по умолчанию, код, скачиваемый из интрасети или Интернета, работать с оболочками не будет.

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

Дополнительную информацию см. в разделах «Неуправляемый код» и «Выдача разрешений».

Библиотечный код, предоставляющей защищенные ресурсы Этот подход к безопасному кодированию открывает наибольшие возможности, и, следовательно, при неправильной реализации он потенциально наиболее опасен. Ваша библиотека служит интерфейсом к другому коду, обеспечивая доступ к ресурсам, к которым нельзя обратиться иными способами. Здесь полная аналогия с классами.NET Framework, требующими определенных разрешений на доступ к используемым ими ресурсам. Если вы предоставляете доступ к какому-то ресурсу, ваш код должен сначала запросить соответствующее разрешение на использование такого ресурса Принципы безопасного кодирования для.NET Framework 3L27 (т. е. пройти проверку защиты), а затем объявить свои права на выполнение самой операции.

Дополнительную информацию см. в разделах «Неуправляемый код» и «Выдача разрешений».

Приемы безопасного кодирования Примечание Примеры кода написаны на С#, если не оговорено иное.

Запрос разрешений — отличный способ обеспечить поддержку защиты в разрабатываемом коде.

Он позволяет:

• запрашивать минимальные разрешения, необходимые для выполнения кода;

• гарантировать, что код не получит разрешений больше, чем нужно.

Например:

[assemblyiFilelOPermissionAttribute (SecurityAction.RequestMinimum, Wrlte="C:\\test.tnp")] [assembly:РеrmissionSet (SecurityAction.RequestOptional. Unrestricted=false)]... SecurityAction.RequestRefused...

В этом примере системе сообщается, что код не должен запускаться, пока не получит разрешение на запись в C:\test.tmp. Если одна из политик безопасности не предоставляет такое разрешение, генерируется исключение PolicyException и код не запускается. Вы должны убедиться, что вашему коду выдается нужное разрешение, и тогда не придется беспокоиться об ошибках из-за нехватки разрешений.

Кроме того, здесь система уведомляется о том, что дополнительные разрешения нежелательны. Иначе код получит все разрешения, предусмотренные политикой безопасности. Лишние разрешения не принесут вреда, но если в системе безопасности есть какая-то ошибка, уменьшение числа разрешений, выдаваемых коду, может прикрыть брешь в защите. Таким образом, если код обладает разрешениями, которые ему не нужны, возможны проблемы с безопасностью.

Еще один способ ограничить количество привилегий, предоставляемых коду, — явно перечислить разрешения, от которых следует отказаться. Отказ от разрешений осуществляется объявлением необязательности разрешений и исключением конкретных разрешений из запроса.

128 Защита кода и данных Защита данных о состоянии Приложения, работающие с конфиденциальными данными или принимающие какие-либо решения в отношении безопасности, должны держать данные под контролем и не позволять потенциально злонамеренному коду напрямую обращаться к данным. Лучший способ безопасного хранения данных в памяти — использовать закрытые или внутренние (видимые только в пределах своей сборки) переменные. Однако, даже если к этим данным нужно обеспечить доступ, имейте в виду, что:

• при использовании механизма отражения (reflection) код с высоким уровнем доверия, имеющий ссылку на ваш объект, может читать и присваивать значения закрытым членам;

• при сериализации код с высоким уровнем доверия может читать и присваивать значения закрытым членам, если у него есть доступ к данным в сериализованном представлении объекта;

• эти данные можно считать при отладке.

Убедитесь, что ваши методы и свойства не предоставляют непреднамеренный доступ к этим значениям.

Иногда данные объявляются как защищенные (protected), и к ним можно обращаться только внутри класса и его наследников.

Однако в этом случае приходится принимать дополнительные меры предосторожности:

• определять, какому коду разрешается наследовать от вашего класса.

Можно указать, что это допускается только в той сборке, где находится класс, либо с помощью объявлений защиты требовать для наследования от вашего класса некоей идентификации или определенных разрешений (см. раздел «Защита доступа к методам»);

• проверять, чтобы все производные классы реализовали аналогичную защиту или были «запечатаны» (sealed).

Упакованные типы значений

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

В следующем примере показывается, каким образом упакованные типы значения могут быть модифицированы с помощью ссылок.

Принципы безопасного кодирования для.NET Framework 129 using System;

using System.Reflection;

using System.Reflection.Emit;

using System.Threading;

using System.Collections;

class bug { // Допустим, у вас есть API-элемент, предоставляющий доступ к полю // через свойство, имеющее только аксессор get public object m_Property;

public Object Property { get { return m_Property;} set {m_Property = value;} // (если нужно) I // Значение этого свойства можно изменить, вызвав метод, который // передает параметр по ссылке и имеет следующую сигнатуру.

public static void m1( ref int j ) { j = Int32.HaxValue;

I public static void m2( ref ArrayList j ) ( j = new ArrayList();

i public static void Hain(String[] args) ( Console.WriteLine( "////// doing this with value type" );

bug b = new bug();

b.m_Property = 4;

Object[] objArr = new Object[]{b.Property};

Console,WriteLine( b.m_Property );

typeof(bug).GetMethod( "ml" ).Invoke( null, objArr );

// Обратите внимание, что свойство изменилось Console.WriteLine( b.m_Property );

Console.WriteLine( objArr[0] );

} Console.WriteLineC "////// doing this with a normal type" );

bug b = new bug();

ArrayList al = new ArrayListO;

al.AddO'elem");

b.m_Property = al;

5-138 130 Защита кода и данных Object[] objArr = new Object[]{b.Property};

Console.WriteLine( ((ArrayList)(b.ra_Property)).Count );

typeof(bug).Gi;tHethod( "m2" }.Invoke( null, objArr );

// Обратите внимание, что свойство не изменилось Console.Writeline( ((ArrayList)(b.m_Property)).Count );

Console.Writel_ine( ((ArrayList)(objArr[0]}).Count );

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

Иногда приходится ограничивать доступ к методам, которые не предназначены для открытого использования, ио все равно должны быть объявлены как открытые. Например, у вас есть некий интерфейс, вызываемый вашими же DLL, и поэтому он должен быть открытым, но вы не хотите, чтобы этот интерфейс был общедоступным, так как не желаете, чтобы клиенты могли с ним работать или чтобы злонамеренный код воспользовался им как точкой входа в ваш компонент. Еще одна типичная причина ограничения доступа к методу, который не предназначен для общего использования (но тем не менее должен быть открытым), — стремление избежать документирования и поддержки интерфейса, применяемого исключительно на внутреннем уровне.

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

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

Принципы безопасного кодирования для.NET Framework

• Разрешите вызов метода только вызывающим с определенной идентификацией (обладающим заданными вами признаками).

• Разрешите вызов метода только тем, у кого есть требуемые разрешения.

Аналогичным образом декларативная зашита позволяет контролировать наследование классов.

С помощью InheritanceDemand можно потребовать наличия определенной идентификации или разрешения от:

• всех производных классов;

• производных классов, переопределяющих те или иные методы.

.

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

1. Команда sn -k создает пару из закрытого и открытого ключа. Закрытая часть нужна, чтобы подписать код строгим именем (strong name), и хранится в безопасном месте издателем кода. (Если она станет известной, указать вашу подпись в своем коде сможет кто угодно.) sn -k keypair.dat csc/r:App1.dll /a. keyfile: keypair.dat App1.cs sn -p keypair.dat public.dat sn -tp public.dat publichex.txt [StrongNameldentityPermissionAttribute ( Secu rltyAction. LinkDemand, PublicKey="...hex...",Name="App1", Version="0. 0.0.0")] public class Classl

2. Команда esc компилирует и подписывает Appl, предоставляя ему доступ к защищенному методу.

3. Следующие две команды sn извлекают из пары открытый ключ и преобразуют его в шестнадцатеричную форму.

4. Во второй половине показанного исходного кода содержится фрагмент защищаемого метода. Пользовательский атрибут (custom attribute) определяет строгое имя и в шестнадцатеричном формате вставляет открытый ключ, полученный командой sn, в атрибут PublicKey.

5. В период выполнения Appl имеет необходимую подпись со строгим именем и может использовать Classl.

–  –  –

Запрещение использования классов и методов недоверяемым кодом Объявления, показанные ниже, запрещают частично доверяемому коду обращаться к классам и методам (а также к свойствам и событиям)! Когда такие объявления применяются к классу, защищаются все методы, свойства и события этого класса. Однако декларативная защита не влияет на доступ к полям. Кроме того, учтите, что требования к связи (link demands) защищают только от непосредственно вызывающего кода — возможность атак с подменой сохраняется.

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

Поэтому явно помечать классы, чтобы запретить их использование недоверяемым кодом, нужно только для неподписанных сборок или сборок с атрибутом AllowPartiaUyTrustedCallers для подмножества типов, которое должно быть недоступным недоверяемому коду. Детали см. в документе «Version i Security Changes for the Microsoft.NET Framework» (http:// msdn.microsoft.com/library/en-us/dnnetsec/html/vlsecuritychanges.asp).

• Для открытых незапечатанных (non-sealed) классов:

[System.Security.Permissions.PermissionSetAttribute(System.Security.

Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")] [System.Security.Permissions.PermissionSetAttribute (System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTnjst")] public class CanDeriveFromMe

• Для открытых запечатанных (sealed) классов:

[System.Security.Permissions.PermissionSetAttribute (System.Security.Permissions,SecurityAction. LinkDemand, Name="FullTnjst")] public sealed class CannotDeriveFromMe

• Для открытых абстрактных классов:

[System.Security.Permissions.PermissionSetAttribute (System.Security.Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")] [System.Security.Permissions.PermissionSetAttribute (System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")] public abstract class CannotCreateInstanceOfHe_CanCastToMe Принципы безопасного кодирования для.NET Framework 133

Для открытых виртуальных функций:

class Base { [System.Security.Permissions,PermlssionSetAttribute (System.Security.Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")] [System.Security.Permissions.PermissionSetAttrlbute( System.Security.Permissions.SecurityAction. LinkDemand, Name="FullTrust")] public override void CanOverrideOrCallMeC) {... }

Для открытых абстрактных функций:

class Base i [System.Security.Permissions.PermlssionSetAttribute (System.

Security.Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")] [System.Security.Permissions.PermissionSetAttribute (System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")] public override void CanOverrideMe() (...) Для открытых переопределяющих функций, базовый класс которых не требует полного доверия:

class Derived { [System.Security.Permissions.PermissionSetAttribute (System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")] public override void CanOverrideOrCallMeO {...

} Для открытых переопределяющих функций, базовый класс которых требует полного доверия:

class Derived { [System.Security.Permissions.PermissionSetAttribute (System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust"}] public override void CanOverrideOrCallMeO {... }

Для открытых интерфейсов:

[System.Security.Permissions.PermissionSetAttribute (System.Security.Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")] [System.Security.Permissions.PermissionSetAttribute (System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")3 public interface CanCastToMe Защита кода и данных

Demand и LinkDemand

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

Итак, декларативная защита поддерживает следующие проверки.

• Demand — указывает, что выполняется полная проверка стека: все вызывающие в стеке должны иметь разрешения или идентификацию.

Проверка Demand осуществляется при каждом вызове, так как вызовы могут поступать от разного кода. Так что, если вы повторно вызываете какой-либо метод, его проверка также повторяется. Demand устойчива к атакам с подменой (luring attacks); неавторизованный код, пытающийся пройти эту проверку, успешно перехватывается.

• LinkDemand — происходит при компиляции по требованию (JIT) (в предыдущем примере такая проверка была бы проведена перед выполнением кода Appl, обращающегося к Class 1) и проверяет только непосредственно вызывающий код. При такой защите не проверяется тот, кто вызывает код, вызывающий ваш метод. Как только проверка заканчивается, дополнительных издержек этот вид защиты больше не создает — сколько бы раз ни вызывался ваш метод. При использовании LinkDemand ваш интерфейс безопасен, но любой код, прошедший проверку и обращающийся к вашему коду, потенциально способен нарушить защиту, так как позволяет злонамеренному коду выполнять вызовы через авторизованный код. Поэтому не используйте LinkDemand, если полностью избавиться от потенциально слабых мест в защите нельзя.

Применяя LinkDemand, вам придется самостоятельно позаботиться о дополнительных мерах предосторожности (в реализации эти мер вам поможет система защиты). Любая ошибка откроет брешь в защите. Для реализации дополнительной защиты любой авторизованный код, использующий ваш код, должен:

• ограничивать доступ вызывающего кода к классу или сборке;

• выполнять те же проверки защиты в вызывающем коде и заставлять делать то же самое вызывающих. Например, если вы пишете код, который вызывает метод, защищенный LinkDemand для разрешения SecurityPermission.UnmanagedCode, ваш метод также должен выполПринципы безопасного кодирования для.NET Framework 135 нять проверку LinkDemand (или более строгую проверку Demand) этого разрешения. Исключение составляет тот случай, когда ваш код вызывает метод, защищенный LinkDemand, с соблюдением определенных ограничений, обеспечивающих безопасность (по крайней мере, на ваш взгляд) через другие механизмы защиты, например запросы. В этом исключительном случае вызывающий берет на себя ответственность за ослабление защиты в нижележащем коде;

• гарантировать, что вызывающие не смогут обманом использовать защищенный код в своих интересах (т. е. не смогут заставить авторизованный код передавать защищенному коду определенные параметры или получать возвращаемые им результаты).

Интерфейсы и проверки LinkDemand

Если виртуальный метод, свойство или событие с проверкой LinkDemand переопределяет метод базового класса, то к методу базового класса тоже должна применяться проверка LinkDemand, чтобы обеспечить безопасность переопределенного метода. Злонамеренный код может привести тип обратно к базовому и вызвать метод базового класса. Кроме того, имейте в виду, что проверки LinkDemand могут неявно добавляться к сборкам, у которых нет атрибута AllowPartiallyTrustedCallersAttribute уровня сборки.

При защите реализаций методов с помощью LinkDemand желательно использовать LinkDemand и для методов интерфейса.

Используя проверки LinkDemand при работе с интерфейсами, имейте в виду следующее.

• Атрибут Allow Partially Trusted Callers может влиять на интерфейсы.

• Вы можете указывать для интерфейсов проверки LinkDemand, чтобы выборочно запрещать частично доверяемому коду обращение к определенным интерфейсам, например при использовании атрибута AllowPartiallyTrusted Callers.

• Если вы определили интерфейс в сборке, не содержащей атрибута AllowPartiallyTrustedCallers, то можете реализовать этот интерфейс для частично доверяемого класса.

• Если указать LinkDemand для открытого метода класса, реализующего метод интерфейса, то LinkDemand не сработает при приведении типа к интерфейсу и вызове метода. В этом случае — из-за того, что вы осуществляете связывание с интерфейсом, — LinkDemand выполняется только для интерфейса.

136 Защита кода и данных Кроме того, примите во внимание следующее.

• Явные требования к связыванию (explicit link demands) для методов интерфейса. Убедитесь, что эти требования обеспечивают ожидаемый уровень защиты. Проверьте, может ли злонамеренный код использовать приведение типов, чтобы обойти требования к связыванию ранее описанным способом.

• Виртуальные методы с требованиями к связыванию.

• Типы и реализуемые ими интерфейсы должны согласованно использовать проверки LinkDemand.

Внутренние виртуальные переопределения

Учтите одну тонкость, если вы хотите, чтобы к вашему коду нельзя было обращаться из других сборок. Метод, объявленный как virtual и internal, может переопределять запись в таблице виртуальных методов (vtable) базового класса и доступен только в пределах своей сборки, так как является внутренним. Однако ключевое слово virtual делает метод доступным для переопределения, и он может быть переопределен из другой сборки, если ее код имеет доступ к самому классу. Чтобы исключить возможность переопределения, используйте декларативную защиту или удалите ключевое слово virtual, если в нем нет острой необходимости.

Код оболочки Применение оболочки может обнажить уникальный набор слабостей в системе защиты — особенно в том случае, когда оболочка имеет более высокий уровень доверия, чем использующий ее код. Если в интересах вызывающего кода выполняются какие-либо действия и при проверке защиты не учитываются его ограниченные разрешения, возникает потенциальное слабое место, которым может воспользоваться злоумышленник.

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

Делегаты Когда ваш код принимает делегаты от менее доверяемого кода, убедитесь, что менее доверяемому коду не позволяется расширять свои разрешения.

Принципы безопасного кодирования для.NET Framework 137 Учтите, что код, создавший делегат, в стеке вызовов не находится, и его разрешения не будут проверяться, если код самого делегата или код, вызываемый делегатом, попытается выполнить защищенную операцию. Если ваш код и код делегата обладают более высокими привилегиями, чем вызывающий, то последний получает возможность изменять цепочку вызовов, не присутствуя в стеке вызовов.

Чтобы решить эту проблему, ограничьте либо круг вызывающих (например, требуя конкретное разрешение), либо разрешения, под которыми может работать делегат (например, используя переопределения стека Deny или PermitOnly).

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

Если полностью доверяемый код вызывает свойство, событие или метод, защищенный LinkDemand, вызов выполняется успешно при успешной проверке разрешений LinkDemand. Кроме того, если полностью доверяемый код предоставляет класс, который принимает имя свойства и вызывает его аксессор get через механизм отражения, то вызов аксессора get выполняется успешно, даже если у пользовательского кода нет прав доступа к этому свойству. Дело в том, что LinkDemand проверяет непосредственного вызывающего, который является полностью доверяемым кодом. По сути, полностью доверяемый код выполняет привилегированный вызов в интересах пользовательского кода, не проверяя, есть ли у него право на такой вызов. Если вы создаете оболочку для функциональности, использующей механизм отражения, прочтите статью «Version I Security Changes for the Microsoft.NET Framework» (http://msdn.microsoft.com/library/en-us/ dnnetsec/html/vlsecuritychanges.asp).

Чтобы избежать дыр в защите, подобных описанной выше, исполняющая среда полностью проверяет стек при любом использовании метода Invoke (создавая экземпляр или вызывая метод, аксессор set или get) для вызова метода, конструктора, свойства или события, защищенного требованием к связыванию (link demand). Такая защита вызывает некоторое снижение производительности (одноуровневая проверка LinkDemand проходит быстрее) и изменяет семантику проверки защиты — полная проверка стека может потерпеть неудачу там, где одноуровневая проверка пройдет успешно.

Защита кода и данных

Оболочки, загружающие сборки

Несколько методов, используемых для загрузки управляемого кода, в частности Assembly.Load(byte[]), загружают сборки с признаком (evidence) вызывающего. Если вы создаете оболочки для таких методов, система зашиты при загрузке сборок будет использовать разрешения вашего кода вместо разрешений вызывающего- Вы вряд ли захотите, чтобы через вашу оболочку менее доверяемый код мог загружать по своему усмотрению код, которому выдаются более высокие разрешения по сравнению с вызывающим.

Любой полностью доверяемый код или код, значительно более доверяемый по сравнению с потенциальными вызывающими (в том числе обладающими разрешениями уровня кода, загружаемого из Интернета), может оказаться уязвимым по отношению к такому ослаблению безопасности.

Если в вашем коде есть открытый метод, который принимает массив байтов и передает его Assembly.Load(byte[]), тем самым создавая сборку в интересах вызывающего, это может привести к разрушению защиты.

Такая проблема возникает при использовании следующих API-элементов:

• System.AppDomain.DefineDynamic Assembly;

• System. Reflection. Assembly.LoadFrom;

• System.Reflection.Assembly.Load.

Обработка исключений Выражение фильтра (filter expression), расположенное выше по стеку, вычисляется до выполнения любого оператора finally. Блок catch, связанный с этим фильтром, выполняется после оператора finally.

Рассмотрим следующий псевдокод:

–  –  –

Вот что выводит этот код:

Throw Filter Finally Catch

Фильтр выполняется перед оператором finally, поэтому любая ситуация, каким-либо образом изменяющая состояние, может создать проблемы с безопасностью, так как этим может воспользоваться другой код. Например:

–  –  –

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

Рекомендуемое решение — ввести обработчик исключения, изолирующий изменения в состоянии потока вашего кода от блоков фильтрации вызывающих. Однако важно правильно установить обработчик исключения, иначе решить эту проблему не удастся. В следующем примере код на Microsoft Visual Basic переключает культуру пользовательского интерфейса (UI), но аналогичным образом возможны и другие изменения состояния потока.

140 Защита кода и данных

You г-ObJect. You rMethod()

Culturelnfo saveCulture = Thread.CurrentThread.CurrentUICulture;

try { Thread.Current/Thread.CurrentUICulture = new CultureInfo("de-DE~);

// Выполняется действие, приводящее к генерации исключения.

} finally { Thread.CurrentThread.CurrentUICulture = saveCulture;

} Public Class UserCode Public Shared Sub Main() Try Dim obj As YourObject = new YoucObJect obJ.YourMethodO Catch e As Exception When FilterFunc Console.WrlteLine("An error occurred: ' { 0 } ' ", e) Console.WriteLine("Currant Culture: {0}", Thread.CurrentThread.CurrentUICulture} End Try End Sub Public Function FilterFunc As Boolean Console.WriteLlne("Current Culture: {0}", Thread.CurrentThread.CurrentUICulture) Return True End Sub End Class

Правильным решением в этом случае является включение существующего блока try/finally в блок try/catch. Простое указание catch-throw в существующем блоке try/finally не решает проблему:

You rObj ect.You rMethod() { Culturelnfo saveCulture = Thread.CurrentThread.CurrentUICulture;

try { Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");

// Выполняется действие, приводящее к генерации исключения.

I catch { throw; } finally { Thread.CurrentThread.CurrentUICulture = saveCulture;

} !

Принципы безопасного кодирования для.NET Framework Проблема не решается, так как оператор finally не выполняется до тех пор, пока функция FilterFunc не получит управление.

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

YourObject.YourMethodO '.

Culturelnfo saveCulture = Thread.CurrentThread.CurrentUICulture;

try { try { Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE"};

// Выполняется действие, приводящее к генерации исключения finally { Thread.CurrentThread.CurrentUICulture = saveCulture;

catch { throw; }

Неуправляемый код Иногда библиотечный код должен вызывать неуправляемый код (например встроенные функции такого API, как Win32), Поскольку это означает выход за периметр защиты управляемого кода, соблюдайте осторожность.

Если ваш код нейтрален к защите (см. раздел «Код, нейтральный к защите»), то и ваш код, и код, который его вызывает, должны иметь разрешения на выполнение неуправляемого кода ( Security Permission.UnmanagedCode).

Однако зачастую неразумно требовать от вызывающего кода таких широких прав. В подобных ситуациях ваш доверяемый код может выступать в роли посредника, как описанные ранее управляемая оболочка и библиотечный код. Если функциональность неуправляемого кода полностью безопасна, можно предоставить к нему прямой доступ; в противном случае сначала следует выполнить соответствующую проверку защиты (Demand).

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

Объявление блокирует проверку стека в пределах вашего фрейма. Будьте крайне осторожны, чтобы при этом не создать дыру в системе защиты.

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

В некоторых случаях (например при получении времени дня) неуправляемый код можно напрямую предоставлять вызывающим без проверки зашиты. Но любой код', объявляющий права, всегда несет ответственность за безопасность.

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

• Функциональность. Обеспечивает ли неуправляемый API безопасную функциональность, не позволяющую путем вызова этого кода выполнять потенциально опасные операции? При защите по правам доступа кода для предоставления доступа к ресурсам используются разрешения, поэтому нужно выяснить, использует ли код API файлы, UI-элементы, многопоточность (threading) и не открывает ли доступ к защищенной информации. Если да, то прежде чем разрешать вызов кода API, управляемая оболочка должна требовать наличия необходимых разрешений. Кроме того, пока нет защиты с помощью разрешений, для обеспечения безопасности необходимо ограничение доступа к памяти с помощью строгого контроля типов (strict type safety).

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

Принципы безопасного кодирования для.NET Framework 143

Использование SuppressllnmanagedCodeSecurity

При объявлении прав (asserting) и последующем вызове неуправляемого кода следует принимать во внимание вопросы производительности. При каждом таком вызове система защиты автоматически запрашивает разрешение неуправляемого кода, из-за чего каждый раз проверяется весь стек, Если вы объявили права и тут же вызвали неуправляемый код, проверять стек бессмысленно: в нем содержатся только ваши объявление прав и вызов неуправляемого кода.

Чтобы отключить обычную проверку защиты, требующую Security Permission. UnmanagedCode, для точек входа в неуправляемый код можно указывать пользовательский атрибут SuppressUnmanagedCodeSecurity. При этом будьте крайне осторожны, так как здесь открывается возможность вызова неуправляемого кода без проверки защиты. Следует отметить, что даже при использовании SuppressUnmanagedCodeSecurity выполняется разовая проверка защиты (в момент компиляции по требованию), чтобы убедиться, есть ли у непосредственно вызывающего разрешение на вызов неуправляемого кода.

При использовании атрибута SuppressUnmanagedCodeSecurity учитывайте следующее.

• Точка входа в неуправляемый код должна быть недоступной вне вашего кода (ее можно, например, объявить как внутреннюю).

• Любое место, из которого вызывается неуправляемый код, — потенциальная дыра в системе защиты. Убедитесь, что ваш код не служит своего рода порталом для злонамеренного кода, позволяющим косвенно вызывать неуправляемый код и обходить проверку защиты. Если есть возможность, требуйте разрешения.

• Используйте такую схему именования, которая позволяет четко видеть потенциально рискованный путь доступа к неуправляемому коду (об этом рассказывается в следующем разделе).

Схема именования методов неуправляемого кода

Для методов неуправляемого кода весьма полезна и настоятельно рекомендуется следующая схема именования. Все методы неуправляемого кода делятся на три категории: safe (безопасный), native («родной») и unsafe (небезопасный). Эти ключевые слова можно использовать как имена классов, содержащих точки входа в неуправляемый код. В исходном коде эти ключевые слова следует добавлять к имени класса, например Safe.GetTimeOfDay, Native.Xyz.MH Unsafe. Dangerous API. Принадлежность к любой из этих категорий однозначно указывает разработчикам, как использовать метод (см. таблицу ниже).

144 Защита кода и данных Ключевое слово Соглашения по безопасности

–  –  –

Пользовательский ввод Пользовательские данные, каким бы образом они ни передавались (через Web-запрос или URL, ввод в элементы управления Microsoft Windows Forms и т. д.), могут неблагоприятно повлиять на код, так как эти данные часто напрямую используются в качестве параметров при вызове другого кода. Эта ситуация аналогична той, где злонамеренный код вызывает ваш код, передавая недопустимые параметры, так что она требует принятия тех же мер предосторожности. На практике сделать пользовательский ввод безопасным гораздо труднее, так как в этом случае нет стека, где можно было бы проверить наличие потенциально недоверяемых данных.

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

Ниже перечислено, на что следует обращать внимание при работе с пользовательскими данными.

• Любые пользовательские данные, возвращаемые сервером, обрабатываются на клиенте в контексте сайта сервера. Если ваш Web-сервер получает данные от пользователя и вставляет их в возвращаемую WebПринципы безопасного кодирования для.NET Framework 145 страницу, то они могут, например, содержать тэг script и выполняться так, будто получены с сервера.

• Помните, что клиент может запросить любой URL.

• Учитывайте возможность ввода «хитрых» или неправильных путей:

•..\, а также крайне длинных путей;

• использования знаков подстановки (*);

• раскрытия маркеров (%token%);

• путей странного вида, имеющих особый смысл;

• альтернативных имен NTFS-потоков (streams), например имя_файла::$ОАТА;

• кратких версий имен файлов, например longfilename и longfi-l.

• Ev'^.(пользовательские_данпые) позволяет делать что угодно.

• Позднее связывание (late binding) с именем, включающим некие пользовательские данные.

• Если вы имеете дело с Web-данными, учитывайте допустимость разных видов escape-последовательностей, в том числе:

• шестнадцатеричных последовательностей (%пп);

• Unicode-последовательностей (%nnn);

• очень длинных последовательностей UTF-8 (%nn%nn);

• двойных последовательностей (%пп превращается в %mmnn, где %mm — escape-последовательность для '%').

• Будьте осторожны с именами пользователей, которые могут быть представлены более чем в одном каноническом формате. Так, в Microsoft Windows 2000 вместо usen2ame@redmond.microsoft.com нередко используется REDMOND\wsername.

Удаленное взаимодействие Удаленное взаимодействие (remoting) позволяет реализовать прозрачные вызовы между доменами приложений (application domains, AppDomains), процессами или компьютерами. Однако защита по правам доступа кода 146 Защита кода и данных не позволяет проверять стек при межмашинном или межпроцессном вызове (такая проверка выполняется только при вызовах между доменами приложений в одном и том же процессе).

Любой класс, поддерживающий удаленное взаимодействие (производный от класса MarsbalByRefObject), сам отвечает за безопасность. Либо этот код следует использовать только в закрытой безопасной среде, где вызывающий код является доверяемым, либо удаленные вызовы нужно проектировать так, чтобы они не открывали доступ к защищенному коду через внешние точки входа.

Кроме того, обычно не следует предоставлять доступ к методам, свойствам или событиям, защищенным декларативными проверками защиты LinkDemand и InheritanceDemands. При удаленном взаимодействии эти проверки не выполняются. Другие проверки, например Demand, Assert и т, д., выполняются при взаимодействии между доменами приложения в процессе, но не между процессами и компьютерами.

Защищенные объекты Некоторые объекты содержат в себе информацию о состоянии защиты.

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

Рассмотрим, например, создание объекта FileStream. Во время создания запрашивается разрешение FilelOPermission и, если оно получено, возвращается файловый объект. Однако, если ссылка на этот объект передается объекту, не имеющему файловых разрешений, то последний получает возможность выполнять чтение/запись в данный файл.

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

Сериализация

Из-за сериализзции другой код может увидеть или изменить данные экземпляра объекта, недоступные иными способами. По сути, коду, выполняющему сериализацию, следует выдавать специальное разрешение:

SecurityPermission.SerializationFormatter. При политике по умолчанию это разрешение не выдается коду, загружаемому из Интернета или интрасети, но предоставляется любому коду, выполняемому с локального компьютера.

Принципы безопасного кодирования для.NET Framework 147 Обычно сериализуются все поля экземпляра объекта, а значит, они присутствуют в сериализованном представлении данных экземпляра. Код, способный интерпретировать соответствующий формат, может получать значения членов класса независимо от их доступности. Аналогичным образом при десериализации извлекаются данные из сериализованного представления, и состояние объекта задается напрямую независимо от правил доступа.

По возможности старайтесь, чтобы любые объекты, содержащие конфиденциальные данные, были несериализуемыми. А если сериализация объекта все же нужна, попробуйте сделать несериализуемыми его поля с конфиденциальными данными. Если и это невозможно, имейте в виду, что эти данные будут доступны любому коду, имеющему разрешение на сериализацию, и поэтому убедитесь, что злонамеренный код не сумеет получить такое разрешение.

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

• GetObjectData следует явным образом защищать, требуя разрешение

SecurityPermission.SerializationFormatter или добиваясь, чтобы в выходных данных метода не было закрытой информации. Например:

[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFo matter =true)] public override void GetObjectData(SerializationInfo info, StreamingContext context)

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

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

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

Один домен приложения может заставить другой загрузить сборку и выполнить содержащийся в ней код вызовом прокси объекта, находящегося в другом домене приложения. Чтобы получить прокси из другого домена приложения, домен приложения, содержащий объект, должен передать его (как параметр метода или возвращаемое значение). Кроме того, если домен приложения только что создан, его создатель получает прокси объекта АррDomain. Таким образом, чтобы избежать нарушения изоляции кода, более доверяемый домен приложения не должен передавать ссылки на свои объекты Marshal By Re f Object менее доверяемым доменам приложений.

Обычно домел приложения по умолчанию (default application domain) создает дочерние домены приложения с управляющим объектом (control object) в каждом из них. Этот объект управляет новым доменом приложения и иногда получает указания от домена приложения по умолчанию, но не может взаимодействовать с доменом напрямую. В некоторых случаях домен приложения по умолчанию вызывает свой прокси управляющего объекта. Однако возможны случаи, когда управляющему объекту необходимо выполнять обратный вызов домена приложения по умолчанию. Тогда домен приложения по умолчанию передает объект обратного вызова с маршалингом по ссылке конструктору управляющего объекта. За защиту этого прокси отвечает управляющий объект. Если управляющий объект поместит прокси в открытое статическое поле или открытый класс или каким-то другим способом откроет доступ к прокси, другой код сможет выполнять обратный вызов домена приложения по умолчанию, что весьма опасно. Поэтому управляющие объекты всегда неявно хранят прокси в закрытом виде.



Pages:     | 1 || 3 | 4 |   ...   | 5 |

Похожие работы:

«1 ОГЛАВЛЕНИЕ № Наименование разделов Стр. I. ЦЕЛЕВОЙ РАЗДЕЛ 5 1.1 Пояснительная записка 5 Цели и задачи реализации Программыв соответствии с ФГОС 6 Цель и задачи образовательной области "Физическое развитие" Цель и задачиОО "Физическое развитие" с учетом НРК При...»

«Ассоциация налогоплательщиков Казахстана Центр изучения общественного мнения МОНИТОРИНГ И ОЦЕНКА НАЛОГОВЫХ УСЛУГ В РЕСПУБЛИКЕ КАЗАХСТАН Проведение исследования стало возможным благодаря поддержке ТОО "ENRC KAZAKHSTAN", ТОО ПРОКТЕ...»

«разработка программного обеспечения Облако SiteRemote 3 Удаленное управление SaaS Программное обеспечение для удаленного мониторинга и управления терминалом SiteRemote – это программный продукт, который используется для удаленного монит...»

«Диагностическая работа Литература. 11 класс. Вариант 1 2 по ЛИТЕРАТУРЕ Инструкция по выполнению работы Экзаменационная работа по литературе состоит из 3 частей. На её 3 декабря 2010 года выполнение даётся 4 часа (240 минут). Рекомендуем распред...»

«Применение тепловых насосов в кольцевом контуре Докладчик: Владимир Райх Генеральный директор компании Аэроклимат генерального дистрибьютора ClimateMaster (США) Ввод Тепловые насосы в основном ассоциируются в России с геотермальным теплом. Многие еще не знают, что на з...»

«Урок толерантности. О тех, кто рядом.Цели: Воспитывать у детей человеколюбие, доброту и сопереживание, внимание к окружающим: близким людям, соседям по дому, по парте. Предоставить учащимся возможность узнать своих одноклассников, развить умение наблюдать и правильно оценивать свои...»

«Ф е д е ра л ь н о е гос ударс твенное бюджетное учреждение науки ИНСТИТУТ КОСМИЧЕСКИХ ИССЛЕДОВАНИЙ РОССИЙСКОЙ АКАДЕМИИ НАУК (ИКИ РАН) Пр-2168 И. М. Сидоров, Г. В. Веселова Свёртывание орбитальной троСовой СиСтемы Представлено к печати зам. директора ики ран р. р. назировым...»

«Борьба антикоммунистической российской эмиграции за солдатские и матросские массы в СССР в условиях "холодной войны" А. В. Антошин Уральский государственный университет Проблема роли армии в российских революциях в течение мно­ гих лет был...»

«Система автоматизации дачи "САД1-ОАЗИС" 1. НАЗНАЧЕНИЕ, ХАРАКТЕРИСТИКА И СТРУКТУРА СИСТЕМЫ 1.1 Назначение Система автоматизации дачи "САД1-Оазис" предназначена для: учёта потребления водных ресурсов; защиты от протечек воды; предупреждения пользова...»

«Скачать настройка bios. практическое руководство 25-03-2016 1 Исполински отличающее ныряние — по-гвардейски не отрепетированный обскурантист, после этого несознанный мицкевич может притерпеться промежду. Радиолюбительский и висячий супермен скачает гибких кабели гомеопатической чужбине, а не выкладывающие чревоугодники клубоч...»

«Содержание ВВЕДЕНИЕ 7 1 Научно – исследовательская часть 11 1.1 Постановка задачи.......................... 11 1.1.1 Области интереса и параметры опорной орбиты..... 11 1.1.2 Желаемая конфигурация и функционал качества форма...»

«E115A 115B 130B 140B 150F 200A 200F L200F 250EAT 250AETO L250AETO РУКОВОДСТВО ПО ЭКСПЛУАТАЦИИ 61U-28199-Q0 EMU01449 СЛОВО К ВЛАДЕЛЬЦУ ПОДВЕСНОГО ЛОДОЧНОГО МОТОРА ОСТОРОЖНО Благодарим Вас за покупку подвесного лодочного мотора "Ямаха". Это Помета ОСТОРОЖНО означает, что руководство по эксплуатации содержит необходимо прин...»

«Расширенные Правила ККИ Берсерк для игры один на один. Пятая базовая редакция. Обновление от 19 августа 2014 г. (Версия 19.2). Разработчики. Создатели игры: Иван Попов (Berserk) и Максим Истомин (Warlock). Главный редактор: Илья Гн...»

«ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ СМК РГУТИС УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ "РОССИЙСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТУРИЗМА И СЕРВИСА" Лист 1 из 39 ОЦЕНОЧНЫЕ СРЕДСТВА ПО ДИСЦИПЛИНЕ дисциплины Б1.Б.19 Технология продукции общественного питания основной образов...»

«FIG, Article of the Month, March 2012, www.fig.net После 10 лет критики: что осталось от идей De Soto? Пол ван дер Молен, Нидерланды Перевод: Максим Федорченко, ИРЦ "Реформирование земельных отношений в Украине", www.myland.org.ua, 2012, в рамках инициативы "Інформування про досвід...»

«109012, г.Москва, ул.Никольская, д.17 офис 2.3 www.continenttour.ru Тел: +7 (495)710-41-62; +7 (495) 621-55-14 continenttours@rambler.ru ЭКСКЛЮЗИВНЫЙ ЛЮКС ТУР "СПОРТИВНЫЕ АВТОМОБИЛИ" Маршрут: Болонья Модена Мил...»

«Директор ИСГТ _Чайковский Д.В. _"_2015 г. БАЗОВАЯ РАБОЧАЯ ПРОГРАММА ДИСЦИПЛИНЫ ИСКУССТВО ВЕДЕНИЯ ПЕРЕГОВОРОВ Направление (специальность) ООП 27.03.05 Инноватика Профиль(и) подготовки (специализация, пр...»

«  АО "БТА Банк" Республика Казахстан, 050051 г. Алматы, мкр. Самал-2, ул. Жолдасбекова, 97 Тел.: 8 (727)2 66-72-54, 2 66-72-69 Факс: +7 (727) 250-02-24 Телекс: (785)251393 ROSA KZ www.bta.kz Материалы годового общего собрания акционеров АО "БТА Банк", назначенного на 30 июня 2011 года (в случае отсутствия кворума 01 июля...»

«Газета №2 группы "Улыбка" декабрь, январь, февраль 2016 -2017 г. "Сорока белобока"Поздравляем с днм рожденья: В декабре Ларина Илью В январе: Крюкову Милану, Юрчак Арину, Тарабрина Володю! В феврале: Хомутова Дмитрия! Пословицы о зиме. Зима – не лето, в шубу одета. Снегирь прилетит о...»

«Как управлять мужчиной-эгоистом. 48 простых правил-Елена Рвачева Елена Рвачева Как управлять мужчиной-эгоистом. 48 простых правил Введение Ведь я всего только и хочу, чтобы все всегда было по-моему". (Джордж Бернард Шоу) "А кто этого не хочет!" – может воскликнуть любой из на...»

«УТВЕРЖДЕНА протоколом заочного голосования Правительственной комиссии по координации деятельности открытого правительства от 26 декабря 2013 г. № АМ-П36-89пр МЕТОДИКА мониторинга и оценки открытости федеральных органов исполнительной власти I. Общие пол...»

«ANLAGENTECHNIK Алюминиевые сушильные камеры HS-Alu-Premium Сушильные камеры для бетонной промышленности в новом исполнении из алюминия + устойчивы к коррозии + легкие + простые в сборке благодаря встроенным соединени...»

«47610 Public Disclosure Authorized Public Disclosure Authorized Н а п ра в л е Н и я в ра з в и т и и Человеческое развитие Public Disclosure Authorized Создание университетов мирового класса Джамиль Салми Public Disclosure Authorized Создание университетов м...»










 
2017 www.book.lib-i.ru - «Бесплатная электронная библиотека - электронные ресурсы»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.