Вы находитесь на странице: 1из 303

том 4

альманах
програ ста
Тематический сборник материалов
MSDN" Library и IN/ISDN' Magazine

Microsoft"

Безопасность в .NET

Шифрование

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

Н.РШШР Е
альманах программиста

Microsoft*

Составитель Ю. Е. Купцевич

Москва 2004

И.РШШ Р Е Ц П Ц У
УДК 004.45
ББК 32.973.26-018.2
А57

А57 Альманах программиста, том 4. Безопасность в Microsoft .NET/


Сост. Ю. Е. Купцевич. — М.: Издательско-торговый дом «Русская Ре-
дакция», 2004. — 304 с.; ил.
ISBN 5-7502-0184-8
Альманах представляет собой тематическую подборку статей из
журнала MSDN Magazine/Русская Редакция- Издание адресовано
широкому кругу программистов, интересующихся современными и
перспективными информационными технологиями. Четвертый том
альманаха, посвященный проблемам безопасности, состоит из двух
тематических рубрик, содержащих 16 статей. В этих статьях рассмат-
риваются такие вопросы, как шифрование и защита данных и кода
на платформе 2003.

УДК 004.45
ББК 32.973.26-018.2

.NET, ActiveX, IntelliSense, JScript, Microsoft, Microsoft Press, MSDN, VBScript,


Visual Basic, Visual C++, Visual J++, Visual Studio, Win32, Windows и Windows
NT являются либо охраняемыми товарными знаками, либо товарными зна-
ками корпорации Microsoft в США и/или других странах. NT — товарный
знак компании Northern Telecom Limited. Все другие товарные знаки являются
собственностью соответствующих фирм.
Информация, приведенная в этой книге, в том числе URL и другие ссылки
на Web-сайты, может быть изменена без предварительного уведомления.

© Microsoft Corporation и CMP Media LLC, 2004


ISBN 5-7502-0184-8 © ИТД «Русская Редакция», 2004
Оглавление

Шифрование 7

Дэн Фокс
Шифрование Пространство имен Cryptography
в .NET Framework 9
Джеймс Мак-Каффри
Шифрование Защита данных с помощью
Advanced Encryption Standard 25
Кит Браун
Безопасный код Хэширование паролей,
атрибут AllowPartiallyTrustedCallers 52
Стефен Тауб
.NET Remoting Защита трафика .NET Remoting с помощью
приемников каналов асимметричного шифрования 60

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

Дон Бокс
Защита в .NET Сервисы признаков, политик, разрешений
и применения политик в инфраструктуре CLR 83
Microsoft
Принципы безопасного кодирования для .NET Framework 122
Кит Браун
Безопасный код Обзор расширений S4U Kerberos
в Windows Server 2003 155
Уэйн Берри
Защита в IIS 6.0 Новые средства IIS надежно защищают
информацию и серверные процессы 166
Кит Браун
Авторизация Ролевая защита на основе Authorization
Manager в .NET-приложениях на промежуточном уровне 179
Оглавление

Нирадж Сривастава
Безопасность Защита SOAP-пакетов на основе WS-Security
и приемников каналов Remoting 195
Майкл Говард
Оценка уязвимостей Полезные советы по выявлению
уязвимостей в вашем коде 210
Габриэл Торок и Билл Лич
Затемнение кода Пресечение попыток обратного
проектирования вашего кода Visual Basic .NET или С# 219
Алек Дэвис
Защита Закрытие доступа к строкам подключения
баз данных и другим секретным параметрам в коде 233
Джейсон Фишер
Охота на вирусы Знание типичных механизмов
вирусных атак поможет лучше защитить приложения 251
Стэн Зундблат и Пер Зундблат
Шаблоны Архитектура автономного приложения 268
Михаил Коготков-Лизин
Защищенный вход Средства безопасной аутентификации
через Microsoft .NET Passport 280
От составителя

Уважаемый читатель!
Альманах программиста представляет собой тематический сборник статей
из Microsoft MSDN Library и журнала «MSDN Magazine* по наиболее ак-
туальным и перспективным технологиям разработки программного обес-
печения. Он выпускается издательством «Русская Редакция» периодичес-
ки, по мере накопления материалов. Альманах избавляет вас от поисков
нужной информации, опубликованной в отдельных номерах журнала
«MSDN Magazine» или документах MSDN Library за 2002-2003 гг. Пер-
вый том был посвящен работе с базами данных, второй — технологиям
ASP/ASP.NET, третий — основным программным продуктам, образующим
платформу 2003 (Windows Server 2003, IIS 6.0, Visual Studio .NET 2003 и
Microsoft Office System, в том числе Microsoft InfoPath 2003), а четвертый
адресован программистам, желающим освоить методы шифрования и за-
щиты кода и данных на платформе 2003.
Четвертый том альманаха состоит из двух тематических рубрик.
• Шифрование. Пространство имен Cryptography в .NET Framework и
его применение для шифрования по различным алгоритмам, шифрова-
ние с применением AES (Advanced Encryption Standard), хэширование
паролей, атрибут AllowParti ally Trusted Callers, использование прием-
ников каналов асимметричного шифрования для защиты трафика
.NET Remoting.
• Защита кода и данных. Модель защиты по нравам доступа кода, об-
зор расширений S4U Kerberos в Windows Server 2003, новые средства
IIS 6.0 для надежной защиты информации и серверных процессов, ро-
левая защита на основе Authorization Manager в .NET-приложениях,
защита SOAP-пакетов на основе WS-Security и приемников каналов
Remoting, рекомендации по выявлению уязвимостей в вашем коде, за-
темнение кода для пресечения попыток обратного проектирования ва-
шего кода, написанного на Visual Basic .NET или С#, закрытие досту-
па к строкам подключения баз данных и другим секретным параметрам
в коде, типичные механизмы вирусных атак, архитектура защищенно-
го автономного приложения и средства безопасной аутентификации
через Microsoft .NET Passport.
От составителя

Исходный код для статей альманаха можно скачать по ссылке, указанной


в конкретной статье или в полном комплекте (для всех статей альманаха)
с Web-сайта издательства «Русская Редакция» по ссылке www.rusedit.ru/
downloaoVcode_ap4.zip.

Пятый том альманаха будет посвящен тематике, связанной с базовыми


концепциями и функциональностью .NET Framework, в том числе систе-
ме типов, механизмам отражения (reflection) и удаленного взаимодей-
ствия (reraoting).

В дальнейшем планируется выпуск альманахов по специфике языков про-


граммирования, поддерживающих .NET, отладке и тестированию и другим
темам. Если вас интересует какая-то специфическая тематика из материа-
лов MSDN Magazine или MSDN Library, обращайтесь на сайт издательства
www.rusedit.ru или по адресу almanah@rusedit.ru. Мы постараемся учесть
ваши пожелания в будущих выпусках альманаха.
Дэн Фокс

Шифрование

Пространство имен
Cryptography в .NET
Framework*

.NET Framework включает набор криптографических сервисов,


расширяющих аналогичные сервисы Windows через Crypto API. Автор
исследует пространство имен System.Securlty.Cryptography и модель
программирования, применяемую в криптографических преобразованиях.
В статье обсуждается, почему в .NET использовать шифрование стало
проще, показывается, насколько легко разработчики могут программно
обращаться к криптографическим API-функциям, и поясняется разница
между алгоритмами симметричного и асимметричного шифрования. Кроме
того, кратко рассматривается большинство наиболее распространенных
алгоритмов, в том числе RSA, DSA, Rljndael, SHA и др.

Взрывное развитие электронной коммерции резко усилило потребность в


надежных алгоритмах шифрования для защиты конфиденциальной ин-
формации. Для решения этой задачи Microsoft еще в 1996 г. ввела Crypto-
graphy API (Crypto API). Продолжающаяся эволюция средств защиты
способствует появлению новых криптографических сервисов, таких как
пространство имен System.Securlty.Cryptography общеязыковой испол-
няющей среды (cominon language runtime, CLR) в Microsoft .NET.
Это пространство имен открывает программный доступ к самым разнооб-
разным криптографическим сервисам, с помощью которых приложения
могут шифровать и дешифровать данные, обеспечивать их целостность,

Публиковалось в MSDN Magazine/Русская Редакция. 2002. № 1 (июль). — Прим. изд.


3,0 Шифрование

а также обрабатывать цифровые подписи и сертификаты. Некоторые из


таких сервисов применяются непосредственно в самой .NET Framework
(например для поддержки аутентификации в ASP.NET). Я изложу базовые
сведения о криптографических сервисах, предоставляемых .NET Frame-
work, и приведу несколько примеров кода, демонстрирующих возможное
применение соответствующих классов в приложениях.

Пространство имен Cryptography


На самом высоком уровне пространство имен Cryptography можно разбить
на четыре основные части (табл. 1). Главное предназначение этого про-
странства — предоставлять классы с алгоритмами таких операций, как
шифрование и создание хэшей. Эти алгоритмы реализуются на основе
расширяемого шаблона (pattern), включающего два уровня наследования.
На вершине иерархии располагается абстрактный базовый класс (вроде
AsymmetricAlgorithm или Hash Algorithm), имя которого соответствует ти-
пу алгоритма. От такого класса наследует абстрактный класс второго уров-
ня, предоставляющий открытый интерфейс для использования данного
алгоритма. Например, SHA1 (Secure Hash Algorithm) представляет собой
производный от HashAlgorithm класс и содержит методы и свойства, спе-
цифичные для алгоритма SHA1. Наконец, сама реализация алгоритма яв-
ляется производной от класса второго уровня; именно ее экземпляр созда-
ется и используется клиентским приложением. На этом уровне реализа-
ция может быть управляемой, неуправляемой или и той и другой,
Табл. 1. Основные элементы пространства имен Cryptography

Элемент Описание

Алгоритмы шифрования Набор классов, применяемых для реализации


алгоритмов симметричного и асимметричного
шифрования, а также хэширования
Вспомогательные классы Классы, обеспечивающие генерацию случайных
чисел, выполнение преобразований, взаимодейст-
вие с хранилищем Crypto API и само шифрование
на основе потоковой модели
Сертификаты Х.509 Классы, определенные в пространстве имен
System. Security. С гу ptograph у. Х509 Certificates
и представляющие цифровые сертификаты
Цифровые подписи XML Классы, определенные в пространстве имей
System.Cryptography.Xml и представляющие
цифровые подписи в XML-документах
Пространство имен Cryptography в .NET Framework

К именам неуправляемых реализаций обычно добавляется суффикс «Crypto-


ServiceProvider» (скажем, SHAlCryptoServiceProvider), указывающий на то,
что данная реализация на самом деле предоставляется провайдером крипто-
графического сервиса (Cryptographic Service Provider, CSP), который уста-
новлен на уровне операционной системы и действует как оболочка Crypto
API. В имена управляемых реализаций включается суффикс «Managed» (на-
пример SHAlManaged). Такие реализации не опираются на Crypto API и со-
держат исключительно управляемый код.

Еще один важный момент. При создании экземпляра одного из конкрет-


ных классов исходные конструкторы всегда записывают в параметры
объекта разумные и безопасные значения (если это возможно). Так, алго-
ритмы асимметричного шифрования, опирающиеся на криптографию с от-
крытым ключом, генерируют случайную пару ключей, а алгоритмы сим-
метричного шифрования — случайный ключ и вектор инициализации
(initialization vector, IV); при этом они автоматически настраивают такие
свойства, как Mode и Padding. Более того, алгоритмы второго типа по
умолчанию стараются использовать «стойкие* значения.

Второй основной набор классов в пространстве имен System.Security.Cryp-


tography включает как классы, реально применяемые в процессе шифро-
вания и расшифровки данных, так и разнообразные вспомогательные
классы. Это пространство имен содержит, например, абстрактный класс
RandomNumberGenerator, от которого наследуют классы RNGCrypto-
ServiceProvider, ToBase64Transform и FromBase64Transform (используют-
ся при соответствующих преобразованиях данных).

Пространство имен Cryptography не только предоставляет алгоритмы


шифрования, но и содержит дочернее пространство имен Х509Certificates.
Последнее объединяет всего три класса, предназначенных для операций
с сертификатами Authenticode X.509 v.3. Класс X509Certificate предостав-
ляет статические методы CreateFromCertFile и CreateFromSignedFile для
создания экземпляра сертификата:
Dim с As X509Certificate
с = X509Certificate.CreateFromCertFile("myCert.cer")
Console.WriteLine(c.GetName)
Console.WriteLine(c.GetPublicKeyString)
Console.WriteLine(c.GetSerialNumberString)
Console.WriteLineCc.GetExpirationDateString)

После этого сертификат можно использовать в нескольких целях, в том


числе для проверки на Web-сервере: вы связываете сертификат с клиент-
ским запросом через свойство ClientCertificates объекта System.Net.Http-
WebRequest (оно открывает доступ к объекту X509CertificateCollection).
£2 Шифрование

В пространстве имен Cryptography также присутствует дочернее про-


странство имен XML, используемое системой защиты .NET Framework для
цифровой подписи XML-объектов в соответствии с проектом WSC-специ-
фикации по синтаксису и обработке XML-подписей (http://www.w3.org/
TR/2000/WD-xmldsig-core-20000228/). Эта спецификация описывает
синтаксис, а также правила создания и представления цифровых подпи-
сей, которые могут быть применены к любому информационному напол-
нению — внутреннему или внешнему по отношению к XML-документу с
цифровой подписью. Хотя в этой спецификации ничего не говорится о
шифровании XML (пока!), она по-настоящему важна в реализации под-
держки целостности данных, аутентификации сообщений и сервисов ау-
тентификации владельцев подписей для XML-документов.

В оставшейся части статьи я сосредоточусь на алгоритмах шифрования и


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

Алгоритмы шифрования
Пространство имен Cryptography, в частности, открывает доступ к алго-
ритмам симметричного шифрования (далее — симметричные алгоритмы).
Симметричные алгоритмы называются так потому, что для шифрования и
дешифрования используется один секретный ключ. Очевидно, что и от-
правитель, и получатель должны держать такой ключ в секрете, иначе
шифрование станет бессмысленным. Кроме того, в режиме СВС симмет-
ричные алгоритмы используют IV — несекретное двоичное значение; оно
инициализирует алгоритм и вносит дополнительные вариации в процесс
шифрования. SymmetricAlgorithm является абстрактным базовым клас-
сом, от которого наследуют другие классы, специфичные для конкретных
алгоритмов.

К числу поддерживаемых симметричных алгоритмов относятся Data En-


cryption Standard (DES), RC2, Rijndael и Triple Data Encryption Standard
(TripleDES). Каждый алгоритм включает какой-нибудь производный
от SymmetricAlgorithm абстрактный класс вроде DES и производный от
базового управляемый класс или класс провайдера сервиса, например
DESCryptoServiceProvider.

Иерархия симметричных алгоритмов показана на рис. 1.


Вы можете создавать экземпляры конкретных классов, приведенных на
рис. 1 (например класса RijndaelManaged), и обращаться к их свойствам
(рис. 2). В частности, на рис. 2 создается новый экземпляр класса алгорит-
ма, который автоматически генерирует ключ и IV как массивы типа Byte,
Пространство имен Cryptography в .NET Framework 13

которые потом сериализуются в значения типа String и выводятся на кон-


соль. Заметьте, что свойства KeySize и BlockSize позволяют определять
длину ключа и размер блока данных (в битах), который может быть за-
шифрован или расшифрован за одну операцию.

SymmetricAlgorithm

RijiidaelManaged

TripleDES

TripieDESCryptoServiceProvider

Рис. 1. Симметричные алгоритмы

Рис. 2. Применение класса RijndaelManaged


Dim oEnc As New RljndaelManagedO
Dift 1 As StMJrt
Dift strKey, strlV As String

For 1 * 1 To (oEnc.KeySize / 8)
strKey &= o€nc.Key(i - t>.ToStrlna &
Next

for i » 1 To 16
striv &« oBie.lVCi - D.ToStrlng & "
Next

Car»sole.«rlt8Llfle<strIV)
Console. *fiteUne(o£oc.KeySlze.T^trina>
Console. WriteLineCoEnc. BlockSize. Tostring)

Второй тип — асимметричное шифрование или шифрование с открытым


ключом (далее — асимметричные алгоритмы); соответствующий класс
является производным от абстрактного класса AsymmetricAlgorithm.
К общеизвестным асимметричным алгоритмам относятся Digital Signature
14 Шифрование

Algorithm (DSA) и RSA [название образовано от первых букв фамилий


его создателей: Ron Rivest (Рон Ривест), Adi Shamir (Ади Шамир) и Len
Adleman (Лен Эйдлман)]. Б асимметричных алгоритмах применяется па-
ра ключей: один — закрытый, другой — открытый. Как правило, откры-
тый ключ доступен кому угодно и используется отправителем для шиф-
рования данных, тогда как закрытый ключ хранится в защищенном мес-
те и применяется для расшифровки данных, зашифрованных с помощью
открытого ключа. Наиболее распространен алгоритм RSA.

Эти алгоритмы в конечном счете являются производными от абстрактных


классов DSA и RSA, в свою очередь производных от AsymmetricAlgorithm
(рис. 3). Так как эти алгоритмы очень сложны, им нужны вспомогатель-
ные классы, производные, например, от AsymmetricKeyExchangeFormatter
и AsyrametricSignatureFormatter,

Asymmetric Algorithm

Asymmetric KeyExchangeDeformattei AsymmetricSignatureDeformatter

RSAOAEPKeyExchangeDeformatter DSASignatureDeformatter

RSAPKCS1 KeyExchangeDeformatter RSAPKCSISignatureDeformatter

Asymmetric Key Exchange Form alter AsymmetricSignatureFormatter

RSAOAEPKeyExchangeFormatter DSASSignatu re Formatter

RSAPKCSfKeyExchangeFormatter H5 RSAPKCSlSignatureFormatter

Рис. З. Асимметричные алгоритмы

Вы можете не только позволить исходному конструктору асимметричных


алгоритмов сгенерировать пару ключей, но и извлечь уже существующую
пару из хранилища, поддерживаемого CSR Для этого вы создаете экземп-
ляр класса асимметричного алгоритма, указываете в качестве имени кон-
тейнера ключа хранилище Crypto API, заполняя нужные свойства объек-
та СspParameters, а затем предоставляете этот объект конструктору клас-
са асимметричного алгоритма. Пример, иллюстрирующий, как сохранять
и извлекать пары ключей, см, по ссылке http://www.gotdotnet.com/team/
cl r/about_security. aspx.
Пространство имен Cryptography e .NET Framework 15

Последний тип алгоритмов, предоставляемых пространством имен Crypto-


graphy, связан с хэшированием. Алгоритм хэширования вычисляет дай-
джест (хэш), уникальную последовательность (фиксированного размера)
двоичных значений на основе более длинной последовательности байтов.
Этот алгоритм позволяет проверять, не были ли изменены данные. Когда
вы посылаете вместе с данными дайджест, получатель может повторно вы-
числить его. Если на вход алгоритма хэширования поступают другие дан-
ные, на выходе он дает другой результат, и тогда отличие нового дайдже-
ста от старого указывает на то, что данные изменены. Именно на основе та-
ких алгоритмов обычно реализуются цифровые подписи
Пространство имен Cryptography содержит базовый класс HashAlgorithm
и производные классы, поддерживающие алгоритмы MD5, SHA1, SHA256,
SHA384 и SHA512. Алгоритм MD5 дает 128-битный хэш, a SHA1 - 160-
битный. Числа в названиях других версий SHA-алгоритмов соответству-
ют длине создаваемых ими хэшеЙ. Чем больше хэш, тем надежнее алго-
ритм и тем труднее его взломать. Все эти алгоритмы реализованы в двух
версиях: на основе управляемого и неуправляемого кода (рис. 4).
HashAlqorithm

KeyedHashAlgorithm

HMACSHA1

MACTripleDES

SAH256Managed

SHA3B4Managed

SHA512Managed

Рис. 4. Алгоритмы хэширования


16 Шифрование

Чтобы вычислить дайджест, нужно просто создать экземпляр класса алго-


ритма хэширования и вызвать его перегруженный метод ComputeHash, на-
следуемый от HashAlgorithm:

Dim fsData As New FileStream("mydata.txt", _


FileHode.Open, FileAccess.Read)
Dim digest() As Byte

Dim oSHA As New SHA512Managed(>


digest - oSHA.ComputeHash(fsData)
fsKey.Close()

Здесь методу ComputeHash передается объект Stream, но он принимает и


байтовый массив.

В пространстве имен Cryptography также имеется абстрактный класс Keyed-


HashAlgorithm (рис. 4). Алгоритмы, реализованные в классах HMACSHA1 и
MACTripleDES, производных от KeyedHashAlgorithm, позволяют генери-
ровать Message Authentication Codes (MAC). С помощью MAC можно
определить, были ли модифицированы данные, переданные по незащи-
щенному каналу связи, — при условии, что и отправитель, и получатель
используют общий секретный ключ.

Клиент не может напрямую создавать экземпляры абстрактных базовых


классов SymmetricAlgorithm, Asymmetric Algorithm, HashAlgorithm и Keyed-
HashAlgorithm, но все они предоставляют перегруженный статический (об-
щий) метод Create. Вызов этого метода без параметра приводит к созданию
экземпляра алгоритма по умолчанию: RijndaelManaged (при симметричном
шифровании), RSACryptoServiceProvider (при асимметричном шифрова-
нии), SHAlCryptoServiceProvider (при хэшировании) или HMACSHA1
(при хэшировании по ключу). Вторая версия метода принимает строку, ука-
зывающую конкретную реализацию (список реализаций см. в справочной
системе .NET Framework). Аналогичным образом абстрактный класс каждо-
го алгоритма, например SHA, тоже предоставляет перегруженный статиче-
ский метод Create, который либо создает экземпляр реализации по умолча-
нию, либо принимает строковый идентификатор.

Так или иначе, но в конечном счете объект создается вызовом метода


CreateFromName класса CryptoConfig с передачей ему строкового иденти-
фикатора. Например, если вы вызываете метод Create без параметра, клас-
су SHAlCryptoServiceProvider передается строка по умолчанию «Sys-
tem.Security.Cryptography.HashAlgorithm».
Пространство имен Cryptography в .NET Framework 17

Чтобы продемонстрировать работу этих методов, рассмотрим код, в кото-


ром каждый оператор, создающий экземпляр класса RijndaelManaged, эк-
вивалентен остальным операторам:
Dim r, г1, г2, гЗ As RijndaelManaged

гЗ = CType(CryptoConfig.CreateFromName("Rijndael"), RijndaelManaged)
г2 = CTypeCSymmetricAlgorithm.Create, flijndaelManaged)
M = CType(Rijndael. Create, RijndaelManaged)
г = New RijndaelManagedO

Это несколько сбивает с толку, но классы разработаны таким образом,


чтобы выбор конкретного алгоритма можно было отложить до периода вы-
полнения или сразу указать при настройке системы. Фактически в обще-
системный конфигурационный файл (machine.config) можно вставить раз-
дел cryptoName Mapping, который позволяет переопределить сопоставле-
ния по умолчанию или добавить нужную запись в список сопоставлений,
«зашитый» в код Mscorlib.dll. Например, если вы включите в этот файл
XML-код, показанный на рис. 5, то реализацией хэширования по умолча-
нию станет MDSCryptoServiceProvider и, кроме того, будет добавлено но-
вое сопоставление «dan».

Рис. 5. Настройка пространства имен Cryptography

<iascorlib>
<с rypt08raphySettlfigs>
<cryptoKaHieMapping>
<cryptQClasses>
«coryptoClass rayH05=
"System. Security, Cryptog ra phy.RBSCryptoServieeProvlder,
mscorUb, Ver=1. 0.2411.0, CuUure=neutral,
PubliGKeyToken=b77a5c561934e089"/>
</c ryptoClasses>
<naraeEntry пагвевМая" class="fliyHD5*V>
<fiante Entry/ na«eis"SyBteieI Security. Cryptography. HashAigorithtn

</cryptoNaKeMapping>
</cryptograpliySettings>

Как видите, вставка элемента cryptoClass в элемент cryptoClasses позволя-


ет определить новое ссылочное имя (в данном случае — myMDS), полно-
стью описав нужные класс и сборку. Затем с помощью элементов name-
Entry создаются новые сопоставления. После этого можно писать такой
клиентский код:
18 Шифрование

Dim m, ml As MDSCryptoServlceProvider

m = CType(HashAlgorithm.Create,
MDSCryptoServiceProvider)
ml = CType(CryptoConfig,CreateFroiriName("dan"),
MDSCryptoServlceProvider)

В обоих примерах создаются объекты типа MD5CryptoServiceProvider.

Использование криптографических сервисов


.NET Framework поддерживает модель программирования на основе пото-
ков (streams). Классы потоков, производные от System.lO.Stream, представ-
ляют данные из различных хранилищ (текстовых файлов, XML-докумен-
тов, MSMQ-сообщений, памяти и сети) и позволяют считывать данные из
соответствующих хранилищ или записывать в них. Не удивительно, что
пространство имен Cryptography построено на такой концепции, ведь она
дает элегантный и эффективный способ шифрования и дешифрования дан-
ных с применением уже рассмотренных алгоритмов. В основе этой функ-
циональности лежит класс CryptoStream. производный от System.IO.Stream
и служащий в качестве модели потоков при криптографических преобразо-
ваниях,

Чтобы показать вам, как пользоваться базовой криптографической функ-


циональностью в этом пространстве имен, я написал простой класс Text-
FileCrypto (рис. (>). Он позволяет зашифровать и расшифровывать любой
текстовый файл по алгоритму DES с ключом, который генерируется этим
классом и сохраняется в файле.

Рис. 6. Базовая криптографическая функциональность


с применением алгоритм^ DES
Option Strict Or*

Imports System.S&curity.Cryptography
rtports System.10
Imports System.Threading
Public Class TextFileCrypto
' Шифрует и расшифровывает текстовый на основе ключа
Private mstrFlle As String ' расшифровываемый файл
Private mstrKey As String ' файя ключа
Private aiKey(7) As lyte ' ключ oes
Private iBDES As PESCryptoServlceProvider
Private nIV(7) As Byte ' ректор инициализации
см. след. стр.
Пространство имен Cryptography в ,NET Framework

Рис. 6. Базовая криптографическая функциональность


с применением алгоритма DES (продолжение)

Public Sub New()


mDES = Hew CESCryptoServiceProviderO
End Sub

Public Property KeyFileO As String


Set
ftettrrn instrKey
End Get
SeUByval Value As String)
If File.Exists(Value) Then
fflstrKey = Value
OpenKeyFileO ' открываем файл клоча и считываем его содержимое
Else
Throw New FileNotFoundExceptionCValue & " file does not exist."} :
End If
End Set
End Property

Public Property FileRameO As String


Get
Return BistrFile
End Get
Set(ByVal Value As String)
If File.Exists(Value) Then
rastrFile * Value
Else
Throw New Filel*otFoundException{Value & " does not exist,")
End If
End Set
End Property

Private Sub OpenKeyFileO


Oin fsKey As New FileStreasiCmstrKey,
FileMode.Open, FtleAccess.Ftead)

' Открываем файл ключа и считываем из него ключ


fsKey.ReadCmKey, 0, 8)
fsKey,Read(ieIV, 0, 8)
fsKey.Close()

mDES, Key = utKey


BiOES. IV = mlV
End Sub

Public Function SaveKeyFile(ByVal FilePath As String) As Boolean


Dim fsKey As New FileStreain(FilePath, _
FUeHode.OpenQrCreate, FileAccess. Write)
см. след. стр.
20 Шифрование

Рис. 6. Базовая криптографическая функциональность


с применением алгоритма DES (продолжение)

' Генерируем новый ключ и IV случайным образом и сохраняем


1
в файле. Заметьте, что они будут сгенерированы
' автоматически, если вы сами не сделаете этого.
mOES.GerterateKeyO
«DES.GeneratfilVO

«Key » HIDES. Key


«iiv = aSES.iv

fsKey.WriteCmKay, Q f яКеу. length)


fsKey.WriteOnXV, 0, вКеу. length)
fsKey.GloseO

ifistrKey =
End Function

Public Function EncryptFileO As Boolean


' Шифруем данный файл

' Проверяем ключ


If «Key Is КоШпд Then
Throw New l=xeeption{"You eiust have a key in place first.")
Return false
End If

Diai fslnput As Ne» FileStreaFiKeistrFile, „


FileHode.Qpen, FileAceess.Read)
Oil» fsQutput As H&M FileStrearaC'tenJp.dat", _
FileHode. Create, FileAccessi. Write)
f sQutput. Settengtft(O)

Din arlf!put() As Byte

' Создав* DES Encryptor из этого зкаемпляра


Bi» desEncr>$t As ICryptoTrarisforni = mOES.CreateEneryptor(>
' Создаем CryptoStream, преобразующий ф&Йлоаый поток
' с применением DES-иифровамия
Diffl sCrypfto As New CryptoStreani{fsGutpyt, desEncrypt, _
CryptoStreamMode.Write)

arInput(Convert.ToInt32CfsInput.Lefigtti - t)>
fsInput,Bead(arlnputr Q, Convert. Tolnt32(fslnpjat. Length))
fsInput.CloseO

* Записываем зашифрованный файл


sCrypto.WritetarlnptFt, 0, arlnput. length)
sCrypto.Close<)

см. след. стр.


Пространство имен Cryptography в .NET Framework '

Рис.6. Базовая криптографическая функциональность


с применением алгоритма DES (продолжение)

' Удаляем и переименовываем


File. GopyC'temp. dat", nistrFile, True)
File . Delete( " teiip. dat " )
End Function

Function DecryptFileO As Boolean


Расшифровываем данный файл

клвч
If вКеу Is Nothing Than
Throw New ExceptionC'You must have a key in place first.")
Return False
End If

' Создаем файловый поток для считывания зашифрованного файла


Dim fsfiead As New FileStreaB<BstrFlle, FileHode.Qpefl, _
Fileftocess.Read)
Dim fsQtitput As New FileStreairt("terap.dat", _
FileHoele . Create, FileAeeess. Write)
• создаем DES Decryptor из нашего экземпляра des
81m desPecrypt As ICryptoTransforB =* siDES,CreateOecryptor(>
" Создаем кри?гго-поток, настроенный на чтение потока байтов
* и их расшифровку по DES
Die sCrypto As Hew CryptoStreajn(fsRead, desDecrypt, „
С ryptoSt reamHode . flead )
Oirai swWriter As Hew StreaBWriter(fsQutpyt)
Dim srReatier As Hew StreaBfleader(sCrypto)

' Записываем расшифрованный файл


swWriter. Write(srReader.FteadToEnd}

' Очистка
swWriter, Closet)
fsOutput.CleseC)
fsRead.GloseO

' Удаляем и переименовываем


WaitForExcltisiveAeces8(iistrFile)
File. Copyf'tefflp. dat", fBStrFile, True)

End Function

Private Sub WaitForExclusiveAcce8s(SyVal fuliPath As String)


mile (True)
Try
Dim file As FileStrea»
file * New FlleStreaffiCfullPatn, FileMode. Append, _
см. след. стр.
22 Шифрование

Рис.6. Базовая криптографическая функциональность


с применением алгоритма DES (окончание)
FileAccess.Write, FileShare.None)
file.CloseO
Exit SuD
Catch e As Exception
Thread.Sleep(IOO)
End Try
End While
End Sub

'End Class

Как видите, конструктор класса создает экземпляр DESCryptoService-


Provider, который предоставляет методы CreateEncryptor и CreateDecryp-
tor. Впоследствии эти методы используются для возврата объектов, вы-
полняющих шифрование и дешифрование. Свойства KeyFile и FileName
позволяют соответственно открыть существующий файл, содержащий
ключ и IV, и указать имя файла, подлежащего шифрованию или дешифро-
ванию. Если такой файл действительно есть, он открывается закрытым ме-
тодом OpenKeyFile, после чего ключ и IV считываются в массивы типа
Byte. Метод SaveKeyFile вызывает методы GenerateKey и GeneratelV ни-
жележащего класса DES для генерации случайного ключа и IV. Потом эти
значения сохраняются в файле, имя которого было передано как аргумент.
С этого момента файл ключа должен храниться в секрете и выдаваться
лишь тем, кто будет с его помощью расшифровывать информацию.
Заметьте, что алгоритм DES использует 56-битный (7-байтовый) ключ. DES
поддерживает ключи лишь этой длины, но узнать допустимые размеры клю-
чей для других алгоритмов можно через свойство LegalKeySizes, которое воз-
вращает массив объектов KeySizo; их свойства MinSize и MaxSize сообщают
минимальную и максимальную длину ключа. Например, алгоритм Rijndael
поддерживает ключи размером 128, 192 и 256 битов. Для более стойкого шиф-
рования можно указать больший размер (через свойство KeySize).
Сердцевина класса TextFileCrypto - методы EncryptFile и DecryptFile.
Первое, что делает метод EncryptFile, — открывает шифруемый файл и
временный файл для хранения зашифрованных данных. Затем он создает
шифрующий объект (encryptor object), вызывая метод CreateEncryptor
провайдера сервиса (в нашем случае — DESCryptoServiceProvider). Этот
объект реализует интерфейс ICryptoTransform и поэтому может быть пе-
редан конструктору CryptoStream, который преобразует данные по мере
их записи в файл, управляемый FileScream (здесь — fsOutput). Исходный
файл перезаписывается временным. В результате создается зашифрован-
Пространство имей Cryptography в ,NET Framework 23

ный файл, который можно безопасно передавать по Интернету через FTP


или как почтовое вложение.
Метод DeeryptFile создает дешифрующий объект (decryptor) методом
CreateDecryptor. Этот объект аналогичным образом передается конструк-
тору CryptoStream для расшифровки данных из файла, считываемого
FileStream-объектом fsRead. Наконец, расшифрованные данные сохраня-
ются в текстовом файле классом Stream Writer, который использует при
этом метод ReadToEnd класса Stream Reader.
Для шифрования данных с помощью класса TextFileCrypto в клиентской
программе достаточно написать всего четыре строки кода:
Dim objCrypt As New TextFileCryptoO

objCrypt.FileName = "students.txt"
1
Генерируем и сохраняем ключ
ob]Crypt.SaveKeyFile("mykey.dat")
1
Шифруем файл
objCrypt.EncryptFile()

Когда клиент получает зашифрованный файл, он может расшифровать его так:

Dim objCrypt As New TextFileCryptoO

objCrypt.FileName = "students.txt"

' Считываем ключ


objCrypt.KeyFile = "mykey.dat"

' Расшифровываем файл


objCrypt. OecryptFileO

Стойкое шифрование в Windows


Хотя ограничения на экспорт средств стойкого шифрования сняты, .NET
Framework не будет использовать стойкое шифрование для неуправляемых
реализаций, если вы приобрели операционную систему в экспортной версии.
Чтобы обойти это препятствие, установите High Encryption Pack (он входит
в Service Pack 2 для Windows 2000, который можно скачать по адресу http://
www.microsoft. com/ windows2000/downloads/recommended/encryption).

Для Windows NT 4.0 сервисные пакеты распространяются как в стандарт-


ной версии, так и в версии со стойким шифрованием. Если у вас еще не
установлен такой сервисный пакет, скачайте Service Pack ба в версии со
стойким шифрованием по а/фесу http://www.microsoft.com/ntserver/nts/
downloads/recommended/SP6/allSP6.asp).
24 Шифрование

В случае Windows Me, Windows 98 и Windows 95 пакет High Encryption


Pack устанавливается вместе с Microsoft Internet Explorer 5.5. Если ваша
версия Internet Explorer ниже 5.5, скачайте соответствующий High Encryp-
tion Pack по адресу http://www.microsoft.com/windows/ie/download/128bit/
default.asp).

Дэн Фокс (Dan Fox) — директор компании Quilogy по технологиям


(Оверленд-Парк, штат Канзас). Автор книг «Pure Visual Basic» (Sams, 1999)
и «Building Distributed Application with Visual Basic .NET» (Sams, 2002),
а также соавтор «Exam Cram: Visual Basic 6» (The Coriolis Group, 1999).
Вы можете связаться с ним по адресу dfox@quilogy.com.
Джеймс Мак-Каффри

Шифрование

Защита данных с помощью


Advanced Encryption
Standard*

Advanced Encryption Standard (AES) — спецификация шифрования


электронных данных, предложенная Национальным институтом стандартов
и технологий (National Institute of Standards and Technology). Ожидается,
что она найдет широкое применение в шифровании цифровых данных,
в частности финансовой, телекоммуникационной и правительственной
информации. В статье дан обзор AES и применяемых алгоритмов, а также
приведены полная реализация AES на С# и примеры шифрования .NET-
данных. Прочитав эту статью, вы научитесь шифровать данные с помощью
AES, тестировать программное обеспечение с поддержкой AES
и использовать такое шифрование в своих системах.

Национальный институт стандартов и технологий (National Institute of


Standards and Technology, NIST) выпустил 26 мая 2002 г. новую специфи-
кацию AES (Advanced Encryption Standard). В этой статье я продемонст-
рирую работающую реализацию AES на СП и подробно объясню, что та-
кое AES и как работает код ее реализации. Я покажу, как шифровать дан-
ные с помощью AES и как расширить приведенный в статье код, чтобы
создать AES-класс, пригодный для коммерческого применения. Кроме то-
го, мы поговорим о том, как добавить шифрование AES в программные
системы и зачем это нужно, а также как тестировать программное обеспе-
чение, использующее AES.

Публиковалось в MSDN Magazine/Русская Редакция. 2003. № И (ноябрь). — Прим. изд.


26 Шифрование

Заметьте, что исходный код, приведенный в этой статье, и любые реализа-


ции, основанные на материалах данной статьи, подпадают под законодатель-
ство, регулирующее экспорт криптографических модулей (подробную нор-
мативную информацию см. по ссылке http://www.bxa.doc.gov/Encryption).

AES — новый криптографический алгоритм для защиты электронных дан-


ных. Точнее, AES — это итеративный блочный шифр с симметричным клю-
чом, который использует 128-, 192- и 256-битные ключи и шифрует/дешиф-
рует данные блоками по 128 битов (16 байтов). В отличие от шифров с от-
крытым ключом, использующих пару ключей, шифры с симметричным
ключом применяют для шифрования и дешифрования данных один и тот
же ключ. Зашифрованные данные, возвращаемые блочным шифром, содер-
жат ровно столько же битов, сколько и входные данные. При итеративном
шифровании выполняется цикл, на каждой итерации которого над входны-
ми данными выполняются операции перестановки (пермутаиии) и замены.
На рис. 1 показан пример использования AES: 1б-байтный блок данных
шифруется 192-битным ключом, а затем дешифруется.

С: '.AESiAe5DemoCQn$a!e\bini,Deb«e\

Rduanet'd E n c r y p t i o n Standtirtl Puno in „NET

T h e pla i n t e x t i c :
ИЙ 11 22 33 44 $5 66 77 88 9 9 J «a bb CL- dd ее ft

Us i n s я Bitsi92i-key o f :
P0 01 02 83 84 65 Й6 07 88 S9i 6a 0b Йс Set 0e 8f
11 12 13 1-4 15 16 J?

1 he re suit in у L iphe r-t ex t is :


dd a9 7c a4 86 4c df t?B 6e af 70 аЙ ее 8tl 71 91

Й / t e t 1 deciphering t h e c i p h e i - t b x t , t l t e i-esult i s :
№ 11 22 33 44 55 66 77 88 9?| aa bb cc del ее f f

Рис. 1. Шифрование/дешифрование данных

Предшественником AES является старый стандарт DES (Data Encryption


Standard). DES утвердили в качестве федерального стандарта в 1977 г. Он
считался пригодным для использования до 1998 г., когда оказалось, что с
помощью новейших достижений аппаратно-программного обеспечения и
криптографического анализа можно расшифровать сообщение, зашифро-
ванное по алгоритму DES, за 50 часов. С тех пор начались многочислен-
ные успешные атаки на данные, зашифрованные по алгоритму DES. По-
этому в настоящее время DES считается устаревшим,
Защита данных с помощью Advanced Encryption Standard

В конце 1999 г. NTST предложил использовать в новом стандарте алгоритм


Rijndael (произносится как «rain doll»), разработанный исследователями
Джоан Демен (Joan Daemen) и Винсентом Риджменом (Vincent Rijmen),
как в наибольшей степени отвечающий критериям безопасности, эффек-
тивности реализации, гибкости и простоты. Термины AES и Rijndael ино-
гда употребляются как синонимы, но это разные понятия. Ожидается, что
AES станет стандартом де-факто в шифровании всех видов электронных
данных, в том числе используемых коммерческими приложениями (на-
пример для банковских и финансовых транзакций), в телекоммуникаци-
ях, при передаче частной и правительственной информации.

Обзор алгоритма AES


Алгоритм AES основан на перестановках (permutations) и заменах (sub-
stitutions). Перестановка — это изменение порядка данных, а замена -
замещение одного блока данных другим. В AES используется несколько
видов перестановок и замен. Чтобы в них разобраться, рассмотрим кон-
кретный пример — шифрование по алгоритму AES данных, показанное
на рис. 1.

Здесь шифруется следующее 128-битное значение (во второй строке пока-


заны индексы массива):

00 11 22 33 44 55 66 77 99 аа bb cc dd ее ff
0 1 2 3 4 5 6 7 9 10 11 12 13 14 15

и используется 192-битный исходный ключ:

00 01 02 03 04 05 06 07 08 09 Оа ОЬ Ос Od Oe Of 10 11 12 13 14 15 16 17
О 1 2 3 4 5 6 7 6 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

При вызове конструктора AES-класса инициализируются две таблицы, ис-


пользуемые методом шифрования. Первая таблица — матрица замен Sbox
размером 16x16. Первые пять строк и столбцов Sbox показаны на рис. 2.
Кроме того, в процедуре шифрования по массиву байтов ключа формиру-
ется «таблица ключей» («key schedule») w[], показанная на рис. 3.

О f
63 7с 77 7Ь и
са 82 с9 7d fa
Ь7 fd 33 26 36
04 с? 23 сЗ 18
09 83 2с 1а 1b
Рис. 2. Sbox
28 Шифрование

0 00 01 62 03
1 04 05 06 07
2 OS 09 Oa Qb
3 fle Od Oe «
4 IS 11 12 13
5 14 15 16 П
6 5ft 46 f2 «I
7 5C 43 14 le
8 54 48 fe n
9 58 47 № lil
10 48 56 e2 «9 0 1 2 3
0 00 44 88 cc
49 la 76 dc 09 1 11 Э9 dd
50 c4 18 c2 7! 2 22 66
51 ез 84 Jd 5d 3 33 7? ЬЬ

Рис. З. Таблица ключей Рис. 4. State


Первые Nk (в данном случае Nk = 6) строк w[] заполняются значением ис-
ходного ключа (от 0x00 до 0x17), а остальные строки генерируются по ис-
ходному ключу (seed key). Переменная Nk — это размер исходного ключа
в 32-битных словах. В дальнейшем, когда я буду рассказывать о реализа-
ции AES, вы увидите, как именно генерируются элементы w[]. Дело в том,
что в AES применяется несколько ключей вместо одного. Эти новые клю-
чи называются итеративными (round keys), чтобы подчеркнуть их отличие
от исходного ключа, задаваемого при вызове процедуры шифрования.
Сначала подпрограмма шифрования по алгоритму AES копирует 16-байт-
ный входной массив в матрицу State размером 4x4 (рис. 4). Процедура
шифрования называется Cipher. Она работает с матрицей State[] и описы-
вается псевдокодом на рис. 5.
Рис. 5, Псевдокод Cipher
.Clpe&r£byte[3 iniMit, byte[} output)
byte[4,4| State;
Копирование inputH s 8tate[]
AtHFtoundKey
for (round = 1; round < Hr-1; ++round)
{
SubBytes
Shiftfiows
MixColumrts
AddRoundKey
}
SubBytes
Shift flows
Копирование Stated в output[]
Защита данных с помощью Advanced Encryption Standard 29

Алгоритм шифрования выполняет операцию предварительной обработки,


которая в спецификации называется AddRoundKey. AddRoundKey выпол-
няет над каждым байтом входной матрицы State операцию XOR с первы-
ми четырьмя строками таблицы ключей. Байту State[r,c] присваивается ре-
зультат операции XOR с элементом таблицы ключей w[c,r], т. е. State[r,c] =
State[r,c] XOR w[c,r].

Например, если первая строка матрицы State содержит байты { 00, 44, 88, ее },
а первый столбец таблицы ключей имеет вид { 00,04, 08, Ос }, то новым зна-
чением State[0,2] будет 0x80 — результат операции XOR над State[0,2]
(0x88) и w[2,0] (0x08):
10001000
0 0 0 0 1 0 0 0 XOR

10000000

В основном цикле алгоритма шифрования AES выполняются четыре опе-


рации над матрицей State, которые в спецификации называются SubBytes,
ShiftRows, MixColumns и AddRoundKey. Операция AddRoundKey — то же,
что и предварительная операция AddRoundKey с тем исключением, что
при каждом вызове AddRoundKey используются следующие четыре стро-
ки таблицы ключей. Подпрограмма SubBytes выполняет операцию заме-
ны: замещает каждый байт матрицы State новым байтом, определяемым по
таблице Sbox. Например, пусть значением State[0,l] является 0x40 и тре-
буется найти его замену. Берется значение State[0,l] (0x40), и переменной
х присваивается его левая цифра (4), а переменной у — правая (0). Затем
по индексам х и у из таблицы Sbox берется значение замены (рис. 2).

ShiftRows — это операция перестановки, при которой байты матрицы State


циклически сдвигаются влево. На рис. 6 показано, как ShiftRows обраба-
тывает State[]. Строка 0 матрицы State циклически сдвигается на 0 пози-
ций влево, строка 1 — на одну позицию влево, строка 2 — на две позиции
влево, а строка 3 — на три позиции влево.

Рис. 6. Обработка State операцией ShiftRows

MixColumns — это операция замены, самая сложная для понимания часть


алгоритма AES. Она заменяет каждый байт результатом математических
операций сложения и умножения в поле (mathematical field additions and
30 Шифрование

multiplications), применяемых к элементам столбца, который содержит


этот байт. Я расскажу о сложении и умножении элементов полей в сле-
дующем разделе,

Допустим, значением State[0,l] является 0x09, а остальные элементы


столбца 1 — 0x60, Oxel и 0x04; тогда новым значением State[0,l] будет ре-
зультат следующего выражения:

State[0,1] = (State[0,1] - 0x01) +


<State[1,1] - 0x02) +
(State[2,1] * 0x03) +
(State[3,1] * 0x01)
= (0x09 * 0x01) t (0x60 - OxQ2) + (Oxe1 * 0x03) + (0x04 * 0x01)
= 0x57

Здесь сложение и умножение — это специальные математические опера-


ции над элементами поля, а не обычное целочисленное сложение и умно-
жение.

Четыре подпрограммы SubBytes, ShiftRows, MixColumns и AddRoundKey


вызываются в цикле, выполняемом Nr - 1 раз, где Mr — число итераций
для ключа данного размера, Количество итераций алгоритма шифрования
равно 10, 12 или 14 в зависимости от размера исходного ключа (128, 192
или 256 бит). В нашем примере Nr равно 12, поэтому четыре операции вы-
зываются 11 раз. По завершении цикла алгоритм шифрования еще раз вы-
зывает SubBytes, ShiftRows и AddRoundKey, а затем копирует матрицу
State в выходной параметр.

Подведем итог: ядро алгоритма AES-шифрования образуют четыре опера-


ции. AddRoundKey заменяет группы из 4 байтов, комбинируя их с итера-
тивными ключами, которые генерируются по значению исходного ключа.
SubBytes замещает отдельные байты в соответствии с таблицей замен.
ShiftRows переставляет группы из 4 байтов, циклически сдвигая 4-байто-
вые строки. MixColumns заменяет байты результатами операций сложения
и умножения элементов поля.

Сложение и умножение в поле GF(28)


Как видите, алгоритм шифрования AES использует достаточно простые
операции перестановки и замены, если не считать процедуры MixColumns.
В MixColumns применяются специальные операции сложения и умноже-
ния. Сложение и умножение в AES основаны на математической теории
8
полей. А конкретнее, в AES используется поле GF(2 ).
Защита данных с помощью Advanced Encryption Standard 31

8
Поле GF(2 ) состоит из 256 значений от 0x00 до Oxff, над которыми опре-
Н
делены операции сложения и умножения. Отсюда (2 ) в названии. Поле
GF (Galois Field) названо в честь Галуа, математика, основавшего теорию
полей. Одной из характеристик GF(2*) является то, что результат сложе-
ния или умножения всегда принадлежит множеству {0x00 ... Oxff). Теория
8
полей достаточно сложна, но применительно к сложению в GF(2 ) полу-
H
чается простой конечный результат: сложение в GF(2 ) — это обыкновен-
ная операция XOR.
W
Однако умножение в GF(2 ) — более сложная операция. Как вы в дальней-
шем увидите в реализации на С#, в процедурах шифрования и дешифро-
вания алгоритма AES используется умножение только на семь констант:
0x01, 0x02, 0x03, 0x09, OxOb, OxOd и ОхОе. Поэтому вместо объяснения об-
8
щей теории умножения в GF(2 ) я ограничусь рассмотрением этих семи
частных случаев.
8
Умножение на 0x01 в GF(2 ) занимает особое место; оно соответствует
умножению на 1 в обычной арифметике и выполняется точно так же: при
умножении любого значения на 0x01 получается то же самое значение.

Теперь рассмотрим умножение на 0x02. Как и в случае сложения, теория


трудна, зато результат сравнительно прост. Если умножаемое значение
меньше 0x80, оно сдвигается влево на 1 бит. Если же умножаемое значе-
ние больше или равно 0x80, оно сначала сдвигается влево на 1 бит, а затем
к результату сдвига применяется операция XOR со значением Oxlb. Тем
самым предотвращается «переполнение поля», и результат умножения
остается в допустимом диапазоне.

Освоив операции сложения и умножения на 0x02 в поле GF(28), вы може-


те выразить через них умножение на любую константу. При умножение на
0x03 в GF(2H) значение 0x03 можно представить как сумму степеней чис-
ла 2. Чтобы умножить произвольный байт b на 0x03, представьте 0x03 как
0x02 + 0x01. Следовательно:

b * 0x03 = b * (0x02 + 0x01}


= (b * 0x02) + (Ь * 0x01)

Эту операцию можно выполнить, зная, как умножать на 0x02 и 0x01 и как
складывать. Аналогично умножение произвольного байта на OxOd сводит-
ся к следующим операциям:

b * OxOd = b * (0x08 + 0x04 + 0x01)


= {b * 0x08) + (b * 0x04) + (b * 0x01)
= (b * 0x02 * 0x02 * 0x02) + (b * 0x02 * 0x02) + (b * 0x01)
32 Шифрование

Другие операции умножения, необходимые процедуре MixColumns при


шифровании и дешифровании по алгоритму AES, выполняются по тому
же универсальному шаблону:
b * 0x09 = Ь * (0x08 + 0x01)
= (Ь * 0x02 * 0x02 * 0x02) + (Ь * 0x01)

Ь * ОхОЬ = Ь * (0x08 + 0x02 + 0x01)


= (Ь * 0x02 * 0x02 * 0x02) + (Ь * 0x02) + (Ь * 0x01)

Ь * ОхОе = Ь * (0x08 + 0x04 + 0x02)


= (Ь * 0x02 * 0x02 * 0x02) + {Ь * 0x02 * 0x02) + (Ь • 0x02)

8
Подведем итог. Сложение в GF(2 ) — это операция XOR. Умножение в
8
GF(2 ) сводится к операциям сложения и умножения на 0x02, а умноже-
ние на 0x02 — это сдвиг на 1 бит влево, выполняемый в зависимости от
8
условия. Дополнительную информацию об операциях в GF(2 ) см. в спе-
цификации AES.

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

KeyExpansion(byte[] key, byte[][4] w)


<
Копирование исходного ключа в первые строки w

Для каждой оставшейся строки w


!
Создание новой строки по двум предыдущим
I

Процедура «создание новой строки по двум предыдущим» использует две


подпрограммы, Rot Word и Sub Word, а также таблицу констант Rcon (аббре-
виатура от round constants — итеративные константы). Рассмотрим каждый
из этих трех элементов, а потом вернемся к подпрограмме KeyExpansion
в целом.
Защита данных с помощью Advanced Encryption Standard

Подпрограмма RotWord весьма проста. Она принимает массив из 4 бай-


тов и циклически сдвигает их на 1 позицию влево. В таблице ключей w[]
четыре столбца, и RotWord сдвигает заданную строку w[] влево. Заметь-
те: функция RotWord, вызываемая KeyExpansion, очень похожа на под-
программу ShiftRows, используемую алгоритмом шифрования, с тем
исключением, что применяется к одной строке таблицы ключей w[], а не
ко всей таблице состояния State[].

Подпрограмма Sub Word выполняет побайтовую замену заданной строки


таблицы ключей w[] в соответствии с Sbox. Замена в KeyExpansion выпол-
няется так же, как и в алгоритме шифрования. Входной байт, который за-
мещается, разбивается на пару (х,у), задающую индексы в таблице замен
Sbox. Например, замена 0x27 дает х = 2 и у = 7, a Sbox[2,7] возвращает
Охсс.

Подпрограмма KeyExpansion использует массив Rcon[], называемый таб-


лицей итеративных констант. Каждая из этих констант содержит 4 байта,
соответствующие строке таблицы ключей. В AES-подпрограмме Key-
Expansion используется 11 итеративных констант (рис. 7).

Рис. 7. Инициализация Rcon


private void BuildRconO

ttds.Rcon » new tyte[11,4] { {0x00, 0x00, OxQO, 0x00},


{0x01, OxOQ, 0x00, 0x00},
{0x02, 0x00, 0x00, 0x00},
{0x04, 0x00, 0x00, OxGQh
{0x08, 0x00, 0x00, 0x90},
{0x10, 0x00, 0x00, 0x00},
(0x20, 0x00, 0x00, OxQO},
{0x40, 0x00, 0x00, 0x00},
{0x80, 0x00, 0x00, 0x00),
{Qxlfe, 0x00, 0x00, 0x00),
{0x36, 0x00, 0x00, 0x00} };
} // BuildBconO

Левый байт каждой итеративной константы — это степень числа 2 в поле


GF(28). Еще один способ вычисления значений этого байта — умножать
каждое предыдущее значение на 0x02 в соответствии с правилами ум-
ножения в поле GF(28), приведенным в предыдущем разделе. Заметьте:
0x80 х 0x02 = 0x36, так как 0x80 сдвигается влево на 1, а затем над резуль-
татом сдвига выполняется XOR с Oxlb.

2-138
34 Шифрование

Теперь более пристально рассмотрим цикл подпрограммы KeyExpansion.


Если детализировать показанный выше псевдокод, этот цикл приобретет
следующий вид:

for (row = Nk; row < (4 * Nr+1); f+row)


{
temp = w[row-1]

if (row % Nk == 0)
temp = SubWord(RotWord(temp)) xor Rcon[row/Nk]
else if (Nk == В and row % Nk == 4)
temp = SubWord(temp)

w[row] = w[row-Uk] xor temp


}

Если пока отвлечься от операторов if, то видно, что каждая строка таблицы
ключей vv[] — результат XOR предыдущей строки со строкой, находящейся на
Nk (4, б или 8 в зависимости от размера ключа) строк выше. Первое условие
if означает, что к каждой четвертой, шестой или восьмой строке (в зависимо-
сти от размера ключа — 128,192 или 256 битов) применяются подпрограммы
SubWbrd, Rot Word, а затем — операция XOR с итеративной константой. Вто-
рое условие означает, что в случае 256-битного ключа изменяются строки 12,
20, 28 и так далее через восемь строк. Это делается для того, чтобы в таблице
ключей содержались более разнообразные значения.

Посмотрим, как KeyExpansion приступает к обработке ключа, показанного


в примере в начале статьи. Исходный ключ — 192-битное значение (6 слов):

00 01 02 03 04 05 06 07 08 09 Оа ОЬ Ос Od Oe Of 10 11 12 13 14 15 16 17

В таблице ключей w[] 4 столбца и Nb x (Nr + 1) = 4 х (12 + 1) = 52 строки.


Подпрограмма KeyExpansion копирует значения исходного ключа в первые
строки таблицы ключей w[], содержащей байты. Поскольку исходный ключ
содержит 192 бита (24 байта), а таблица w[] всегда содержит 4 столбца, в
данном случае KeyExpansion копирует исходный ключ в первые 6 строк w[].
Как KeyExpansion заполняет остальные строки таблицы ключей? В данном
примере первой вычисляемой строкой является строка 6, поскольку строки
от 0 до 5 заполнены значениями исходного ключа:
temp = w[row-1] = 14 15 16 17

Условие (row % Nk == 0) истинно, поэтому к строке применяется подпро-


грамма Rot Word:
temp = 15 16 17 14
Защита данных с помощью Advanced Encryption Standard 35

Затем применяется подпрограмма Sub Word:


temp = 59 47 fO fa

Далее выполняется XOR с Rcon[row / Nk] - Rcon[6 / 6] = 01 00 00 00:


temp = 58 47 fO fa

Наконец, выполняется XOR с w[row-Nk] = w[6-6] = 00 01 02 03, и в резуль-


тате получается строка:
w[6] = 58 46 f2 f9

Этот процесс повторяется для остальных строк таблицы ключей \v[].

Подведем итог. В шифровании и дешифровании AES важное место зани-


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

Конструктор AES-класса на С#
Итак, мы рассмотрели все компоненты алгоритма шифрования AES, и те-
перь реализуем этот алгоритм на С#. Официальная спецификация алго-
ритма AES содержится в документе Federal Information Processing Stan-
dards Publication 197. Я решил, что моя реализация должна соответство-
вать ей максимально точно, но вскоре обнаружил, что спецификация -
скорее теоретический документ, чем руководство по реализации. Для
удобства я предпочел использовать те же имена переменных, что и в опуб-
ликованном стандарте (даже такие загадочные, как «Nr> и «w»).

В моей реализации девять полей данных и один перечислимый тип:


public enum KeySize { Bits128, Bits192,
Bits256 };
private int Nb;
private int Nk;
private int Nr;
private byte[] key;
private byte[,3 Sbox;
private byte[,] iSbox;
private byte[,] w;
private byte[,] Rcon;
private byte[,] State;

Поскольку ключи бывают только 128-, 192- и 256-битные, для описания


размера ключа очень удобно создать перечислимый тип:
36 Шифрование

public егшт KeySize < Bits128, Bits192,


Bits256 };

Как правило, в спецификации в качестве единицы хранения данных ис-


полвзуются байты, но имеется два важных поля, задающих размер в 4-
байтовых словах. Члены Nb и Nk содержат соответственно размер блока
в словах и размер ключа в словах. Кг задает количество циклов. Размер
блока — всегда 16 байтов (или 128 битов, т. е. 4 слова в терминологии
AES), так что можно было бы объявить его константой. Размеру ключа
присваивается значение 4, 6 или 8 в зависимости от значения перечисли-
мого параметра KeySize. Чтобы усложнить зашифрованные данные, ал-
горитм AES содержит цикл, выполняемый заданное число раз — 10, 12
или 14. Эти значения выбраны в соответствии с теорией криптографиче-
ского анализа. Количество циклов напрямую зависит от размера ключа.

При проектировании интерфейса класса я исходил из обратного — пред-


ставил, что вызываю конструктор и методы класса из приложения. При-
менив такой подход, я решил, что экземпляры AES-объектов будут созда-
ваться следующим образом:
Aes a = new Аез(размер ключа, исходный ключ)

Подпрограммы шифрования и дешифрования вызываются так:


а.Clpher(plainText, cipherText);
a.InvCipher(cipherText, decipheredText);

Я выбрал слегка неуклюжие имена методов Cipher и InvCipher, посколь-


ку они используются в спецификации AES. Вот как выглядит код конст-
руктора AES-класса:
public Aes(KeySize keySize, byte[] keyBytes)
{
SetNbNkNr(keySize);

this.key = new byte[this.Nk * 4];


keyBytes.CopyTo(this.key, 0);

BuildSboxO;
BuildlnvSboxO;
BuildRconO;
KeyExpansion();

Сначала конструктор присваивает значения полям Nb, Nk и Nr, вызывая


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

Рис. 8. Метод SetNbNkNr


private void SetNbNkNr(KeySize keySize)

tftls.Nb « 4;

if (keySize « KeySize.flits128>

thls.Nk = 4;
this.Nr * 10;

else if (keySize == KeySize. 8Ш192)

this.Mk » 6;
this.Nr - 12;
}
else if (keySize == KeySize.81ts256)
i
this.Mk * 8;
tfcis.Nr « H;

t If SetHbNkHrO

Затем байты, передаваемые конструктору, копируются в поле класса.


Ключ объявлен как поле класса и инициализируется операторами:
this,key = new byte[this.Nk - 4];
keyBytes.CopyTo(this. key, 0);
Для инициализации таблиц замен Sbox[] и iSbox[] я решил вызывать в
конструкторе закрытые вспомогательные методы BuildSbox и Buildlnv-
Sbox. Sbox[] и iSbox[] необходимы подпрограмме расширения ключа и ме-
тодам Cipher и InvCipher соответственно, поэтому можно было бы помес-
тить инициализацию Sbox[| и вызов метода Key Expansion в методы Cipher
и InvCipher. Но код, который выполняет эти операции в конструкторе, вы-
глядит понятнее. Как заполняется sBox[], показано на рис. 9. Таблица
iSbox[] заполняется аналогично. Для удобства чтения код структурирован.
В дальнейшем вы увидите весьма интересный альтернативный способ за-
полнения таблиц Sbox и iSbox.

Я посчитал, что лучше всего объявить таблицу ключей w[], таблицу ите-
ративных констант Rcon[] и таблицу State[] членами класса и присваивать
значения таблицам Rcon[] и w[] закрытыми вспомогательными методами.
Но это в основном вопрос стиля. Код заполнения таблицы итеративных
констант Rcon показан на рис. 7.
38 Шифрование

Рис. 9. Инициализация Sbox

private void BulldSboxO


{
this.Sbox = new byte{16,1S] { // заполняем матрицу Sbox
/• 0 1 2 3 4 5 6 7 8 9 а
Ь с d e f «/
/*0«/ {0X63, Ox7c, 0x77, Gx7b, Oxf2, Ox6b, Ox6f, Фхсб, 0x30, 0x01, 0x67,
Ox2b, Oxfe, Oxd7, Oxab, 0x76},
/*1*/ {Oxca, 0x82, Oxc9, 0x76, Oxfa, 0x59, 0x47, OxfO, Oxad, 6xd4, Оха2,
Oxaf, Ox9c, Oxa4, 0x72, OxcO}.
Д2*/ {Oxb7, Oxfd, 0x93, 0x26, 0x36, Ox3f, Oxf7, Oxcc, 0x34, Oxa5, Охе5,
Oxf1, 0x71, Oxd«, 0x31, 0x15},
/*3*/ {0x04, Oxc7, 0x23, ОхсЗ, 0x18, 0x96, 0x05, Ox9a, 0x07, 0x12, ОхВО,
Oxe2, Oxeb, 0x27, Oxb2, 0x75},
/*4*/ {0x09, 0x83, Ox2c, Ox1a, Ox1b, Охбе, Ox5a, OxaO, 0x52, Ox3b, Oxd6,
ОхЬЗ, 0x29, ОхеЗ, Ox2f, 0x84},
/*5*/ {0x53, Oxd1, 0x00, Oxed, 0x20, Oxfc, ОхЫ, Ох5Ь, Охба, Oxcb, Ox be,
0x39, Ox4a, Ox4c, 0x58, Oxcf},
/*6*/ {OxdO, Oxef, Oxaa, Oxfb, 0x43, Ox4d, 0x33, OxffS, 0x45, OxfS, 0x02,
0x7f, 0x50, ОхЗс:, Ox9f, Охав),
/-7«/ {0x51, ОхаЗ, 0x40, Ox8f, 0x92, Ox9d, 0x38, OxfS, Oxbc, Oxb6, Oxda,
0x21, 0x10, Oxff, Oxf3, Oxd2},
/*B*/ {Oxcd, OxOc, 0x13, Oxec, Ox5f, 0x97, 0x44, 0x17, Qxc4, Oxa7, Ох7е,
Ox3d, 0x64, Ox5d, 0x19, 0x73},
/*S*/ {0x60, 0x81, Ox4f, Oxdc, 0x22, Ox2a, 0x90, 0x88, 0x48, Oxee, охьа,
6x14, Qxtfe, Ox5e, QxOb, Oxdb},
/*a*/ {OxsO, 0x32, ОхЗа, ОхОа, 0x49, 0x06, 0x24, OxSc, Oxc2, OxdS, Охас,
0x62, 0x91, 0x95, Qxe4, Qx79b
/*b*/ {Oxe7, OxoB, 0x37, 0x66, Ox8d, OxdS, Ox4e, Oxa9, Охбс, 0x56, Oxf4,
Oxea, 0x65, Ox7a, Охав, 0x08},
/*c*/ {Oxba, 0x78, 0x25, Ox2e, Oxlc, Охав, ОхЬ4, Охсб, Охвв, Oxdd, 0x74,
Ox1f, Ox4b, Oxbd, Ox8b, Ox8a},
/*d«/ {0x70, ОхЗв, Oxb5, 0x66, 0x48, 0x03, Oxf6, OxOe, 0x61, 0x35, 0x57,
Oxb9, 0x86, Oxc1, Ox1d, OxSe},
Лв*/ {Oxe1, OxfB, 0x98, 0x11, 0x69, OxdS, 0x86, 0x94, Ox9b, Ox1e, 0x87,
Oxe9, Oxce, 0x55, 0x28, Oxdf},
/-f*/ (Ox8c, Oxa1, 0x89, 0x06, Oxbf, Охеб, 0x42, 0x68, 0x41, 0x99, Ox2d,
OxOf, QxbO, 0x54, Qxbb, 0x16} )

} // BuildSbOxO

Вспомните: левый байт каждой строки Rcon[] — это степень 2 в поле


GF(2H), следовательно, эту таблицу можно заполнить, выполнив вычисле-
ния вида:
newVal = prevVal * 0x02;
Защита данных с помощью Advanced Encryption Standard

В конце конструктора AES-класса вызывается метод Key Expansion, фор-


мирующий таблицу ключей w[] (рис. 10). Код метода довольно прост.
В спецификации используется гипотетический тип данных — 4-байтное
слово. В С# такого типа нет, поэтому я смоделировал его как массив из 4
байтов. Сначала таблице \v[] выделяется память оператором new. Затем
первые Nk (4, 6 или 8) строк w[] заполняются значениями массива key[],
передаваемого конструктору:

this.w[row,0] = this.key[4*row];
this.w[row,1] = this.key[4*row+1];
this.w[row,2] = this.key[4«row+2];
this.w[row,3] = this.key[4*row+3];

Рис. 10. Метод KeyExpansion


private void KeyExpansionO

this.и = new byt;e[Nb * (Nr+1), 43;

for (int row = 0; raw < Nk; ++row)

this. v*t row, 0] = tftis.keyE4*row};


this.w[row,1] ж this.key£4*rowH3;
tftls.w[row,23 - this,key(4»row+2];
this.w[row,3] = thls.key{4*row+31;

Syte[] teiap = new byte[4];

for (int row я Nk; row < Ub * (Nf+1); ++row)

temp[Q] * this.wCrow-1,0]; temp[1] = this.w[row-1,1];


tempt2] = this.wtrow-1,23; tespCS] = this.wCrow-1,3];

if (row X Nk == 0)
{
* SubWorcJ(flotWordCteiap));

tenptO] * (byte К (int)tespE03 ** Cint)this.BconErow/Nk, 0] );


teraptl3 * (byte)( (int)te«tpC13 " Cint)this,Rcontrow/Nk,l3 );
terap[2] * (byteK (int)temp[2] ~ (int)this.Rqon[row/Nk,2] );
teapfS] * (byteX (int)teffifjE33 " (lRt)tfiis.ReonErow/Nk,3] );

else if ( Nk > 6 4& (row % Nk = 4) )


{
temp = SubWord(temp);

см. след. стр.


40 Шифрование

Рис. 10. Метод KeyExpansion


= w[row-Nk] xor
this.w[row,03 = (byte) ( (int)this.w[row-Nk,OJ " Cint>tespEO] );
this,w[row,1} = (byte) ( (iirt}this.w[rQw-Nk,13 " (tnt)teinp[13 );
this.w[row,2] = (bytej ( (irrt)this.wErow-Nk, 2] " (int)tefflp[2] );
this.w[row,S] - (byte) С (ItttJthis.wErow-Nk.Sj * (int)temp[33 };

} II конец цикла
} // KeyExpanslonO

В этом коде часто встречается операция XOR над парами байтов. Так как
в языке С# не определен оператор А (XOR) для типа byte, приходится при-
водить byte к int, а результат — обратно к byte. Например, приходится ис-
пользовать:

temp[0] = (byte)( (int)tempEQ] " (lnt)this.Rcon[row/Nk,0] );

вместо:

temp[0] = temp[0] " tfiis.Rcon[row/Nk,0];

При выполнении условия метод Key Expansion вызывает закрытые методы


SubWord и RotWord. Выбор имен методов объясняется тем, что такие
имена используются в спецификации. И в этом случае, поскольку в С#
нет типа word, я моделирую его массивом из четырех байтов. Код методов
SubWord и RotWord весьма прост, и вы с легкостью разберетесь в нем. Он
содержится в исходном коде AesLib, который можно скачать по ссылке
http://msdn.microsoft.com/msdrimag/code03.aspx в разделе за ноябрь.

Более сложной операцией является поиск значения замены в процедуре


SubWord. Вспомните: чтобы найти замену, входной байт разбивается на
левые 4 бита и правые 4 бита. То есть индекс х получается сдвигом байта
на 4 бита вправо оператором ». а индекс у — выполнением логического
оператора AND со значением 00001111. Если записать код в менее лако-
ничном, но более удобном для чтения виде, чем в моем примере, он будет
выглядеть так:

int х = word[0] » 4;
int у = word[0] & OxOf;
byte substitute = this.Sbox[x,y];
result[0] = substitute;

а я использовал следующий код:

result[0] = this.Sbox[ word[0] » 4, word[0] & OxOf ];


Защита данных с помощью Advanced Encryption Standard

Подведем итог. Конструктор AES-класса принимает размер ключа (128,


192 или 256 бит) и массив байтов, содержащий исходный ключ. Затем при-
сваивает значения размеру входного блока, размеру исходного ключа и ко-
личеству итераций алгоритма шифрования, а потом копирует исходный
ключ в поле key. Кроме того, конструктор формирует четыре таблицы: две
таблицы замен, используемые методами шифрования и дешифрования,
таблицу итеративных констант и таблицу ключей, содержащую итератив-
ные ключи.

AES-метод Cipher на С#
Код метода Cipher показан на рис. 11. Он очень прост, поскольку за него ра-
ботают закрытые методы AddRoundKey, SubBytes, ShiftRows и MixColumns.

Рис, 11. Метод Cipher


public void Cipher(uyteH input, byte[] output}
{
// state s input
this. State = new bytet+.Hfr};
for (int 1 = 0; 1 < (4 * Nb); ++1)
{
this. Stated * 4, i / 43 = input ti];

AddfloundKey(O);

for (Int round <= 1; round <* (Nr - 1); Around)


I
SubBytesO;
ShlftRowsO;
HixColumnsC ) ;
AddRoundKey( round) ;

SubBytee(>;
ShlftfloweO;
AddfioutidKey(Nr);

// output = state
for (int 1 = 0 ; i < (4 * Nb}; ++i)
{
outputtl] « this. Stated X 4, i / 4];

} // ClpherO
42 Шифрование

Сначала метод Cipher копирует входной массив текста в матрицу состоя-


ния State[]. Далее метод Cipher первый раз вызывает метод AddRoundKey,
после чего выполняет цикл, количество итераций которого на 1 меньше
общего числа итераций. Потом выполняется еще одна итерация, на кото-
рой, согласно спецификации, не вызывается метод MixColumns.

Код закрытых методов AddRoundKey и SubBytes показан на рис. 12. Мето-


ду AddRoundKey нужен номер выполняемой итерации, чтобы определить,
какие четыре строки таблицы ключей wf] следует использовать. Заметьте,
что к State[r,c] применяется операция XOR с w[c,r], а не с w[r,c]. Метод
SubBytes извлекает индексы из входного байта с помощью сдвига вправо на
4 бита и операции AND с OxOf, т. е. так же, как и метод KeyExpansion.

Рис. 12. Методы AddRoundKey id SubBytes

private void AddRourvdKeydnt round)


{
for (int r «s 0; r < 4; ++r)
f
for (int с = 0; с < 4; -м-с)
i
this.State[r,c] * <feyte) С (int)thie.8tate[r,o3 "
, r} >;

} // AddRoundKeyC)

private void SubBytesO


{
for (int r = 0; r < 4; *+r)
{
for (int с = 0; с < 4; ++c)
<
s
this. Stat»( r,c] this,Sbox£ (tnis.StateJjr.c] » 4),
(thls.Statetr.c] & OxOf) ];
}
}
J // SubBytes

Код метода SlnftRows приведен на рис. 13. Вспомните: ShiftRows (кото-


рый правильнее было бы назвать RotateRows) циклически сдвигает row[0]
на 0 позиций влево, row[l] — на 1 позицию влево и т. д.
Защита данных с помощью Advanced Encryption Standard

Рис. 13, Метод ShiftRows


private void ShiftRo«s()

byte[,} temp = new byte[4,43;


for (int г = 0; f < 4; ++r)

for (Int с « 0; с < 4; ++c)

tenptr.c] = this.Statetr.c];

fur (int r » 1; r < 4; ++r) //

fo=r (int с « 0; с < 4; ++c)


tfiis.State[rtc] « tempt r, (c + r> X Nto ];

}
// ShiftRtftfflO

State[] копируется в матрицу temp[], а затем, чтобы выполнить сдвиг, в


цикле вызывается оператор:
,
this.State[r, (с + г) X Nb ] = temp[r,c];

Чтобы перейти через конец строки к началу, используется оператор %.


Метод MixColumns (рис. 14) заменяет каждый байт линейной комбинаци-
ей всех других значений столбца, используя сложение и умножение в
GF(28). Из теории полей следует, что при умножении должны использо-
ваться постоянные коэффициенты, равные 0x01, 0x02 или 0x03. Замена за-
данного столбца определяется следующим образом:
State[0,c] = 0x02 " State[0,c] + 0x03 * State[1,c] + 0x01 * State[2,c] +
0x01 * State[3,c]
State[1,c] = 0x01 * State[0,c] + 0x02 * Stated.c] + 0x03 * State[2,c] +
0x01 * State[3,c]
State[2,c] = 0x01 * State[0,c] + 0x01 * State[1,c] + 0x02 * State[2,c] +
0x03 * State[3,c]
State[3,c] = 0x03 * State[0,c] + 0x01 * Stated,c] + 0x01 * State[2,c] +
0x02 * State[3.c]
Эти выражения довольно громоздки, поэтому я решил написать закрытые
вспомогательные функции, вычисляющие произведения на 0x01, 0x02 и
0x03 в поле GF(28). Вспомогательные функции очень короткие. Например,
код, умножающий в поле байт b на 0x03, выглядит так:

return (byte) ( (int)gfmultby02(b) " (int)b );


44 Шифрование

Рис. 14. Метод MixColumns


private void MixColusmsO

byte[,] temp * new byte[4,4l;


for (int г = 0; r < 4; ++r)
{
for (int с = Q; с < 4; -н-с)
i
terap[r,c] = this.State[r, c];

for (int c = 0 ; с < 4;

this.StateEO.c] * (byte) ( ( int)gfmultby02(tenip[;o,c})


(int)gfmultby03(temp[1,c])
(int)gffflultby01(teeip[2,c])
(int)gfnuiltbyQ1(teuipE3,c]}

this. Stated ,c] = (byte) ( (int)gfinultby01(tenip[0,c])


(int)gfmultby02(temp[1,c3)
(int)gfmultby03(t6mp[2,c3)
(int)gfntultby01(teflip[3,c])

this.State[2,c] = (byte) ( (int)gfroultbyQ1(tefBp(0,c]}


(irvt}0fmultby01{tenipCl,c])
(int)gfmultby02(temp[2,c]}
(int)gf*ultby03(teaip[3,c3)

tnis.State£3,c] » (byte) ( (int)gfmultby03(temp[S,c3)


(int)gfmultby01(temp[1,c]}
(ir>t)gfmultbyS1(temp[2,c])
(int)gfraultby02(tei)ip[3,c3>
}
} // KixColuntns

Как я уже говорил, любое умножение в поле GF(28) можно свести к умно-
жению на константу 0x02 и сложению. Я назвал метод, умножающий на
0x02, gfmultby02, отступив от принципа использовать те же имена методов,
что и в спецификации, где эта подпрограмма называлась xtime.

Метод Cipher выполняет цикл, где к входным данным применяются четы-


ре операции. В результате формируются зашифрованные выходные дан-
ные. AddRoundKey заменяет байты, используя несколько итеративных
ключей, получаемых из исходного ключа. SubBytes заменяет байты в со-
ответствии со значениями в таблице замен. ShifcRows переставляет байты,
сдвигая строки матрицы, a MixColumns заменяет байты, применяя опера-
ции сложения и умножения в поле к элементам столбцов.
Защита данных с помощью Advanced Encryption Standard 45

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

В спецификации AES подпрограмма дешифрования называется InvCipher,


а не Decipher или Decrypt. Это связано с тем, что в математическом опи-
сании AES используются обратные операции.
Сравнив код InvCipher с кодом Cipher, вы увидите, что многое в нем соот-
ветствует вашим ожиданиям, но есть два исключения. Во-первых, порядок
вызовов обратных методов (таких как InvSubBytes) в InvCipher не совсем
обратен порядку вызовов соответствующих методов (вроде SubBytes) в
Cipher. Во-вторых, InvCipher вызывает AddRoundKey, а не InvAddRound-
Кеу. Заметьте: алгоритм InvCipher также использует таблицу ключей, но
начинает обработку с самого большого индекса и в цикле уменьшает ин-
декс до нулевого значения.
Код методов InvSubBytes, InvShiftRows и InvMixColumns почти обратен
коду методов SubBytes, ShiftRows и MixColumns. Код метода InvSubBytes
почти совпадает с кодом метода SubBytes за исключением того, что ис-
пользуется таблица обратных замен iSbox[] вместо таблицы Sbox[].
Легко догадаться, что с помощью iSbox[] просто отменяется преобразова-
ние, выполняемое на основе Sbox[]. Например, если байт b равен 0x20, его
заменой в таблице Sbox[] является ОхЬ7. А заменой значения ОхЬ7 в iSbox[]
является 0x20.
Аналогично метод InvShiftRows отменяет операцию метода ShiftRows:
строка [0] сдвигается на 0 позиций вправо, строка [1] — на 1 позицию
вправо, строка [2] — на 2 позиции вправо и строка [3] — на 3 позиции
вправо.
Метод InvMixColumns отменяет операцию метода MixColumns, но не на-
столько очевидным способом. Вспомните: MixColumns заменяет каждый
байт матрицы состояния линейной комбинацией байтов исходного столб-
ца и коэффициентов 0x01. 0x02 и 0x03. Здесь снова применяется теория
полей. Оказывается, что обратная операция выглядит аналогично, но вы-
полняет умножение на ОхОЭ, OxOb, OxOd и ОхОе:
\
State[0,c] = ОхОе * State[0,c] + OxOb * State[1,c] + OxOd * State[2,c] +
0x09 * State[3.c]
+
Stated,c] = 0x09 * State[0,c] + OxOe * Stated,c] OxOb * State[2,c] +
OxOd * State[3,c]
46 Шифрование

State[2,c] = OxOd * State[0,c] + 0x09 * State[1,c] + OxOe * State[2,c] +


OxOb * State[3,c]
State[3,c] = OxOb * State[0,c] + OxOd - State[1,c] + 0x09 - State[2,c] +
OxOe • State[3,c]
Как и в случае метода MixColumns, я решил написать вспомогательные
функции, а не развертывать выражения прямо в коде или создавать уни-
версальную вспомогательную функцию умножения. Давайте я покажу, как
написать функцию, умножающую произвольный байт b на константу ОхОе
(14 в десятичной системе). Число 14, как и любое другое, можно разло-
жить по степеням 2. 14 = 2 + 4 + 8. Поскольку 4 — это 2 в квадрате, а 8 — 2
в кубе, 14 можно представить как 2 + 22 + 23. С учетом того, что сложение
в GF(2H) — просто XOR ( А ), а функция gfmuliby02 уже написана, я могу
получить результат с помощью следующего оператора:
return (byte)< (int)gfmultby02(gfmultby02(gf(nultby02Cb))) ~ /* 23 + */
(Int)gfmultby02(gfnnjltby02(b)) " /* 2г + */
(int)gfmultby02(b) ); /• 2 */

Все операции, используемые алгоритмом шифрования AES, являются об-


ратимыми, поэтому алгоритм дешифрования фактически выполняет опе-
рации, обратные тем, которые выполняются при шифровании.

Использование AES-класса
Одно из преимуществ реализации AES на языке С# — простота. Рассмот-
рим код на рис. 15, который формирует вывод, показанный на рис. 1. Сна-
чала объявляются «зашитые» в код значения 16-байтного входного текста
и 24-байтного (192-битного) исходного ключа, затем инициализируется
AES-объект, далее метод Cipher формирует по входному тексту зашифро-
ванный текст, а метод InvCipher дешифрует зашифрованный текст. Все
очень просто и понятно.

Рис. 15. Применение AES


static void Main(stringU args)
{
bytetl plaintext * new byte[j {0x00, 0x11, 0x22, 0x33, flx44, 0x56,
0x66, 0x77,0x88, 0x99, Qxaa, Gxbb, Oxcc, Oxdd, Gxee, Oxff};

byt&[3 eipherText = new


byte£3 decipherstfText = m* bytettSJ;

bytetJ keyBytes = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05,

см. след. стр.


Защита данных с помощью Advanced Encryption Standard 47

Рис. 15. Применение AES


0x06, 0x07,0x08, ОхО&, ОхОа, GxOb, OxOe, QxOd, OxOe, OxGf,Qx10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x18, 0x17};

Aes a = new Aes(Aes.KeySi2e.8ttsl92, keyBytes);

Console.WriteLine("\nAdvanced Encryption System Demo in .ЯЕТ"};


CoRsole.WriteLine("\n^e plaintext is; "};
BisplayAs8ytes(plainText);

Console.WriteLineC'ViLfstng a " + Aes.KeySize.Bits192.ToString() +


"-key of: ");
DisplayAsBytes(keyBytes);

a.Cipher(plainText, cipherText);

Console.WriteLlne("\nBie resulting ciphertext is; ">;.


0isplayAsBytes{eipherText);

a.IflvCipher(cipherText, deciptiaredText);

Console.WritelirteC"\nAfter deciphering the ciptiertext, the result


is; ");
0isplayAsBytes(ttecipheredText);

Console,WriteLine("\nDone");
Console, ReadlineO;
} // NainO

static void DisplayAsBytes(byteE3 bytes)


{
for (Int i * 0; i < bytes.Length; ++i)
{
Console.Write(bytesCi].ToStrlng("x2") + " " );
if (1 > 0 44 i X 16 == 0) Console.write("\n">;
}
Console.WriteliReC"");
} // DisplayAs8ytes()

Поскольку AES-объект работает с массивами байтов, его легко приспосо-


бить к работе с любыми типами данных .NET. Я написал небольшое демон-
страционное Windows-приложение (рнс. 16), которое принимает на входе
строку длиной 8 символов (16 байтов), а затем шифрует и дешифрует ее.
48 Шифрование

192 bits

plaintext cipher text deciphered lext


deciphers I aes.NET

Рис. 16. Пример программы шифрования

Поскольку размер ключа нужен и процедуре шифрования, и процедуре де-


шифрования, я объявил его в переменной, являющейся членом класса:

private Aes.KeySize keysize;

Заметьте: исходный ключ не вводится пользователем. В примере применя-


ется «нулевой ключ» — массив, заполненный нулями. Этот ключ формиру-
ется, когда конструктору передается пустой аргумент new byte[16]. Размер
пустого ар [у мента не имеет значения, поскольку исходный ключ также ини-
циализируется нулями. Шифрование и дешифрование с нулевым ключом —
эффективный способ избежать случайного раскрытия данных. С помощью
методов Encoding.Unicode.GetBytes и Encoding.Unicode.GetString простран-
ства имен System.Text легко преобразовать .NET-строку в массив байтов и
наоборот.

Другие варианты реализации


Теперь рассмотрим некоторые важные альтернативы той реализации AES,
которая была показана здесь, возможные расширения кода и потенциаль-
но вероятные попытки взлома AES.

Как и любой алгоритм, с которым мне доводилось работать, AES можно реа-
лизовать разными способами. Почему это важно? AES рассчитан на приме-
нение в широком спектре систем -- от смарт-карт с крохотной памятью до
громадных многопроцессорных мэйнфреймов. Во многих случаях критиче-
ски важна производительность, иногда приходится сталкиваться с ограни-
ченностью памяти или других ресурсов обработки данных. Почти каждую
подпрограмму AES можно изменить, оптимизировав производительность за
Защита данных с помощью Advanced Encryption Standard 49

счет памяти, или наоборот. Например, присвоение 256 значений элементам


таблицы замен Sbox[] выглядит вроде бы достаточно прямолинейно. Но эти
8
значения вычисляются в соответствии с теорией GF(2 ), и их можно гене-
рировать программно. То же относится к таблице обратных замен и табли-
це итеративных констант.

Еще одна интересная возможность — разные способы реализации умноже-


R
ния в GF(2 ), используемого методами Cipher и InvCipher. Я написал ба-
зовую функцию gfmultby02, умножающую на 0x02, и шесть дополнитель-
ных функций, вызывающих ее. Альтернатива — написать универсальную
функцию умножения и использовать ее, а не семь разных функций, как в
моем коде. Или экстремальный вариант — составить полную таблицу про-
изведений всех 256 возможных значений байта на 0x01, 0x02, 0x03, 0x09,
OxOb, OxOd и ОхОе. Еще один способ умножения в GF(2") — реализовать
умножение как поиск в двух 256-байтовых массивах, обычно называемых
alog[] и log[], поскольку такое умножение основано на некоторых свойст-
8
вах GF(2 ), аналогичных свойствам логарифмов.

Показанный AES-класс вполне подходит для шифрования любых видов


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

Кроме того, можно было бы подумать о реализации дополнительной функ-


циональности в AES-юшссе. Самое очевидное, с чего можно начать, — до-
бавить методы шифрования и дешифрования базовых типов данных .NET,
таких как System.String и System.Int32. Более амбициозное расширение —
реализация класса шифрованного потока.

Насколько надежен AES? На этот вопрос сложно ответить, но, по общему


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

атака не позволит взломать AES за приемлемое время (даже на самых бы-


стрых существующих компьютерах на это уйдут годы).

Заметьте: наибольшие шансы взломать AES-шифр имеет так называемая


атака с измерением времени (timing attack), возможная при некачествен-
ной реализации AES. Злоумышленник использует различные ключи и за-
меряет точное время, затрачиваемое на выполнение процедуры шифрова-
ния. Если процедура шифрования написана небрежно, время ее выполне-
ния зависит от значения ключа, что позволяет получить информацию о
ключе. В AES наиболее подвержена такой атаке подпрограмма MixCo-
lumns, так как в ней используется умножение в поле. Защититься от ата-
ки можно двумя способами: вставить пустые инструкции, чтобы при всех
умножениях выполнялось одинаковое число инструкций, или реализовать
умножение в поле с помощью поиска по таблице, о чем я уже упоминал.

Реализовать AES можно многими способами, в частности на основе поиск


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

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

Основной помехой реализации AES-класса в Microsoft .NET Framework


для меня было то, что официальная спецификация написана с точки зре-
ния математика, а не программиста. В частности, спецификация предпола-
гает, что читатель отлично знает поле GF(2"), и не акцентирует внимание
на некоторых ключевых особенностях умножения в GF(2H), необходимых
для корректной реализации AES. Я попытался снять с AES покров тайны,
особенно с умножения в поле GF(28).

Появление общедоступных библиотек шифрования по алгоритму AES для


.NET Framework, разработанных Microsoft и сторонними поставщиками, —
это лишь вопрос времени. А пока вам пригодится показанный мной код.
Эта реализация крайне проста и нетребовательна к ресурсам. Наличие ис-
ходного кода и понимание того, как он работает, позволит вам адаптиро-
Защита данных с помощью Advanced Encryption Standard 51

вать AES-класс к своим нуждам и более эффективно использовать любые


другие его реализации.

Безопасность больше не является благим пожеланием для проектировщи-


ков и разработчиков ПО. AES — весьма важное достижение. Понимание
алгоритма AES и умение применять его значительно повысит надежность
и безопасность ваших программных систем.

Джеймс Мак-Каффри (James McCaffrey) работает в компании Volt Informa-


tion Sciences Inc., где руководит обучением инженеров ПО технологиям
Microsoft. Работал по контракту над несколькими продуктами Microsoft,
в том числе над Internet Explorer и MSN Search. С ним можно связаться
по адресу jmccaffrey@volt.com или v-jammc@microsoft.com.
Кит Браун

Безопасный код

Хэширование
паролей,атрибут
Allow PartiallyTrustedCallers

Отвечая на вопросы, ведущий этой рубрики поясняет, как хранить


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

Вопрос Как хранить пароли в нестандартной, пользовательской базе


данных?

Ответ Есть несколько вариантов. Простейший из них оставит вас с паро-


лями в незашифрованном виде. Вот пример на XML, но то же самое легко
делается в таблице базы данных;

<users>
<user name*'Alice' password='7Sy2si(Vldx'/>
<user name='Bob' password='mary'/>
<user name-'Fred' password='marv'/ >
</users>

Реализовав что-нибудь в таком духе, вы, наверное, почувствуете диском-


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

* Публиковалось в MSDN Magazine/Русская Редакция. 2003. № 8 (август). — Прим. изд.


Хэширование паролей, атрибут AllowPartiallyTrustedCailers 53

ку уж слишком просто заполучить пароли — он даже не вспотеет. А если


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

Первое, что вы могли бы предпринять для защиты этих паролей, — зашиф-


ровать их. Это лучше, чем ничего, но все равно не самое удачное решение.
Для проверки пароля пользователя вам понадобится шифровальный ключ,
а значит, он должен быть на машине, где обрабатываются все пароли. Это,
конечно, создает хоть какой-то барьер для атакующего, потому что ему при-
дется искать ключ, однако более эффективное решение — то, которое вооб-
ще не требует никаких ключей: необратимая функция (one-way function).

Криптографический алгоритм хэширования наподобие SHA-1 или MD5


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

<users>
<user name='Alice' password»'D16E9B18FA038_'/>
<user name= - Bob' password='5665331B9B819...T/>
<user name='Fred' password='5665331B9B819.-V>
</users>

Теперь, когда вы получаете незашифрованный пароль и должны проверить


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

При словарной атаке обычно используется список наиболее популярных


паролей (такие списки можно найти на ftp://coast.cs.purdue.edu/pub/dict/
54 Шифрование

wordlists) и вычисляется хэш для каждого из них. Тогда атакующий может


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

Чтобы замедлить атаку, используйте «расширение» (salt). Расширение -


способ «затемнения» паролей перед хэшированием, который обесценивает
готовый словарь атакующего. Вот как это делается. Добавляя запись в базу
данных, вы вычисляете последовательность произвольных символов (string
of digits), которая будет задействована в качестве расширения пароля. Ска-
жем, если вы хотите получить хэш для пароля Элис, то берете значение рас-
ширения для ее учетной записи, подписываете к паролю и хэшируете их
вместе. В итоге ваша база данных будет выглядеть примерно так:

<users>
<user name='Alice' salt='Tu72*&' password='6DB80AE7...'/>
<user name='8ob" salt='N5sbiX' Dassword='096B1085...'/>
<user name='Fred' salt='q-V3bi' password='9118812E...'/>
</users>

Заметьте: теперь не видно, что Боб и Фред используют одинаковый па-


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

На рис. 1 показана защита разных уровней стойкости. Решив хранить хэ-


шированные пароли, вы поймете, что в этом случае нельзя переслать по
электронной почте пароль тому пользователю, который его забыл. И это
хорошо! Пересылать пароли по электронной почте просто глупо. Перени-
майте опыт paypaf.com. На этом сайте хранится набор вопросов и ответов
типа «Как зовут вашего домашнего любимца?» или «В каком городе вы ро-
дились?». Чтобы сменить пароль, пользователь должен правильно отве-
тить на ранее выбранный вопрос.

еский алгоритм хэширования

Незашифрованные пароли

Рис. 1. Защита разных уровней стойкости

Атакующий не может применить к базе данных с затемненными пароля-


ми словарь с уже вычисленными хэшами. Но все равно может предпри-
Хэширование паролей, атрибут AHowPartiatlyTrustedCallers 55

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


чтобы пересчитать хэшированные значения в своем словаре. Он также мо-
жет попытаться найти короткие пароли методом грубой силы, вычислив
хэш каждого из возможных паролей длиной, скажем, в четыре символа.
Чтобы замедлить эту атаку, предъявляйте к паролям ряд требований, в том
числе по минимальной длине. Кроме того, вы можете потребовать исполь-
зования комбинации строчных и прописных букв, цифр и знаков препи-
нания. Конечно, если пароли окажутся слишком трудны для запоминания,
пользователи начнут записывать их на бумажках. Так что ищите разумный
компромисс.
Затемнение паролей — не панацея. И вы должны немедленно реагировать
на компрометацию вашей базы данных паролей, даже если в ней хранятся
затемненные хэши. Но затемнение дает вам чуть больше времени. Как пра-
вило, его хватает на то, чтобы успеть обнаружить попытку атаки и отклю-
чить атакованные учетные записи на тот период, пока их пользователи не
сменят свои пароли.
В качестве отправной точки на рис. 2 представлен Ctf-класс, проверяю-
щий пароли с применением затемненных хэшей. Он также создает расши-
рения и вычисляет хэши для новых паролей при добавлении в базу дан-
ных новых учетных записей. Вам нужно лишь позаботиться о хранилище
для расширения и хэша пароля (оба значения являются строками). Вот
пример использования этого класса:
string password = Console. ReadLineO;
SaltedHash sh = SaltedHash.Create(password);

// Допустим, что расширение и хэш хранятся в базе данных


string salt = sh.Salt;
string hash = sh.Hash;

Console.WriteLine(."Salt: {0}", salt);


Console.WrlteLlne("Hash: {0}", hash);

// Найдя расширение и хэш, проверяем пароль


SaltedHash ver = SaltedHash.Create(salt, hash);
bool isValid = ver.Verify(password);

Рис, 2. Класс SaltedHash

namespace DevelopKeFitor.Se<;Utils i

using System;
using System.Security.Cryptography;

public sealed,class SaltedHash {

см. след. стр.


56 Шифрование

Рис. 2. Класс SaltedHash (продолжение)

public string Salt { get { return _salt; } J


public string Hash { get { return _haah; } }

public static SaltedHash Cnsate(string password) {


string salt = _createSaltO;
string hash = „calculateHashCsalt, password);
return new Sal tedHash( salt, hash);
I

public static SaltedHash Cr3ate(strlng salt, string hash)


return new SaltedHash(salt, hash);

public bool VerlfyCstrlng password) {


string h = _calc«lateHash<_salt, password);
return _hash.Equals(h);
I

private SaltedHasti(strinfl s, string h) {


_salt = s;
_hash = h;

private static string _create$alt() (


byte[] r = _createRandomBytes(saltLerjgth);
return Convert. ToBase64String(r);

private static byteEl „createRandomBytesfint len) {


byteU г = new byte[len];
new R№3CryptoServiceProvi(Jer{}.Qet8ytes(r}; .
return r;

private static string _calculateHash(strlng salt, string password)


{
byte[] data = _toByteArray(salt + password);
byte[J hash = _calculateHash(data);
return Convert.ToBase64String(hash);

см. след. стр.


Хэширование паролей, атрибут AHowPartiallyTrustedCallers 57

Рис. 2, Класс SaltedHash (окончание)

private static byte[] _calculateHash(byte[] data) {


return new SflAICryptaServieeProviderO.GQmputeHashftfata);

private static byteU „toByteArray(string s) {


ruturn System. Text. Encoding. UTF8.Get8ytes(s);

private readonly string „salt;


private readonly string „hash;
private const int saltLength * 6;

Вопрос В каких случаях следует использовать в сборке (assembly) атри-


бут AHowPartiallyTrustedCallers?

Ответ Только после всестороннего анализа вашего кода. Этот атрибут


был введен на самом последнем этапе разработки Microsoft .NET Frame-
work версии 1.0. Его не было в бета-версиях. В итоге он не попал в саму
документацию, но кое-какие сведения о нем можно почерпнуть из допол-
нений к документации (release notes). Позвольте сначала пояснить, из ка-
ких соображений ввели такой атрибут,

После компиляции сборка со строгим именем (strongly named assembly)


может быть помещена в Global Assembly Cache (GAC), а значит, будет вид-
на не только вашему коду, но и любому другому на вашей машине, в том
числе мобильному (mobile code), который иногда поступает из недоверяе-
мого источника. Если в вашем коде есть какие-то ошибки и вы не исклю-
чаете, что они могут создавать дыры в защите, тогда вы наверняка пред-
почтете, чтобы ваша сборка по умолчанию вызывалась только полностью
доверяемым кодом. Это означает, что случайный код, загружаемый из Ин-
тернета, по умолчанию не получает прав на вызов вашего кода, так как за-
груженный из Интернета код считается лишь частично доверяемым.

На рис. 3 дан пример кода, битком набитого ошибками, которыми может


воспользоваться злоумышленник. Ошибка есть в каждой строке. Оператор
Assert препятствует проходу по стеку (stack walk), каковой позволил бы не
58 Шифрование

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


подключения к базе данных используется учетная запись за, по которой с
базой данных можно сделать что угодно — даже удалить. SQL-оператор
формируется так, что в него включается нефильтруемый внешний ввод в
виде неких имени и пароля. Это открывает возможность внедрения сто-
роннего SQL-кода через параметры Name и Password! Короче говоря, здесь
прорва ошибок, и, как это ни грустно, именно такой код обычно приводит-
ся в примерах, относящихся к программированию баз данных. Если ничего
не подозревающий программист просто вставит этот код в свое прило-
жение, оно окажется крайне уязвимым перед атаками злоумышленников.
Аудит кода помог бы отловить такие ошибки, но, увы, программы часто по-
ставляются без надлежащего тестирования защиты.

Рис. 3. Так никогда не делайте


public class HopeThisIsRt Used By livllCoileBecaus в ItsMot Robust {
public string Name;
public string Password;

// Вся эта функция состоит из ужасающе скверного кода,


// пользоваться которым НЕЛЬЗЯ Ни ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ
public bool IsValidUserO {
new DbDataPeriitis5iofl{Permls3ionState.Unrestricted).Assert(3;
SqlConnection сопл = new SqlConnect£on(
"initial oatalQg=accQurtts; user id=sa; passwords")
conn.QpenO;
cffld.CoiBinandText = string. Format (
"select eount(*) from users where narne='{0}'" +
"and password8'О К'Ч Name, Password);
return {Unt)cBid.ExeeuteSca3ar()) > Q;

Вот здесь и пригодится атрибут AlbwPartiallyTnistedCallers. Б Microsoft


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

Надеюсь, теперь ответ ясен. Атрибут AllowPartiallyTrustedCallers можно


добавлять в сборку только в одном случае: после тщательного аудита безо-
пасности. Будьте вдвойне бдительны, если ваша сборка обращается к не-
управляемому коду. Как только вы применяете такой атрибут к сборке, по-
мещаемой в GAC, вы делаете ее объектом возможной атаки со стороны лю-
Хэширование паролей, атрибут AllowPartiaHyTrustedCallers 59

бого недоверяемого кода. Вот почему даже не у всех базовых сборок самой
.NET есть этот атрибут. Например, частично доверяемому коду не разре-
шается использовать System.Runtime.Remoting.dll или System.Manage-
ment, dll. И причина в том, что Microsoft сама не уверена, что эти библио-
теки точно не позволят частично доверяемому коду повысить уровень сво-
их привилегий. ASP.NET лишь совсем недавно прошла соответствующие
тесты, и ее System. Web.dll присвоен атрибут Allow Partially TrustedC alters в
версии 1.1 инфраструктуры Microsoft .NET Framework.

Кит Браун (Keith Brown) работает в DevelopMentor как исследователь,


технический писатель и преподаватель. Разъясняет программистам концеп-
ции безопасного кода. Автор книги "Programming Windows Security» (Addison-
Wesley, 2000). Сейчас работает над новой книгой по защите в .МЕТ. С ним
можно связаться через http://www.develop.com/kbrown.
Стефен Тауб

.NET Remoting

Защита трафика .NET


Remotlng с помощью
приемников каналов
асимметричного
шифрования*

Популярность .NET Remoting в корпоративном секторе растет, поэтому ее


безопасность должна соответствовать требованиям бизнеса. Трафик
удаленного взаимодействия (rernoting traffic) можно защитить, когда объекты
размещаются в IIS, однако, если это не так, для защиты можно разработать
собственные решения. В этой статье подробно рассказывается о создании
приемников каналов (channel sinks) для .NET. В ней также описывается поток
данных через нестандартные приемники каналов и поясняется, какие
операции возможны с этими данными.

Remoting (механизм удаленною взаимодействия) — это решение, предла-


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

Публиковалось в MSDN Magazine/Русская Редакция. 2003. № 6 (июнь). — Прим. изд.


Защита трафика .NET Remotmg

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


Одно из таких средств — защита и шифрование трафика удаленного взаи-
модействия.

С ростом популярности удаленного взаимодействия в корпоративном сек-


торе возникают вопросы, касающиеся способности Microsoft .NET Frame-
work шифровать трафик удаленного взаимодействия. Чаще всего интере-
суются, поддерживает ли .NET Remoting технологию SSL (Secure Sockets
Layer). Увы, ответить просто «да» или «нет» тут нельзя.

Поддержка HTTPS

Когда говорят об удаленном взаимодействии, часто употребляют слово «ка-


нал* (channel). В общем случае канал — это серия приемников (sinks), че-
рез которые проходит удаленный вызов. HTTP-канал для .NET на самом де-
ле является комбинацией двух каналов: один используется клиентом, вто-
рой — сервером. Каждый содержит свой набор приемников, называемый
цепочкой (sink chain). Цепочка приемников на клиентской стороне закан-
чивается HttpClientTransportSink, который для работы с НТТР-трафиком
использует классы Http Web Request и HttpWebReponse из пространства
имен System.Net. Это дает дополнительное преимущество: поскольку Http-
WebRequest и Http Web Response поддерживают HTTPS, его поддерживает
и клиентский HTTP-канал. То есть, если серверный канал поддерживает
SSL, у вас есть все, что нужно.

Если транспортный приемник клиента — это HTTP-клиент, то HttpServer-


TransportSink — не более чем специализированный Web-сервер. Чтобы это
увидеть, достаточно установить простой хост с поддержкой удаленного
взаимодействия (remoting host) и подключиться к нему через Web-браузер.

Серверу удаленного взаимодействия для обслуживания объекта требует-


ся хотя бы регистрация канала и публикация типа объекта или класса (об-
щеизвестный, активизированный и т. д.). Это делается при помощи клас-
сов из пространства имен System.Runtime.Remoting:
static void Main(string[] args)
\
ChannelServices.RegisterChannel(new HttpChannel(8124));
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(Person), "Person.soap", WellKnownObjectMode.Singleton);
Console.WriteLine ("Press enter to stop the server.,.");
Console. HeadLlneO;
1
Шифрование

Чтобы показать, что это приложение действительно является Web-серве-


ром, я подключусь к выбранной конечной точке на сервере через Microsoft
Internet Explorer для запроса WSDL-документа (Web Services Description
Language) с описанием типа объекта, поддерживающего удаленное взаи-
модействие (remoted type), — так же, как и в случае Web-сервиса ASP.NET.
Полученный документ показан на рис. 1.

<?xmi verston="l-G" encodtng="tfTF-B" ?>


<definitions name ^Person'
targe tName зр а с е =" http: / / sch em a s. microsoft. com / ctr/ n sassem/ M sd n M a g. I

2C4t>2Df>ublicKeyrokert<M>3Dniifr
xmins -"http://sc:hemas.xmlsoap. org / 1 v s d I / *
xm in s : tns ="http: / /sche ma 5 .xml sen p.o rg / wsdl / "
xmlns:xsd="httpV /www.w3.org/3Oai/XMLSchema*
xrn Ins : xsi=' http: / / www. w3 .org / 2 OO 1 / XM L Sch*! ma - i n sta и се °
xmlns : suds="http:/ /www. w3.org / 2OOG ' wsdl/ suds'
к m In s : w 5 dl ="http : / /sc h e m a s .xmlsoa p .org / wstl 1 / "
xmins : soa pe n с ="hltp:// schemes -xmtsoap.org/ soap/ encoding/"
xmlns:ns2=nti tip:/ /schemas.microsaft.com/clt/nsa55em/MsdnMag.Remotii

2C<W)2OPubticKeyroken443Dnuil"

РИС. 1. WSDL

Я подключаюсь к серверу удаленного взаимодействия и посылаю HTTP-за-


прос на «/Person.soap?wsdU, чтобы получить WSDL для Person — пользо-
вательского класса, производного от MarshalByRefObject. На сервере этот
запрос обрабатывается SdlChannclSink — членом серверной цепочки прием-
ников канала, создающим соответствующий WSDL для класса Person. Это
описание отправляется клиент;' как HTTP-ответ:

C:\telnet localhost 8124


GET /Person.soap?wsdl HTTP/1.0

HTTP/1.1 200 OK
Content-Type: text/xml
Server; MS ,NET Remoting, MS .NET CLR 1.0.3705.288
Content-Length: 8663
Connection: Close

<?xml version='1.Q' encoding='UTF-8'?>


<definitions name='Person' targetNamesnace='http://schemes.microsoft.com/
clr/nsa ...
Защита трафика .NET Remoting 03

Хотя теперь вы знаете, что HttpServerTransportSink — это Web-сервер,


остается все тот же вопрос: можно ли подключаться к серверу через
HTTPS? Увы, нет. HttpServerTransportSink не поддерживает SSL. Но не-
стандартные Windows-службы и другие приложения вроде показанного
мной — не единственный способ хостинга объектов с поддержкой уда-
ленного взаимодействия. Microsoft Internet Information Services (IIS)
в сочетании с ASP.NET тоже поддерживает удаленное взаимодействие с
объектами, и при их использовании вся инфраструктура выигрывает от
поддержки HTTPS в IIS. Доступ к объекту, размещенному в ASP.NET,
можно получить по URL наподобие https://localhost:8124/MyApplica-
tion/Person.soap, и тогда весь конфиденциальный трафик будет шифро-
ваться.
К сожалению, применение IIS для хостинга объектов ограничит вас примене-
нием HTTP. Но как защитить данные от чужих глаз, когда вы используете
другой транспорт или не размещаете объекты в IIS? Одно из преимуществ
инфраструктуры .NET Remoting в ее расширяемости — в том множестве спо-
собов, которыми можно расширять и модифицировать ее функциональность.

Вызовы через Remoting и расширяемость

Когда клиент запрашивает ссылку на удаленный объект, ему возвращает-


ся ссылка на TransparentProxy — объект, чьи методы, свойства и функцио-
нальность кажутся такими же, как и у удаленного объекта, но на самом де-
ле при обращении к нему начинается процесс делегирования.

Вызов к TransparentProxy передается как объект MessageData другому


прокси — объекту, тип которого наследует от RealProxy (часто, но не все-
гда — RemotingProxy). Этот прокси в свою очередь создает IMessage из
MessageData и пересылает сообщение, содержащее информацию о вызове
метода, по серии приемников сообщений (message sinks) — классов, реали-
зующих IMessageSink или IDynamicMessageSink, каждый из которых мо-
жет модифицировать или заменять пересылаемое сообщение.

Последний IMessageSink в цепочке должен быть приемником формати-


рующее объекта (formatter sink), который нередко реализует IClient-
FormatterSink — комбинацию IClientChannelSink и IMessageSink. Этот
приемник отвечает за создание транспортных заголовков (transport hea-
ders), а также за прием IMessage и его сериализацию в поток (stream). За-
тем этот поток и набор заголовков вместе с IMessage, используемым
только для ссылки, передаются через серию приемников IClientChannel-
Sink, каждый из которых может подставить новый поток данных вместо
54 Шифрование

полученного от предыдущего приемника (на этой стадии обработки


IMessage уже сериализован в поток и нужен лишь для сведения). По-
следний из этих приемников — транспортный (transport sink) — получа-
ет заголовки и поток и посредством транспорта, такого как HTTP-соеди-
нение или именованный канал (named pipe), посылает данные серверу.

Подобный процесс происходит и на сервере, но в обратном порядке. Транс-


портный приемник сервера получает заголовки и поток от транспортного
приемника клиента. Эти данные обрабатываются серией объектов ISer-
verChannelSink (включая SdlChannelSink, который генерирует WSDL, по-
казанный ранее) так же, как это происходило на клиентской стороне. Фор-
матирующий объект — он же IServerChannelSink — принимает набор заго-
ловков и поток и десериализует их в копию исходного IMessage, который
затем пересылается через серию объектов IServerChannelSink, IMessageSink
и IDynamicMessageSink, пока не попадет в последний приемник — Stack-
BuilderSink (приемник формирователя стека). Этот приемник, используя
отражение (reflection), выполняет запрос и получает сообщение-ответ (re-
turn message). Оно проходит обратный путь через приемники, по транспорт-
ному уровню и через приемники клиента, пока не достигает прокси, где это
сообщение разбирается на возвращаемое значение и выходные параметры.

Как видите, при использовании объекта с поддержкой удаленного взаимо-


действия за кулисами происходит много событий. Одно из преимуществ
этого в том, что существует много мест, где можно подключить собственную
функциональность (рис. 2). Можно создать приемники форматирующих
объектов для сериализации и десериализации IMessage в особый формат
(если встроенные форматирующие объекты SOAP и Binary вас не устраи-
вают), а также ввести дополнительные IMessageSink для изменения переда-
ваемого сообщения. IClientChannelSinks и IServerChannelSinks позволяют
изменять потоки и наборы заголовков. Используя собственные RealProxy,
можно отслеживать или заменять запускаемые методы. А транспортные
приемники позволяют принимать/отправлять данные через любой транс-
портный уровень, который кажется подходящим разработчику.

Приемники шифрования (encryption sinks) должны изменять данные,


передаваемые между клиентом и сервером. Здесь не потребуются ни
специальный транспорт или форматирующий объект, ни модификация
сообщения до его сериализации. Лучшее место для подключения такого
приемника (и наиболее распространенная из точек перехвата) находится
между форматирующим объектом и транспортным приемником. Для под-
ключения нужно реализовать пользовательские классы IClientChannelSink
и IServerChannelSink, которые шифруют проходящие через них потоки.
Защита трафика .NET Remoting

Объект на lepnejje

Пользовательские

.
P'jdlPiuiiy

ip ft рм атВД» щий об ъ в кт
L
Приемник

. йоякаошвяммв :
лринмнихи каналов

ПользаваияЬйнй

Рис. 2. Поток удаленного взаимодействия и точки перехвата

Один приемник или два?


Иногда нужно создавать и клиентский, и серверный приемник. Делать ли
так, зависит от приложения, поскольку нет правила, обязывающего созда-
вать приемники парами. Компонентам, требующим выполнения каких-то
операций на обеих сторонах, например каналам шифрования или сжатия
данных, конечно, нужны два приемника. Но во многих ситуациях доста-
точно одного приемника. Так, ASP.NET поддерживает отличную систему
кэширования вывода, где хранится результат выполнения страницы. Эти-
ми данными можно будет воспользоваться при запросе к странице в буду-
щем без повторного выполнения страницы. Инфраструктура удаленного
взаимодействия не предоставляет подобной системы, но вы можете напи-
сать серверный приемник, выполняющий такие функции. Он может про-
верять полученный объект IMessage, просматривать собственный объект
кэширования в поисках результата предыдущего выполнения и передавать
запрос дальше по цепочке, если такого результата не найдено, или сразу
возвращать Return Message, сформированное на'основе содержимого кэша.
Клиенту не нужно знать, что применяется кэширование, поэтому в кли-
ентском приемнике нужды нет (конечно, можно реализовать кэширование
на клиентской стороне, и тогда потребуется только клиентский прием-
ник). Приемник, выполняющий такие функции, содержится в исходном
коде примеров для этой статьи, которые можно скачать по ссылке http://
msdn.microsoft.com/msdnmag/code03.aspx в разделе за июнь. Ранее упомя-
нутый SdlChannelSink также работает только на сервере.

3-138
66 Шифрование

В этом примере важно отметить, что приемник не обязан передавать что-


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

Разработка протокола

Разработка канала симметричного шифрования (symmetric encryption chan-


nel) с использованием 3DES, RC2 или другого алгоритма симметричного
шифрования, предоставляемого пространством имен System.Security.Cryp-
tography, потребует двух приемников. С каждой стороны размещается один
приемник между форматирующим объектом и транспортом; так как вам
нужно работать с сериализованным потоком, приемник должен размешать-
ся после форматирующего объекта клиента и до аналогичного объекта сер-
вера. На рис. 3 показаны важнейшие элементы этих приемников.

Поскольку это симметричное шифрование, оба приемника должны ис-


пользовать один ключ и вектор инициализации (initialization vector, IV),
содержащиеся в объекте-провайдере Symmetric Algorithm, который переда-
ется функциям CryptoHelper. Эта информация должна быть передана обо-
им приемникам заблаговременно, или один из них должен создавать ключ
динамически и пересылать его другому по защищенному каналу.

Для асимметричного шифрования (asymmetric encryption), также извест-


ного как шифрование с открытым ключом (public key encryption), приме-
няются такие алгоритмы, как RSA, EfGamal и Rabin. Они основаны на кон-
цепции пар ключей — наборе ключей, математически связанных так, что
сообщение, зашифрованное с одним из них, может быть расшифровано
только с другим. Любой, кто хочет получать и расшифровывать зашифро-
ванные сообщения, должен создать пару ключей: открытый (public key)
распространяется свободно, а закрытый (private key), напротив, надо тща-
тельно охранять. Кто угодно может зашифровать сообщение открытым
ключом, и только закрытым ключом его можно будет расшифровать.
Защита трафика .NET Renmting 67

Рис. 3. Клиентский и серверный приемники

// IClientCnannelSlnk.ProcessHessage
public void ProcessMessage(IMessage msg,
ITransportHeaders reqwestHeaders, Stream requestStrea»,
out ITransportHeaders responseHeaders, out Strean responseStream)
f
// Шифруем поток
requestStream = CryptoHelper.Encrypt(requestStreain, .provider);

// Отправляем поток следующему приемнику в цеяочке


_next.ProcessMessage(ms£|,
requestHeaders, requestStreafB,
out responseHeaders, out responseStream);

// Расшифровываем ответный поток


responseSt г earn = CryptoHelper.DecrypttresponseStreaiB, „provider);

// ISe rverChannelSin-k. ProcessMessage


public ServerProcessing ProcessMessage(
IServerChannelSinkStack sinkStack, IMessage requestMag,
ITransportHeaders requestHeaders, Streas requestStream,
out IHessage responseMsg, out ITransportHeaders responseHeaders,
out Stream responseStream)

// Расшифровываем входящий поток запроса


requestStreae * CryptoHelper.Decrypt(requestStreaffl, ^provider);

// Пересыпаем запрос
ServerProcessing result = _next.PropessMessage(
slnkStaek, requestMsg, requestHeaders, requestStrean,
out responseHsg, out responseHeaders, out responseStream);

// Шифруем поток отаета


respoflseStreaui * CryptoHelper.Enerypt(responseStream, .provider);

return result;
68 Шифрование

Создание
пары Открытый Создание
ключей ключ
RSA
{ii шифрование
его открытым
Зашифрованный . ключом)
ключ

Сервер

Рис. 4. Алгоритм обмена ключами

Асимметричное шифрование решает проблему передачи ключей между


двумя приемниками (рис. 4). Клиентский приемник динамически создает
пару ключей и отправляет открытый ключ серверному. Затем серверный
приемник динамически создает симметричный ключ (symmetric key),
шифрует его клиентским открытым ключом и отправляет обратно клиен-
ту. Теперь клиентский приемник может расшифровать симметричный
ключ своим закрытым ключом; оба приемника теперь имеют одинаковые
симметричные ключи и могут обмениваться информацией. Сведущие лю-
ди заметят, что это очень похоже на то, как браузер устанавливает защи-
щенное соединение с Web-сервером по SSL.

Реализация обмена ключами

Как уже говорилось, форматирующие объекты принимают IMessage и соз-


дают два выходных объекта; ITrarisportHeaders и Stream. Поток (stream)
содержит сериализованную версию IMessage, а объект ITransportHea-
ders — заголовки (пары «имя-значение», отправляемые серверу транспорт-
ным приемником). Изначально этот набор содержит заголовки, предостав-
ляемые форматирующим объектом (formatter-supplied headers), такие как
SOAPAction. Однако этот набор используется не только форматирующим
объектом. Любые приемники между форматирующим объектом и транс-
портом могут добавлять заголовки в этот набор, и все они будут так же об-
рабатываться транспортным приемником. Это прекрасное место для раз-
мещения информации, необходимой для согласования (handshake infor-
mation), — она понадобится клиенту и серверу для взаимодействия. Такая
информация может включать состояние согласования (handshake state),
шифровальные ключи и идентификатор соединения.
Защита графика .NET Remoting

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


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

Реализация для клиента теперь вполне очевидна (рис. 5). ProcessEn-


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

Рис. 5, ProcessEncryptedMessage клиентского приемника

private bool ProeessEnctyptedMessageC


IMessage nsg, ITransportHeaders req-uest Headers,
Stream requestStream, out ITransportHeaders responseHeaders,
out Stream responseStreatn)

// Шифруем сообщение
Quid Id;
loek( _t ra^saction Lock)

см. след. стр.


70 Шифрование

Рис. 5. ProcessEncryptedMessage клиентского приемника (окончание')

id = EnsuretDAndProviderCmsg, requestHeatfers);
requestStгеав =
SetupEneryptedMessaQe(requestHeacters, requestSt ream);
I

// Отправляв* зашифрованный запрос на сервер


„next, ProcessMessage(
msg , re quest Heade rs , requestSt ream ,
out responseheaders, out responseStream);

// Расшифронываем ответ
loek(_t raRsactionLoc k )
i
responseStream =
Dec ryptResponse(responseSt ream, responseBeaders):
if (responseStream =» (ищ SA
Id.EqualsLtransactiB» ClearSharediCeyO;

// Возвращаем результат
return respoflseStreara 1= null;

Серверу нужна реализация, не требующая логического соединения (con-


nectionless implementation). Он должен быть готов получать запросы на
разных стадиях согласования or любого числа клиентов. При поступлении
запроса сервер ищет идентификатор и состояние согласования в транс-
портных заголовках (рис. 6). На основе этих двух параметров он может
выбрать нужную линию поведения. Если клиент запрашивает общий
ключ, сервер создает его и отправляет клиенту, зашифровав общий ключ
открытым ключом клиента (и не забыв сохранить ключ для дальнейшего
использования). Если клиент запрашивает обработку зашифрованного со-
общения, сервер пытается найти ключ клиента и, если ключ найден, рас-
шифровывает с его помощью запрос, пересылает запрос по цепочке при-
емников, шифрует полученный результат и передает клиенту. Если ключ
не обнаружен, он должен сообщить об этом клиенту. Пример такого обме-
на показан на рис. 7.
Защита трафика .NET Remoiing

Рис. 6. ProcessMessage серверного приемника


public ServerProcessing ProcessHessage(
IServerChannelSinkStack sinkStaefcj, IMessage requestHsg,
ITransportHeaders requestHeaders, Stream requests-tree»,
out IMessage responseHsg, out ITransportHeaders responseHeaders,
out Streaie responseStream)

// Получаем из заголовка информацию о транзакция


string strTransactlD = {string)reque9tHeaders[CoatffionHeaders.IDl;
Quid transactID = (strTransactJD == null) ?
Quid.Empty : new 3uitl(strTransactID);
Secure! ransactlon. transactType -
(SecureTransaction)Convert.ToInt32<
(stflng)requestHeaders[Coii»onHeaders. Transaction]);

// выясняем, icro к нам подключился


IPAddress clientAddress -
requestHeadersECommonTranSBOrtKeys.IPAddress] as IPAddress;

// Помещаем этот приемник в стек приемников


sinkStack.Push(thls, null);

// Обрабатываем транзакцию в соответствии с ее типом


ServerProcessing proeessingFLesult;
switch{transactType)

// Клиент запрашивает общий ключ


case Secure!ransaction.SendlFigPublicKey:
procesaingResult = MakeSharedKeyf
trsnsactID, requestHeaders, out responseHsg,
out responseHeadors, out responseStream);
break;

// Клиент посыпает зашифрованное сообщение


case SecureTransactlon.SerKM.ngEneryptedMe'Ssage;
if (PreviousTransactlo^KithGlient<trarisaetID»

processingResult = ProcessEncryptedHassageC
transaetlD, sini^tack, requestHsg,
rgquestHeaders, requestStream,
out responseMsg, out responseHeaders,
out responaeStream);

9lse
см. след. стр.
Шифрование

Рис. 6. ProcessMessage серверного приемника (окончание)

processing Res ult ~ SendSnptyToClieatt


SecureTransactiofi.l)nkflQwnT.dentifier,
out respormeHsg, out responseHeaders,
out respontieStrean);
}
break;

// Клиент посылает сооСнценив открытым текстом


case SKcureTransaction.Uninitialized:
if (t RequireSecuril:y(eliantAddress) >
1
proeessingResult = _next.ProcessMessage(
siukStack, requestHsg, requestHeaders,
requestStream, out responseHsg,
out responseHeaders, out responseStream);
)
else throw new SecureRemotingExceptiwK
"Security required."};
break;

// Ara...
default;
throw new SecureRemotin()Exception("Invalid request."};

// Извлекаем приемник из стека и возвращаем результат


sinkStack.Pop(tnis);
return procBssingflesult;

Отпрыгни ключ

Общий ключ

Зашифрованное сообщение

L Зач-ифрованныЙ о г ват

Сеptap

Рис. 7. Обмен между клиентом и сервером

Сервер должен также учитывать возможность отправки некоторыми кли-


ентами незашифрованных сообщений. Если он обнаружит отсутствие
Защита трафика .NET Remoting 73

нужных транспортных заголовков (предполагается, что клиентское прило-


жение не использует клиентский приемник), то может передать информа-
цию следующему приемнику без изменений. Но вы не обязаны применять
эту возможность. А как быть, если если вы хотите ввести обязательное тре-
бование о шифровании трафика для всех или лишь отдельных клиентов?
В этом вам помогут HttpServerTransportSink и Тер Server Transport Sink, ко-
торые предоставляют IP-адрес подключившегося клиента. Извлечь IP-ад-
рес можно из транспортных заголовков:
IPAddress clientAddress =
requestHeaders[CommonTransportKeys.IPAddress] as IPAddress;

А затем определить по этому адресу, принимать ли незашифрованный трафик:

case SecureTransaction.Uninitialized:
if (!RequireSecurlty(clientAddress))
{
processingResult = _next.ProcessMessage(
sinkStack, requestMsg, requestHeaders, requestStream,
out responseMsg, out responseNeaders, out responseStream);
}
else throw new SecureRemotingException("Security required.");
break;

Асинхронная обработка
В .NET Framework версии 1.0 нет поддержки асинхронной обработки для
пользовательских приемников канала на сервере (несмотря на наличие ме-
тода с броским именем AsyncProcessResponse в интерфейсе IServerChan-
nelSink). Все запросы к вашему серверному приемнику будут синхронны-
ми, даже если клиент выдаст асинхронный запрос; поэтому в серверный
приемник не нужно добавлять функциональность для асинхронной обра-
ботки.

Реализовать асинхронную обработку на клиенте не так просто. Клиентский


ProcessMessage служит только для синхронных запросов. Для односторон-
них и асинхронных вызовов логика обработки делится между двумя мето-
дами интерфейса IClientChannel Sink: AsyncProcessRequest и AsyncPro-
cessResponse. AsyncProcessRequest предоставляет приемнику возможность
изменять поток и заголовки, отправляемые серверу, a AsyncProcessResponse
позволяет изменять поток и заголовки, принимаемые от сервера,

В моей реализации (рис. 8) AsyncProcessRequest не всегда может быть


полностью асинхронным. Если на сервере нет информации о соединении,
74 Шифрование

AsyncProcessRequest вызывает EnsurelDAndProvider для получения этих


сведений. Для этого он должен совершить синхронный обмен с сервером
так же, как это делает ProcessMessage. Если повезет, это делается лишь раз,
и остальные запросы, выполняемые через AsyncProcessRequest, будут
асинхронными. В отличие от синхронной обработки здесь вы не сможете
выдать другие асинхронные запросы при неудаче одного из запросов. Так
как AsyncProcessResponse должен возвращать исходящие заголовки и по-
ток и вы не можете повторить асинхронный запрос, для новой попытки
второй запрос должен быть синхронным. Для этого AsyncProcessRequest
заталкивает в стек приемника полученные аргументы как состояние
(state), передаваемое AsyncProcessResponse. Если AsyncProcessResponse
получает от сервера неправильный ответ, он может использовать эти дан-
ные для создания синхронного запроса через ProcessMessage.

Рис. 8. Метод AsyncProcessReqtiest клиента


public void AsyncProcessRequest С
IClientChawielSlrjkStack sinkStack, message «sg,
ITr an spor traders headers, Stream stream)
{
AsyneProcessingState state = null;
Stream encryptedStreaffl « null;
Guid id;

lock(_t ransact iontoc k )


{
// Согласуем с сервером информацию о соединении
// (надеясь, что это понадобится только при первом запросе)
id = £nsEireIDAndProvider(aisg, headers);

//.Сохраняем информация о состоянии на случай сбоев


stete * new AsyncP recess ingStatefmsg, headers, ref stream, id)

// Шифруй* поток
encryptedStrean = S9tupEncryptedHessaae(fteacfers, stream);

// Заталкиваем себя в стек с необходимым состоянием


// и переходим к следующему приемнику
SinkStack. PusMthis, state);
_next.A8yncProGessHeqtiest(sirikStack, ragg, headers,
eneryptedStreaat);
Защита трафика .NET Remoting

Я намеренно не рассказываю об односторонних методах (one-way me-


thods), так как они не поддерживаются этой парой приемников. По своей
природе клиентский приемник никогда не получит ответ для односторон-
него метода, и поэтому обмен с сервером для получения общего ключа по-
требует много дополнительного кода.

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

Рис. 9. Очистка информации о соединениях

private void StartCennectionSweeperO

// Создаем и запускаем тай«ер, который будет запускать


// SweepGonnections
If (_sweepTiffler != null)
{
_sweepTimer = new System. Tiners.Ttiser(_swespFrequency*1000);
jsweepTimer. Elapsed •*•= tie» ElapsedEventHandler{SweepConnectiorts);
_sweepTiffler.Start();

private void SweepCorweeticmsCobject sender, ElapsedEventArgs e)


{
lock (_connectiofis.SyncRoot>
{
ArrayList toCelete * new ftrrayUstCcormeetionE. Count);

// Ищем записи, которые еяедует удалить


foreeDhi(DictionaryEntry entry in ..connections)

см. след. стр.


76 Шифрование

Рис. 9. Очистка информации о соединениях (окончание)

ClientConnectionlnfo cci = (ClientConnectionlnfo)entry.Value;


if(cci. LastUsed,AdcfSeconds(_connectiofiAgeLIfflit}.GofflpareTo{
DateTiw.UtcNow) < 0)
{
toBelete.Add(entry.Key):
<(IDi$posabie)cci).Dispi>seO;

// Удаляем все просроченные записи


foreach(0bject obj in toDeletn> _connections.flemove(obj);
toDelete = null;

Такой процесс способен обеспечить еще один уровень защиты. Кроме


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

Создание приемников
Теперь, когда приемники написаны, нужно помочь инфраструктуре соз-
дать их экземпляры. Инфраструктура .NET Remoting не создает приемни-
ки напрямую. Вместо этого создаются «провайдеры приемников» (sink
providers), которые и отвечают за создание самих приемников. Провайде-
ры и приемники почти всегда связываются по принципу «один к одному»,
т. е. один провайдер служит для создания только одного приемника, одна-
ко ничто не мешает провайдеру создать несколько приемников.

Наибольшее преимущество от передачи работы по созданию экземпля-


ров приемников провайдерам в том, что это добавляет еще один уровень,
через который разработчик приемника получает больший контроль. Ес-
ли провайдер реализует корректный конструктор, инфраструктура пере-
даст провайдеру {Dictionary, содержащий атрибуты и элементы, указан-
ные для провайдера в конфигурационном файле удаленного взаимодей-
Защита трафика .NET Remoting 77

ствия (если конфигурационный файл не используется, этот словарь мож-


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

Когда инфраструктура создает провайдеры, они формируют собственную


цепочку. Интерфейсы IClientChannelSinkProvider и IServerChannelSink-
Provider требуют реализации свойства Next, через которое инфраструкту-
ра указывает следующий провайдер в цепочке. Инфраструктура создает
экземпляры провайдеров и вызывает метод CreateSink первого провайде-
ра. Метод CreateSink отвечает за создание реального приемника и возврат
ссылки на него (рис. 10). Поскольку приемник следует уведомить о сле-
дующем приемнике в цепочке, метод CreateSink должен переслать вызов
следующему провайдеру в цепочке, который вернет ссылку на свой при-
емник:

public IServerChannelSink
CreateSink(IChannelReceiver channel)
I
IServerChannelSink nextSink = null;
if (_next != null)
{
if <(nextSink= _next.CreateSink(channel))
== null) return null;
}
return new SecureServerChannelSink(nextSink, ... );
I

Запрос
на создание
приемника
Ссылка Ссылка
на приемник Провайдер! на приемник Провайдер 2

Рис. 10. Экземпляры приемников создаются провайдерами приемников

Конфигурирование
HTTP-канал можно настроить через конфигурационный файл:

<channels>
<channel ref="http" port="8124" />
</channels>
78 Шифрование

Это предписывает инфраструктуре использовать HTTP-канал и формати-


рующий объект по умолчанию — SoapFormatter. Чтобы переопределить
параметры по умолчанию, укажите нужный форматирующий объект яв-
ным образом:
|.
<channels>
<channel ref="http" port="8124">
<serverProviders>
<formatter ref="binary" />
</serverProviders>
</channel>
</channels>

Когда тэг <serverProviders/> (или <clientProviders/> на стороне клиента)


используется как элемент <channel>, он сообщает инфраструктуре, что
вместо цепочки приемников по умолчанию канал должен быть настроен
на использование провайдеров, указанных в списке, в соответствии с по-
рядком их перечисления. В данном случае вы сообщаете инфраструктуре
задействовать двоичный форматирующий объект (binary formatter). Что-
бы добавить еще провайдеры, внесите дополнительные элементы в список
серверных провайдеров:

<serverProviders>
<provider type=
"MsdnMag.Remoting.SecureserverChannelSinkProvider,SecureChannel" />
<formatter ref="blnary" />
</serverProviders>

Заметьте: я добавил провайдер до форматирующего объекта, так как мне


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

Создав провайдер с конструктором, позволяющим ему принимать пара-


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

После применения RemotingConfiguration.Configure на клиенте и сервере


с соответствующими конфигурационными файлами пользовательские
приемники станут частью цепочек приемников.
Защита трафика .NET Remoting 79

Заключение
Канал, описанный в этой статье, не является идеальным решением. При-
емники не используют серьезные механизмы защиты потоков. Нет аутен-
тификации и подписи сообщений. Нет защиты от атак посредника (man-
in~ the-mid die attacks) или атак типа «отказ в обслуживании» (denial of
service attacks). Но надеюсь, суть решения понятна. Моя статья дает пред-
ставление о типах решений, возможных в .NET Remoting. От них могут
выиграть многие защищенные каналы, но возможности для творчества не
ограничены: транспортные приемники для поддержки именованных кана-
лов или MSMQ, IMessageSinks для кэширования возвращаемых значений,
каналы со сжатием (compression channels), ведением журналов (logging
channels), уведомлениями (notification channels) — на этом список не кон-
чается. По-моему, очень скоро появятся самые разнообразные расширения
от Microsoft и сторонних разработчиков, которые заполнят многие пробе-
лы, но это, безусловно, не должно помешать вам сегодня же начать работу
над приемником вашей мечты.

Стефен Тауб (Stephen Toub) — разработчик и консультант Microsoft в Нью-Йор-


ке. Выпускник и бывший преподаватель Гарвардского университета. С ним
можно связаться по адресу stoub@microsoft.com.
Дон Бокс

Защита в .NET

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

У общеязыковой исполняющей среды (common language runtime, CLR) в .NET


Framework есть своя модель безопасного выполнения кода, независимая
от ограничений операционной системы, в которой она работает. Более того,
в отличие от старой модели защиты на основе участников безопасности
(principal-based security) CLR реализует политику, исходя из того, откуда
поступает код, а не из того, кто является его пользователем. Эта модель
защиты по правам доступа кода (code access security) имеет больший смысл
в современных условиях, поскольку немалая часть кода устанавливается
через Интернет и даже доверяемый пользователь {trusted user) не знает,
какой код действительно безопасен. В этой статье автор объясняет,
как работает защита по правам доступа кода в CLR. Он рассматривает
признаки (evidence), требуемые политикой (policy), а также рассказывает,
как выдаются разрешения (permissions) и каким образом исполняющая
среда применяет, или вводит в действие (enforce), политику безопасности.

Одно из преимуществ виртуализированных исполняющих сред (virtua-


lized 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, написанной на С или с использованием СОМ
(по определению не являющихся верифицируемыми), также рассматри-
вается как доверяемая операция, разрешение на которую по умолчанию
предоставляется только коду, установленному в локальной файловой
системе.
33 Защита кода и данных

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


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

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

CLR поставляется с семью типами признаков. Четыре из них (Site, Url,


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

Собирательное название этих семи типов — признаки хоста (host evi-


dence), так как они реализуются хост-средой. Вы можете определять соб-
ственные типы признаков, которые обобщенно называются признаками
сборки (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 и Secu-
rityZone. Untrusted. URL кодовых баз, не подпадающие ни под одну из
этих трех категорий, включаются в базовую зону SecurityZone.Internet.

Последний тип признака на основе адреса — ApplicationDirectory. Этот тип


признака на самом деле предоставляется хост-приложением и указывает
базовый каталог для выполняемого приложения. Он аналогичен типу
Защита кода и данных

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

CLR предусматривает программные эквиваленты каждого типа признака,


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

Рис. 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.Secu-
rity.Permissions.StrongNamePublicKeyBlob для байтового массива, в кото-
ром содержится открытый ключ. Этот объект принимает либо открытые
ключи, либо маркеры открытых ключей (public key tokens).

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


using System;
using System.Reflection =
using System.Security;
using System.Security.Permissions;
using Systeat. Security. Policy;

class App {
static StrongName CreateFromAssefflbly<Assembly 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);

// Показать интересува^е нас биты


Console.WriteLine(sfl.Каше);
Console.WriteLine(sn.Version);
Console.WrlteLine(sn.PublicKey);
Защита кода и данных

Признак, основанный на строгом имени (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.CreateFronCertFile<name);
Publisher pub * new Publisher<cert);

// Показать интересующие нас биты


XSQSCertificate value = pub.Certificate;
Console. Writeline( value. GetNameO);
Console. ttriteLine<value.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 является простым набором (collec-
tion) и реализует интерфейс System.Collections.ICollection. Тип Evidence
отличается от универсального набора (generic collection) в том, как он хра-
нит два внутренних набора: один — для встроенных объектов-признаков
хоста, другой — для пользовательских объектов-признаков сборки.
Код на рис. 4 иллюстрирует, как создать новый объект Evidence, содержа-
щий признаки Url, Zone и Site. При запуске применительно к URL кодовой
базы http://www.microsoft.com/foo.dll эта программа генерирует вот что:
Защита кода и данных

<System.Security.Policy.Url version="1">
<Url>http://www. microsoft.com/foo.dlK/Url>
</System. Security,, Policy. Url>

<System.Security.Policy.Zone version="1">
<Zone>Internet</Zone>
</System. Security,, Policy. Zone>

<System.Security.Policy.Site version="1">
<Name>www.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 консоли ММС. Политики Ma-
chine и Enterprise считываются соответственно из файлов security.config
и enterprisesec.config. Эти файлы размещаются в подкаталоге CONFIG
специфичного для конкретной версии каталога установки CLR. Поли-
тика User считывается из файла Application Data\Microsoft\CLR Secu-
rity Config\vl.0.n«727Z\security.config, который находится в каталоге про-
филя, специфичного для конкретного пользователя. Политика AppDo-
main должна быть задана программно — вызовом метода System.AppDo-
main. Set AppDomainPolicy.

Комбинация четырех уровней политики называется иерархией полити-


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

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

Такой принцип формирования конечного набора разрешений применяется


потому, что модель защиты в CLR основана на выдаче, а не на отклонении
привилегий. Читателям, знакомым с системой защиты в Windows NT, эта
модель напомнит концепцию привилегий в Win32 или ролей в СОМ+; ина-
че говоря, чтобы отклонить доступ к ресурсу, соответствующие разрешения
просто не выдаются. В отличие от DACL-списков Win32 способа явно от-
клонить доступ к защищенному ресурсу или к выполнению защищенной
операции нет. В этом плане политики по умолчанию уровней Enterprise,
Сервисы признаков, политик, разрешений и применения политик 95

User и AppDomain выдают разрешение полного доверия (full-trust permis-


sion) независимо от представленных признаков. Но политика по умолчанию
уровня Machine выдает такое разрешение только коду, загружаемому из зо-
ны MyComputer. Код, который создан компаниями, отличными от Microsoft
или ЕСМА, загружается из других зон безопасности и получает гораздо
меньше разрешений.

Как и признаки, иерархия политик безопасности используется в инфра-


структуре защиты неявно, но доступна для программного доступа. Каждый
уровень иерархии предоставляется через тип System.Security.Policy.Poli-
cyLevel, а набор уровней политик — через метод System.Security.Security-
Manager. 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;

class App {
static void Maln{> {
lEnuraerator i » SecyrityHanager.^licy^ierarehyO;
while (i.Hovetiexto) {
PolicyLsvel level - (Policy-level)!.Current;
OQflsole,mteUneC4Q,1Q}: {1}",
level.Label,
level.Storelocation);
96 Защита кода и данных

Каждый уровень политики состоит из трех элементов: списка именован-


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

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


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

Рис. 7. Условия членства в группе кода

Условия членства сильно типизированы (strongly typed) и представлены


CLR-типами, реализующими интерфейс System.Security.Policy.IMember-
shipCondition, в котором есть один интересный метод, 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. Остальные шесть типов условий членства (UrlMember-
shipCondition, ZoneMembershipCondition и др.) прямо соответствуют шес-
ти типам признаков хоста (Url, Zone и т. д.) и позволяют принимать реше-
ние о членстве по своей части признака.

Чтобы к одной сборке можно было применить более одного набора разре-
шений, группы кода имеют иерархическую структуру и могут содержать до-
черние группы. Последние доступны через свойство CodeGroup.Children.
Большинство групп кода в политике безопасности являются экземплярами
типа System.Security.Policy.UnionCodeGroup. Если условие членства выпол-
няется, UnionCodeGroup перечисляет все дочерние группы кода и принима-
ет объединение (union) наборов разрешений каждой дочерней группы,

4-138
98 Защита кода и данных

условие членства в которой также выполнено. Поскольку и у дочерней


группы кода бывают свои дочерние группы, этот процесс может потребо-
вать оценки большого количества наборов разрешений. Однако, если ус-
ловие членства для данной группы кода не удовлетворяется, все его дочер-
ние группы игнорируются. Взгляните, например, на иерархию групп кода,
показанную на рис. 8. Если условие членства для группы А не выполня-
ется, остальные группы просто не рассматриваются (и конечный набор
разрешений окажется пустым). Если же условие членства для группы А
удовлетворяется, принимаются группы В, С, G, Н, I и N. А также группы
D и Е — если проверка условия членства для группы С дает положитель-
ный результат.

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

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


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

Второе свойство, изменяющее интерпретацию группы кода, — PolicyState-


mentAttribute. LevelFinal, Оно информирует диспетчер защиты (security
manager) о необходимости игнорировать более низкие уровни политики.
Например, если бы такой атрибут был задан для отвечающей условию
группы кода на уровне SecurityLevel.Machine, уровни политик Security-
Level.User и SecurityLevel.AppDoraain были бы проигнорированы. Это ли-
шает менее привилегированных пользователей и администраторов воз-
можности отключать критически важные компоненты, блокируя выдачу
им требуемых разрешений.
Сервисы признаков, политик, разрешений и применения политик 99

Программное преобразование содержимого признака в набор подходящих


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

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.Resolve-
PolicyGroups. Он просто вызывает PolicyLevel. ResolveMatchingCode-
Groups на каждом уровне в иерархии политики (например 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;

class App {
static void DwpGroup(CodeGroup group, string prefix) {
Console. WriteLine<"{OHlH'{2}']:<3r,
prefix, group. GetType(), Name,
group. Kame, group. PermissionSetName);
fo reach (CodeGroup child in group. Children)
DumpSroop( child, prefix + " ");

static void HaSn{stringt] argv) {


string assrariame = argv[0j;
Evidence ev = Assembly. Load(assiBnarne). Evidence;
lEnunrerator 1 = SecurityHana£er.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

Ш crosaft^Stron g_iN аящ

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

Примечание 2:
NetCodeGroup выдает
сайту - источнику сборки
разрешение WebPermission

Рис. 10. Встроенные группы кода

Я уже упоминал о нескольких встроенных именованных наборах разреше-


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

удаленных файловых систем или Интернета. В обоих случаях разрешается за-


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

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

Набор разрешений Описание

Nothing Пустой набор разрешений (ничего не разрешает)


FullTrust Неявно выдает неограниченные права для всех
типов разрешений
Everything Явно выдает неограниченные права для всех
встроенных типов разрешений
Skip Verification Security Permission: SkipVerification
Execution SecurityPermission: Execution
Internet FileDialogPermission: Open
IsolatedStoragePermission:
DomainLsoIationByUser (квота = 10240)
LTIPermission: OwnClipboard |
SafeTop Level Windows
Printing Permission: SafePrinting
Local Intranet SecurityPermission: Execution Assert
RemotingConfiguration
FileDialogPermission: Unrestricted
IsolatedStoragePermission:
AssemblyIsolatkmByUser (квоты нет)
UI Permission: Unrestricted
PrintingPermission: DefaultPrinting
Environment Permission: Read-only
(USERNAME/TMP/TEMP)
Reflection Permission: ReflectionEmit
DNS Permission: Yes
EventLog: Instrument

Помимо предопределенных наборов разрешений, в 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).

Рис. 11. Получение уровней политик


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 «/ }

// Пройти по каждону уровню политики, попутно разлапая признак


lEnuaierator i = SecMrityManager.PoiicyHierarchyO;
иШе (i.HoveNextO) {
Polieytevel level = (PolicyLevel)i.Current;
PolicyStatement statefflent = level. flesolve(evider)Ge);
Console.WriteLine("Level: {0} (attributes *
level.Label, stateaent.Attributes);
Console.WriteLine(statement.PerBissionSet);
104 Защита кода и данных

Метод Resolve сначала находит подходящие группы кода, а затем принима-


ет объединение разрешений, выданных набором разрешений каждой груп-
пы. Чтобы найти разрешения, используемые в совокупности, вы должны
взять те из них, которые находятся на пересечении всех задействованных
наборов разрешений, и при этом учесть значение свойства Permission-
Statement 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 про-
сматривает список элементов признака хоста и получает дополнительное
разрешение от каждого элемента признака, поддерживающего интерфейс
Сервисы признаков, политик, разрешений и применения политик 105

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).

Операция Результат
A.l6SubsetOf(B) jFalse
[False s
ITrue
A Jnterse cl(B} , IsStibsetOf { В i I True

Рис. 13. Наборы разрешений


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

Рис. 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 с установленными битами Execu-
tion и 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 в конструктор для Secu-
rityPerniission эквивалентна передаче PermissionState. Unrestricted.
Кроме того, выполнение операций объединения (union operations) над
объектами разрешений может дать в результате неограниченный объект
разрешения.

Хотя вы можете определять собственные типы разрешений, реализуя ин-


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

Зачастую полезно комбинировать разрешения разных типов. С этой це-


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

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

Пространство Неограни- На основе


имен Имя Опнсание ченный шаблона

System. Secu- Security Базовые возможности Да Нет


rity. Permissions исполняющей среды
Reflection Чтение и запись Да Нет
метаданных CLR
Environment Доступ к перемен- Да Да
ным окружения ОС
U rl Identity Добавляет в признак Нет Да
сборки URL кодовой
базы
Site-Identity Добавляет в признак Нет Да
сборки Site
Zoneldentity Добавляет в признак Нет Нет
сборки SecurityZone
StrongName- Добавляет в имя сбор- Нет Нет
Identity ки открытый ключ
Publisher- Добавляет в признак Нет Нет
Identity сборки сертификат
•Registry Доступ к реестру Да Да
Windows
FilelO Доступ к каталогам Да Да
и файлам
Isolated Storage Доступ к закрытому Да Нет
хранилищу
FileDialog Вывод пользователю Да Нет
файловых диалоговых
окон
!
I Доступ к иерархии окон Да Нет
и к буферу обмена
System.Dravving Printing Доступ к подключен- Да Нет
ному принтеру и к прин-
теру по умолчанию
System.Net Dns DNS-трансляция адресов Да Нет
Socket Низкоуровневое исполь- Да Да
зование сокетов
(accept/connect)
Web Высокоуровневый доступ Да Да
к Web (accept/connect)
System.Messaging Message Queue Доступ к средствам Да Да
MSMQ
System.Da- DBData Доступ к функциональ- Да Нет
ta. Common ности провайдера базы
данных
110 Защита кода и данных

Рис. 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.AddPermisslon<new FileIOPermissii3R(all, &"C:
\

Тип PermissionSet поддерживает три операции IPermission (Union, Inter-


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

SecarityPermfsslon UIPei|raiss!on

A, Union (В)

Рис. 16. Операции присвоения разрешения

Выполнение операций присвоения над объектами PermissionSet — процесс


тривиальный. Так, на рис. 17 С#-код дает тот же набор разрешений, что и
код на рис. 15 (но делает это не столь явно).
Объекты PermissionSet могут предоставлять и неограниченные разреше-
ния. Для этого в конструктор набора разрешений передается Permission-
State. 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.AddPeriBissien<new FllelOPer«ission(all, 9"C:\etc"));
ps3 = ps1,Union(pa2);

Введение политики безопасности в действие


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

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

using System.Net;

public sealed class Utils {


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

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
Сервисы признаков, политик, разрешений и применения политик 113

в конкретном типе разрешения). Конечный набор разрешений устанавли-


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

Ранее показанный класс System.Net.Dns демонстрировал пример катего-


рического требования к правам доступа (imperative security demand). Ка-
тегорическим оно называется потому, что в коде явно вызывается соот-
ветствующая программная конструкция. Но CLR поддерживает и декла-
ративные требования к правам доступа (declarative security demands) -
на основе атрибутов. Для каждого типа разрешения CLR определяет спе-
цифичный атрибут, производный от System.Security.Permissions.Code-
AccessSecurityAttribute. Обязательный конструктор (mandatory construc-
tor) всех таких атрибутов принимает параметр типа System.Security.Per-
missions. Security Action;
namespace System.Security.Permissions {
public enum SecurityAction {
Demand = 1,
Assert,
Deny,
PermitOnly,
LinkDemand,
InhsrltanceDemand,
RequestMinimum,
RequestOptional,
RequestRefuse,
>
I

В SecurityAction нас сейчас интересует только Demand. Эта операция за-


щиты (security action) позволила бы вместо категорического запроса;

using System.Security,Permissions;
namespace System.Net {
114 Защита кода и данных

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, например:

using System. Net;


using System. Security. Permissions;
,
public sealed class Utlls {
Сервисы признаков, политик, разрешений и применения политик 115

DnsPermission(SecurityAction.PermitOnly,
Unrestricted=true)
f

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 {


[
FilelOPermissionfSecurityAction . Deny,
Unrestricted=true)
.!
public static IPAddress LookupHost(string host) {
return Dns. GetHostByName( host). Add ressList[0];
}
.
И то же самое, но с предъявлением категорических требований:
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 LookupHost<strinfl 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];
}

После того как метод добавляет некое разрешение, при любом запросе это-
го разрешения проход по стеку останавливается на фрейме данного мето-
да. Таким образом, добавляя какое-либо разрешение, вы указываете, что
все разрешения вызывающего следует игнорировать. Но вот ведь ирония:
добавление разрешения — тоже защищенная операция, которая требует от
метода, выполняющего это действие, разрешения SecurityPermission-
Flag.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 указывает разрешения, без ко-
торых успешная загрузка сборки невозможна. Если политика не выдает
эти разрешения, загрузчик не сумеет загрузить сборку. Кстати, реальные
разрешения, предоставляемые методам сборки, являются подмножеством
тех, которые выдаются политикой.
Итоговые разрешения метода — все разрешения с атрибутом Security-
Action. 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 ),

Unrestricted-true),
FileIOPermisslon(SecurityAccees.RequestOptional,
Unrest riot edet rue)
FileIQPerfflission(SecurityAGcess. Bequest Re fuse,
Write=*3"e;\autoexee. bat")
1

public sealed class Utils {


public static IPAddress LookupHost<string 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 для сборки (период загрузки)

Вычислить MaxGrantполитики

Получить ReqMm, RepOpt и ReqRefuse из оборки

ReqMin
подмножество нет
MaxGrant?
Да

I
Granted = jMaxGrarit/'MReqMinwReqOpt)) - ReqRefuse

Вызван Вызван
H
Perm it Only «? Den*

Granted = Granted - Denied

Рис. 20. Вычисление разрешений по фрейму стека

Атрибут Security Action.LinkDemand аналогичен Security Action.Demand.


Однако LinkDemand позволяет предъявлять требования по правам досту-
па только к вызывающему, а не ко всему стеку. Когда JIT-компилятор пы-
тается транслировать какой-либо метод, любые его вызовы к методам,
помеченным атрибутом LinkDemand, заставят выполнять проверку на
120 Защита кода и данных

безопасность (security check). В отличие от нормального Demand-вызова


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

LinkDemand и InheritanceDemand идеальны для использования разреше-


ний идентификации. Допустим, у вас есть набор сборок от одного постав-
щика. К сожалению, обычные модификаторы прав доступа (вроде public
или internal) не обеспечивают тонкой настройки и не позволяют выдавать
разрешение на доступ к какому-либо методу отдельным сборкам. Но при-
менив атрибут LinkDemand к методу, который требует от вызывающего
определенного разрешения идентификации (например StrongNamelden-
tityPermission), вы могли бы потребовать, чтобы у всех вызывающих был
конкретный открытый ключ в именах их сборок. Возьмем простой С#-
класс, показанный на рис. 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" +
"680da9Cif2819a10ef6a1<fbOeb5e5e6ea3822456" +
"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"
"74d511e17f76b2874ee80cb82babea3t>624b745"
"Baca48b7")
1
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).

В следующей таблице показаны некоторые общие типы признаков, ис-


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

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

Hash Хэш сборки


Publisher Лицо или организация, использующее подпись
AuthentiCode
StrongName Открытый ключ+имя+версия
Site Web-сайт источника кода
Url URL источника кода
Zone Зона Internet Explorer источника кода

Система защиты по правам доступа кода выполняет проверку предостав-


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

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

Цели безопасного кодирования


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

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


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

Подходы к безопасному кодированию


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

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


них вопросов защиты может оказаться полезным.

Код, нейтральный к защите

Код, нейтральный к защите (security-neutral code), не делает ничего явно-


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

Библиотека, нейтральная к защите, обладает особыми характеристиками,


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

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


повторно используемым компонентом

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


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

Кроме того, если код получает пользовательский ввод из Интернета или


из других ненадежных источников, остерегайтесь ввода вредоносных
данных.

Дополнительную информацию см. в разделах этого документа «Защита


данных о состоянии» и «Пользовательский ввод»,

Управляемая оболочка для машинного кода

Обычно в этом сценарии некая полезная функциональность реализована


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

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


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

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


«Выдача разрешений».

Библиотечный код, предоставляющей защищенные ресурсы

Этот подход к безопасному кодированию открывает наибольшие возмож-


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

(т. е. пройти проверку защиты), а затем объявить свои права на выполне-


ние самой операции.

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


«Выдача разрешений».

Приемы безопасного кодирования


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

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


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

Например:

[assemblyiFilelOPermissionAttribute
(SecurityAction.RequestMinimum, Wrlte="C:\\test.t<np")]
[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.

В этом примере для защиты API-элемента применяется атрибут LinkDe-


mand; важную информацию об ограничениях, связанных с использовани-
ем LinkDemand, см. в следующих разделах данного документа.
132 Защита кода и данных

Запрещение использования классов


и методов недоверяемым кодом

Объявления, показанные ниже, запрещают частично доверяемому коду об-


ращаться к классам и методам (а также к свойствам и событиям)! Когда та-
кие объявления применяются к классу, защищаются все методы, свойства
и события этого класса. Однако декларативная защита не влияет на доступ
к полям. Кроме того, учтите, что требования к связи (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
134 Защита кода и данных

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

Если виртуальный метод, свойство или событие с проверкой LinkDe-


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

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


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

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


виду следующее.
• Атрибут Allow Partially Trusted Callers может влиять на интерфейсы.
• Вы можете указывать для интерфейсов проверки LinkDemand, чтобы
выборочно запрещать частично доверяемому коду обращение к опре-
деленным интерфейсам, например при использовании атрибута Allow-
PartiallyTrusted 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).

Чтобы избежать дыр в защите, подобных описанной выше, исполняющая


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

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

Несколько методов, используемых для загрузки управляемого кода, в част-


ности 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. Рассмотрим сле-
дующий псевдокод:

void Main{) {
try {
Sub();
} except (FilterO) { ,
Console.WriteLinef"catch");
\

}
bool Filter C) {
Console.WriteLine("filter");
return true;
}
void Sub() {
try {
Принципы безопасного кодирования для .NET Framework 139

Console. WriteLine( "throw");


throw new ExceptionQ;
} finally {
Con sole. WriteLine( "finally");

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

Throw
Filter
Finally
Catch

Фильтр выполняется перед оператором finally, поэтому любая ситуация, ка-


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

try {
Alter_Security_State();
// Здесь возможны любые изменения (изменение
// переменных состояния, переключение в
// неуправляемый контекст, олицетворение и т. д.).
// Этим может воспользоваться злонамеренный код,
// выполняемый до восстановления состояния.
Do_some_work();
} finally {
Resto re_Secu rity_State( ) ;
// Здесь просто восстанавливается ранее
// измененное состояние

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

Рекомендуемое решение — ввести обработчик исключения, изолирующий


изменения в состоянии потока вашего кода от блоков фильтрации вызы-
вающих. Однако важно правильно установить обработчик исключения,
иначе решить эту проблему не удастся. В следующем примере код на
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"};
// Выполняется действие, приводящее к генерации исключения
1
finally {
Thread.CurrentThread.CurrentUICulture = saveCulture;

catch { throw; }
>

Неуправляемый код
Иногда библиотечный код должен вызывать неуправляемый код (напри-
мер встроенные функции такого API, как Win32), Поскольку это означает
выход за периметр защиты управляемого кода, соблюдайте осторожность.
Если ваш код нейтрален к защите (см. раздел «Код, нейтральный к защи-
те»), то и ваш код, и код, который его вызывает, должны иметь разреше-
ния на выполнение неуправляемого кода ( Security Permission.Unmanaged-
Code).

Однако зачастую неразумно требовать от вызывающего кода таких ши-


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

Когда ваш код вызывает неуправляемый код, но вы не хотите, чтобы вы-


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

Обычно это означает, что вы должны запрашивать соответствующие раз-


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

Поскольку любой управляемый код, позволяющий обращаться к машин-


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

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

При объявлении прав (asserting) и последующем вызове неуправляемого


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

Чтобы отключить обычную проверку защиты, требующую Security Permis-


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

При использовании атрибута SuppressUnmanagedCodeSecurity учитывай-


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

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

Для методов неуправляемого кода весьма полезна и настоятельно ре-


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

Ключевое слово Соглашения по безопасности

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


(даже злонамеренным). Может использоваться
точно так же, как и управляемый код.
Пример: получение времени дня
native Нейтральный к защите, т. е. неуправляемый код, требую-
щий для вызова разрешения неуправляемого кода.
Безопасность проверяется, что предотвращает вызов
неавторизованным кодом
unsafe Потенциальна опасная точка входа в неуправляемый код
с отключенной защитой. Разработчикам следует использо-
вать такой код с величайшей осторожностью и только
при том условии, что применяются другие способы
защиты, не допускающие нарушения безопасности.
При использовании таких методов разработчики сами
отвечают за создание системы защиты

Пользовательский ввод
Пользовательские данные, каким бы образом они ни передавались (через
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), сам отвечает за безопасность. Либо этот
код следует использовать только в закрытой безопасной среде, где вызы-
вающий код является доверяемым, либо удаленные вызовы нужно проек-
тировать так, чтобы они не открывали доступ к защищенному коду через
внешние точки входа.

Кроме того, обычно не следует предоставлять доступ к методам, свойствам


или событиям, защищенным декларативными проверками защиты Link-
Demand и 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)

• Специальный конструктор, применяемый при сериализации, должен


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

Проблемы, связанные с пересечением


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

Однако в домене приложения по умолчанию политика для этих сборок ос-


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

Выдача разрешений
Защита на основе признаков основана на предположении, что высокий
уровень доверия (с широкими полномочиями) присваивается лишь коду,
заслуживающему доверия, а злонамеренный код является мало доверяе-
мым или вообще не имеет разрешений. В политике по умолчанию в .NET
Framework разрешения выдаются на основе зон (так, как они определены
в Microsoft Internet Explorer). Ниже приведено упрощенное описание этой
политики по умолчанию.
• Зона локального компьютера (например, c:\app.exe) является полно-
стью доверяемой. Предполагается, что пользователи помещают на свой
Принципы безопасного кодирования для .NET Framework 149

компьютер только код, которому они доверяют, и что большинство


пользователей не собираются разбивать свой жесткий диск на облас-
ти с разной степенью доверия. По существу, этот код может делать все
что угодно, поэтому от злонамеренного кода, находящегося в этой зо-
не, никакой защиты нет.
• Зона Интернета (например, http://www.microsoft.com/). Коду из этой
зоны предоставляется очень ограниченный набор разрешений, который
неопасно предоставить даже злонамеренному коду. Обычно этому коду
нельзя доверять, поэтому его можно безопасно выполнять только с
очень узкими разрешениями, с которыми он не сумеет нанести ущерб:
• WebPermission — доступ к серверу сайта, с которого получен код;
• FileDialogPermission — доступ только к файлам, специально указан-
ным пользователем;
• IsolatedStorageFilePermission — постоянное хранилище, ограничен-
ное пределами Web-сайта;
• UlPermission — возможность записи информации в окно пользова-
тельского интерфейса.

• Зона интрасети (например \\UNC\share). Код из этой зоны выполняет-


ся с чуть большими разрешениями, чем код из Интернета, но среди них'
все равно нет таких, которые предоставляли бы широкие полномочия:
• FilelOPermission — доступ только для чтения к файлам каталога, из
которого загружен код;
• WebPermission — доступ к серверу, с которого загружен код;
• DNSPermission — допускается разрешение DNS-имен в IP-адреса;
• FileDialogPermission — доступ только к файлам, специально указан-
ным пользователем;
• Isolated StorageFilePermission — постоянное хранилище (с меньши-
ми ограничениями);
• UlPermission — код может свободно использовать собственные ок-
на верхнего уровня.
.'
• Зона ограниченных сайтов, код из которой выполняется только с ми-
нимальными разрешениями.

Продумайте свои требования к защите и соответственно измените полити-


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

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


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

Опасные разрешения

Для выполнения некоторых защищенных операций .NET Framework пре-


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

К опасным разрешениям относятся:


• Security Permission:
• U n managed Code — позволяет управляемому коду вызывать неуправ-
ляемый код, что зачастую весьма опасно;
• Skip Verification — код может делать что угодно без всякой верифи-
кации;
• ControlEvidence — управление признаками позволяет обмануть сис-
тему защиты;
• ControlPolicy — возможность изменять политику безопасности по-
зволяет отключить защиту;
• SerializationFormatter — за счет сериализации можно обойти управ-
ление доступом (см. раздел по сериализации);
• ControlPrincipal — возможность указывать текущего пользователя
позволяет обходить защиту на основе ролей;
• ControlThread — возможность манипуляций с потоками опасна, так
как с ними связано состояние защиты;
• ReflectionPermission:
• MemberAccess — позволяет отключать механизм управления досту-
пом (становится возможным использование закрытых членов).

Защита и конкуренция

Еще одна проблема обеспечения безопасности — возникновение дыр в сис-


теме защиты из-за конкуренции (race conditions). Конкуренция проявля-
ется в нескольких ситуациях. В следующих подразделах рассказывается о
некоторых типичных ловушках, которых должны избегать разработчики.
Принципы безопасного кодирования для .NET Framework 151

Конкуренция в методе Dispose

Если метод Dispose класса не синхронизирован, код очистки в этом мето-


де может быть выполнен более одного раза. Рассмотрим такой код:

void Dispose() {
if( _myObj != null ) {
Cleanup(_rnyQbj);
_myObj = null;
}
!

Так как реализация Dispose не синхронизирована, возможна ситуация, ко-


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

Конкуренция в конструкторах

В некоторых приложениях возможна ситуация, когда другие потоки обра-


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

Конкуренция при использовании кэшированных объектов

Код, кэширующий информацию, связанную с защитой, или методы Assert,


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

void SomeSecureFunctionf) {
if(SomeDemandPasses(}) {
_fCallersOk = true;
DoQtherWorkO;
_fCallersOk = false;
}
I
void DoOtherWorkO {
if( _fCallersOK ) {
DoSomethingTrustedO;
152 Защита кода и данных

else {
DemandSomethingO;
DoSomethingTrustedO;

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

Если ваш код котирует информацию, связанную с защитой, убедитесь в


отсутствии это го,, слабо го места.

Конкуренция при подготовке объекта к уничтожению

Еще один источник конкуренции — объекты, которые ссылаются на ста-


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

Другие технологии защиты


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

Генерация кода «на лету»


Некоторые библиотеки генерируют и запускают код, выполняя определен-
ную операцию для вызывающего кода. Основной проблемой является ге-
нерация кода по требованию со стороны менее доверяемого кода и запуск
сгенерированного кода с более высоким уровнем доверия. Проблема обо-
стряется, когда вызывающий может влиять на генерацию кода, поэтому вы
должны убедиться, что генерируется лишь безопасный код.
Вы всегда должны точно знать, какой код генерируется. Это значит, что
нужно строго контролировать любые значения, получаемые от пользова-
теля: строки в кавычках (их следует избегать, так как в них можно поме-
щать непредсказуемые элементы кода), идентификаторы (проверяйте их
на допустимость) и любые другие данные. Идентификаторы могут быть
опасными, поскольку появляется возможность изменить скомпилирован-
ную сборку так, чтобы ее идентификаторы содержали «странные» симво-
Принципы безопасного кодирования для .NET Framework 153

лы, а это, вероятно, нарушит работу сборки (хотя во многих случаях это не
создает уязвимости в защите).
Рекомендуется генерировать код с помощью Reflection.Emit, что позволя-
ет избежать многих описанных выше проблем.
Продумайте, не может ли злонамеренная программа изменить код при его
компиляции. Посмотрите, не возникает ли небольшой промежуток време-
ни, в течение которого злонамеренный код способен изменить исходный
код на диске перед чтением компилятором или загрузкой DLL вашим ко-
дом? Если да, защищайте каталог с этими файлами, используя в зависи-
мости от ситуации защиту по правам доступа кода или список управления
доступом (Access Control List, ACL) файловой системы.
Если вызывающий может воздействовать на генерируемый код, вызывая
ошибки компиляции, это тоже может угрожать безопасности.
Выполняйте сгенерированный код с наименьшими возможными разреше-
ниями (используя PermitOnly или Deny).

Защита на основе ролей: аутентификация и авторизация

Кроме защиты кода, в некоторых приложениях реализуется защита, огра-


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

Управление секретами

Секретность данных можно весьма эффективно обеспечивать, пока они


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

Шифрование и подписи

Пространство имен System.Security.Cryptography содержит богатый набор


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

Случайные числа

Если для системы защиты нужно генерировать случайные числа и требуется,


чтобы они были истинно случайными, используйте System.Security.Crypto-
graphy. RandomNumberGenerator Применение генераторов псевдослучайных
чисел приводит к их предсказуемости, чем может воспользоваться злоумыш-
ленник.

Проблемы установки

Здесь поясняется, как проверить процесс установки вашего приложения


или компонентов с целью гарантировать максимальную безопасность
установленного кода. Чтобы убедиться, что сама установка управляемого
или неуправляемого кода является безопасной, рекомендуется выполнить
следующие действия. Эти действия нужны на всех платформах, поддержи-
вающих NTFS.
1. Настройте систему на использование двух разделов жесткого диска.
2. Переформатируйте второй раздел; не изменяйте ACL по умолчанию в
корневом каталоге диска.
3. Установите свою программу, сменив каталог установки на каталог, соз-
даваемый во втором разделе.

Проверьте следующее.
1. Имеется ли код, выполняемый как служба или обычно запускаемый
пользователями с правами администратора и доступный для записи
кому угодно?
2. Если код устанавливался на терминальный сервер, работающий в ре-
жиме сервера приложений, могут ли одни пользователи записывать
двоичные файлы, а другие — их запускать?
3. Есть ли файлы в системном каталоге или его подкаталогах, доступные
для записи пользователям, отличным от администраторов?

Кроме того, если программа взаимодействует с Web, учтите, что при рабо-
те с некоторыми Web-серверами пользователям разрешается запускать ко-
манды, часто выполняемые в контексте учетной записи IUSR_MACHINE.
Убедитесь в отсутствии файлов и элементов конфигурации, доступных для
записи кому угодно, так как этим могут воспользоваться приложения, вы-
полняемые под гостевой учетной записью.
Кит Браун

Безопасный код

Обзор расширений
S4U Kerberos в Windows
Server 2003*

В этой рубрике рассматриваются новые расширения Kerberos,


которые получили общее название Service-for-User (S4U); они позволяют
разработчикам задействовать встроенную модель защиты Windows
с хорошо знакомой администраторам схемой авторизации на основе
членства в группах.

Нелегко создавать Web-сайты, работающие через корпоративный бранд-


мауэр. Обычно внешним пользователям нежелательно назначать учетные
записи корпоративного домена, а с чисто практической точки зрения ра-
бота Kerberos через Интернет неприемлема из-за типичной конфигурации
клиентских брандмауэров. Следовательно, сайт должен поддерживать ка-
кой-то механизм аутентификации и авторизации, отличный от встроен-
ных в Windows.

Для решения этой и других проблем, о которых я расскажу ниже, Microsoft


реализовала два расширения Kerberos для применения на серверах. Эти
расширения получили общее название Service-for-User (S4U); они позво-
ляют разработчикам задействовать встроенную модель защиты Windows с
хорошо знакомой администраторам схемой авторизации на основе членст-
ва в группах.

Публиковалось в MSDN Magazine/Русская Редакция. 2003. N° 4 (апрель). — Прим. изд.


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

В этой статье я исхожу из того, что у вас есть базовое представление о КегЬе-
ros. Такое представление можно получить из статьи «Exploring Kerberos,
the Protocol for Distributed Security in Windows 2000», опубликованной в но-
мере «Microsoft Systems Journal» за август 1999 г. (http://www.microsoft.com/
msj/0899/Kerberos/Kerberos.htrn). Более детальную информацию см. в книге
«Programming Windows Security» (Addison-Wesley, 2000). Спецификацию по
Kerberos см. в RFC 1510 на сайте IETF (http://www.ietf.org/rfc/rfcl5iO.txt).

Распознавание информации,
необходимой для авторизации
Первое расширение, о котором я расскажу, помогает серверу определить
группы, к которым относится пользователь в домене. Инфраструктура ав-
торизации Windows так усложнилась, что разработчику серверного ПО
стало практически невозможно определять эти группы вручную, Глобаль-
ные группы (global groups) задаются в основном домене (home domain)
пользователя, универсальные (universal groups) могут определяться в лю-
бом домене из основного леса (home forest) пользователя и хранятся в гло-
бальном каталоге, а локальные группы домена хранятся в домене сервера.
И все эти группы могут быть вложенными. Поскольку локальные группы
хранятся на сервере, с ними работать проще всего. Функция SIDHistory в
Windows 2000 приводит к тому, что для некоторых пользователей прихо-
дится неоднократно выполнять операцию раскрытия групп (group expan-
sion). А из-за другой функции, называемой карантином в домене (domain
quarantine), некоторые группы могут быть удалены. Короче, самостоятель-
ное определение членства доменного пользователя в группах — просто
кошмар для разработчика, и этого следует избегать.

Чтобы определить список групп, в которых состоит пользователь, лучше


всего организовать регистрацию и проверять получаемый маркер (token).
При аутентификации через Kerberos основную работу, выполняют кон-
троллеры домена. Единственная проблема в том, что для аутентификации
через Kerberos в интересах клиента серверу нужны основные удостовере-
ния защиты (primary credentials) клиента, т. е. его пароль или TGT (ticket-
granting ticket) и соответствующий сеансовый ключ (session key). Если бы
сервер знал пароль пользователя, то вот как просто было бы получить мар-
кер для клиента под именем Alice в домене sales:
HANDLE hToken;
tf (LogonUserfalice", "sales", alicesPassword, LOGON32_LOGON_NETWORK,
0, ihToken)) {
// hToken содержит маркер со всеми группами для Alice
I
Обзор расширений S4U Kerberos в Windows Server 2003 157

Очевидный недостаток — на сервере должен храниться пароль пользова-


теля, что резко снижает уровень безопасности.

S4U2Self
Решение, предлагаемое S4U, заключается в том, что сервер проходит че-
рез процедуру аутентификации Kerberos и регистрирует клиент, не пре-
доставляя его удостоверений. То есть на самом деле аутентификация кли-
ента не производится — сервер лишь получает идентификаторы защиты
(SID) для групп, в которых состоит клиент. Чтобы это было возможным,
контроллеры домена под управлением Windows Server 2003 теперь прини-
мают новый тип запросов Kerberos, в которых сервис запрашивает себе би-
лет (ticket) клиента, применяя для этого собственные удостоверения вме-
сто удостоверений клиента. Это расширение называется Service-for-User-
to-Self(S4U2Self).

Если сервис и клиент находятся в разных доменах, между ними требуется


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

Низкоуровневые детали всего этого весьма сложны, но, чтобы сделать пер-
вый шаг, разработчику сервиса достаточно вызвать всего одну функцию:
LsaLogonUser. По своему духу это похоже на показанный мною вызов
LogonUser, однако указывать пароль клиента здесь не требуется. В резуль-
тате возвращается маркер, который сервис может использовать с такими
функциями, как AccessCheck и CheckTokenMembership, а также с новым
семейством функций авторизации AuthZ. Это позволяет сервису прове-
рять права доступа по дескрипторам защиты (security descriptors) управ-
ляемых им объектов.

Для защиты клиента LsaLogonUser обычно возвращает маркер с особым огра-


ничением. Маркер будет соответствовать уровню олицетворения (imper-
sonation level) Identify, который означает, что сервису не разрешается откры-
вать объекты ядра, подменяя клиент по этому маркеру. Однако сервисам из
состава доверяемой вычислительной базы (trusted computing base, TCB),
например сервисам SYSTEM, LsaLogonUser вернет маркер с уровнем оли-
цетворения Impersonate, разрешающим доступ к локальным объектам ядра
по идентификации клиента. Это не дает недоверяемым сервисам поднимать
уровень своих локальных привилегий по билету S4U2Self.

Чтобы задействовать этот механизм, достаточно вызвать LsaLogonUser. Но


ее вызов непрост, так как она принимает 14 аргументов, в том числе указа-
тели на структуры переменной длины и описатели (handles) тех или иных
сущностей, которые должны быть предварительно открыты. В Microsoft
±58 Защита кода и данных

.NET Framework 1.1 (на момент написания статьи была в бета-версии)


включен новый конструктор класса Windows Identity, радикально упрощаю-
щий всю процедуру:

public Windowsldentity(string userPrincipalName);

На рис. 1 показан пример С++-кода, напрямую вызывающего LsaLogonUser


для запроса регистрации S4U2Self.

Рис. 1. Вызов LsaLogonUser


«define UNICODE
«define .UNICODE
«include <windows.h>
«include <ntsecapi.h>
Ktnclude <stdio.h>

((pragma comment{Hb, "seeur32.1ib")


.
// Простые функции обработки ошибок
void „err(const wcharjt* fen, DWORD err = Get Last ErrorO) {
wchar_t msg[256j;
If {!ForBa1;Message(fORMAT_MESSAGe_FF«JH_SYSTEM, 0, err,
0, rnsg, sizeof msg / sizeof *msg, 0)) (
wsprintffmsg, L"hr = ОхШХ", err);

*w printf(L"Xs failed: %&\n", fen, msg);


exitd);
1

void ,nterr(eonst wcnar_t* fen, NTSTATUS s) {


_err(fcn, UaNtStatLtsToWinErrDr(s));

void _checknterr( const wchar_t* fen, NTSTA74JS s) {


if (s) {
nterr(fcn, s);
}
else {
«printf(L"Xs succeededAn", fen);

//Вспомогательная фу^юция дня инициализации AKSI-строк


// Kernel на строковых литералов
void _si{LSA_STRI(JG- a, const char* b) {
a->Length - (USHORT)(strlen(b) * sizeof(*b»;
a- >Maxi mum Length = (USTORT}(a->Length + sizeof(*t>»;
a->Buffer = const Gast<char*>Cb);
}
см. след. стр.
Обзор расширений S4U Kerberos в Windows Server 2003 159

Рис. 1. Вызов LsaLogonUser (продолжение)

HANDLE _s4uLogon{eonst wchar_t* pszUserPrincipalNaiee) {


// Подключаемся к Local Security Authority,
// Это пеовойит получить маркер через S4U2Self
// (заметьте, у нас нет привилегий ТСВ, чтобы использовать
// этот маркер для доступа к объектам ядра),
HANDLE hlsa;
_qheeknte r r( L" LsaConoectUnt rusted " ,
tsaConnectynt rusted (Shlsa) ) ;

// проверяем индекс провайдера аутентификации Kerberos


LSA_ST8ING pkgHame;

ULONG authnPkg;
_cnecknterr(L"LsaLookupAuthenticatlonfackage",
LsaLookupAuthenticatioriPackageCnlsa,
&pkgName, &authnPkg));

const DWORD cchUPN = lstrlen<pszUserPrlncipalNMe);


WiORO cbUPN = cefcUPN * sizeof(wcnar_t);

// Для правильной работе LsaLogonUser


// KERB_S4U_L№UK надо передавать как один
// непрерывный буфер, содержащий все строки
const DWORD Cblogon = sizeof{KER8_S4U_LOGON) + cbUPN;
КЕРВ_84и_1»Ш1* s4uLogon =
( KERB_S4U_LOGON* )calloc< GbLogon , 1 ) ;
s4uLogon->«essageType * KerbS4ULogon;
s4uLogon->ClientUpn. Buffer «
<wchar_t*)((cNir*)s4uLogon + sizeof *s4ulogon);
<^3pyHeBiory(34uLogon»>ClientUpn.fiuffer,
psziiserPrinGipalNeHie, cbUPN);
84uLogon->CliestUpn, Length = (U^ORT)ebUPN;
s4uLoflon->ClientUpn,KaxiEiiueiLength -

// Эта информация копируется в возвращаемый маркер.


// SoyreeName - ASCII-буфер длиной В символов.
TGKEH_SOUJ!CE tokenSource;
AllQcateLoeally4JniqueId(&tokenSource.SourceIdentifier);
strcpyttokenSource.SojrceNarae, "test");
tSA_S"raiMG origirWame;
_8i<ftoriginNaBe, "ШМ S4U Logon Sanple1');

// Наконец, вызываем LsaLogonUser, запрашивая регистрацию


// s стиле Network через расширение S4U2Self
a
void* profile 0;
DWORD cbPrafile = Q;
LOIO logonld;
KAMDL€
см. след. стр.
160 Защита кода и данных

Рис. 1. Вызов LsaLogonUser (окончание)


QUOTAJJHITS quotsLifnits;
NTSTATUS siJbStattis;
_ehecKnterr(L" LsaLogonUser",
Lsal.og0ftUser(hlsa,
SoriginHarae,
Network,
authnPkg,
s4ul.ogon, cfaLogon,
0,
&tokenSource,
Aprofite, icbfrofile,
Uogonld,

SquotaLiitiits,
&sitb8tatus));

// Очистка
f ree(s4uLoflon);
LsaFree8eturaBuff6r( prof lie);
LsaClose(hlsa);

return htok;

void mairtO i
HANDLE htok s _84uLogon(L"aliceeesec,com");

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

В Windows 2000 введен механизм неограниченного делегирования (uncon-


strained delegation), позволяющий клиентам делегировать удостоверения за-
щиты определенным сервисам, помеченным как «доверяемые для делегиро-
вания» («trusted for delegation»). Под словом «неограниченное» в данном слу-
чае подразумевается, что сервис имеет право применять эти удостоверения в
Обзор расширений S4U Kerberos в Windows Server 2003 161

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

Ограниченное делегирование или S4U2Proxy


Реализация Active Directory в Windows Server 2003 значительно превос-
ходит версию из Windows 2000 и поддерживает более практичную мо-
дель ограниченного делегирования (constrained delegation). Идея в том,
что сервису по-прежнему можно делегировать удостоверения клиента, но
контроллеры домена гораздо избирательнее выдают такие удостовере-
ния. Например, если сервис А запрашивает билеты Kerberos для сервиса
В, используя делегированные удостоверения клиента, контроллер доме-
на сверяется со специальным списком, чтобы узнать, имеет ли сервис А
право делегировать удостоверения сервису В. Иначе говоря, администра-
тор сети может контролировать область действия делегирования.

AppServer Properties

wn ] :: Member Of
Delegation Managed By -' Diet-in

Irust this compute! for delegation to any service [Kerfcwes оф)

M*w и Computer ..J Poitj/Ss^Name i Dc-


rnssqlserver DataSefver.foo.com

Cancel

Рис. 2. Ограниченное делегирование

6-138
162 Защита кода и данных

Пример — СОМ+-сервис, в котором размещена бизнес-логика. Ему может


потребоваться доступ к удаленной базе данных по удостоверениям клиен-
та. Администратор вправе выдать данному СОМ+-сервису привилегии
ограниченного делегирования, разрешив ему делегировать удостоверения
только сервису базы данных. На рис. 2 показано, как настроить такую схе-
му, используя оснастку Active Directory Users and Computers. Здесь СОМ+-
сервис выполняется под встроенной учетной записью Network Service на
компьютере AppServer, а удаленная база данных расположена на компьюте-
ре DataServer. Сравните это с реализацией в Windows 2000, где единствен-
ный выбор заключался в установке или сбросе флажка, разрешающего не-
ограниченное делегирование (рис. 3).

LA-KEITH Properties

Вепиа! Operating System j MembetQf ] Lacatioi'j Managed By!

1|JL

Tsust спщрйе Idf delegation


\ This seeuiitji-sehsitive option means thai services

Рис. З. Неограниченное делегирование

Согласно RFC 1510, Kerberos поддерживает механизм ограниченного де-


легирования, известный как билет прокси (proxy ticket), но клиент должен
запрашивать эти билеты в интересах сервиса. Microsoft-расширение для
Kerberos — S4U2Proxy (Service-for-User-to-Proxy) — позволяет сервису
получать билеты от имени клиента, исходя из конфигурационных пара-
метров Active Directory. Благодаря S4U2Proxy сервис может получать би-
леты для другого сервиса (прокси) в интересах пользователя. Это значит,
что клиент должен доверять каталогу (и его администраторам), посколь-
ку он больше не контролирует то, как делегируются его удостоверения.

С технической точки зрения, S4U2Proxy сильно отличается от обычного


нрокси-делегирования Kerberos тем, что в стандартном Kerberos для полу-
Обзор расширений S4U Kerberos в Windows Server 2003 165

Билеты S4U2Proxy недействительны, если путь от клиентского домена к


домену целевого ресурса проходит более чем через два леса. Причина это-
го в том, как работает механизм межлесовой фильтрации идентификато-
ров защиты (cross-forest security identifier filtering).

Как я уже говорил, LsaLogonUser функционирует по-разному в зависимо-


сти от наличия у вызывающего привилегии ТСВ. Если до вызова Lsa-
LogonUser сервису выдана привилегия ТСВ, функция вернет маркер, по
которому можно получать доступ к локальным ресурсам компьютера, ина-
че это будет запрещено в маркере на уровне олицетворения. Здесь безраз-
лично, установлен ли флаг forwardable, так что технически вы можете по-
лучить маркер, который можно олицетворять и применять для делегиро-
вания удаленным сервисам, но при олицетворении вам будут недоступны
все локальные объекты ядра. Пример — сервис, выполняемый как NET-
WORK SERVICE (в противоположность SYSTEM), которому выдано
право на ограниченное делегирование.

Обращаю ваше внимание: для краткости я говорил, что сервису должны


быть выданы те или иные привилегии. Под этим я подразумевал, что при-
вилегии должны быть присвоены учетной записи, под которой выполняет-
ся этот сервис. Так, если сервис работает под учетной записью Bob, приви-
легии следует выдать именно Бобу. Если сервис выполняется как NET-
WORK SERVICE или SYSTEM, он выступает от имени самого компьютера,
и тогда соответствующие привилегии должны быть назначены учетной за-
писи этого компьютера.

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

Кит Браун (Keith Brown) работает в DevelopMentor как исследователь, техни-


ческий писатель и преподаватель. Разъясняет программистам концепции
безопасного кода. Автор книги «Programming Windows Security» (Addison-Wesley,
2000). С ним можно связаться через http://www.develop.com/kbrown.
Уэйн Берри

Новые средства 115


надежно защищают
информацию и серверные
процессы*

В процессе развития MS основное внимание уделялось совершенствованию


его защиты. IIS 6.0 — часть Windows Server 2003 — обладает улучшенной
защитой, и, кроме того, изменен подход к его настройке. Новые средства MS,
в том числе MS LockDown, как никогда раньше упрощают защиту вашего
сервера от атак. В статье объясняется, как и зачем завершать работу служб
с помощью MS LockDown. Автор также рассматривает TCP/IP-фильтрацию,
с помощью которой можно ограничивать доступ к портам, управление
обслуживанием файлов по их расширениям, новую функциональность
для SSL, применение URLScan и многое другое.

Вы наверняка заметили, что Microsoft прилагает очень много усилий в об-


ласти безопасности. На это указывают недавно выпущенные инструмен-
тальные средства для IIS 5.0 и переработанная архитектура защиты в IIS
6.0, поставляемом с Windows Server 2003. Безопасность — широкое поня-
тие, включающее аутентификанию, авторизацию, шифрование и защиту от
попыток проникновений. Хотя предыдущие версии IIS поддерживали
шифрование и аутентификацию, защита в других сферах, например от
проникновений злоумышленников, атак типа «отказ в обслуживании»
(denial of service) и Web-вирусов, возлагалась на брандмауэры (firewalls).

Публиковалось в MSDN Magazine/Русгкая Редакция. 2002. № 3 (сентябрь). — Прим. изд.


Новые средства IIS надежно защищают информацию и серверные процессы 167

В IIS 6.0 и новых средствах для IIS 5.0 особое внимание уделяется защите
Web-сервера от атак. Об аутентификации рассказывал Джеф Просиз (Jeff
Precise) в своей статье «ASP.NET: An Introductory Guide to Building and
Deploying More Secure Sites with ASP.NET and IIS», состоявшей из двух
частей (см. номера «MSDN Magazine» за апрель и май 2002 г.). В этой ста-
тье рассматриваются предотвращение проникновений, URLScan, IIS Lock-
Down для IIS 5.0, новые средства IIS 6.0, связанные с защитой, а также
функциональность Web-сервера, запланированного к выпуску вместе с
Windows Server 2003.

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

Первый шаг в пресечении попыток проникновения — ограничение круга лиц,


имеющих права на размещение файлов в корневом каталоге Web-сервера; для
запуска помещенной туда ASP-страницы не требуются никакие дополнитель-
ные разрешения. С той же целью следует запретить посторонним лицам за-
грузку файлов через HTTP, WebDAV или расширения FrontPage. Кроме то-
го, надо блокировать злоумышленникам доступ к специальным файлам, в том
числе к системным файлам Windows, и запретить их изменение.

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


ников выдавать запросы вне области Web-сайта. Например, запрос IDC-
файла (еще один тип динамического файла) в отсутствие таких файлов на
Web-сервере подскажет хакеру, что он может найти способ поместить ту-
да IDC-файл и что, поскольку сейчас эти файлы не используются, адми-
нистратор скорее всего не защитился от них должным образом. Записав на
сервер свой IDC-файл, хакер может запустить этот файл. Вы должны пре-
дусмотреть защиту и от атаки, которая заключается в передаче через
HTML-форму фальшивых данных, способных изменить выполнение кода,
обрабатывающего такие формы.

Иногда злоумышленники атакуют компьютеры, обращаясь к неотслежи-


ваемым службам, выполняемым на сервере. Пример — сервер домена.
168 Защита кода и данных

на который по умолчанию установлен IIS, хотя его никто не планировал


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

Вот что однажды произошло со мной. Один мой приятель должен был пе-
редать мне файл по FTP. Я запустил FTP-сервер, установленный вместе с
IIS 5.0, и разрешил привилегии записи для анонимного пользователя. Во-
обще-то я знал, что тем самым я прямо-таки напрашиваюсь на неприятно-
сти, но приятель сказал, что он немедленно закачает файл. И сделал это —
только через три дня, а за это время два типа закачали на мой сервер пол-
нометражные фильмы и бесплатно использовали мой канал связи и место
на сервере. Хотя на моем жестком диске достаточно места для хранения
этих фильмов, хакер вполне мог бы чем-нибудь заполнить весь диск и по-
ставить сервер на колени. (Не говоря уже о том, что я стал распространи-
телем незаконной продукции.)

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


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

Не забывайте, что мой сайт совсем невелик — он даже не среднего разме-


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

Приятно, конечно, что функциональность Web-сервера устанавливается


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

Защита IIS 5.0


Недавно Microsoft выпустила два новых инструмента, которые упрощают
администраторам IIS 4.0, 5.0 и 5.1 защиту серверов от проникновения: IIS
LockDown и URLScan. В мастер IIS LockDown Wizard встроен интерфейс,
позволяющий защищать сервер за счет установки URLScan, изменения па-
раметров метабазы и отключения неиспользуемых служб. URLScan отсле-
живает входящие запросы URL и отсекает те из них, которые адресованы
файлам вне области Web-сайта.

Мастер IIS LockDown в интерактивном режиме определяет, какие имен-


но функции должны работать (рис. 1).
Новые средства MS надежно защищают информацию и серверные процессы 169

*Ч Internet Inforni.ilion Servites Lui.kijuwii Wt?artl

Select Seivei template


You can easily configiHe Ihis server by selecting Ihe template thai most closely
matches its rote.

SmalBusiness Server 2000


Exchange Server 5.5 (0 Ли*. Web Access]
Exchange Server 2000 (OWA,PF Management, IM, SMTP, NNTP]
SharePoint Portal Server

Commeice Seiver 20CD

Static Web server


Dynamic Web server [ASP enabledj
Other [Server that does not match any of the listed role
Iha* rliw: nn) im-itr» \K

Kent:

Рис. 1. Серверные шаблоны в LockDown Wizard

Если вас беспокоит уязвимость расширений FrontPage, WebDAV или воз-


можность несанкционированного запуска опасного сценария, изменяющего
содержимое вашего Web-сервера, IIS LockDown запретит запись в Web-ка-
талоги по анонимной учетной записи и отключит WebDAV (рис. 2). Адми-
нистраторы, использующие фиксированный список динамических расшире-
ний (скажем, только ASP-файлы), обнаружат, что IIS LockDown удаляет все
IDC-, STM- и PRINTER-файлы, а также другие типы динамических сцена-
риев. Кроме того, IIS LockDown может восстановить исходные параметры
защиты метабазы IIS и ACL (Access Control List) файловой системы, на-
страиваемые при установке по умолчанию, и создать резервную копию ме-
табазы перед установкой на тот случай, если ваш Web-сайт окажется нера-
ботоспособным с новыми параметрами защиты.
В IIS 5.0 HTTP- и SMTP-серверы устанавливаются по умолчанию, а уста-
новка FTP- и NNTP-серверов предлагается как дополнительный вариант;
с помощью мастера IIS LockDown все эти службы можно настроить на за-
пуск вручную. Так как серверы, где эти службы запущены, но не исполь-
зуются, могут быть атакованы кем угодно — от спамеров, использующих
SMTP-сервер как открытый сервер ретрансляции, до хакеров, ищущих
свободное дисковое пространство на вашем FTP-сервере, — то незадейст-
вованные службы полезно отключать.
URLScan — это ISAPI-фильтр, который отслеживает все входящие запро-
сы и проверяет, не являются ли они попыткой атаки на систему (напри-
мер, вируса Code Red или Nimda). Обычно хакеры пытаются переполнить
170 Защита кода и данных

буфер Web-сервера, чтобы получить доступ к операционной системе, или


заставить этот сервер запустить либо как-то иначе манипулировать фай-
лами, доступ к которым запрещен.

4f£ internet Information Services Lorkdowri Wiza

Applying Security Setting*


Please wait while the n«ad apple; the security settings you selected

..:

Dergiing exfetute permission tot system «liitajs toanarjienoususa'accewk.

i У I , '

Рис. 2. Отключение WebDAV

Переполнение буфера возникает, когда происходит обращение к памяти за


пределами выделенного для задачи диапазона. Это может случиться, если
программист не проверяет, помещаются ли переданные программе данные
в отведенный им объем памяти. Так как многие приложения выделяют па-
мять под URL, для переполнения буфера обычно посылаются длинные
URL. Чтобы защитить приложения, которые могут получать такие данные
(IIS, ASP, SSL, пользовательские ISAPI-фильтры и т.д.), URLScan отбра-
сывает URL, длина которых превышает некую заданную величину. Как и
остальные параметры URLScan, максимальная длина URL задается адми-
нистратором.
URLScan также блокирует применение в запросах символов, отличных от
ASCII. Обычно такие символы посылаются на сервер сразу после перепол-
нения буфера и представляют собой машинный код, который по замыслу
хакера должен выполнить Web-сервер. Запретив прием таких символов,
вы оградите свой сервер от атак этого типа.
URLScan позволяет администратору Web-сервера ограничивать размер за-
просов, обрабатываемых сервером, и размер ответов. Первое дает возмож-
ность предотвратить переполнение буфера, а второе — защититься от пе-
регрузки системы (еще один тип атак).
Новые средства IIS надежно защищают информацию и серверные процессы 171

URLScan можно настроить на запись в журнал всех запросов, нарушаю-


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

В URLScan есть параметр, который позволяет возвращать пользователь-


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

Кстати, один из самых простых способов для внутренних злоумышленников


проникнуть в вашу систему — совместное использование общих ресурсов на
уровне дисков, настраиваемое по умолчанию. Например, с$ — скрытый об-
щий ресурс на каждом компьютере под управлением Windows NT и Win-
dows 2000. Обратиться к такому ресурсу можно по UNC-пути: \\имя_маши-
ны\с$. Поэтому после установки операционной системы настоятельно реко-
мендуется отключать подобные общие ресурсы.
-
Что нового в IIS 6.0

В защиту ITS 6.0 внесено три основных изменения, которые уменьшают


риск проникновения. При установке Windows Server 2003 IIS устанавли-
вается, только если вы этого требуете. По умолчанию IIS обслуживает
лишь статические файлы. Для обслуживания динамических файлов нуж-
на специальная настройка. Кроме того, по умолчанию IIS 6.0 полностью
закрыт (locked down), но для разрешения работы приложений предостав-
ляется простой в применении интерфейс. Другое усовершенствование в
защите связано с новой архитектурой обработки запросов в IIS 6.0, вклю-
чающей многократное использование процессов (process recycling). Адми-
нистратор может легко устанавливать большинство обновлений защиты
IIS и новые DLL рабочего процесса, не прерывая работу сервера. К допол-
нительным средствам изоляции относятся управление полосой пропуска-
ния и распределением процессорного времени, а также рециклинг на уров-
не памяти (memory-based recycling). Эта функциональность создает среду
для независимой защиты нескольких сайтов на одном Web-сервере.

В IIS 6.0 способ регистрации по умолчанию при базовой и анонимной ау-


тентификации изменен HaNETWORK_CLEARTEXT. В предыдущей вер-
сии IIS таким способом был INTERACTIVE. Таким образом, пользовате-
ли больше не имеют привилегий на интерактивную регистрацию, благода-
ря чему Web-серверы могут работать на контроллерах домена с гораздо
меньшим риском.
£72 Защита кода и данных

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


го утилитами командной строки. IIS 6.0 блокирует запуск таких программ.

Получив доступ к Web-сайту, хакеры часто удаляют его контент. Б US 6.0


предусмотрена защита контента от записи, и анонимные Web-пользовате-
ли не смогут перезаписывать информационное наполнение сайта. Ограни-
чения на закачивание данных позволяют администраторам лимитировать
данные, которые злоумышленник может загрузить на сервер. Таймауты,
ранее слишком длительные, в IIS 6.0 более «агрессивны», а рабочий про-
цесс теперь распознает переполнение буфера и завершает соответствую-
щую программу. Более того, ядро сервера, прежде чем передавать запрос
обработчику расширения ISAPI, проверяет имеется ли нужный контент.

На одном Web-сервере все чаще запускается множество приложений или


сайтов. Это налагает на Web-сервер определенные ограничения. Например,
если провайдер размещает на одном Web-сервере сайты двух компаний
(возможно, конкурирующих), эти сайты должны быть полностью изолиро-
ваны друг от друга. Такой уровень изоляции в IIS 6.0 предоставляется че-
рез конфигурируемую идентификацию (configurable identity) рабочего про-
цесса, управление полосой пропускания и распределением процессорного
времени, а также рециклингом на уровне памяти.

Рабочий процесс запускается под новой учетной записью с ограниченны-


ми привилегиями — NetworkService. Один из важнейших принципов безо-
пасности — выдавать учетным записям лишь необходимый для работы по
ним минимум привилегий. Так как у NetworkService очень низкий уровень
прав в операционной системе, у несанкционированно запущенных прило-
жений меньше шансов воспользоваться прорехами в защите. Кроме того,
в Windows Server 2003 введена фильтрация портов на уровне TCP/IP.

Windows Server 2003


Продукты из семейства Windows NT Server уже давно используются в ка-
честве файл-серверов, и лишь с недавних пор они стали и надежными
Web-серверами. IIS в Windows 2000 выпусков Server и Enterprise устанав-
ливается автоматически. В зависимости от лицензии IIS при установке
Windows Server 2003 по умолчанию либо полностью блокируется (подроб-
нее об этом режиме — чуть позже), либо вообще не устанавливается.
То есть, если вам*нужен почтовый сервер, SQL сервер или просто опера-
ционная система, самостоятельно удалять US для большей безопасности
не требуется. IIS поставляется со всеми версиями Windows Server 2003 и
устанавливается отдельно.
Новые средства IIS надежно защищают информацию и серверные процессы 173

Кроме того, IIS отключается при обновлении сервера до Windows Server


2003, и при необходимости его нужно включать вручную. Это сделано спе-
циально, чтобы не допустить случайного переноса сомнительных настро-
ек из предыдущих версий в новую. Администраторы доменов могут запре-
щать пользователям установку IIS на компьютерах с Windows Server 2003.

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

В IIS 5.0 удаленно управлять SSL-сертификатами нельзя, так как храни-


лище сертификатов провайдеров криптографических сервисов (CSP) не
предусматривает удаленного доступа. CertObject (новшество IIS 6.0) по-
зволяет администраторам управлять сотнями или даже тысячами серверов
IIS с помощью SSL-сертификатов.
При включении SSL производительность заметно падает, так как шифро-
вание требует значительных процессорных ресурсов. В дополнение к стан-
дартным сервисам CryptoAPI вы можете подключить к системе собствен-
ный программный или аппаратный CSP. Аппаратные средства снимают с
процессора нагрузку, связанную с шифрованием, и увеличивают произво-
дительность сервера.

ТСР/1Р-фильтрация
Другое средство защиты — TCP/IP-фильтрация портов в рамках сетевой
конфигурации операционной системы. Данный термин в определенной
мере неправилен, так как это средство фильтрует TCP, IP и UDP. Однако
TCP/IP-фильтрация действительно повышает безопасность Web-сервера.

В Windows 2000 при назначении IP-адреса любое приложение, запущен-


ное в ящике IIS (IIS box), способно отслеживать любой порт на этом IP-
адресе, т. е. некое приложение или служба может использовать порты без
вашего ведома. Пример такого приложения — интерфейс Web-админист-
ратора, доступ к которому осуществляется через порт 8080. Иначе говоря,
какое-либо лицо с внутренней стороны брандмауэра или даже приложение
в самом ящике IIS могло бы через порты открыть этот ящик для атак.

Windows Server 2003 позволяет ограничивать число открытых портов и


тем самым снизить вероятность атак этого типа. Необходимые параметры
устанавливаются в Internet Protocol (TCP/IP) Settings в разделе Local
174 Защита кода и данных

Area Network Settings. Щелкните кнопку Advanced и перейдите на вклад-


ку Options. Выберите TCP/IP Filtering, затем щелкните кнопку Properties.
Появится диалоговое окно TCP/IP-фильтрации, где вы указываете фильт-
руемые порты (рис. 3).

Рис. 3. ТСР/1Р-фильтрация

Здесь вы можете соблазниться выбрать Permit Only и задать порт 80, огра-
ничив трафик исключительно Web-запросами. Но тогда вы не сможете пе-
ресылать файлы на этот компьютер, отправлять и принимать электронную
почту, запускать FTP-сервер и т. д. Прежде чем закрывать все порты, сто-
ит провести небольшое исследование. К призеру, если вы используете SSL
и FTP-сервер, вы должны открыть порты 443 и 20*. Так как вам нужен
доступ к данному компьютеру по TCP/IP, эти порты следует добавить не
только в столбец TCP, но и в столбец UDP Если вы хотите получать с
ASP-страницы имя базы данных SQL Server по имени компьютера, на ко-
тором она находится, например:

Set оСопп = Server. CreateObject("AOODB. Connection")
oConn.Open "Trusted_Connect ion-yes; drive r=SQL
Server; serve r=SQLMACHINE001;database=NyData; "

то откройте порты WINS: 137, 138, 139 (UDP). А чтобы WebTrends могла
выполнять обратную DNS-трансляцию применительно к файлам журнала,
откройте стандартные порты DNS: 42 (UDP) я 53 (TCP/UDP). Независи-
мо от того, сколько времени займет определение нуждающихся в защите
портов, лучше всего выбрать Permit Only и найти эти порты автоматиче-
ски. Список стандартных портов приведен в табл. 1.

Вам также понадобится порт 21. — Прим. перев.


Новые средства US надежно защищают информацию и серверные процессы 175

Такие функции не заменяют брандмауэр. В отличии от ТСРДР-фильтров


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

При TCP/IP-фильтрации ограничения на порты можно применять к каж-


дому сетевому адаптеру по отдельности. Это удобно, если у вас, например,
два сетевых адаптера, один из которых подключен к внутренней сети и ис-
пользуется для доступа к внутренним ресурсам вроде SQL Server, DNS,
WINS и Domain Authentication, а второй — соединен с брандмауэром и
применяется для Web-запросов.
Табл. 1. Некоторые стандартные ТСР/1Р-порты

Порт Протокол

21 FTP
23 Telnet
25 SMTP
53 DNS
68 DHCP
70 Gopher
80 HTTP
110 POP3
119 NNTP
135 RPC, DNS, DHCP
139 Входящие SMB-сеансы
143 ШАР
389 LDAP
443 HTTP (SSL)
445 Исходящие SMB-сеансы
1433 Microsoft SQL Server
3389 , Microsoft Terminal Services
8080 HTTP Alternate

Другие усовершенствования в защите


В Windows Server 2003 в качестве механизма аутентификации для IIS 6.0
встроена поддержка Passport. Как только пользователь прошел аутентифи-
кацию по Passport, его удостоверения (учетные данные) могут быть ото-
бражены на Active Directory локального домена. Local Security Authority
(LSA) создает для пользователя маркер (token), применяемый IIS для
176 Защита кода и данных

НТТР-залроса. Приложения и Web-сайты могут задействовать эту модель


защиты для локальной авторизации пользователей. Новая функциональ-
ность Windows Server 2003 — Constrained Delegation (я расскажу о ней
чуть позже) — позволяет приложениям делегировать соответствующие
удостоверения.

Благодаря URL-авторизации IIS 6.0 можег выполнять авторизацию по


конкретному URL. Чем важна эта новая функция? В IIS 5.0 для принятия
решения об авторизации применяются ACL. Проблема с этим механизмом
в том, что он привязан к объектам, блокируя доступ к отдельным файлам
и каталогам, и подогнан под требования файловой системы NTFS. А боль-
шинство корпоративных Web-приложений решает бизнес-задачи и привя-
зано к конкретным задачам или операциям. Новая модель авторизации в
Windows Server 2003 как раз и отвечает этим требованиям. URL-автори-
зация IIS 6.0 в сочетании с диспетчером авторизации в Windows Server
2003 позволяет Web-приложению управлять, из единого хранилища поли-
тик доступом к URL, которые используются этим приложением и соответ-
ствуют специфике его задач и операций.

При авторизации по URL, а не ACL вы должны убедиться, что у процесса


есть нужные права. Они выдаются через механизм, называемый делегиро-
ванием (delegation). Делегирование заключа ется. в разрешении приложе-
ниям действовать в сети от лица пользователя. Например, Web-служба в
интрасети собирает информацию от нескольких серверов предприятия и
через HTTP предоставляет пользователю консолидированный отчет в ви-
де Web-страницы. Этот подход особенно удобен при разработке пакета
прикладных программ для развертывания в Windows.

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


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

Эти проблемы решает такая функциональность Windows Server 2003, как


ограниченное делегирование (constrained delegation). Администраторы до-
мена могут разрешить делегирование только компьютерам и службам из
ограниченного списка. Делегирование этого вида работает независимо от
протокола аутентификации пользователей на сайте.
Новые средства HS надежно защищают информацию и серверные процессы 177

Подводя итоги
IIS 6.0 устанавливается в режиме полной блокировки (lockdown mode) и
не обслуживает никакие страницы за исключением тех, которые вы указы-
ваете явно. Для статических файлов и динамических расширений эта
функциональность реализуется по-разному. Статическим файлам не нуж-
но выполнять код в ответ на запрос. К ним относятся графические файлы,
HTML-страницы и каскадные таблицы стилей. А динамические файлы
(страницы ASP и Cold Fusion), напротив, требуют выполнения.

Страницы с расширениями вроде .htm, .html и .gif обслуживаются, только


если они перечислены в MIME Map — новом параметре настройки мета-
базы ITS, не связанном с сопоставлениями MIME-типов и расширений
файлов в операционной системе. Расширения добавляются через новую
страницу свойств IIS 6.0; часто используемые типы внесены в список по
умолчанию.

Параметр, позволяющий выбирать, какие файлы могут посылаться клиен-


ту для просмотра, весьма полезен. Рассмотрим его использование на при-
мере. Допустим, вы не хотите, чтобы клиенты могли просматривать вклю-
чаемые файлы (include files). Пусть файл default.asp включает файл sql.inc.
Если бы злоумышленник узнал о файле sql.inc, он смог бы напрямую по-
слать Web-серверу запрос на этот файл и получить его текст, И если в нем
содержится имя пользователя и пароль для доступа к базе данных SQL
Server, пиши пропало. В IIS 6.0 таких проблем нет: если вы явно не ука-
жете, что файлы с расширением .inc надо обслуживать, злоумышленник не
сумеет запросить их. Здесь возникает другой интересный момент. Для пра-
вильной работы включаемых файлов не требуется добавлять их расшире-
ния в MIME Map. Они будут работать и так, но клиент не сможет полу-
чать их текст.

После стандартной (по умолчанию) установки US файлы с динамическими


расширениями не компилируются, не запускаются и не обслуживаются.
Чтобы такие файлы обслуживались, их расширения надо внести в список
Web Service Extensions. Это блокирует запросы к страницам с незащищен-
ными динамическими расширениями. Таким образом, чтобы ASP-страницы
работали после обновления до IIS 6.0, вам придется добавить в этот список
расширение .asp. Но, прежде чем добавлять это расширение, убедитесь, что
запуск страниц на вашем компьютере безопасен. Например, нельзя разре-
шать кому попало помещать ASP-страницы в корень Web-сайта. Кстати, за-
метьте, что при использовании IIS LockDown, поставляемого с IIS 5.0, ва-
ши действия прямо противоположны: там вы запрещаете расширения,
здесь — включаете (и можете запрещать, когда они больше не нужны).
178 Защита кода и данных

Интересно отметить, что при запросе файла с запрещенным расширением


IIS 6.0 возвращает браузеру ошибку «404 file not found», а не «403 access
denied», даже если такой файл есть. Это ограничивает объем информации,
доступной потенциальным злоумышленникам, и держит их в неведении
относительно запрещенных расширений.

Заключение
В новом IIS 6.0 ответственность за защиту перекладывается с поставщика
программного обеспечения на плечи администраторов. Теперь именно они
решают, какие службы включать, а какие — отключать. Усилия, приложен-
ные к обеспечению безопасности IIS 6.0, делают его более защищенным, а
инструменты вроде IIS LockDown пред ос таил я ют администраторам пока
не обновленных Web-серверов некоторые преимущества IIS 6.O.

Уэйн Берри (Wayne Berry) — архитектор таких продуктов компании XCache


Technologies, как XCache, XCompress и XTune. Бывший инженер по проекти-
рованию (design engineer) в Microsoft, основатель «15 Seconds» и ASP-раз-
работчик. Имеет опыт в проектировании и разработке ПО, онлайновом
бизнесе и консалтинге по вопросам производительности.
Кит Браун

Авторизация

Ролевая защита на основе


Authorization Manager
в .NET-приложениях
на промежуточном уровне*

Authorization Manager в Windows Server 2003 существенно улучшает


управление защитой на основе ролей, делает ее более масштабируемой,
гибкой и простой в реализации. Используя Authorization Manager, вы можете
определять роли и задачи, которые можно выполнять по этим ролям.
Вкладывая роли друг в друга, можно обеспечить наследование характеристик;
кроме того, разрешается определять группы приложений. Authorization
Manager позволяет применять сценарии (scripts) для динамического изменения
полномочий и ^обертывать» логику защиты в политику безопасности, которая
хранится в Active Directory. Authorization Manager также предоставляет простой
API для проверки прав доступа. Автор рассматривает все эти возможности
и демонстрирует их применение на рабочем примере.

Ролевая защита реализована на платформе Windows, начиная с первой


версии Windows NT. На основе ролей операционная система может опре-
делять привилегированность процесса, проверять контекст защиты для
группы BUILTIN\Administrators. Операционная система исходит из этой
логической роли, когда принимает решение, можно ли, например, разре-
шить вам установку служб или драйверов устройств. При установке опе-
рационной системы вы должны определить пользователей, которым на-
значена эта роль, добавив их в группу Administrators.

Публиковалось в MSDN Magazine/Русская Редакция. 2003. № 11 (ноябрь). — Прим. изд.


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

При введении Microsoft Transaction Services (MTS) и СОМ+, в том числе,


была предпринята попытка сделать ролевую защиту более привлекатель-
ной для прикладных разработчиков, создав простую инфраструктуру
ролевой авторизации для СОМ-серверов. Целью было создание модели
доверяемых подсистем (trusted subsystem model) для многоуровневых сер-
верных приложений, где серверные ресурсы доверяют серверу приложе-
ний авторизацию запросов. Благодаря авторизации на ранних этапах необ-
ходимость в делегировании клиентских удостоверений серверам отпадала.
Делегирование связано с массой проблем — от потенциальной уязвимости
защиты до плохой масштабируемости.

Если вы искали универсальное решение для авторизации на промежуточ-


ном уровне (middle tier), то вполне вероятно, что ваш поиск завершен.

Знакомство с Authorization Manager


Authorization Manager (часто называемый AzMan) — новая универсальная,
основанная на ролях архитектура защиты в Windows. AzMan не привязан к
СОМ+, поэтому его можно использовать в любом приложении, которому
требуется ролевая авторизация, в том числе в Web-приложениях ASP.NET
или Web-сервисах, клиент-серверных системах на базе .NET Remoting и т. д.
На момент написания этой статьи Authorization Manager доступен только в
Windows Server 2003 и Windows 2000 Service Pack 4; в дальнейшем он бу-
дет включен в состав очередного пакета обновлений для Windows XP.

AzMan состоит из двух частей: исполняющей среды и административного


Ш. Исполняющая среда, файл которой называется AZROLES.DLL, пре-
доставляет набор СОМ-интерфейсов, используемых приложениями, кото-
рым нужна ролевая защита. Административный Ш — это ММС-оснастка,
для запуска которой запустите файл AZMAN. MSC или добавьте оснастку
Authorization Manager к ММС-консоли. (Заметьте, что административный
Ш не работает на более ранних платформах, поэтому для управления
AzMan вам потребуется компьютер с Windows Server 2003.) Первое, что вы
увидите после запуска административной утилиты AzMan (рис. 1), — это
то, что она гораздо сложнее СОМ+. Теперь у нас есть не только роли и их
назначения; вам также доступны низкоуровневые операции, которые мож-
но назначать задачам, а те в свою очередь — ротям. Задачи могут включать
другие задачи, то же самое относится к ролям. Такой иерархический под-
ход позволяет охватить практически безграничный набор ролей, нужных
в современных сложных приложениях.

Теперь о создании задач и ролей. Проектировщик приложения определяет


набор низкоуровневых операций, требующих защиты. После этого он опре-
деляет набор задач, соответствующих этим операциям. Задачи должны быть
Ролевая защита на основе Authorization Manager 181

понятны бизнес-аналитикам, поэтому конкретная задача всегда состоит из


одной или нескольких низкоуровневых операций. Если пользователю вы-
дается разрешение на выполнение какой-либо задачи, ему разрешается вы-
полнение и всех операций в задаче. Например, задача Submit Purchase
Order («отправить заказ на покупку») может состоять из операций: Ac-
quire Next PO Number («получить номер следующего заказа»), Enqueue
РО («поставить заказ в очередь») и Send Notification («отправить уведом-
ление»). Конечно, одну задачу можно всегда сопоставлять одной операции
и обеспечить максимальную простоту, но при необходимости поддержива-
ется разделение задач и операций.

Authorization Manager
ReadCataloo,
Groups PlaceHold
Corporate Library Application
CheckOutBook
Gil Groups
ChecUnBook
-j (Tyj Definitions
Ad dBookTolnventory
RemoveBookFromlnventory Qpsration
Task Definitions
ШШ=йЙЙйНйй*
ИРРИИДИЯ!
Ш-Щ Rote Assignments

eaoHrstoryForSel boolean variable indicates the special


case of a user allempiing to read her own history

Рис. 1. Administration Manager

Определив задачи и операции, можно приступать к кодированию и доба-


вить вызовы исполняющей среды AzMan при каждом выполнении защи-
щаемых операций. Вызывать следует lAzClientContext.AccessCheck; при-
мер такого вызова я приведу чуть позже.

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


хранилище AzMan как часть Active Directory или просто как XML-файл,
а затем устанавливает базовые низкоуровневые операции и задания. Адми-
нистратор с помощью оснастки AzMan просматривает определения и опи-
сания задач приложения. Затем определяет роли, имеющие смысл в его
132 Защита кода и данных

организации. Задача состоит го набора низкоуровневых операций, а роль


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

До сих пор я принимал во внимание только разработчика приложения и


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

Приложение-пример: корпоративная библиотека


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

Первым делом необходимо составить список защищаемых операций:


• чтение каталога;
• заказ книги;
• отметка о выдаче книги;
• отметка о возврате книги;
• добавление книги в хранилище;
• удаление книги из хранилища;
• просмотр истории операций («листка учета») читателя.

Заметьте, что некоторые операции зависят от информации, доступной


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

Если вы хотите опробовать описанное далее, запустите AZMAN.MSC и убе-


дитесь, что вы находитесь в режиме разработки, выбрав Action | Options. Соз-
Ролевая защита на основе Authorization Manager 183

дайте новое хранилище в XML-файле, а затем создайте в хранилище новое


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

Оснастка AzMan работает в двух режимах: разработки и администрирова-


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

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


вым операциям, чтобы администраторы могли легко определить роли. Так
как список операций прост, можно определить одну задачу для каждой
операции. Есть веская причина на то, чтобы без абсолютной необходимо-
сти не усложнять себе жизнь, и она связана со сценариями бизнес-логики,
но об этом позже, На рис, 2 показано, как выглядит AzMan при редакти-
ровании определения задачи.

Oead patron diitory Definition Риоре

-t' 1 3 ' 'K-in>. ' i « - ' •


,_.

Рис. 2. Определения задач в AzMan

Хранилище
А сейчас вообразите себя администратором, развертывающим приложе-
ние. Переключитесь в режим администрирования через Action | Options и
184 Защита кода и данных

обратите внимание на изменение UI: теперь редактировать низкоуровне-


вые операции для приложения запрещено. Добавьте роли для приложения,
приведенные на рис. 3.

Patron Clerk Manager


Browse catalog Browse catalog Browse catalog
Place hold Place hold Place hold
Read patron history Read patron history Read patron history
Check out book Check out book
Check in book Check in book

Рис. З. Роли и задачи

Один из способов облегчить себе жизнь в нашем случае — воспользовать-


ся вложением ролей. Например, роль Clerk можно определить через роль
Patron, добавив задачи «Check out book» («запись о выдаче книги») и
«Check in book» («запись о возврате книги»), как показано на рис, 4. По-
пробуйте-ка сделать это в СОМ+!

Groups
Q Corporate Library Appkatlon
Groups
Definitions

T** Definitions
[*] Ш Opefatlon Definitions

Рис. 4. Вложение ролей

Последнее, что необходимо сделать администратору, — превратить абстракт-


ные роли в конкретные, назначив их реальным пользователям. Для этого
Ролевая защита на основе Authorization Manager 135

выберите папку Role Assignments и действие Assign Roles. Заметьте: роль не


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

Интерфейс исполняющей среды AzMan


Определив операции и задачи, можно приступать к реализации проверок
прав доступа в коде. Первым делом следует подумать об аутентификации.
Если вы задействуете встроенную в Windows инфраструктуру, скажем,
поддержки аутентификации Kerberos для Web-сервера, то сможете полу-
чать маркер (token) для клиента. Вообще говоря, это самый естественный
способ использования AzMan, поскольку маркер содержит все группы, в
которые входит пользователь, а это ускоряет сопоставление пользователя
с набором ролей AzMan.
С другой стороны, если для аутентификации вы применяете некую разно-
видность сертификата Х.50Э, то маркер вы не получите — только имя
пользователя. Однако это не означает, что вы не сможете использовать
AzMan. Вам даже не понадобится писать никакого дополнительного кода.
Но использование AzMan в этом случае сопряжено с дополнительными
издержками, так как исполняющей среде придется самостоятельно опре-
делять группы, в которые входит пользователь. А это потребует обраще-
ний к контроллерам домена.

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


AzMan, указать выбранное хранилище и местонахождение в нем парамет-
ров приложения. Рассмотрим пример с простым XML-хранилищем:

AzAuthorizationStore store = new AzAuthorizationStoreClassO;


store.Initialized, e"msxml://c:\MyStore.xml", null);
lAzApplication app = store.OpenApplication(
"Corporate Library Application", null);
Чтобы собрать это приложение, потребуется ссылка на interop-сборку AzMan,
находящуюся в каталоге %WINDIR%\Microsoft.NET\Framework\AuthMan.

Теперь, когда исполняющая среда загружена, вы должны после аутентифи-


кации нового клиента создать представление его контекста защиты. Кон-
текст похож на маркер в том смысле, что в нем хранятся сопоставления
пользователя ролям:
1S6 Защита кода и данных

lAzClientContext ctx =
арр.InitializedlentContextFromToken(htoken, null);
Где же взять маркер клиента? Ну, это зависит от типа создаваемого при-
ложения. Вот, например, фрагмент С#-кода из ASP.NET-страницы, полу-
чающий маркер клиента. Б этом примере в файле web.coring указан режим
аутентификации «Windows*, a IIS настроен на использование аутентифи-
кации средствами Windows:

Windowsldentity id = (WindowsIdentity)User,Identity;
IntPtr htoken = id.Token;

Если вам известно только имя клиента и нет доступа к его маркеру, поду-
майте, нельзя ли все же как-нибудь раздобыть маркер, потому что мар-
кер — самый надежный способ получить группы, в которые входит клиент.
Кроме того, как я уже упоминал, это самый быстрый способ. Если вы уве-
рены, что до маркера вам никак не добраться, используйте альтернативный
способ инициализации контекста по имени учетной записи в форме «до-
мен\пользователь». Такой вызов может потребовать обращений к контрол-
лерам домена для определения доменных групп, так что будьте готовы к
тому, что его выполнение займет некоторое время:

lAzClientContext ctx =
арр.InitializeCHentContextFromName(name, null);

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


тод принимает несколько аргументов, но я пока продемонстрирую простои
вариант. Допустим, вы реализуете функцию, добавляющую книги к храни-
лищу. Я определил операцию «Add book to inventory» («добавление кни-
ги в хранилище») под номером 5. Код приведен на рис. 5.

Первый строковый аргумент nameOfBook используется, если включен ау-


дит исполняющей среды. Он идентифицирует объект, над которым выпол-
няется операция, поэтому вы должны всегда предоставлять в этом аргумен-
те какую-то описательную информацию. Для второго аргумента, scopes, я
оставил значение по умолчанию, смысл которого я объясню позже. Третий
аргумент содержит список из одной или нескольких проверяемых операций.
Результатом метода является массив, размер которого совпадает с размером
массива операций; в каждый элемент массива записывается целочисленный
код статуса для каждой операции, указывающий, разрешен доступ или за-
прещен. Нуль соответствует успешному результату, т. е. клиенту, определен-
ному в данном контексте, разрешено выполнять данную операцию. Любое
другое число означает ошибку (обычно возвращается 5, или ERROR_AC-
CESS_DENIED).
Ролевая защита на основе Authorization Manager 187

Рис. 5. Добавление книги в хранилище


// Всегда определяйте для своих операций константы или пвречишчные)
coast int opAddBookToInventory * 5;
const int NQJERROfl - 0;

// flanee в коле. ..
Qbjectn operations = { opAddSookTotnventory H
objectH scopes = { "" };
и
objeetn results («bject[])
ct>^AceessCheck(name0f8GOk,
scopes, operations,
null, null, null, null, null);
int result = (int)resultEG];
if <NG_£RRGR == result) {
// Доступ разрешен

e
*lse {
// Доступ заменен, результат содержит код ошибки

Интерфейс исполняющей среды AzMan не является строго типизирован-


ным. Для большинства аргументов в нем используется тип VARIANT. Бла-
годаря этому разработчики на классическом ASP, использующие сценар-
ные языки, могут обращаться к AzMan, но разработчики на строго типи-
зированных языках вроде С# и Visual Basic .NET могут допускать ошибки
при обращении к AccessCheck, которые будут выявлены только в период
выполнения. Так, у массива операций должен быть тип object[], а не int[],
однако компилятор не выдаст никаких предупреждений, даже если вы пе-
редадите int[], поскольку на самом деле тип аргумента — object. Я спо-
ткнулся на этом, когда только начал изучать этот API, и мне потребовалось
определенное время, чтобы научиться писать код, не приводящий к ошиб-
кам периода выполнения из-за несовпадения типов параметров. Ходят
слухи, что будет выпущен управляемый интерфейс к AzMan, но до той по-
ры вам может понадобиться строго типизированная оболочка AccessCheck,
позволяющая избежать ошибок. Вот пример такой оболочки, попутно
упрощающей наиболее распространенный способ вызова этой функции:
public class AzManWrapper {
public static int AccessCheckC
IClientContext ctx,
string objectName,
int operation) {
object[] results = (object[])ctx.AccessCheck(
188 Защита кода и данных

objectName, scopes, ops,


null, null, null, null, null);
return (int)results[0];

Используя оболочку, вы можете создавать перегруженные версии Access-


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

Вероятно, сейчас вас заинтересовало, можно ли применять AzMan, не ста-


вя в соответствие пользователям учетные записи Windows. Архитектура
исполняющей среды рассчитана на это, хотя тогда вам придется опреде-
лять свои идентификаторы защиты (SID) для каждого пользователя. Это
не так уж трудно, но вам понадобится вызывать другой метод для инициа-
лизации контекста защиты клиента — InitializeClientContextFromStrmg-
Sid. Самая серьезная проблема в том, что вы не сможете использовать ос-
настку AzMan для управления хранилищами, которые тесно увязаны с
пользователями л группами Windows. Дополнительную информацию об
этом способе см. в пособии, написанном Дэйвом Мак-Ферсоном (Dave
McPherson), ссылка на которое дана в начале статьи.

Хранилища, приложения и области


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

Если у вас есть тестовый домен, попробуйте запустить оснастку AzMan и


создать новое хранилище в Active Directory с именем типа «CN^MyStore,
CN=Program Data, DC^MyDomain, DC=com», заменив MyDomain на имя
вашего домена. Чтобы увидеть объекты, созданные AzMan в Active Direc-
tory, воспользуйтесь утилитой вроде ADSIEdit, оснасткой ММС, которую
можно установить с компакт-диска Windows Server 2003, запустив SUP-
PORT\TOOLS\SUPTOOLS.MSI. Создайте приложение в хранилище и
Ролевая защита на основе Authorization Manager 189

вызовите его страницу свойств. Вы увидите вкладки Security и Auditing,


которых нет при использовании простого XML-файла.

В Active Directory вы можете делегировать ответственность за админист-


рирование отдельных приложений в рамках хранилища и вести аудит из-
менений в хранилище на очень высоком уровне детализации. При исполь-
зовании XML-файла вы ограничены защитой самого файла через разреше-
ния NTFS и аудит. В настоящее время аудит поддерживается, только если
хранилище находится в службе каталогов, а аудит жизненно важен во мно-
гих приложениях. Если Active Directory доступна, настоятельно советую
разместить хранилища AzMan там, так как это само подходящее место для
хранения политик безопасности в Windows.

В одном хранилище может быть несколько приложений. Каждое из них


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

Каждое приложение может определить несколько областей (scopes), но это


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

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

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


Если атаки такого типа вам не знакомы, прочтите главу о каноническом фор-
мате в книге «Writing Secure Code, Second Edition» (Microsoft Press, 2002).
Чтобы подробнее ознакомиться с продвинутыми возможностями вроде под-
держки областей, см. пособие Дэйва Мак-Ферсона, о котором я уже говорил.

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

В AzMan предусмотрено два типа групп приложений: базовые и LDAP-за-


просов (Lightweight Directory Access Protocol). Базовые группы очень по-
хожи на группы в Active Directory, но с небольшим отличием: вы можете
определить как включаемых в нее членов, так и исключаемых. Например,
можно определить группу EveryoneButBob («все, кроме Боба»), как я сде-
лал на рис. 6.

Преимущество базовых групп в увеличении функциональности и удобст-


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

Группы LDAP-запросов — «дорогостоящая», но очень удобная функция


AzMan. Синтаксис LDAP-запросов можно применять для определения груп-
пы пользователей, обладающих некими общими характеристиками. Напри-
мер, вот как определить набор инженеров, возраст которых старше 21 года:
(&(age>=21)(memberOf= CN=eng,DC=foo, DC=com))

Независимо от типа группы приложения администратор может использо-


вать их как альтернативный способ назначения ролей пользователям.
Ролевая защита на основе Authorization Manager 191

Authorization Manager
-Д my*cr*.xrrtl
p'1 Groups
3 Я corporate Library Application

. Kl-ЩЭ Rote Definitions


• В* ом! Task Definitions
. &j Ш Operation Defritions
Ш-Ш Rote Assignments

Рис. 6. Доступ разрешен всем, кроме Боба

Сценарии
Когда статических полномочий недостаточно, приложение может предо-
ставлять дополнительный контекст в виде переменных и ссылок на объек-
ты, передаваемых AccessCheck. Это позволяет автору сценария добавить
бизнес-логику на языках JScript или VBScript без перекомпиляции прило-
жения. Например, ранее определенной задаче «получить историю опера-
ций читателя» можно предоставить дополнительный контекст, содержа-
щий, скажем, набор ролей, которые есть у данного пользователя, и булево
значение, указывающее, обращается ли клиент к своей истории или к чу-
жой. Это позволит написать сценарий, разрешающий менеджерам про-
сматривать историю операций любого читателя, но запрещающий обыч-
ным пользователям читать любую историю, кроме своей. Вариант сцена-
рия, решающего это задачу, приведен на рис. 7.

Рис. 7. Просмотр истории операций читателя


' Всегда предполагаем худшее
AzBizRuleContext.SusinessRuleflesult = false
isHansger = false
roles * AzBizSuleCentext.8etPara«ieter("roles")
for each role In roles
if "Manager" * role then
isManager ~ tree
см. след. стр.
192 Защита кода и данных

Рис. 7. Просмотр истории операций читателя


exit for
end if
next
if isManager then
' Менеджерам разрешено читать любую историю
AzBizRuleContext, BusinessRuleEiesult % true
else
" Остальным разрешается читать только сво» история
self = AzSizRuleContext-GetParameterC'self")
Az8izRuleCont«xt.BusinessRuleflesult * self
and if

Чтобы связать сценарий с задачей «Read patron history», откройте страни-


цу определения задачи, выберите файл, содержащий сценарий, укажите
язык (VBScript) и нажмите кнопку Reload Rule into Store.

Поддержка сценариев авторизации


Чтобы сценарий, приведенный на рис. 7, работал, нужно передавать при
каждой проверке прав доступа, связанной с задачей «Read patron history»,
дополнительные аргументы. Так как в нашем случае все просто и для ка-
ждой операции определена одна задача, вы можете передавать этот кон-
текст (в виде дополнительных аргументов) всякий раз, когда запрашива-
ется соответствующая операция. Вот фрагмент кода, где демонстрируется
передача дополнительного контекста сценарию:
bool self = _userIsCheckingOwnHistory();
object[] operations = { opReviewPatronHistory };
object[] scopes ={""};
object[] varNames = { "roles", "self };
object[] varValues = { ctx.GetRoles(""), self >;

object[] results =: (object[])


ctx.AccessCheck(nameOfPatronHistory,
scopes, operations,
varNames, varValues,
null, null, null);

AzMan в некотором роде привередлив относительно порядка элементов в


массивах varNames и varValues. Элементы в varNames должны быть в ал-
фавитном порядке, как у меня. А массив varValues должен содержать зна-
чения, соответствующие именованным параметрам в varNames, что в об-
щем-то очевидно. Для большего изящества можно передавать в трех по-
следних аргументах AccessCheck ссылки на именованные объекты. Это
раскроет видимую из сценария объектную модель далее за объектом по
Ролевая защита на основе Authorization Manager 193

умолчанию AzBizRuleContext. Оставляю вам эксперименты с этой воз-


можностью и решение возникающих при ее использовании проблем в ка-
честве домашнего задания.

Первая странность сценариев, на которую вы, возможно, обратили внима-


ние, состоит в том, что они определяются на уровне задачи и роли, а не
операции. Но разработчики приложений выполняют проверку прав досту-
па и передают контекстные переменные для отдельных операций. Откуда
автор сценария узнает, что все необходимые переменные будут доступны
для его задачи? Очевидно, что разработчику придется детально докумен-
тировать каждую операцию. Одна из простых стратегий — передавать од-
ни и те же контекстные переменные независимо от вызываемой операции.
Конечно, это облегчит жизнь автору сценариев. В своем примере я испо-
ведовал принцип «одна задача — одна операция», чтобы можно было на-
строить контекстные переменные для каждой задачи, но вспомните: адми-
нистраторы могут определять новые задачи в режиме администрирования.
Что будет, если администратор создаст новую задачу с двумя операциями,
для которых передаются разные контекстные переменные? Поэтому сове-
тую придерживаться максимальной простоты и тщательно документиро-
вать, как писать сценарии, чтобы предотвратить неприятные ситуации.

При создании сценариев следует остерегаться еще одной ситуации. Ре-


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

Помните, что сценарии предназначены для выдачи полномочий ролям или


задачам, с которыми они связаны. Допустим, Элис — член ролей R1 и R2.
Роли R1 разрешено выполнять операцию X. Роль R2 имеет те же разреше-
ния, но они определяются сценарием. Когда Элис пытается выполнить
операцию X, AzMan даже и не подумает запускать сценарий, связанный с
ролью R2, потому что операция X уже разрешена в роли R1. Таким обра-
зом, сценарии не могут «отбирать» выданные полномочия. Их можно при-
менять только для того, чтобы выяснить, следует ли учитывать при про-
верке прав доступа конкретную задачу или роль. Если задаче или роли, с
которой связан сценарий, отказано в доступе из-за того, что соответствую-
щий сценарий возвратил false, то это не означает, что нет другой задачи
или роли, выдающей разрешение на выполнение проверяемой операции.
Дэйв Мак-Ферсон весьма подробно описывает, как в исполняющей среде
реализованы проверки прав доступа. Это описание находится в разделе
Performance. Я бы посоветовал внимательно изучить этот раздел каждому,
кто на деле планирует использовать AzMan.

7-133
194 Защита кода и данных

Аудит
Аудит проверок доступа в исполняющей среде — очень важная функция,
доступная только при размещении хранилища Authorization Manager в
Active Directory. Если вы хотите включить эту функцию, щелкните имя
приложения правой кнопкой мыши, выберите Properties и откройте вклад-
ку Auditing. В период выполнения важна учетная запись, под которой ра-
ботает серверный процесс: ей необходимо выдать привилегию Generate
Audits. Встроенные учетные записи Local Service, Network Service и SYS-
TEM по умолчанию обладают такой привилегией. Наконец, обратите вни-
мание на то, чтобы результаты аудита сохранялись в журнале событий
безопасности; для этого на сервере нужно разрешить аудит доступа к объ-
ектам.

После включения аудита вы обнаружите, что каждый вызов AccessCheck


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

Заключение
Authorization Manager — важное средство создания защищенных систем в
Windows. Он расширяет концепцию ролевой защиты, появившейся в MTS
и СОМ+, но применим в любом серверном приложении, а не только в
СОМ-серверах. Authorization Manager помогает централизовать логику за-
щиты в виде четкой политики безопасности, которую можно хранить в
Active Director}' и предоставляет простой API для проверки прав доступа.

Authorization Manager содержит обширную функциональность, и часть ва-


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

Кит Браун (Keith Brown) — независимый консультант. Специализируется


на защите приложений на платформе Windows. Автор книги «Programming
Windows Security» (Ad di son-Wesley, 2000). Преподает в DevelopMentor.
Сейчас работает над новой книгой, доступной через Интернет по ссылке
http://www.develop.com/kbrown.
Нирадж Сривастава

Безопасность

Защита SOAP-пакетов
на основе WS-Security
и приемников каналов
Remoting

Все больше организаций использует Web-сервисы XML, поэтому стала


очевидной необходимость защиты на уровне сообщений (message-level
security). Эту проблему решает WS-Security, теперь поддерживаемая Microsoft
.NET Framework. С помощью инфраструктуры WS-Security разработчики могут
реализовать приемники каналов (channel sinks) для перехвата Remoting-
сообщений в процессе их передачи через инфраструктуру удаленного
взаимодействия (.NET Remoting). Приемник может прочитать сообщение,
изменить его и передать дальше. При этом сообщение можно подписать для
большей безопасности. В статье рассказывается, как реализовать приемник
канала удаленного взаимодействия, который изменяет сообщение, добавляя
в заголовок маркер (token) UserNarrm, а затем подписывая этим маркером
тело сообщения.

В последние несколько лет в промышленности бурно растет интерес к Web-


сервисам XML. Но этот интерес отчасти сдерживало отсутствие специфи-
кации протокола, описывающей защиту на уровне сообщений. Недавно
опубликованная спецификация WS-Security является попыткой решить эту
проблему. Для поддержки WS-Security в Microsoft .NET Framework исполь-
зуются классы Web Services Enhancements (WSE) 1.0 for Microsoft .NET.

Публиковалось в MSDN Magazine/Русская Редакция. 2003. № И (ноябрь). — Прим. изд.


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

WSE предоставляет инфраструктуру защиты SOAP-сообщений и поддер-


живает спецификации WS-Routing, DIME и WS-Attachments. Мощная
модель программирования, предлагаемая WSE, позволяет работать с SOAP-
сообщениями напрямую и, в частности, изменять их.

Приемники каналов (channel sinks) — мощное средство расширения .NET


Remoting, которое дает возможность перехватывать Remoting-сообщения,
передаваемые инфраструктурой удаленного взаимодействия. Приемник
может прочитать сообщение, изменить его и передать дальше по цепочке.
В статье рассказывается, как реализовать приемник канала удаленного
взаимодействия, который изменяет сообщение, помещая в заголовок мар-
кер UserName, а затем подписывая этим маркером тело сообщения. По-
скольку используется WSE, генерируется сообщение, соответствующее
спецификации WS-Security (рис. 1).

Рис. 1. Генерация сообщения с помощью WSE

Архитектура приемников каналов


Рассмотрим архитектуру приемников каналов, в частности элементы, ис-
пользуемые при вызове методов через Remoting, этапы, на которых допус-
кается перехват сообщений, что можно делать с перехваченными сообще-
ниями.
При конструировании прокси и вызове метода генерируется объект Message,
который при обмене данными с сервером передается по цепочке приемников.
Класс Message, к которому приемники обращаются через интерфейс IMes-
sage, содержит словарь, где хранятся данные, описывающие вызов.
В инфраструктуре Remoting имеется несколько классов, реализующих ин-
терфейс IMessage, который предназначен для описания различных сооб-
щений, передаваемых при конструировании (ConstmctionCall, Construc-
tionResponse) или при вызове методов (MethodCall, Method Response).
Есть два важных приемника, через которые проходят сообщения удален-
ного взаимодействия: FormatterSink и Transport Sink. FormatterSink сериа-
Защита SOAP-пакетов на основе WS-Security и приемников каналов Remoting 197

лизует сообщение в поток и создает транспортные заголовки. Transport-


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

Приемники, находящиеся между FormatterSmk и TransportSink, должны


реализовать интерфейс ICHentChannelSink (если это клиентский прием-
ник) или IServerChannelSink (если это серверный приемник). На рис. 2
показана архитектура приемников каналов, а на рис. 3 даны определения
этих интерфейсов. Позже я поясню, как реализовать эти интерфейсы.

Прокси Сервер

FormatterSmk FormatterSink

ICIientChannciSink IServerChannelSink

Рис. 2. Архитектура приемников каналов

Рис. 3. Определения интерфейсов


Public interface ICHentCharnmlSink
{
iCliervtChannelSinfc NextChannelSink {get;}

void AsyflcProcessRequest(ICHentChannelSinkStac& sinkStack,


sg, ITransportHeaders headers, Stream strearo);

см. след. стр.


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

Рис. 3. Определения интерфейсов

void AsyncProcessResponseCIClientResponseGhanRelSiftkStack
sinkStack, object state, ITransportHeatiers headers,
Stream stream);

Stream SetfiequestStream (message tesg, ITransportWeaders


headers);

void ProcesisHessafle(IMessa{ie rasg, ITransportHeaders


requestHeaders, Stream requestStream. out
ITransjiQTtHeaders responseHeaderSj out Stream
responseStream);

Public interface IServerGhannelSink


{
IServerChannelSink NextGtiannelSink {get;}

void AsyncF'rocessResponsedServerResponseChannelSiRkStack
sirtkStack, abject state, message iasg, ITransportHeadars
headers, Stream stream);

Stream SetflequestStreain (IServerResponseCHannelSinkStack


sinkStack, object state, IHessage resg, ITransportHeaders
headers);

void ProcessMessagedSeFverResponseChafinelSinkStack
IMessage requestMsg, ITransportNeaders requestJ-teaders,
Stream requestStreai», out IHessage respofrse^sfl, out
ITransportHeaders responseHeacters, out Stream
responseStream);

Любой приемник, вставленный перед FormatterSink, может обращаться


только к объекту неформатированного сообщения (raw message object). Та-
кие приемники должны реализовать интерфейс IMessageSink. Определе-
ние JMessageSink выглядит так:

Public interface IMessageSink


{
IMessageSink NextSink {get;}
IMessageSink AsyncProcessHessage (Imessage
msg, ImessageSink replySink);
IMessageSink SyncProcessHessage (IHessage msg);
Защита SOAP-пакетов на основе WS-Security и приемников каналов Remoting 199

Обычно FormatterSink — первый приемник цепочки на стороне клиента.


FormatterSink реализует IMessageSink, а также IClientChannelSink (на
клиентской стороне) или IServerChannelSink (на серверной стороне).
Приемник форматирующего объекта (formatter sink) на клиенте принима-
ет сообщение по интерфейсу IMessageSink, сериализует его и передает
дальше по IClientChannelSink. На сервере процесс идет в обратном поряд-
ке, и сообщение передается через интерфейс IServerChannelSink.

Каналы удаленного взаимодействия связываются с цепочкой приемников,


используемой по умолчанию. Например, HttpChannel по умолчанию свя-
зан с SoapFormatterSink, a TcpChannel — с BinaryFormatterSink.

Можно переопределить цепочку приемников, указав в файле конфигура-.


ции ссылку на собственные провайдеры приемников. Провайдер приемни-
ка (sink provider) реализует интерфейс IClientChannelSinkProvider для
создания клиентских приемников каналов и интерфейс IServerChannel-
SinkProvider для создания серверных приемников каналов:

public interface IClientChannelSinkProvider


I
ICHentChannelSinkProvider Next {get; set;}
IClientChannelSink CreateSink (IChannelSender channel, string url,
object remoteChannelData);

public interface IServerChannelSinkProvider


{
IServerChannelSinkProvider Next {get; set;}
IServerChannelSink CreateSink (IChannelReceiver channel);
void GetChannelData(IChannelDataStore channelData);
I

Цепочка провайдеров инициализируется, когда считывается файл конфи-


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

WSE-классы
Теперь рассмотрим WS-Security и использование маркеров имени пользо-
вателя для подписи Remoting-сообщений. Начнем со знакомства с WSE-
классами. Вот эти классы и их описания.
200 Защита кода и данных

Microsoft.Web.Services.SoapWebRequest Наследует от System.Net.Web-


Request и передает SOAP-сообщение через последовательность фильтров,
выполняющих преобразования. Этот класс относится к инфраструктуре
WSE и для преобразования сообщения обращается к информации класса
SoapContext.
Microsoft.Web. Services.SoapContext Содержит информацию, приме-
няемую при преобразовании маркеров защиты сообщений, информацию
о маршрутизации (referral information) и вложения. Инфраструктура ис-
пользует эти данные, чтобы выполнять различные преобразования вход-
ных и выходных SOAP-сообщений.
Microsoft. Web.Services.SoapEnvelope Инкапсулирует класс SOAP-кон-
верта и упрощает работу с SOAP-сообщениями.
Microsoft.Web.Services.Security.Security Содержит набор маркеров за-
щиты (таких как маркер имени пользователя) и набор элементов защиты
(например подпись). У этого класса имеется метод GetXml, который при-
нимает XML-документ на входе и возвращает XML-подпись, соответст-
вующую спецификации WS-Security.
Microsoft.Web.Services.Security.Signature Содержит подпись, соответ-
ствующую маркеру защиты. С помощью этого класса можно подписать не-
сколько разных фрагментов сообщения. В этой статье я буду подписывать
только тело SOAP-сообщения.
На примере реализации приемника я покажу, как эти четыре класса при-
меняются для передачи и подписания сообщений удаленного взаимодей-
ствия. Приемник использует маркер имени пользователя, содержащий
имя и пароль пользователя. Имя пользователя передается вместе с SOAP-
сообщением; пароль служит для подписи сообщения и тоже может (необя-
зательно) передаваться с сообщением. В данной реализации приемника я
решил не передавать пароль ни в каком виде.

Реализация приемника канала


Давайте вкратце рассмотрим работу с приемниками Remoting. Для использо-
вания приемника надо написать класс, реализующий интерфейс lUserName-
TokenHelper, и класс, реализующий интерфейс IPasswordProvider.

HJserNameTokenHelper объявлен в пространстве имен SecuritySink, реа-


лизованном в примере кода к этой статье (см. http://msdn.microsoft.com/
msdnmag/code03.aspx в разделе за ноябрь). Клиентский приемник вы-
зывает методы GetUserName и GetPassword этого интерфейса, чтобы
получить имя пользователя и пароль для подписи SOAP-сообщения.
Защита SOAP-пакетов на основе WS-Security и приемников каналов Rerouting 201

В данной реализации я создал в провайдере клиентского приемника


свойство TokenHelper, который ссылается на объект, реализующий этот
интерфейс.

Интерфейс I Pass word Provider объявлен в пространстве имен Micro-


soft. Web. Services. Security. Инфраструктура WSE вызывает этот интерфейс,
чтобы при проверке подписи получить пароль, соответствующий имени
пользователя. Этот пароль служит для подписи SOAP-сообщения: значение
его подписи сравнивается со значением, получаемым в SOAP-сообщении.
Класс, реализующий этот интерфейс, указывается в разделе WSE файла
app.config.

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


в нужном месте цепочки приемников. В клиентском файле app.config
приемник должен находиться после Formatter, а в серверном файле
app.config — перед Formatter. Это объясняется тем, что нам нужно рабо-
тать с сообщением после того, как оно сериализовано на клиентской сто-
роне, но до того, как оно будет десериализовано на серверной. На рис. 4
и рис. 5 показаны соответствующие фрагменты каждого файла конфигу-
рации. На рис. 4 приведен клиентский файл конфигурации, содержащий
ссылку на провайдер клиентского приемника канала (в разделе Pro-
vider). Кроме того, в разделе Provider содержится ссылка на объект
TokenHelper как на одно из свойств провайдера.
Рис. 4. Файлы конфигурации
Конфигурация клиентского приемника удаленного взаимодействия

<channels>
ref=-"http"
<clientPrQvlders>
«formatter ref="soap" />
<provider type="SecuritySink.SecurityChannelSinkProvltier,
SeouritySlnk"
TokenHeIper="SecyritySink.TokenHelperObJ,SecuritySiRk'"/>
</GltentProvitlers>

Конфигурация серверного приемника удаленного взаимодействуя

<channels>
<ehannel ref="http" port="99Sr>
<serverpfavidet"s>
<provtber s
aw. след. стр.
202 Защита кода и данных

Рис. 4. файлы конфигурации (окончание)


type="SecuritySink,SecurityServerChannel$inkProvider,
SecuritySirtk" />
<fonnatter ref*"soap" />
</se rve г P rovi de r s>
</chartnel>

Параметры WSE в app.config на серверной стороне


version="1.0" efieodlng="ul:f~8" ?
<eonfiguration>

<section nanie="microsoft.vffi[(, services"


type= "Microsoft. Web. Services.GoRfi gyration.
We bSecvicesConfiguration, Microsoft. Web. Services,
Version=1. 0.0,0, Culture^reutral,
PublicKeyTokeR-31bf385Sad364e35" />
</configSections>
<fflicrosoft.web.aerviGes>
<security>
< password Provider
tyi>es"$ecuritySink. Password? roviderCIass,
SecuritySink" />
</secyrity>
</mic rosof t . web - se rvices>
</coflfiguration> •

Рис. 5. Конфигурация сервера


versloo="1.Q"
<configuratiort>
<configSections>
<sectl(Hi (iaKe~"iBicri>soft.web.services"
type="Microsoft.We6,Services,Gonfigtiration,WebServicesCoflfi6Mratiofl,
Microsoft,Web.Services, Version=1.0,0.0,
Culture=ne«tral1 PufrlicKeyToken'=31bf3856a£(364e36" />
</configSectiOFis>
<Biicrosoft.web.services>
<security>
<passwordl:i rovider
type="SeCiiritySink.Passwjrd?roviderCla8S,
SecuritySink" />
</secarity>
см. след. стр.
Защита SOAP-пакетов на основе WS-Security и приемников каналов Remoting 203

Рис. 5. Конфигурация сервера (окончание)


</microsoft.web.sersrices>
<systeiB.Runtime. RefflQting>
Application пате="АррНаве">

<1 —Activated type="Testliti.repiioteobj,TestLib" />— >


<! —Octivated type~"TestLib.remoteobj,TestLib" />-->
<we!lknowR type="TestLib. renoteobj , TestLib"
" ofe]ectUri="Object3.soap" />

<ofiannel8>
<charvnel re?="tep" port="9999">
<se rve r Provide rs>
«provider
type= "SeeurttySink. SecurityServerCbannelSinkProvider,
SecurityStnk" />
<formatter ref="soap" />
</serverprovlders>

<l— <channel ref^'http" port="8888" />-->


</channsls>
</application>
</systeia. Runtime. Reniotins>

Заметьте: файлы конфигурации на стороне сервера можно объединить в


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

Реализация клиентского приемника канала


Реализацию приемника удаленного взаимодействия удобно начинать с
провайдера клиентского приемника канала, который будет создавать
клиентский приемник канала по запросу. Провайдер клиентского прием-
ника реализует интерфейс IClientChannelSinkProvider. Кроме того, у
этого провайдера имеется конструктор, принимающий два параметра:
204 Защита кода и данных

объект словаря и набор SinkProviderData. Объект словаря содержит


свойства провайдера приемника, заданные в файле конфигурации, а на-
бор — дополнительную информацию о конфигурации, заданную в фай-
ле в элементе <provider>. Строка, соответствующая маркеру имени поль-
зователя, передается в наборе свойств, а провайдер в свою очередь пере-
дает это значение приемнику.

Самый интересный метод провайдера приемника называется CreateSink.


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

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

public IClientChannelSink CreateSink(IChannelSender channel, String url,


Object remoteChannelData)
(
IClientChannelSink nextSink = null;
if (.next != null)
{
nextSink = _next.Crea1:eSink(channel, url,
remoteChannelData);
if (nextSink == null)
return null;
}
return new
SecurityClientChannelSink
(nextSink, _tokenHelperString); /

Client Channel Sink, наследующий от BaseChannelSinkWithProperties, явля-


ется базовым классом для приемников, поддерживающих именованные
свойства. (Такой приемник должен реализовать именованные свойства Keys
и Indexer.) Кроме того, объект должен реализовать интерфейс IClient-
ChannelSink. Реализация IClientChannelSink означает, что вы должны так-
же реализовать метод Process Message для поддержки синхронной обработ-
ки и процедуры AsyncProcess Request и AsyncProcess Response для поддерж-
ки асинхронной обработки. Центральное место занимает закрытый метод
SignMessage, выполняющий основную работу по подписанию SOAP-сооб-
щения.
Защита SOAP-пакетов на основе WS-Security и приемников каналов Remoting 205

Перед тем как углубиться в реализацию ProcessMessage, рассмотрим реа-


лизацию метода GetRequestStream клиентского приемника канала. Get-
RequestStream возврашает поток, в который может выполнять запись пре-
дыдущий приемник в цепочке. Если он возвращает null, предполагается,
что предыдущий приемник в цепочке должен создать собственный поток,
чтобы начать цепочку, и передать этот поток следующему приемнику. Ес-
ли вы собираетесь в своей реализации приемника канала тем или иным об-
разом изменять поток или перемещаться по нему, рекомендуется, чтобы
ваша реализация GetRequestStream возвращала null.

ProcessMessage и SignMessage
Функция ProcessMessage обрабатывает сообщение перед тем, как передать
его следующему приемнику. При этом предварительная обработка поруча-
ется закрытому методу SignMessage. Последующая обработка ответа не
выполняется.

SignMessage создает объект SoapEnvelope для потока, передаваемого в ка-


честве параметра. Кроме того, он создает вспомогательный объект User-
nameToken по строке, считываемой из файла конфигурации и передавае-
мой провайдером приемника.

Метод получает имя и пароль пользователя из этого объекта:


string helperOb] = _tokenHelperStrlng;
stringd strarry = helperObj .Split(new Split(new C h a r t ] { ' , ' } ) ;
ObjectHandle ob] = Activator.Createlnstance(strarry[1],strarry[0]);
IllserNameTokenHelper tokHelper = obj.Unwrap() as lUserNameTokenHelper;
string usr = tokHelper.GetUserNameO;
string pass = tokHelper.GetPasswordO;

При создании UserNaineToken указываются эти имя и пароль пользовате-


ля. Далее создается объект защиты, и UserNameToken добавляется в его
набор маркеров защиты. Затем создается объект подписи, который ини-
циализируется созданным UserNameToken. Этот объект добавляется в на-
бор элементов защиты объекта защиты.

Рис. 6. Обработка сообщения


ff Stream returRStreani;
// Загружаем

Soaplftvelope req&iv = яви SoapEnvelopeO;


reqEnv.Load<requestStreaiB);

см. след. стр.


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

Рис. 6. Обработка сообщения ] (окончание)

// Создаем маркер имени пользователя

ysernaseToken tok = new


UsernaBeTokenftoktfelper.GetUserNaffleO.tokHelper.QetPasswordO);
tok.PasswordOption - PasswordOption.SendNone;

// Соэдаеи обьект защиты

Security sec * new Security(actor);


sec. Tokens. Add (tok);
if (reqEnv. Header =™ null)
reqEnv, CreateHeader( );

// Подпись

Signature slg = new Sigfmtyre(tnk);

'// Подписываем

regEiw. Header. ApperKlChild(see.Gf!tXinl{ reqEn

// Сохраняем

ifUflStream ==» null)


{
inStreaBi = new MeaoryStream();
reqEnv. Save(inStreae);
InStream. Positloft=4;

else

. Save{ in Stream);

Далее SOAP-кониерт подписывается. Для этого он передается методу Get-


Xml объекта защиты. GetXml возвращает элемент WS-Security Header, до-
бавляемый в заголовок SOAP- конверта. Наконец, новое SOAP-сообщение
сохраняется в исходящем потоке. Детали см. в исходном коде на рис. 6.

Асинхронная реализация этих операций не так уж сильно отличается от


синхронной. В AsyncProcessRequest текущий приемник помещается в стек
Защита SOAP-пакетов на основе WS-Security и приемников каналов Remoting 207

приемников, чтобы этот приемник вызывался при асинхронной обработ-


ке ответа. Никакой последующей обработки сообщения не требуется; вме-
сто этого вызов просто передается очередному приемнику.

Реализация серверного приемника канала


Реализация ServerChannelSinkProvider аналогична реализации клиент-
ского провайдера. Вы передаете ссылку на объект канала, необходимый
для реализации приемника. В настоящее время серверные приемники не
поддерживают асинхронную обработку запроса. Однако возможна асин-
хронная обработка ответа. Поэтому достаточно реализовать только мето-
ды Process Message и AsyncProcessResponse. В данной реализации прием-
ника канала ответ сервера не обрабатывается, а просто передается даль-
ше по цепочке. Поэтому реализация метода GetResponseStream всего
лишь делегирует вызов следующему приемнику в стеке приемников для
дальнейшей обработки. Обработка метода AsyncProcessResponse тоже
тривиальна — и в этом случае вызов просто делегируется следующему
приемнику в стеке.

В методе ProcessMessage вы должны сначала убедиться, что метод еще не


десериализован. Затем создать SOAP-конверт для потока, передаваемого в
качестве параметра, а из SOAP-сообщен и я извлечь заголовки WS-Security.
Создавая объект защиты из XML-кода каждого из заголовков, вы проверяе-
те подпись.

При WSE-обработке вызывается объект, реализующий интерфейс IPass-


wordProvider. Этот объект считывает пароль, соответствующий имени
пользователя, которое содержится в заголовке. Конкретный объект, реа-
лизующий IPasswordProvider, задается в файле конфигурации WSE в
разделе <microsoft.web.services> ... <passwordProvider>. Чтобы получить
дополнительную информацию, изучите файлы конфигурации в примере
кода к статье. Кроме того, перед дальнейшей передачей сообщения по це-
почке приемников следует удалять заголовки Security из SOAP-сообще-
ния (рис. 7).

В приемниках каналов на стороне сервера очень важно обрабатывать ис-


ключения; сгенерированное исключение передается обратно до транспорт-
ного приемника, который обрабатывает его и завершает соединение. Необ-
ходимо обработать исключение и возвратить соответствующее сообщение
SOAP Fault с нужными кодами HTTP-ошибки. Для изменения кодов
HTTP-ответа используются ключи транспортного заголовка HttpSta-
tusCode и HttpReasonPhrase.
208 Защита кода и данных

Рис. 7, Обработка элементов зйщиты


SoapEnvelope reijEnv = new SoapEnvelopeO;
reqEnv, Load(refjuestStreaft);

// Обрабатываем несколько элементов защиты

XffllftodeUst nodelist;
IHmiaerator rtodellstSnuis;
nodelist = req£nv.Header.GetHeBtentsByTagNaiBe<e"Secarity
@"http r //sctramas . xslsoao. о rg/ws/29S2/Q7/saeext " ) ;
nodelistEfluB = nodellst.EetEnunnsratorO;
ArrayList nodestodelete = new ArrayListO;
try
{
wMle(nodelistEnuifi.MoveNextO)
{
XmlElement elea = nodelistEnum.Currertt as

// СОЗДАЕМ объект защиты


Security seeserver * new Security(elee);
// He можем удалить узел, так как список изменится,
// поэтому запоминаем узея
nocfestodelete.Add(eleit);

// Удаляем узлы защиты из заголовка


nodei>todelete£num =

XmlElefflent)FiodestodeleteEnum. Current);
}
MemoryStreaffl stnr - new KemoryStreanK);
req£nv, SaveCstrfi);
stra. Position^;
processing * „nextSink.ProeesEiMessaflefsinkStack, rtull,
cequestHeaders, sitrnt, oot respoflseHsg,
out responseHeaders, out responaeStrea»);

В обработчике исключения создается соответствующее исключению сооб-


щение SOAP Fault, которое сериализуется в поток ответа; при этом зада-
ются нужные HTTP-заголовки со сведениями об ошибке на серверной сто-
роне. Если используется TCP-канал, обратно передается лишь сообщение
SOAP Fault, а заголовки не устанавливаются.
Защита SOAP-пакетов на основе WS-Security и приемников каналов Remoting 209

Расширение реализации
Одно из возможных расширений этого приемника — реализация эквива-
лента олицетворения (impersonation), или подмены. Когда подпись прове-
ряется на сервере, на этой стороне имеется допустимый объект защиты, из
которого можно получить маркер UserName:
SecurityTokenCollBction col = reqCtx.Security.Tokens;
lEnumerator e = col.GetEnumerator();

e.MoveNextO; // ожидаем только один маркер

UsernameToken tokn = (UsernameToken) e.Current;


string username = tokn.Usernane;
Genericldentity idn = new Genericldentity (username);
GenericPrincipal p = new GenericPrincipal (idn, null);

System.Threading.Thread.CurrentPrincipal = p;

Предыдущий фрагмент кода можно расширить, реализовав на сервере ро-


левую защиту. На клиентской стороне пользователя можно идентифици-
ровать по участнику безопасности потока (thread principal). Еще одна воз-
можная реализация — написать, опять же с помощью WSE, приемник уда-
ленного взаимодействия, шифрующий SOAP-сообщения.

Заключение
Благодаря новым WSE-классам, совместимым с .NET Framework, реали-
зовать клиентский и серверный приемники удаленного взаимодействия
очень легко. Эти классы упрощают подписание и шифрование SOAP-co-
общений, а архитектура цепочки приемников очень удобна для расшире-
ния обработки сообщений.

Нирадж Сривастава (Neeraj Srivastava) работает в Microsoft Product Support


Services в группе Solutions Integration Engineering. Занимается разработкой
около восьми лет, в основном использует технологии Microsoft. Сейчас
специализируется на технологиях разработки ПО промежуточного уровня:
СОМ+, Remoting, Web-сервисы и ASP.NET.
Майкл Говард

Оценка уязвимое гей

Полезные советы
по выявлению
уязвимостей в вашем коде

Оценка кода на предмет наличия уязвимостей в нем — важнейший этап


процесса создания программного обеспечения наравне с планированием,
проектированием и тестированием. В статье автор делится своим многолетним
опытом оценки защищенности кода и дает ряд советов, которые помогут всем
разработчикам выявлять возможные «дыры» в программе. Процесс начинается
с изучения среды, в которой выполняется код, определения ролей его
пользователей, а также с изучения хронологии присущих данному коду
уязвимостей. После того как понимание базовых проблем достигнуто, можно
исследовать код на предмет конкретных уязвимостей, в том числе перед
атаками с внедрением SQL-кода, с использованием кросс-сайтовых сценариев
и переполнения буфера. В дополнение можно проверить код на наличие
очевидных, но распространенных «дыр» вроде переменных с именами
-password» или «secret».

Значительная часть моей работы заключается в оценке кода, написанного


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

Надеюсь, вы понимаете: хотя проверка кода, написанного другими, — шту-


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

Публиковалось в MSDN Magazine/Русская Редакция. 2003. № 11 (ноябрь). — Прим. изд.


Полезные советы по выявлению уязвимостей в вашем коде 211

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


предусматривать оценку безопасности, обучение и использование инстру-
ментальных средств. Простым проектированием, написанием, тестировани-
ем и документированием, а затем поиском «дыр» защищенную программу
не создать. Оценка кода — лишь часть процесса, и сама по себе она не обес-
печивает разработку защищенного кода.

В этой статье я не стану обсуждать природу таких уязвимостей, как внедре-


ние SQL-кода (SQL injection) и переполнение буфера. Об этом вы можете
узнать из книг, например из моей книги «Writing Secure Code» (Microsoft
Press, 2002). Вместо этого я проведу высокоуровневый анализ ошибок, на
которые в первую очередь обращаю внимание при оценке кода. Заметьте:
так оцениваю код я, а вы не обязаны делать так же, кроме того, не обещаю,
что это самый полный способ оценки кода на наличие определенных недос-
татков. Я попытаюсь описать, что происходит у меня в голове, когда я смот-
рю на код, и надеюсь, что вам это тоже поможет.

Как мне представляется, есть три способа оценки кода: глубокий анализ,
беглый анализ и смешанный подход. Я обычно пользуюсь третьим, так как
он позволяет быстро оценивать большие объемы кода. Если мне встреча-
ется место, требующее на мой взгляд более глубокого анализа, я помечаю
его для более глубокого анализа, возможно, с привлечением других экс-
пертов в данной области. Но сейчас я хотел бы обсудить беглый первона-
чальный анализ кода — «Огляди и отметь», как я называю этот способ бы-
строй оценки кода с выделением областей, нуждающихся в более деталь-
ном анализе.

Распределение времени и усилий


Для оценки времени, которое мне следует потратить на анализ кода, я раз-
работал специальную систему. Она основана на определении потенциаль-
ного вреда, который способно нанести раскрытие уязвимости в результа-
те атаки. Вот вопросы, на которые я отвечаю.
• Выполняется ли код по умолчанию?
• Выполняется ли код с повышенными привилегиями?
• Принимает ли он данные от сетевого интерфейса?
• Аутентифицируется ли сетевой интерфейс?
• Написан ли код на C/C++?
• Были ли уже связаны с этим кодом проблемы с безопасностью?
212 Защита кода и данных

• Настороженно ли относятся к этому компоненту специалисты по безо-


пасности?
• Имеет ли дело код с секретными данными?
• Пригоден ли код для повторного использования (например, не явля-
ется ли он DLL, заголовочным файлом О+-класса, библиотекой или
сборкой)?
• Если исходить из модели угроз, то находится компонент в уязвимой
среде или подвержен опасным угрозам?

Если я получаю более трех-четырех положительных ответов, то рассмат-


риваю код глубже. Фактически, если код прослушивает сокет TCP (Trans-
mission Control Protocol) или UDP (User Datagram Protocol) и выполня-
ется по умолчанию, готовьтесь потратить на его анализ массу времени.

В поисках уязвимостей я оцениваю три обширные категории кода: код на


C/C++, код на Web-сервере (например ASP, ASP.NET, CGI и Perl) и управ-
ляемый (в основном на С#, но и на Visual Basic .NET тоже).

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


основная проблема в С и C++ — переполнения буферов (buffer overruns).
Конечно, есть и другие проблемы, но когда вы слышите о буфере и о пе-
реполнении в одной фразе, речь почти наверняка идет о С или C++. Язы-
ки более высокого уровня (скажем, С#, Visual Basic .NET и Perl) не под-
вержены переполнению буфера. А если оно все же возникает, то ошибка
скорее всего в исполняющей среде, а не в самом коде. Однако такого рода
языки часто применяются для создания приложений на Web-сервере и
подвержены другим недостаткам. Переполнения буферов очень опасны,
так как злоумышленник может встроить код в выполняемый процесс и
скомпрометировать его. Поэтому сначала давайте рассмотрим именно .-ату
проблему.

Переполнения буфера в С и C++


Переполнения буферов — проклятие индустрии ПО, поэтому следует при-
ложить все усилия и избавиться от них. А еще лучше, стремитесь к тому,
чтобы они вообще не были возможны в вашем коде.

Я оцениваю вероятность переполнений буферов двумя способами. Первый


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

ные способны вызвать сбой в этом коде?». Это очень тщательный метод,
требующий много времени. Другой прием состоит в поиске известных и
потенциально опасных конструкций, а затем отслеживания данных обрат-
но до входной точки. Например, рассмотрим следующий код:
void fuction(char *p) {
char buff[163;

strcpy(buff ,

Встретив такой код, я отслеживаю переменную р до ее источника, и, если


она передается из ненадежного места или не проверяется на корректность
рядом с точкой копирования, значит, я нашел «дыру» в защите. Заметьте,
что сама по себе strcpy не является опасной или незащищенной. Нет, это
данные делают функции типа strcpy опасными. Если вы проверяете дан-
ные на корректность, strcpy безопасна. Конечно, если вы где-то ошиблись,
то получите «дыру» в защите. Кроме того, я проверяю все «п»-строковые
функции типа strncpy, так как необходимо убедиться, что размеры буфе-
ров вычисляются правильно.

Я настороженно отношусь к коду, который работает с тэговыми форматами


файлов. Под тэговыми я подразумеваю файлы, состоящие из блоков, каждый
из которых имеет описывающий его заголовок. Хороший пример — музы-
кальный формат MIDI. Так, в Windows-компоненте quartz.dll, работающем с
MIDI-файлами, была обнаружена и устранена серьезная ошибка защиты. Не-
корректная MIDI -инструкция вызывала в обрабатывающем коде сбой, а то и
что похуже. Подробнее об этой ошибке см. http://www.microsoft.com/technet/
security /bulletin/M S03-030 .asp.

Еще один пример конструкций, на которые я обращаю внимание:

while (*s != P \ V )
*d++ = *s++;

Этот цикл ограничен символом в строке -источнике, а не размером строки-


приемника. В основном я ищу конструкции вида *х++ = *у++ с помощью
следующего регулярного выражения:
\*\w+\+\+\s?=\s?V\w+\+\+
Конечно, кто-то может пользоваться конструкциями вида *++х = *++у, так
что их тоже следует искать. Хочу еще раз подчеркнуть, эта конструкция
214 Защита кода и данных

опасна, только если источник ненадежен, поэтому вам нужно определить


надежность источника данных.

Следующая проблема, связанная с переполнением буфера, о которой сто-


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

Целочисленное переполнение в С и C++


Если вы не знаете, что такое атаки целочисленного переполнения и как с
ними бороться, сначала прочтите мою статью по ссылке http://msdn.micro-
soft.com/library/en-us/dncode/html/secure06122003.asp. Пробел в защите
возникает, когда для вычисления буфера применяются арифметические
выражения, вызывающие переполнение или потерю значимости. Рассмот-
рим следующий пример:

void func(char *Ы, size_t c1, char *Ь2, size_t c2) {


const size_t MAX = 48;
if {c1 + c2 > MAX) return;
char «buff = new char[MAX];
memcpy(buff, Ы,с1);
memcpy(buff+c1, Ь2,с2);

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


232-1, возникает проблема. Например, в результате сложения OxFFFFFFFQ
и 0x40 получается 0x30 (десятичное 48). Если в качестве значений cl и с2
взять эти числа, результат сложения пройдет проверку, и код скопирует
примерно 4 Гб в 48-байтовый буфер. Вот вам и переполнение буфера!
Многие такие ошибки вполне могут быть использованы злоумышленни-
ком для внедрения своего кода в ваш процесс.

При анализе кода на С и C++ на целочисленное переполнение, я обращаю


внимание на вызовы оператора new и функций динамического выделения
памяти (alloca, malloc, calloc, НеарАПос и т. д.), а затем смотрю, как вычис-
ляется размер буфера. После этого я задаю себе такие вопросы.
• Могут ли значения превысить некую максимальную величину?
• Могут ли они оказаться меньше О?
• Обрезаются ли данные (копируется ли 32-битное значение в 16-битное
и обратно)?
Полезные советы по выявлению уязвимостей в вашем коде 215

Есть эмпирическое правило, выведенное моим коллегой в Microsoft: если в


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

Код доступа к базе данных на любом языке


Обычно разработчики создают приложения для работы с базами данных
на языках более высокого уровня типа С#, языков сценариев и т. д. В об-
щем-то относительно малая часть кода для работы с базами данных напи-
сана на С и C++, но некоторые применяют библиотеки классов вроде
CDatabase из MFC.

Есть две проблемы, на которые следует обратить внимание. Первая -


строки подключения, содержащие пароли или учетные записи админист-
раторов. Вторая — уязвимости наподобие внедрения SQL-кода.

Анализируя управляемый код, я первым делом ищу весь код, работающий


с пространством имен System.Data, особенно System.Data.SqlClient. Когда я
вижу такой код, у меня в голове звенит сигнал тревоги. Затем я ищу в коде
строки типа «connect» (обычно попадаются строки подключений). У стро-
ки подключения есть два интересных свойства, на которые имеет смысл об-
ратить внимание: идентификатор соединения (зачастую uid) и пароль (не-
редко pwd). Вот строка, которая создает потенциальную угрозу:

DRIVER={SQL Server};SERVEH=hrserver;UID=sa;PWD=$esame

На самом деле в этом примере две ошибки. Первая — подключение выпол-


няется под учетной записью администратора sa, что нарушает принцип
наименьшей привилегии. Код никогда не должен подключаться к базе дан-
ных под учетной записью системного администратора, так как эта учетная
запись может принести неисчислимые беды, если ею воспользуется злона-
меренный пользователь. Вторая ошибка — пароль прописан в коде. Это не-
правильно по двум причинам: во-первых, его можно обнаружить, во-вто-
рых, что произойдет, если пароль сменят? (Придется вносить изменения
на всех клиентах.)

Теперь рассмотрим атаки с внедрением SQL-кода. Суть такой атаки — кон-


катенация строк для создания SQL-выражений. При проверке кода я смот-
рю, где создаются SQL-выражения. В целом, я ищу слова типа «update»,
216 Защита кода и данных

«select», «insert», «exec» и все известные мне имена таблиц и баз данных.
В этом мне помогает следующая команда:

ildasm /adv /metadata /out:file test.exe

Затем я смотрю в раздел «User Strings» в результатах работы команды. За-


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

Конкатенация строк для создания хранимых процедур тоже не защищает


от внедрения SQL-кода. Если коротко, то конкатенация строк в сочетании
с SQL-выражениями опасна, но конкатенация строк в сочетании с SQL-
выражениями и учетной записью sa равноценна катастрофе.

Код Web-страницы на любом языке

Самые распространенные ошибки в Web-приложениях связаны с кросс-


сайтовыми сценариями (cross-site scripting, XSS). Хотя есть и другие
ошибки, на которые я обращаю внимание, в том числе внедрение SQL-ко-
да и нестойкое шифрование, XSS-ошибки встречаются весьма часто. Суть
XSS-уязвимости — вывести данные, введенные недоверяемым пользовате-
лем, в браузере жертвы, поэтому первым делом я ищу выражения, посы-
лающие данные пользователю. Например, в ASP я ищу Response.Write и
тэги <%= %>. Затем смотрю на отправляемые данные и выясняю, откуда
они берутся. XSS-ошибка возникает, если данные поступили от HTTP, на-
пример из формы или строки запроса, и не были проверены на коррект-
ность. Вот довольно простой, но распространенный пример XSS:

Hello,
<Х Response.WriteOtequest.QueryStringC'Name")} X>

Как видите, параметр «Name» отправляется пользователю без какой-либо


проверки на корректность.

Секреты и шифрование на любом языке


У некоторых разработчиков есть привычка хранить секретные данные в
коде, например пароли или шифровальные ключи, а также изобретать соб-
ственные «волшебные» алгоритмы шифрования. Никогда так не делайте!
.
Прежде всего я ищу переменные и функции, имена которых содержат сло-
ва «key», «password», «pwd», «secret», «cipher» и «crypt». Совпадения тре-
Полезные советы по выявлению уязеимостей в вашем коде 217

буют анализа. «Key» зачастую безобиден, но все прочие могут содержать


секретные данные или «волшебные» системы шифрования. Когда я ищу
алгоритмы шифрования, я анализирую операторы XOR — они часто при-
меняются в криптографии. Худший способ шифрования — побитовая ло-
гическая операция XOR над встроенным ключом и потоком данных!

ActiveX-элементы в Visual Basic и C++

Анализируя новый ActiveX-элемент, я всегда спрашиваю: «Почему он не


реализован в управляемом коде»? Я задаю этот вопрос, потому что в управ-
ляемом коде разрешены частично доверяемые сценарии, а в ActiveX — нет.

Далее я изучаю все методы и свойства элемента управления (лучший источ-


ник — IDL-файл) и ставлю себя на место злоумышленника. Что бы такого
плохого я мог сделать с этими методами или свойствами? Обычно имена
многих методов состоят из глагола и существительного, например Read-
Registry, WriteFile, GetUserName и NukeKey, поэтому я ищу странно звуча-
щие глаголы и существительные, относящиеся к секретным ресурсам.

Например, метод SendFile потенциально опасен, если злоумышленник мо-


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

Я провожу дополнительную работу, если элемент управления помечен как


«безопасный в сценариях» (safe for scripting, SFS), так как в этом случае
он может быть выполнен в Web-браузере без предупреждения. Элемент
управления безопасен для использования в сценариях, если он реализует
ATL-интерфейс lObjectSafetylmpl или задает при установке категории
реализации «безопасен в сценариях» или «безопасен для активации»:

[HKEY_CLASSES_ROOT\CLSID\<GUID>\Implemented
Categories\{7QD95801-9882-11CF-9FA9-OOAA006C42C4}]
[HKEY_CLASSES_HOOACLSID\<GUlD>\Implemented
Categories\{7DD95802-9882-11CF-9FA9-OOAA006C42C4}]

Я уже упоминал, что доступ к файлам пользователя через SendFile плох.


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

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

Возможно, вы заметили, что распространенная причина многих уязвимо-


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

Майкл Говард (Michael Howard) — старший менеджер программы, один


из основателей группы Secure Windows Initiative в Microsoft, а также соавтор
книги "Writing Secure Code» (Microsoft Press, 2002). Наибольшее внимание
уделяет проектированию, созданию, тестированию и развертыванию
приложений, противостоящих атакам и полезных миллионам технически
неподкованных пользователей.
Габриэл Торок и Билл Лич

Затемнение кода

Пресечение попыток
обратного проектирования
вашего кода Visual Basic
.NET или С#

Одно из преимуществ архитектуры .NET в том, что сборки, созданные


с ее использованием, содержат массу полезной информации, которая
может быть получена через ILDASM — дизассемблер IL-кода. Но, как
побочный эффект, любой, у кого есть доступ к вашим двоичным файлам,
может получить хорошее подобие оригинального исходного кода. Авторы
рассказывают о затемнении программ (program obfuscation) как о способе
противодействия обратному проектированию (reverse engineering). Кроме
того, в статье рассматриваются разные типы существующих технологий
затемнения кода и демонстрируется новый инструмент для затемнения
кода, включенный в пакет Visual Studio .NET 2003.

Наверное, вы уже знакомы со всеми преимуществами архитектуры Micro-


soft .NET Framework, ориентированной на метаданные, — от упрощения
развертывания ПО и контроля версий (versioning) до богатой функцио-
нальности IDE (integrated development environment), опирающейся на
двоичные файлы со встроенными описаниями (self-describing binaries).
Возможно, вы не задумывались об этом, но легкость доступа ко всем
этим метаданным породила проблему, до сих пор не беспокоившую боль-
шинство разработчиков. Программы, написанные для общеязыковой
исполняющей среды (CLR), легче поддаются обратному проектированию

Публиковалось в MSDN Magazine/Русская Редакция. 2003. № И (ноябрь). — Прим. изд.


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

(reverse engineering). Это не является следствием каких-то упущений в


архитектуре .NET Framework — просто таковы реалии современных язы-
ков с промежуточной компиляцией (Java-приложения демонстрируют те
же качества). И Java, и .NET Framework широко используют метаданные,
встраиваемые в исполняемый код: байт-код (bytecode) в Java и MSIL в
.NET. Находясь на гораздо более высоком уровне, чем двоичный машин-
ный код, современные исполняемые файлы насыщены информацией, ко-
торую можно легко декодировать.

С помощью таких инструментов, как ILDASM (MSIL-дизассемблер. по-


ставляемый с .NET Framework SDK), или декомпиляторов (decompilers)
вроде Anakrino (http://www.sanrik.com/net/exemplar) и Reflector for .NET
(http://www.aisto.com/roeder/dotnet) кто угодно может легко просмотреть
ваши сборки и разобрать их обратно в исходный код. Эти могут восполь-
зоваться хакеры, чтобы отыскивать бреши в защите, красть уникальные
идеи или взламывать программы. По-моему, этого достаточно, чтобы при-
задуматься.

Но не волнуйтесь. Есть такое решение — затемнение кода (code obfusca-


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

Несколько сторонних поставщиков отреагировали на эту ситуацию, создав


«затемнители» (obfuscators) для .NET-кода. Microsoft включает Dotfuscator
Community Edition в Visual Studio .NET 2003 совместно с нашей компани-
ей PreEmptive Solutions, которая поставляет разнообразные пакеты средств
затемнения.

На примере Dotfuscator Community Edition мы расскажем все о затемне-


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

Чтобы продемонстрировать декомпиляцию и затемнение, мы будем ис-


пользовать реализацию классической игры Vexed с открытым исходным
кодом. Vexed.NET написана Руи Бин-Амоцем (Roey Ben-amotz) и доступ-
на по ссылке http://vexeddotnet.benamotz.com. Это головоломка, где надо
сдвигать одинаковые блоки друг к другу, чтобы они исчезали. Вот простой
метод из исходного кода Vexed.NET:
Пресечение попыток обратного проектирования 221

public void undo() {


if (numOfMoves>0) {
numOfMoves--;
if (_UserNoves.Length>=2)
_UserHoves = _UserMoves.Substring(0, _UserMoves.Length02);
this.loadBoardCthis.moveHistory[nijinOfHinoves - (numOfHoves/50) * 50]);
this.drawBoard(this.gr);
}
I

Дизассемблирование
.NET Framework SDK поставляется с дизассемблером ILDASM, который
позволяет декомпилировать сборки .NET Framework в операторы IL-ac-
семблера. Для запуска ILDASM убедитесь, что .NET Framework SDK уста-
новлен, и в командной строке наберите ILDASM, указав далее имя про-
граммы, которую вы хотите дизассемблировать. В данном случае мы набе-
рем «ILDASM vexed.net.exe». Запустится пользовательский интерфейс
ILDASM, через который можно просматривать структуру любого прило-
жения .NET Framework. На рис. 1 показан дизассемблированный метод
Undo.

QnPaiHivndl]
Refresh • voidlclass |Si4lem DiawmglSjistern.Draw«ig.6re
Tolmage. class [System DfawingJSystem Dialing Burriapl
ToShng
ToStimg
draw VHd[i
.itefiftedi public fiidebysig instance «old undoO cil »a

// Code size 11Э (ВИ71)


.naxstack 5
lL_eeee: Idarg.B
int32 «eKed.net.board::nundfHo«es
IL BOB6: Uc.ll.e
11_ВвВ7: ble.s IL_M78
Idarg.B
IL еова: eup
а яяаь: Idfid int32 vexed.net.board::nunDFHoues
IL_eeiB: ldc.14.1
IL ami: sub
IL~B012: stfld int32 uened.net .board: :nui«OfMoues
A yertissfafa \ IL 8817; Idarg.B
• A movei - iittarj H BB18: idfld string 4Sxed.net.board:: UserMoyes
A numOFiecesL IL eeid: callulrt Instance int32 [wscorIt6JSiFeten.StrIrig::9et_i«ngth()
I chooseLevel IL BB22: Idc.i*!.2
! lirvMam IL BB23: blt.s

string wesed,net.board: :_tlserHoues

Рис. 1. Дизассемблированный метод Undo


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

Декомпиляция
Если вы сейчас думаете, что ваш исходный код сможет увидеть и понять
лишь узкий круг знающих IL-ассемблер, вспомните, что декомпиляция на
этом не заканчивается. С помощью декомпилятора можно воссоздать под-
линный исходный код. Утилиты такого рода способны декомпилировать
.NET-сборку непосредственно в исходный код на высокоуровневом языке
вроде С#, Visual Basic .NET или C++. Посмотрим на метод Undo, рекон-
струированный декомпилятором Anakrino:

public void undo() {


if {this.numOfMcves > 0} {
this.numOfMoves =
this.numOfMoves - 1;
if (this. JJserMoves.Length >= 2)
this._UserMC'ves =
this. JJserMoves.Substring(0, this.JJserMoves.Length - 2);
this.loadBoa.rd(
this.nraveHistory[this.numOfMoves - this.nunOfHoves / 50 * 50]);
this.drawBoard(this.gr);
>
I

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


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

Углубляемся в затемнение
Затемнение выполняется с применением ряда смежных технологий. Его
цель — скрыть характер программы, не меняя ее поведение в период вы-
полнения. Это не шифрование, но в контексте .NET-кода это даже к луч-
шему. Дело в том, что вы могли бы зашифровать .NET-сборки и сделать их
полностью нечитабельными. Но такой способ создает классическую ди-
лемму: поскольку исполняющая среда способна выполнять лишь неза-
шифрованный код, вместе с зашифрованной программой должен хранить-
ся ключ для расшифровки. А раз так, то нетрудно создать утилиту для из-
влечения ключа, расшифровки кода и записи IL на диск в его исходном
виде. После этого программа будет абсолютно беззащитна перед декомпи-
ляцией.

Шифрование в какой-то мере аналогично тому, как если бы вы заперли


в сейфе обед из шести блюд. Ключ есть лишь у того, кому этот обед пред-
назначен (в данном случае — у CLR), и мы не хотим, чтобы кто-то дру-
гой узнал, что будет есть владелец ключа. Увы, в обеденное время вся
Пресечение попыток обратного проектирования 225

if (this.г.Length >= 2)
this.г = this.r.Substring(0, this.r.Length - 2);
this.a(this.q[this.p - this.p / 50 * 50]);
this.a(this.e);

Как видите, и без других способов затемнения понять этот метод уже на-
много труднее.

Удаление несущественных метаданных


Не все метаданные в скомпилированном .NET-приложении используются
исполняющей средой. Некоторые из них предназначены для других средств,
таких как дизайнеры (designers), IDE и отладчики. Например, если вы опре-
деляете свойство с именем «Size» в С#-типе, компилятор создает метадан-
ные для имени свойства «Size» и сопоставляет это имя с методами, реали-
зующими операции get и set (которые он назовет соответственно «get_Size»
и «set_Size»). Когда вы пишете код, устанавливающий свойство Size, ком-
пилятор генерирует вызов самого метода set_Size и никогда не ссылается на
свойство по его имени. По сути имя свойства присутствует здесь для IDE и
разработчиков, использующих ваш код; оно никогда не применяется CLR.
Если ваше приложение предназначено для использования только испол-
няющей средой, а не другими средствами, затемнитель может безопасно
удалить соответствующие метаданные. Кроме имен свойств в ту же кате-
горию попадают имена событий и параметров методов. Dotfuscator Com-
munity Edition удаляет все эти типы метаданных, когда полагает, что это
безопасно.

Дополнительные технологии
С помощью описанных выше технологий Dotfuscator Community Edition
обеспечивает хорошее затемнение, но вы должны знать и о дополнительных
технологиях, которые предоставляют еще большую защиту и способны во-
обще предотвратить обратное проектирование. Dotfuscator Professional
Edition реализует множество дополнительных технологий, включая затем-
нение потока управления (control flow obfuscation), шифрование строк
(string encryption), инкрементное затемнение (incremental obfuscation) и
уменьшение размера (size reduction).

Затемнение потока управления Мощная технология затемнения, цель


которой — скрыть предназначение последовательности инструкций без
226 Защита кода и данных

изменения логики. Что важнее, она удаляет ключи, которые ищет деком-
пилятор для правильного восстановления высокоуровневых операторов
исходного кода, таких как if-t hen-else и циклы. Фактически эта техноло-
гия нацелена на нарушение работы декомпиляторов.

Чтобы увидеть ее эффект в действии, снова взгляните на декомпилирован-


ный метод Undo — после переименования и затемнения потока управле-
ния (рис. 3). Как видите, вместо ранее существовавших вложенных опера-
торов if, декомпилятор создал оператор if, два вложенных цикла while и
несколько goto. Имеется ссылка на метку 11, но сама метка декомпиля-
тором не создана (похоже, что это «баг» в самом декомпиляторе).

Рис. 3. Метод Undo после переименования и затемнения


public void cO {
if (this.о > 0) {
goto iO;
do {
while (true) {
this.a£thls.p[this,o - this.о / 50 * 503);
this.a(this.d);
goto 11;
i2: this.q = tliis.q.Substring(0, this.q.Length - 2);
>
iO; this.o = this.o - 1;
} while (this.q.Length < 2);
goto 12;

Шифрование строк Технология, применяющая простой алгоритм шиф-


рования к строковым литералам (string literals), встроенным в ваше при-
ложение. Как уже говорилось, любое шифрование (или дешифрование),
проводимое в период выполнения, уязвимо. То есть умный хакер со вре-
менем взломает его, но для строк в коде приложения оно имеет смысл. Не
будем прятать голову в песок: если хакеры захотят разобрать ваш код, они
не станут слепо искать переименованные типы. Скорее всего они будут ис-
кать «Invalid License Key», что приведет их прямо в то место кода, где вы-
полняется проверка лицензии. Поиск строки невероятно прост, а шифро-
вание строк в какой-то мере препятствует этому, поскольку в скомпилиро-
ванном коде присутствуют только зашифрованные строки.

Инкрементное затемнение Помогает решить при затемнении проблему


с «заплатками» (patches), выпускаемыми для устранения ошибок. При
Пресечение попыток обратного проектирования 227

устранении ошибок в коде часто создаются или удаляются классы, мето-


ды или поля. Изменение кода (например добавление или удаление мето-
да) может привести к тому, что при последующем затемнении названия бу-
дут переименовываться несколько иначе. То, что раньше называлось «а»,
теперь может называться «Ы». И это, увы, непредсказуемо.

Инкрементное затемнение позволяет справиться с этой проблемой. Dot-


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

Уменьшение размера Прямо не препятствует обратному проектирова-


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

Код для многократного применения предполагает наличие дополнитель-


ного кода, рассчитанного на самые разнообразные случаи (contingent
code); однако в каждом конкретном приложении, как правило, встречает-
ся лишь один-два случая. Продвинутый затемнитель обнаруживает это и
удаляет весь незадействованный код (опять же, из скомпилированной
сборки, а не из исходного кода). В результате на выходе оказываются толь-
ко те типы и методы, которые нужны вашему приложению, — больше ни-
чего. Более компактное приложение расходует меньше вычислительных
ресурсов и быстрее загружается. Это особенно важно для приложений, вы-
полняемых под управлением .NET Compact Framework, и для распреде-
ленных приложений.

Использование Dotfuscator Community Edition

Теперь применим Dotfuscator Community Edition для затемнения прило-


жения Vexed. Dotfuscator Community Edition использует файл конфигура-
ции, указывающий параметры затемнения для конкретного приложения.
228 Защита кода и данных

Он предоставляет GUI, помогающий легко создавать и эксплуатировать


файл конфигурации, а также запускать затемнитель и проверять результат.
Кроме того, интерфейс командной строки для Dotfuscator Community
Edition позволяет легко инте]рировать затемнение в процесс автоматизи-
рованной сборки. Запустить GUI можно непосредственно из меню Tools в
Visual Studio .NET 2003.

Чтобы подготовить Vexed для затемнения, укажите в GUI три параметра:


входную сборку, местоположение файла сопоставлений и выходной ката-
лог. Входные сборки (в Dotfuscator они называются «trigger assemblies»)
указываются на вкладке Trigger. Здесь их можно добавлять в любом коли-
честве, но для Vexed нужна лишь одна.

Адрес файла сопоставлений указывается на вкладке Rename | Options


(рис. 4). Файл сопоставлений содержит однозначное сопоставление меж-
ду исходными и незатемненными* именами. Очень важно сохранить этот
файл после затемнения приложения; без него искать и устранять пробле-
мы в затемненном приложении будет непросто. Из-за важности этого фай-
ла Dotfuscator по умолчанию не перезаписывает существующий файл со-
поставлений, пока вы не установите флажок Overwrite Map file.

Рис. 4. Вкладка Rename I Options

* Видимо, имелись EI виду затемненные имена. — Прим. перев.


Пресечение попыток обратного проектирования 229

[) vexed.net
ii 18 slBls/PackDele
41 *rj slats
raadME
*i{ PixslData
*J frmMain
8! *» prelerences
ilH -*f about
*ij board
h
% .dor: voidt^Bxed.nelfrwWain. siring. System.Drawing.Point int32)
dor: void(System Drawing.Graphics, string.
ctor: void(Syste(n Drawing.Graphics, string, int3?. System Drawing.Point]
S If* mil. voidlSyalem.Crowing.Graphics, string, in(32. System.Dtewing.Point)

••!; •» Tolmoge : System.Drawing.Вitmep(siting. inl32)

Рис. 5. Браузер результатов

Наконец, вкладка Build позволяет указать каталог, где будет размещено


затемненное приложение. Сделав это, вы готовы затемнить приложение.
Вы можете сохранить файл конфигурации для дальнейшего использо-
вания, а затем нажать кнопку Build на вкладке Build или кнопку Play
на панели инструментов. Во время сборки Dotfuscator отображает инфор-
мацию о ходе процесса на панели вывода GUI. Чтобы изменить объем вы-
водимой здесь информации, выберите Quiet или Verbose на вкладке Options.

По завершении сборки можно наглядно оценить результаты на вкладке


Output (рис. 5). Как видите, Dotfuscator отображает графическое пред-
ставление приложения, подобно браузеру объектов (object browser). В ре-
жиме просмотра новые имена располагаются сразу под исходными. За-
метьте, что класс с именем «board» переименован в «h», а два метода с раз-
ными сигнатурами (init и Tolmage) переименованы в «а».

Исследование файла сопоставлений


Создаваемый с помощью Dotfuscator файл сопоставлений имеет XML-
формат и, помимо уже упомянутых сопоставлений имен, содержит кое-ка-
кую статистику, дающую представление о том, насколько эффективным
был процесс затемнения. В табл. 1 суммирована статистика для типов и
методов после затемнения приложения Vexed.
230 Защита кода и данных

Табл. 1. Статистика переименования

Переименований Методов, переимено-


Всего (кол-во) (%} ванных в «а» (%)

Классон 20 20 100
Методой 164 144 88 39

Файлы сопоставлений также используются для инкрементного затемне-


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

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

Во-первых, придется поработать чуть больше, если ваше приложение


включает сборки со строгими именами (strongly named assembly). Такие
сборки содержат цифровую подпись, которая позволяет исполняющей
среде определить, изменялась ли сборка после подписания. Подпись пред-
ставляет собой SHAl-хэш, подписанный закрытым ключом (private key)
из RSA-пары ключей «закрытый-открытый». Подпись и открытый ключ
(public key) встраиваются в метаданные сборки. Поскольку затемнитель
изменяет сборку, важно, чтобы подписание было выполнено после затем-
нения. В период разработки и перед затемнением следует реализовать от-
ложенное подписание (delay-signing) сборки и завершать процесс подпи-
сания позже. Подробнее о сборках с отложенным подписанием см. в доку-
ментации .NET Framework. Также не забудьте отключить верификацию
строгих имен (strong name validation) при тестировании сборок с отложен-
ным подписанием.

Применение Reflection API и динамической загрузки классов (dynamic


class loading) усложнит процесс затемнения. Поскольку эти технологии
являются динамическими, они не сочетаются с технологиями статиче-
Пресечение попыток обратного проектирования 231

ского анализа, которые используются в большинстве затемнителей. Рас-


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

public Mylnterface GetNewTypeO {


Type type = Type.6etType( GetUserlnputStringO, true };
object newlnstance = Activator.Createlnstance( type );
return newlnstance as Mylnterface;
}

Имя типа код получает от другого метода. GetUserlnputString может


предлагать пользователю ввести строку или извлекать ее из базы данных.
В любом случае имя типа в коде отсутствует, и его нельзя получить ста-
тическим анализом, поэтому затемнитель не сумеет выяснить, для каких
типов в исходных сборках могут создаваться экземпляры подобным об-
разом. Выход из данной ситуации — запрет переименования всех потен-
циально загружаемых типов, реализующих Mylnterface (заметьте, пере-
именование методов и полей по-прежнему допускается). Именно здесь
важную роль играет настройка вручную и некоторые знания о затемняе-
мом приложении. Dotfuscator Community Edition предоставляет средст-
ва, предотвращающие переименование заданных типов, методов или по-
лей. Можно выбирать отдельные имена или писать правила исключения
(exclusion rules), используя регулярные выражения и другие критерии
вроде области видимости. Например, из переименования можно исклю-
чить все открытые методы.

Еще одна проблема с использованием затемнителей — сопровождение уже


развернутого затемненного приложения. Скажем, ваше приложение ини-
циирует исключение (что случается даже с лучшими из нас), и пользова-
тель отправляет вам дамп стека, который выглядит примерно так:
System.Exception: A serious error has occurred
at cv.a()
at cv..ctor(Hashtable A_0)
at ar.a(di A_0)
at ae.a(Strlng[] A_0)
Разумеется, этот дамп стека гораздо менее информативен, чем дамп стека
незатемненного приложения. Хорошая новость в том, что с помощью фай-
ла сопоставлений, созданного в ходе затемнения, можно декодировать ин-
формацию из стека в исходный вид. А плохая новость — информации из сте-
ка иногда недостаточно для того, чтобы по файлу сопоставлений однознач-
но восстановить исходные символы. Например, обратите внимание, что в
дампе опущены возвращаемые типы метода. В приложениях, затемненных
232 Защита кода и данных

с помощью усовершенствованного алгоритма переименования с использо-


ванием технологии введения перегрузки, методам, различающимся только
возвращаемыми типами, могут быть присвоены одинаковые имена. Так что
информация из стека может оказаться весьма туманной. В большинстве
случаев вы сможете сузить круг возможных вариантов, чтобы с высокой сте-
пенью достоверности установить исходные имена. С этой целью Dotfuscator
Professional предоставляет инструмент для автоматического перевода ин-
формации из стека к исходному «провинившемуся» методу.

Заключение
Не давайте хакерам применять удобную утилиту ILDASM к вашему при-
ложению с сомнительными целями. Защитить ваш код поможет хороший
затемнитель. Затемнение препятствует обратному проектированию. Dot-
fuscator Community Edition, поставляемый с коробочной версией Visual
Studio .NET 2003, обеспечивает хорошее затемнение кода всего нескольки-
ми щелчками кнопки мыши.

Габриэл Торок {Gabriel Torok) — президент PreEmptive Solutions. Соавтор


книг «JavaScript Primer Plus» и «Java Primer Plus» издательства Macmillan.
Выступает с докладами на международных семинарах и конференциях
по разработке программного обеспечения.

Билл Лич {Bill Leach) — технический директор PreEmptive Solutions. Архитек-


тор и главный технолог линейки продуктов Dotfuscator. Раньше часто
выступал в роли научного рецензента книг и статей по разработке про-
граммного обеспечения.
АлекДзвис

Защита

Закрытие доступа
к строкам подключения
баз данных и другим
секретным параметрам
в коде*

Защита секретов приложения, таких как строки подключения к базам данных


и пароли, требует тщательного учета множества факторов, в том числе,
насколько секретны данные, кто может получать к ним доступ, как
сбалансировать защиту, производительность и удобство в работе и т. д.
В этой статье объясняются основы защиты данных и сравниваются различные
технологии, которые могут быть использованы для защиты параметров
приложения. Автор рассказывает, чего следует избегать, например скрытия
ключей в исходном коде и использования Local Security Authority. Кроме того,
он представляет некоторые эффективные решения вроде Data Protection API.

Проблема защиты строк подключения баз данных, паролей и других сек-


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

Публиковалось в MSDN Magazine/Русская Редакция. 2003. № 11 (ноябрь). — Прим. изд.


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

и шифрование (encryption). Эти технологии можно использовать по от-


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

Скрытие данных
Скрытие данных (data hiding) иногда называют зашитой через затумани-
вание (security through obscurity). Полагаясь на этот способ, вы считаете,
что только вам известно, где хранится секретная информация, и надеетесь,
что никто другой не сможет это выяснить. Подвох в том, что приложение
тоже должно знать, как получить доступ к данным, поэтому возможность
сохранения секретов во многом зависит от способности приложения защи-
щать это знание.

В число самых распространенных мест для скрытия секретных параметров


приложения входят исходный код приложения, файлы конфигурации и
реестр Windows. Данные можно хранить и в таких экзотических местах,
как метабаза IIS (IIS metabase), UDL-файлы (Universal Data Link), фай-
лы собственного формата — где угодно.

Process
iexplore.exe SelValue HKOJ\Softi«ate\Mic-№soft\lnternelExplQreAMdn\FulScreeri SUCCESS
iexplore.exe: SetValue HKCU\Soltwaie\Microsoft^nternelExploFer\Main\Window_.. SUCCESS 2COO s
iexpbie.exe: SefValue HKCUXSo/lrtareSMic ra SoftWindows \S hellN oRoamVB agN! R . .
SUCCESS 02 02 l!
ienplore exe: SelValue HKCU\Software\.Mic rosofftWindoi'MSfieilNoR oam\B agMR . .
SUCCESS HOOf
iexplore.exe. DeleteVal. HKCUVSoftW3ie\Miaosoft\Whdow\ShellNoRaam\BagMR .. NOTFOU...
iexpiore.exe SetValue HKCUVS oftwaieSM ic rasoft\Windows\S hefN oRoanAB agMR SUCCESS
iexplore.exe: DeleteVal НКШ \Sofl i*are\MicrosoftVWind№vs\She!INoR oarnVBagMP. .SUCCESS
iexptue exe: DeleleKeji H*CCU\SoHwate\.MJciosoltVWJndows\S hellNoR oamVB agM R . .
SUCCESS Kev. Q
iexplore.exe: SetValue H KCU \S oftware\M icroso/t\WindowE4ShelfNoRoam\BagMR. ..
SUCCESS 0300!
iexDlore.exe: SetValue HKCU\SoltwaJe\Micro3oH\Wmdows\ShellNoR oam\BagM R . . . SUCCESS 02021,
iexpbie.exe SetValue HKCU VS oltwaieXM ic-osof t\Windows\S heIN ofi oam\8 agMR.
SUCCESS Ox8E
iexptare еке: SetValue HKCU\Software\Mbosof[VWindows\ShellNoRoarn^agMR. SUCCESS FFFFI
(explore exe: SetValue SUCCESS OxFFFF
(explore exe: SetValue HKCUVSoftwaieSMiciosollWindcjwisVSheDNciFloamSBags1.!.. SUCCESS OxFFFf •
S «Value HKCUXSo»ware\Miciosoft Wind ows\SheflNoRoam \Bags\i. SUCCESS
т
Рис. 1. Regmon
Закрытие доступа к строкам подключения баз данных 235

За редкими исключениями, простое скрытие данных обеспечивает в луч-


шем случае примитивную защиту. Как правило, объем работы, необходи-
мой, чтобы хакер выяснил ваши секреты, минимален. Так, взломщик мо-
жет обнаружить местоположение данных, отслеживая изменения в систе-
ме, выполняемые работающим приложением (рис. 1). Это можно сделать
с помощью таких утилит, как regmon, filemon и diskmon от Syslnternals
(http://www.sysinternals.com).

Простота декомпиляции .NET-сборок представляет еще более серьезную угро-


зу. При помощи декомпиляторов вроде Anakrino (http://www.saurik.com/
net/exemplar) или Salamander (http://www.remotesoft.com) код приложения
может быть подвергнут обратному проектированию (reverse engineering),
что приведет к раскрытию секретных данных или логики приложения
(рис. 2).

*• Anakrino

Back

i•
щ Co isoleApplicationl
jpnvate static void Main(stnng[] args) {
Щ Г"Ц ConsoteApplicatnnl exe string localG;
И- {) ConsoteApphcationl string local!;
Я *i* Class! SglConnection loca!2;
. ьф .ctor(). void local 0 = ";
: ,§Ф Connect!oSqlSeivei locall = "ЭЕВШЕШ1'.
--*• DoSomething(Si^Con Iocal2 = null;
loca.12 = Classl.ConnectToSqlServer(localO,
Classl.DoSomethingClctcalZ);
е
-За ТуР Reference locaT2.Closei;j;
Reteiences

Рис. 2. Декомпиляция

Ограничение доступа к данным


Если доступ к данным ограничен, скрывать их местоположение не нужно.
При таком подходе вы полагаетесь на технологические функции, встроенные
в операционную систему и способные предотвратить несанкционированный
доступ к данным. Обычно ограничения доступа базируются на идентифика-
ции вызвавшего (caller's identity) или знании общего секрета (рис. 3). Этот
способ защиты данных используют Access Control Lists (ACL), Microsoft Data
Protection API (DPAPI) и изолированные хранилища.

При правильном применении управление доступом может стать одной из


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

Например, управление доступом на основе идентификации пользователя


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

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

вызвавший должен что-то знать ... вызвавший должен кем-то быть

Пароля • Криптографического ключа Щ$ Пользователя I Машины 1 Сборки

Рис. 3, Управление доступом

Табл. 1. Проблемы управления доступом на основе идентификации

Тип идентификации Проблемы

Пользователь Не срабатывает для приложений, выполняемых аноним-


но (например приложений ASP.NET) или под несколь-
кими идентификациями. Данные должен задавать и
считывать один и тот же пользователь. Позволяет
несвязанным приложениям, выполняемым под одной
и той же идентификацией пользователя, получать доступ
к данным друг друга

Машина Позволяет всем приложениям, выполняемым на одной


машине, получать доступ к данным друг друга

Сборка Данные должно задавать и считывать одно и то же


приложение. Поддерживается только при защите
по правам доступа кода (code access security) и в изоли-
рованных хранилищах

С другой стороны, когда управление доступом основывается на знании


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

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

Хотя эта технология защиты данных проверена временем, шифрование не


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

то Data link Properties

[cledb]
; Everything after
this line is an OLE DB
initstring
provider=MSDASQL.l;
user io=sa;
Password-Hi
Persist security
=True;
Data source=localhost;
initial catalog-master

Рис. 5. Удостоверения в UDL-файле

Выбирая или создавая механизм защиты данных, взвесьте его уязвимость


перед потенциальными угрозами. Такая оценка может оказаться проще, ес-
ли мыслить в категориях основных технологий зашиты данных, описан-
ных выше. Например, если вы храните информацию для подключения к
базе данных, содержащую SQL-удостоверения пользователя в UDL-фай-
ле (рис. 5), безопасность ваших данных сильно зависит от разрешений для
файла (ACL-списков), которые являются одной из форм ограничения
доступа. (В какой-то мере она зависит и от скрытия данных, но в данном
238 Защита кода и данных

конкретном случае скрытие данных — очень слабая защита, поскольку тре-


буется немного усилий, чтобы найти UDL-файл.) Хотя ACL-списки иг-
рают важную роль в защите данных, важно понимать, что, если хакер су-
меет получить доступ к UDL-файлу для чтения, ваши SQL-удостоверения
будут раскрыты.

Если речь не идет о ситуации, когда потенциальная угроза слишком мала


или ваши данные не слишком важны, избегайте решений на основе лишь
одной технологии защиты данных. Чем больше технологий задействовано
в решении, тем оно надежнее.

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

К сожалению, избежать хранения секретных данных можно не всегда. Ес-


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

• Какие версии операционных систем должно поддерживать прило-


жение?
• Что вам важнее; производительность приложения или безопасность?
Готовы ли вы пожертвовать производительностью ради большей безо-
пасности?
• Каков механизм определения данных? Это выполняется вручную,
например с помощью текстового редактора, или программно? Если
данные создаются программно, параметры приложения определяют-
ся из того приложения, которое их использует, или из другого при-
ложения?
• Сколько приложений в вашей организации или компании используют
защиту данных? Если их больше одного, все ли они предъявляют оди-
наковые требования? Хотите ли вы применить единое решение, удов-
летворяющее требованиям всех приложений, или предпочитаете раз-
ные механизмы защиты данных для каждого приложения?
• Насколько сложно обслуживающему персоналу будет работать с защи-
той приложения после его развертывания? Согласна ли группа техни-
ческой поддержки с вашим решением?

В зависимости от ответов на эти вопросы можно выбрать тот или иной


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

Все способы хранения секретных данных в открытом виде считаются


ненадежными, поскольку раскрывают данные любому, кто получит дос-
туп к их источнику, — задача, которая может оказаться проще, чем кажет-
ся. Следует избегать хранения незашифрованных данных в реестре Win-
dows, конфигурационных файлах, каталогах СОМ+, изолированных
хранилищах, файлах собственных форматов, метабазе IIS и UDL-файлах.
Более того, вообще никогда не храните секретные данные в открытом
виде.

Достичь абсолютной защиты нельзя, но получить ее приемлемый уро-


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

ACL-списков, а также скрытие и ограничение доступа к шифровальным


ключам.

Реализуя защиту данных, можно принять во взимание и другие аспекты


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

Хранение зашифрованных данных


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

Некоторые разработчики настаивают, что приложения Microsoft .NET


Framework не должны использовать реестр, но это не закон. Главный не-
достаток реестра в том, что он не вполне подходит для развертывания при-
ложения через XCOPY, однако, поскольку многие современные приложе-
ния развертываются с помощью программ установки и требуют выполне-
ния определенных этапов конфигурирования в тех системах, куда они
устанавливаются, совместимость с ХСОРУ может оказаться необязатель-
ной. Другой минус реестра в его специфичности для Windows-платформ —
этот подход не сработает в других операционных системах. Впрочем, это
ограничение станет серьезной проблемой, только когда и другие платфор-
мы станут поддерживать общеязыковую реализацию (common language
implementation, С LI).

Если вас интересует развертывание через XCOPY или взаимодействие с


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

конфигурационных файлах. В любом случае не забудьте применить со-


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

Необратимое и обратимое шифрование


Существуют два метода шифрования данных: необратимое (one-way)
(обычно называемое хэшированием) и обратимое (two-way). Технически
хэширование — это не шифрование, но поскольку обе технологии при за-
щите данных могут применяться сходным образом, т. е. преобразовывать
данные из открытого текста в шифрованный, я буду считать их логически
связанными.

Хэширование, служащее и для других целей, в области защиты данных да-


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

Аутентификация на основе паролей — типичный случай, в котором хэ-


ширование более приемлемо» чем обратимое шифрование. Если ваше
приложение хранит пароли только в целях аутентификации, не зашиф-
ровывайте их симметричным или открытым ключом — храните вместо
этого их хэшированные значения. При регистрации пользователя на вхо-
де вместо расшифровки и сравнения паролей в открытом виде приложе-
ние может сравнивать хэши паролей. Чтобы уменьшить риск атаки по
словарю (dictionary attack), всегда используйте в хэшах «расширяющие»
значения (salt values). «Расширение» — это случайные данные, добавляе-
мые к открытому тексту до того, как он будет хэширован, и сохраняемые
вместе с хэшем для последующего использования при сравнении друго-
го открытого текста с хэшем. Пример использования хэша с «расширени-
ем» для аутентификации на основе паролей см. в рубрике «Безопасный
код» за август 2003 г, (http://msdn.micTosoft.com/msdnmag/issues/03/08/
SecurityBriefs/default.aspx).
242 Защита кода и данных

Алгоритмы хэширования
Самые популярные алгоритмы хэширования — MD5 и SHA-1. Длина хэ-
шей SHA-1 равна 160 битам, а длина хэшей MD5 — 128, Алгоритм
SHA-1 чуть медленнее, но обеспечивает более высокую защиту, чем MD5.
В дополнение к MD5 и SHA-1 NET Framework предлагает поддержку 256-,
384- и 512-битных версий алгоритма SHA, что еще надежнее, но, вероят-
но, и медленнее.

Простейший способ хэшировать данные — вызвать метод HashPassword-


ForStoringlnConfigFile класса FormsAuthentication, как в следующем
примере:

using System.Web.Security;
...
string base64Hasn\'alue =
FormsAuthentlcatlon.HashPasswordForStoringlnConfigFileC'mypassword",
"shaT);

К сожалению, этот метод поддерживает только алгоритмы хэширования


MD5 и SHA-1, так что, если вы хотите использовать хэши SHA-256,
SHA-384 или SHA-512, вам придется писать чуть больше кода. Пример
но ссылке http;//www.obviex.com/samples/hash,aspx поясняет, как созда-
вать и сравнивать хэши с применением различных алгоритмов хэширо-
вания.

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

Хотя шифрование с открытым ключом можно использовать для защиты


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

ми и цифрового подписания данных, я сосредоточусь на шифровании с


симметричным ключом.

При выборе алгоритма шифрования имеет смысл выбрать самый надеж-


ный алгоритм с максимально длинным ключом. Самым надежным среди
всех алгоритмов с симметричным ключом, поддерживаемых .NET Frame-
work, считается одобренный правительством США алгоритм Rijndael (так-
же известный как алгоритм Advanced Encryption Standard, или AES). Этот
алгоритм поддерживает 128-, 192- и 256-битные ключи. Более подробную
информацию о Rijndael см. в статье Джеймса Мак-Каффри в этом номере.

Алгоритм Rijndael обладает еще одним преимуществом по сравнению с


другими алгоритмами с симметричным ключом, поддерживаемыми .NET
Framework. Тогда как другие алгоритмы предлагаются в форме «тонких»
классов-оболочек .NET Framework над имеющимися модулями Crypto-
API, Rijndael (реализованный как класс RijndaelManaged) написан полно-
стью в управляемом коде. Замечу, что некоторые разработчики считают
это недостатком и предпочитают использовать неуправляемую реализа-
цию алгоритма Rijndael для большей производительности.

К сожалению, эта реализация алгоритма Rijndael поддерживается толь-


ко в Windows XP и выше, а также в системах, где установлена .NET
Framework. Если ваше приложение должно быть совместимо с неуправ-
ляемыми программами, работающими под Windows 2000 или ниже, ис-
пользуйте Triple DES — улучшенную версию менее защищенного алго-
ритма DES. Алгоритм Triple DES поддерживает ключи длиной в 112 и
168 бит, но рекомендуется использовать 168-битные ключи. Из-за разной
трактовки битов четности (parity bits) ключа 168-битные ключи Triple
DES иногда называют 192-битными. Если вы хотите предоставить управ-
ляемым и неуправляемым приложениям возможность зашифровывать
или расшифровывать данные одинаковым ключом Triple DES, учтите,
что кодовая фраза (или пароль), из которого формируется ключ, должна
содержать только печатаемые ASCII-символы; иначе сгенерированные
ключи не совпадут (вследствие ограничений .NET-реализации алгорит-
ма Triple DES). .

Оригинальные алгоритмы DES, RC2 и RC4 в целом считаются менее стой-


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

На рис. 6 показаны различные схемы шифрования данных, которые вы


можете задействовать.
244 Защита кода и данных

Необратимое
(кэширование)

Шифрование данных
Алгоритм Rijndae! 126, 192, 256 (AES)

Симметричные
Triple DES- 112, 168
ключи

Открытые DES, RC2, RC4


и закрытые ключи

Рис. 6. Схемы шифрования данных

Защита криптографических ключей


При использовании симметричных ключей для шифрования и расшиф-
ровки данных приложения важно иметь возможность создать такой же
ключ через некоторое время. И здесь возникает проблема защиты ключей
или логики генерации ключей (не путать с алгоритмами шифрования).
Хотя для достижения этой цели можно применить несколько технологий,
все они имеют свои недостатки. Рассмотрим доступные варианты и ситуа-
ции, для которых они наиболее приемлемы.
Постоянные симметричные ключи можно генерировать двумя способами:
определяя ключи самостоятельно, или позволив операционной системе сде-
лать это за вас. Выбрав первый вариант, вы можете закодировать байты
ключа в исходном коде приложения или реализовать соответствующую ло-
гику для создания ключа по определенным неизменным характеристикам.
Эти характеристики, как правило, включают кодовую фразу, по которой соз-
дается ключ, и, возможно, другие значения, например инициализирующий
вектор (initialization vector). Что бы вы ни выбрали — кодирование байтов
ключа или реализацию логики генерации ключа, вы в конечном счете скры-
ваете ключ в исходном коде приложения. Такой вариант дает вам больший
контроль и гибкость, но и подвергает ваши данные большему риску в слу-
чае обратного проектирования вашего приложения. В качестве альтернати-
вы можно хранить ключ в постоянном хранилище вроде файла или реестра
и защищать это хранилище с помощью ACL. Но такой подход чреват ошиб-
ками при сопровождении, поэтому я не рекомендую его.
Более безопасный, но менее гибкий способ генерировать ключи — позво-
лить операционной системе делать это за вас. Для этого используются та-
Закрытие доступа к строкам подключения баз данных 245

кие средства операционной системы, как Local Security Authority (LSA)


или DPAPI.

Берегитесь LSA
Во времена расцвета Windows NT 4.0 функции LSA Policy LsaStore Private-
Data и LsaRetrieve Private Data предоставляли достаточно надежный спо-
соб защиты секретов приложения. Хотя функции LSA Policy доступны в
Windows 2000 и выше (и по-прежнему используются для защиты таких
данных, как пароли для служб Windows), Microsoft не рекомендует приме-
нять их, поэтому я упомянул эти функции лишь для полноты картины,
чтобы объяснить, почему их следует избегать.
Проблема с функциями LSA Policy в том, что они не только управляют
ключами и шифруют их, но и работают с хранилищами данных, используя
защищенную область реестра Windows. Может показаться, что это хорошо,
но это не так, потому что объем доступного места для функций LSA Policy
ограничен 4096 слотами, половина из которых уже занята системой. Если
приложения будут использовать LSA Policy для хранения секретных дан-
ных, им просто не хватит места. Далее, поскольку лишь в высшей степени
привилегированные пользователи могут вызывать функции LSA Policy,
они не сработают для приложений, выполняемых под непривилегирован-
ными учетными записями, такими как ASP.NET. Хуже того, существуют
инструменты вроде LSADUMP2 (http://razor.bindview.com/tools/desc/
Isadump2_readme.html), способные раскрыть секреты LSA. Вывод: не при-
меняйте функции LSA Policy для защиты данных.

Использование DPAPI
Как альтернативу функциям LSA Policy компания Microsoft рекомендует
применять подмножество Crypto API, которое называется DPAPI. DPAPI
включает две функции, пригодные для защиты данных: CryptProtectData
и CryptUnprotectData. Эти функции реализованы в crypt32.dll и могут вы-
зываться из приложений .NET Framework через P/Invoke. DPAPI являет-
ся частью операционной системы и доступен в Windows 2000 и выше.
DPAPI — в отличие от функций LSA — не работает с хранилищами дан-
ных, но способен создавать индивидуальные для машины или пользовате-
ля ключи для шифрования и расшифровки данных. Чтобы различать два
типа ключей, в документации DPAPI их называют хранилищем машины
(machine store) и хранилищем пользователя (user store).

Ключи, специфичные для машины, в общем небезопасны, поскольку мо-


гут быть использованы любым, кто получает доступ к системе. Можно сге-
нерировать более защищенную версию такого ключа, передав функции
246 Защита кода и данных

DPAPI пароль (также называемый вторичной энтропией). Этот вариант


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

Хотя ключи, специфичные для пользователя, считаются более безопасными,


им свойственны те же ограничения, что и всем другим видам управления дос-
тупом на основе идентификации. Во-первых, поскольку эти ключи могут ге-
нерироваться только программами, выполняемыми с загружаемыми профи-
лями пользователей, они не сработают в приложениях ASP.NET и в приложе-
ниях, выполняемых под определенными встроенными учетными записями,
Хотя существует' способ обойти эти ограничения через обслуживаемые ком-
поненты (serviced components), расплатой за него станет рост сложности и
снижение производительности приложения. Этот способ также требует, что-
бы обслуживаемый компонент выполнялся под привилегированной учетной
записью, что нарушает принцип наименьшей привилегии. Если вы решите ис-
пользовать DPAPI с ключами для пользователей, учтите, что только пользо-
ватель, зашифровавший данные, сможет расшифровать результат. Очевидно,
это неприемлемо для приложений, выполняемых под разными учетными за-
писями, или для программ, чьи параметры могут определяться разными поль-
зователями. Другая проблема ключей, специфичных для пользователей, в
том, что они позволяют всем приложениям, выполняемым в одном профиле
пользователя, получать доступ к данным /[руг друга, что может стать брешью
в защите. Как и в случае с DPAPI, использующим хранилища машины, эту
проблему можно решить, требуя от вызвавшего кода предоставить вторичную
энтропию, но, как и в предыдущем примере, возникнет проблема с хранени-
ем этой энтропии. К сожалению, DPAPI не позволяет одновременно исполь-
зовать ключи, специфичные для машины и специфичные для пользователя
(в одном вызове CryptProtectData).

DPAPI рекомендуется для приложений, которые выполняются под одной


учетной записью с загружаемым профилем пользователя, и чьи парамет-
ры определяются одним пользователем. (Более подробную информацию
см. во врезке «Загружаемый профиль пользователя».) Типичный при-
мер — служба Windows, выполняемая под учетной записью локального
или доменного пользователя. Если ваше приложение удовлетворяет этим
требованиям, ему следует задействовать DPAPI с ключами, специфичны-
ми для пользователей. В других приложениях можно использовать DPAPI
Закрытие доступа к строкам подключения баз данных 247

с ключами, специфичными для машины и защищенными вторичной эн-


тропией (см. http://msdn.microsoft.com/library/en-us/dnnetsec/html/Sec-
NetHTOS.asp и http://www.obviex.com/samples/dpapi.aspx). Вы можете оп-
ределить эту энтропию в исходном коде приложения и затемнить двоич-
ный файл приложения, чтобы обнаружить энтропию было непросто. Но
если вы изберете такой способ, учтите, что даже затемненный код может
быть подвергнут обратному проектированию. Чтобы узнать, что может и
чего не может предложить вам затемнение, прочтите статью Брента Рек-
тора (Brent Rector) «The Premier Obfuscator for Microsoft .NET Appli-
cations!» по ссылке http://www.wintellect.com/resources/newsletters/2002/
aug2002.aspx и статью Габриэла Торока (Gabriel Torok) и Билла Лича (Bill
Leach) в этом номере.

Загружаемый профиль пользователя


Хотя в документации Microsoft одним из требований к приложению,
в котором применяется DPAPI с хранилищем пользователя, указан
загружаемый профиль пользователя, там не объясняется, что это та-
кое. Если вы озадачены этим термином, считайте, что это профиль
интерактивного пользователя, который создается, когда пользова-
тель впервые регистрируется в системе. Все приложения, интерак-
тивно запущенные пользователем, выполняются с загруженным
профилем интерактивного пользователя. Неинтерактивные прило-
жения, такие как службы Windows, настроенные на выполнение под
LocalSystem, не могут загружать профиль пользователя и, таким об-
разом, не в состоянии задействовать DPAPI с хранилищем пользо-
вателя. То же можно сказать и о системных процессах, таких как
процессы ASP.NET, которые выполняются под встроенными учет-
ными записями. Чтобы позволить службам Windows обращаться к
DPAPI с хранилищем пользователя, их следует настроить на выпол-
нение под учетными записями локальных или доменных пользова-
телей с уже созданным профилем. На практике это означает, что до
того, как приложение сможет выполнять вызовы DPAPI от имени :
пользователя, тот должен хотя бы один раз интерактивно войти в
систему.

Если ваше приложение не может напрямую использовать DPAPI с ключа-


ми, специфичными для пользователей, а вы не хотите примириться с угро-
зой обратного проектирования, подумайте о реализации DPAPI с обслу-
живаемым компонентом, но учтите, что это негативно отразится на произ-
водительности вашего приложения [см. «Use DPAPI (User Store) from
I

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

ASP.NET with Enterprise Services» по ссылке http://msdn.microsoft.com/


library/en-us/dnnetsec/html/SecNet-HT09,asp]. Выбрав такой вариант,
будьте готовы реализовать схему авторизации для защиты от вызовов об-
служиваемого компонента злонамеренными приложениями и пользовате-
лями, что может оказаться весьма непросто. Чтобы понять, как справить-
ся с этой проблемой, изучите утилиту CipherSafe.NET и ее документа-
цию — она иллюстрирует применение DPAPI для критичных задач и
предлагает интересный подход к решению проблемы авторизации (см.
http://www.obviex.com/ciphersafe).

Скрытие ключей в исходном коде приложения


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

Другая ситуация, в которой встраивание ключа в исходный код приложе-


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

Основная угроза в данной ситуации исходит от обратного проектирования


и в настоящий момент с ней можно бороться лишь с помощью затемнения.
Затемнение не делает обратное проектирование невозможным, но способ-
но усложнить и замедлить этот процесс. Сравните рис. 7, на котором по-
казана декомпилированная сборка, затемненная с помощью Demeanor
(http://www.wiseowl.com), и рис. 3. Поскольку все закрытые (nonpublic)
символы в затемненной сборке переименованы с использованием непеча-
таемых символов, а строки зашифрованы, воссоздание логики приложения
из сборки может оказаться практически невозможным. Коммерческие де-
компиляторы, такие как Salamander (http://www.remotesoft.com), способ-
ны облегчить задачу обратного проектирования, преобразуя непечатаемые
символы в их печатаемые эквиваленты, а классы приложения — в исход-
ные файлы, но они стоят денег, а хакеру по-прежнему придется разбирать
нечитаемые символы (именно здесь «макаронный код» может действи-
тельно помочь!).

*• Andkitno

ConsoleAp()ication1
Iprivate static void i(string[] )
ComoleApplcalionl .exe string localQj
В i*S> Type References string local!;
SqlConnection local 2;
.ctoi[); void
localO = l.lC"3L"lL.
local! = 1.1 (||~
£* niSqComection); void local 2 i nul ,
string, siring]: SqIConneclion
Ioca12 > i-iClocalO, locall);
loca!2.Close();
References
*r!
Рис. 7. Декомпиляция затемненных сборок

Если вы определяете ключ в приложении, то, кроме затемнения сборки,


старайтесь не хранить реальные байты ключа в исходном коде. Вместо это-
го реализуйте логику генерации ключа на основе постоянных характери-
стик, таких как алгоритм шифрования, размер ключа, кодовая фраза, ини-
циализирующий вектор, и «расширение» (см. пример по ссылке http://
www.obviex.com/samples/encryption.asp). Это внесет дополнительный уро-
вень неопределенности, и ключ не удастся получить простым дампом сим-
волов из двоичного файла сборки. Так как логика генерации ключа и его
характеристики постоянны, неизменность результирующего ключа гаран-
тирована. Также хорошей идеей будет отказ от использования статических
строк в качестве характеристик для генерирования ключа и их создание
250 Защита кода и данных

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

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

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

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

Алек Дэвис (Alek Davis) — старший разработчик в группе инженеров


по защите в корпорации Intel (Фолсом, штат Калифорния). Обладает
опытом разработки Windows-приложений с акцентом на защиту корпоратив-
ных приложений. Имеет степени бакалавра компьютерных наук и магистра
в области программирования, полученные в Калифорнийском университете
(Сакраменто).
Джейсон Фишер

Знание типичных
механизмов вирусных
атак поможет лучше
защитить приложения*

Компьютеры разработчиков зачастую более уязвимы для вирусов,


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

В зависимости от степени опасности компьютерные вирусы могут приво-


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

Публиковалось в MSDN Magazine/Русская Редакция. 2003. № 5 (май). — Прим. изд.


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

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


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

Поскольку способы зашиты от вирусов постоянно меняются, я не претен-


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

Готовимся к бою
Как в принципе работают вирусы? Ну, во-первых, его создателю нужно
написать исполняемый код для активизации вируса. Чего хочет автор от
своего вируса? Должен ли он форматировать ваш винчестер? Удалять
JPG-файлы? Отправлять по почте свои копии вашим друзьям и колле-
гам? Для всего этого потребуется некий исполняемый код. А чтобы за-
пустить этот код, нужно активизировать вирус. Обычно исполняемый
код вируса запускается напрямую: некий беспечный пользователь полу-
чает по электронной почте письмо с вложенным файлом типа «Дважды
щелкни здесь и будет тебе счастье.ехе» или с чем-то столь же притяга-
тельным. Он запускает программу, и вирус срывается с цепи.

Казалось бы, противостоять такому способу заражения проще простого, тем


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

Из чего состоит исполняемый код вируса?


Конечно же, вы знаете, что исполняемыми являются ЕХЕ-файлы, а также
некоторые другие типы файлов с расширениями вроде .cmd и .com. По-
скольку документы текстовых процессоров могут содержать макрокоман-
ды для выполнения определенных задач, они тоже способны запускать не-
безопасный код. Существует много других типов файлов, содержащих или
запускающих исполняемый код. а любой исполняемый код может оказать-
ся небезопасным. В целом исполняемый код делится на три основных ви-
да: самостоятельные программы, код в ресурсах или библиотеках и код
сценариев или макросов, исполняемых каким-либо интерпретатором.
Знание типичных механизмов вирусных атак 253

В широком смысле самостоятельная программа — это файл любого


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

Запустите редактор реестра (regedit.exe) и раскройте узел HKEY_CLASS-


ES_ROOT (HKCR). В нем операционная система хранит информацию о
сопоставлениях файлов и относящихся к ним командах. Раскрыв HKCR,
вы увидите узлы для всех типов файлов, зарегистрированных на вашей ма-
шине. Просмотрите дерево и найдите раздел .ехе. Выберите его и обрати-
те внимание, что в нем хранится параметр по умолчанию со значением
«exefile» (оно показывается в правой панели окна). Это указатель на дру-
гой раздел в HKCR — exefile.

Раздел exefile содержит подраздел shell. В нем определяются возможные


действия для данного типа файлов. В терминологии ОС эти действия назы-
ваются операциями (verbs). Так, для документа Microsoft Word может быть
определена операция печати, что позволяет, щелкнув файл правой кнопкой
мыши, выбрать из контекстного меню команду Print. Раскройте подраздел
shell узла exefile и вы увидите возможные операции для ЕХЕ-файлов. В за-
висимости от конфигурации вашей системы вы скорее всего найдете два
или три подраздела. Возьмем для примера подраздел open. Раскройте его и
выберите подраздел command. Для каждой операции создается собственный
подраздел, каждый из которых в свою очередь включает подраздел com-
mand. Параметр по умолчанию в этом разделе точно описывает, что про-
изойдет при выполнении данной операции.

Двойной щелчок значка файла в Explorer дает тот же эффект: выполняется


команда для операции по умолчанию (для ЕХЕ-файлов — операция open),
Как видите, для ЕХЕ-файлов раздел command в open содержит параметр %1
%*. Если вы помните командные файлы MS-DOS, то, вероятно, почувствуе-
те что-то знакомое. Идея в том, что вместо параметра %1 подставляются
путь и имя запускаемого ЕХЕ-файла, а все ключи и параметры командной
строки, идущие за именем файла, передаются через параметр %*.

Так что любые другие типы файлов, для которых операция open содержит
параметр %1, могут представлять угрозу. Таких типов уйма, и все они по-
тенциально опасны. Создателю вируса известно, что многие не станут два-
жды щелкать файлы с расширением .ехе и скорее всего не будут запускать
ВАТ-файлы, но не запустят ли они CMD-файл? А как насчет расширений
.com, .pif или .vbs? Операция open для всех этих типов файлов содержит
параметр %1. Создатель вируса может просто заменить расширение фай-
ла с исполняемым кодом вируса с «.ехе» на, скажем, «.com», и тем самым
254 Защита кода и данных

увеличит вероятность того, что многие неподготовленные пользователи


запустят этот файл. Особенно опасны скромные файлы экранных заставок
с расширением .scr.

Волк в овечьей шкуре


Сравните в редакторе реестра параметр по умолчанию разделов command
операции open для ЕХЕ- и SCR-файлов. Как видите, они почти совпада-
ют: "%1" %* для ЕХЕ-файлов и "%1" /s для SCR-файлов. Получается, что
экранные заставки — самостоятельные приложения. Единственное отли-
чие в параметрах по умолчанию — ключ /S для SCR-файлов. Операция
open для файла экранной заставки изначально предназначена для предва-
рительного просмотра заставки; ее исполняемый файл именно так и вос-
принимает ключ /S. Ничто не может помешать создателю вируса присво-
ить файлу своей программы расширение .scr и проигнорировать ключ /S,
передаваемый при ее вызове пользователем.

Эксплуатировать популярность заставок особенно просто, потому что в


контекстном меню команда для операции open называется «Test». Пользо-
ватель думает, что он просто предварительно просматривает заставку, а на
самом деле активизирует вирус. Самые «умные» вирусы могут даже пока-
зывать настоящую заставку, чтобы занять вас на то время, пока вирус бу-
дет разрушать файлы на жестком диске. Название команды хранится в па-
раметре по умолчанию раздела open. Может, стоит заменить ее название на
«O&pen and Test», что точнее, — ведь для того, чтобы проверить заставку,
операционной системе придется открыть файл. Это поможет пользовате-
лям понять, что при выборе данной команды меню они запускают испол-
няемый код экранной заставки, что может оказаться небезопасным, если
код заражен вирусом.

Библиотеки бывают опасны


Исполняемый код также может находиться в различных ресурсах или биб-
лиотеках компонентов. На первый взгляд, это странное место для вирусов,
но на самом деле оно прекрасно подходит для них. К таким ресурсам от-
носятся файлы динамически подключаемых библиотек (,dll), апплетов па-
нели управления (.cpl), библиотек типов (.tlb, .olb и т, д.), ActiveX-элемен-
тов и СОМ-комлонентов (.осх, .vbx, .dll и др.). Этот код не запускается на-
прямую с параметром %1, как ЕХЕ-, CMD-, СОМ- и прочие исполняемые
файлы, но это не значит, что его нельзя выполнить.

Почти любую функцию из DLL-библиотеки можно вызвать при помощи


вспомогательной программы RUNDLL32.EXE. Возьмем такой пример:
Знание типичных механизмов вирусных атак 255

rundll32.exe
shell32.dll,OpenAs_RunDLL
c:\winnt\win.ini
Функция OpenAs_RunDLL из SHELL32.DLL принимает один параметр —
имя файла. При вызове она отображает диалог Open With (рис. 1). Выбрав
в нем имя файла и щелкнув кнопку ОК, можно открыть этот файл в соот-
ветствующем приложении, которому имя файла передается в виде пара-
метра командной строки. И примеров таких вызовов в системном реест-
ре — масса. Возможно, вы захотите взглянуть на некоторые из них, чтобы
лучше понять их работу и предоставляемые ими системные функции. Для
этого просто выполните в реестре поиск по RUNDLL32.
win.lnj - War dP ad
Fia fflit View Jnsert Fori

; f o r 16-bit app support


[fonts]
[extensions]
[me! extensions]
[files]
[I35F]
EELETE=C : 1\ TEHP\_ISTHPO . DIH1»
[ИС1 E x t e n s i o n s . S A E ]
aif=MPEGVileo
aifc-HPECVideo
aiff=HPEGVideo
asf=HPEGVideo3
asx=HPEGVideo2
au=HPEGVideo

Click the www you wart й- ideate epan

ff the pregram is not И *ba'fe:,

£hoose the ptograrn you want to IBS;


. Visual Bast
tf Visual Modeler
Ш Windows Media Player
Window? Packager
xecutable

XML Notepad Application

!~ ^wavs usethe pf'jg

Рис. 1. Использование вспомогательной программы


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

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


подмене DLL зараженной версией, где соответствующая функция замене-
на функцией с таким же именем, но иного назначения. Тогда при вызове
этой функции операционной системой вместо желаемого результата акти-
визируется вирус. Второй способ состоит в написании новой DLL и вызо-
ве ее функций через RUNDLL32.EXE. Это не так просто, как запустить
код из ЕХЕ-файла, но DLL, OCX, TLB или другие библиотечные файлы с
большей вероятностью будут приняты неподготовленным пользователем
или пропущены антивирусной программой, что вознаграждает дополни-
тельные усилия вирусописателя.

Сценарии и макросы: больше гибкости —


больше риска
Чтобы интерпретировать и запустить код сценария, нужен соответствую-
щий движок (engine), но этот код все же можно использовать для инфи-
цирования. Сценарии бывают нескольких видов. Очевидно, что самые по-
пулярные сценарии — макросы Visual Basic for Applications (VBA), содер-
жащиеся в документах Microsoft Office. Microsoft многое сделала для
обеспечения безопасности этих макросов, но макровирус по-прежнему мо-
жет нанести значительный ущерб, тем более что к документам с макроса-
ми слишком многие относятся безответственно. Другой набирающий по-
пулярность вид сценариев — файлы Windows Script Host (WSH). Эти
файлы, как правило, имеют расширения .wsf, .js или .vbs и по умолчанию
сопоставлены своим приложениям, поэтому при двойном щелчке такой
файл запускается без всяких вопросов. Последствия могут быть катастро-
фическими. Чтобы предотвратить их, желательно изменить операцию по
умолчанию с «open» на «edit». Это делается через реестр или диалоговое
окно Folder Options (рис. 2).

Опасные сценарии могут содержаться и в Web-приложениях. Как вы, ве-


роятно, знаете, в целях безопасности клиентские сценарии и ActiveX-эле-
менты сильно ограничены в правах доступа к системе, но есть малоизве-
стный тип файлов — HTMLAppIication (с расширением НТА), — который
работает как клиентское Web-приложение, но не скован такими же огра-
ничениями доступа. Он предназначен для того, чтобы разработчики мог-
ли использовать свои навыки в Web-разработке при создании полнофунк-
циональных приложений на основе браузера. Но опять же, неподготовлен-
ный пользователь может создать полный хаос, скачав и запустив такой
файл без предварительной проверки его содержимого. Перед запуском все
эти сценарии и макросы можно легко проверить, поэтому следует уделять
внимание любым файлам такого типа в вашей системе.
Знание типичных механизмов вирусных этак 257

JEW!
"iegiyaed m types.
;-..-, | i Щ i n , .
McrasofUetQLEDDAO
JPEG Image
~^'E/ ;
«fjPEG JPEG Image
JPEG Image
JSc<ip^cnp|fte
[1]JSE JScipl Sciipt Fie .1
41

ISbSKie ;- l^j°'|3Script5criptFile

Рис. 2. Изменения операции по умолчанию

Способы активизации вируса


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

R ЕС-файлы
Как вы, наверное, знаете, файлы с расширением .reg — это системные ре-
гистрационные файлы (system registration files) с информацией, которая

9-138
25S Защита кода и данных

вносится в реестр. Проблема в том, что для них по умолчанию применя-


ется операция «open» (с названием «Mer&ge»). Значит, если дважды
щелкнуть любой REG-файл, то в зависимости от конфигурации системы
его содержимое может быть немедленно скопировано в системный реестр
без запроса подтверждения. Ужасная идея, даже если не принимать во
внимание возможность злоупотребления. Прямо сейчас оторвитесь от чте-
ния и измените операцию по умолчанию для REG-файлов с «open» на
«edit». Это можно сделать через диалог Folder Options (рис. 2) или изме-
нить параметр по умолчанию для подраздела shell в разделе regfile, щелкнув
его правой кнопкой и выбрав команду Modify (рис. 3). В любом случае для
файлов этого типа операцией по умолчанию станет редактирование.

Ul related, desc iDCacrViUpdate REG.FJINARY 60 38 Fc 67 5d be tO 01


Ф-SJ SeCEdt 3Debug5erverCammand REG_5Z no
'• Si ServtePack ^DefaultDomainName REG_5Z J=ISHER_UtfTOP
: I a UJi Setup
^DeFaullPassword RE6_SZ )H5her
iti £| Svchost
REG _SZ ftdministretor
SI (Si 1 errrmal Server
1B-Q3 Time Zones
REG_BINARJ BB1602Q1
а Ш TVP« 1 Installer
1
ЁГ.И Llserinstallatle.drivers
: -{|i WIDOWS
REG_SZ
IS *Ш Vrtnlogon REG_S2 0
v/ow
• : aS REG.S2 [
К Ш Windows Script Host
ф 03 Vrfndove Scripting Host RE6.DWO... OKfffffF9d!4^4967197)
[* iiJ WindowSBcript REG^DWO... OlFFfFffFF («294967295)
£j WSCdoc Expkjrer.exe
&-C3 MojoSoFt
.+ UU M02*S
$ -£3 Netscape
£2 NetStudra
•i1 _J Nico Mak Corrputing
&; -U ODBC
i'l 2J ORACLE

в •

Рис. 3. Устранение уязвимости

Почему REG-файлы столь опасны? Рассмотрим пример. В прошлом году


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

Разумеется, из-за удаления самого вируса он не мог запустить ни один ис-


полняемый файл, включая редактор реестра. Можно было попробовать пе-
реименовать RegEdit.EXE в RegEdit.COM. Я надеялся, что, хотя вирус яв-
но изменил параметры реестра для ЕХЕ-файлов, он, возможно, не тронул
параметры для СОМ-файлов. К счастью, это решение позволило запустить
редактор реестра. Как я и думал, вместо "%1" %*, операция open для
ЕХЕ-файлов содержала команду «VirusExecutable.EXE %1>. Таким обра-
зом, при каждом запуске ЕХЕ-файла сначала запускалась программа с ви-
русом. Запрашиваемая программа передавалась вирусу через параметр
командной строки, и вирус мог запустить ее, оставляя пользователя в не-
ведении относительно происходящего. В этом и кроется опасность REG-
файлов: с их помощью можно запросто проделать такие изменения. Но ес-
ли вы разбираетесь в реестре, то сможете отменить эти изменения. Хоро-
шим, но выходящим за рамки данной статьи решением будет контроль
доступа к важнейшим узлам реестра. Подробнее об этом см. по ссылке
http://msdn.microsoft.com/library/en-us/dnexnt01/html/ewn0201.asp.

Узел ЕХЕ — лишь одно из многих мест в системном реестре, где можно
найти опасные изменения. Два других — разделы HKLM\SOFTWARE\
Microsoft\Windows\CurrentVersion\Run и RunOnce. REG-файл или ис-
полняемый файл вируса могут добавить в любой из этих разделов пара-
метры, содержащие путь и имя файла любого приложения, которое долж-
но быть запущено при загрузке системы. Эти параметры не отражаются в
группе Startup меню Start, и, если вы не знакомы с данным разделом рее-
стра, обнаружить их трудно. Разделы Run и RunOnce есть и в кусте реест-
ра HKEY_CURRENT_USER. Борясь с вирусом, проверьте их оба.

Еще один уязвимый раздел — HKLM\SOFTWARE\Microsoft\Windows


NT\CurrentVersion\Winlogon. Он содержит несколько параметров, кото-
рые могут использоваться вирусами. Так, раздел Shell указывает исполняе-
мый файл, используемый в качестве оболочки Windows, и обычно содер-
жит «Explorer.exe», однако вирус способен изменить его. Менее известный
раздел Userinit, который просто указывает на userinit.exe, можно изменить
так, чтобы запускать любую программу без ведома пользователя при за-
грузке системы. Вирус обычно модифицирует этот параметр, чтобы он
указывал на исполняемый файл самого вируса, который затем запускает
userinit.exe, и пользователь наверняка ничего не заметит. Это лишь неко-
торые из уязвимых разделов, о которых следует знать. Если вы самостоя-
тельно боретесь с вирусом, эти разделы и параметры могут подсказать, где
искать что-либо необычное. Конечно, перечислить все разделы, которые
используются вирусами, нельзя. Главное — свободно ориентироваться в
реестре. Исследуйте реестр и учитесь принимать меры для его защиты.
260 Защита кода и данных

Уязвимость PATH
Еще одна опасность таится в переменной окружения PATH. Как вы знае-
те, все самые длинные и часто используемые пути можно перечислить в
переменной PATH, чтобы не набирать их каждый раз. Например, если вы
внесете в переменную PATH путь d:\Program Files\Microsoft Visual Stu-
dio\vb98\, то для запуска Visual Basic 6.0 из командной строки достаточ-
но набрать «vb6». В системном реестре есть масса ссылок на исполняемые
файлы, которые тоже пользуются этим преимуществом. Проблема в том,
что применение этой на первый взгляд безобидной возможности весьма
рискованно. Рассмотрим следующий сценарий.

Windows определяет, какое исполняемое приложение следует использо-


вать в качестве оболочки, из параметра реестра в разделе HKLM\SOFT-
WARE\Microsoft\Windows NT\CurrentVersion\Winlogon. В этом разделе
параметр Shell обычно имеет значение «Explorer.exe». Логично, да? Но в
нем не указан путь. Ведь есть (или должен быть) только один файл Explo-
rer.exe — зачем указывать путь? Не лучше ли просто разрешить Windows
определить расположение файла по стандартной процедуре поиска пути
(включая просмотр значения и PATH)? Нет! Вариации вирусов Nimda и
CodeRed известны тем, что они заражали и создавали новые исполняемые
файлы с именем Explorer.exe, а потом размещали их в таких местах, как
корневой каталог системного раздела. Конечно, вирус не способен переза-
писать выполняемый экземпляр Explorer.exe, поскольку он монопольно
блокирован операционной системой, но при следующей перезагрузке из-
за отсутствия пути в параметре Wmlogon\Shell скорее всего будет запуще-
на фиктивная копия Explorer.exe из корневого каталога системного разде-
ла или другого места, указанного в переменной PATH до пути к системно-
му каталогу. А некоторые особо вредные вирусы способны заразить даже
оригинал — %SystemRoot%\Explorer.exe.

Так что причиной отсутствия пути в этом параметре реестра может быть
только лень. Вновь оторвитесь от чтения и проверьте реестр: если путь не
указан, немедленно исправьте это, как показано на рис. 3. (Учтите, что
такое изменение может нарушить работу некоторых программ установ-
ки, некорректно обращающихся с данным параметром.) Кроме того, на-
чинайте избавляться от привычки слишком сильно полагаться на пере-
менную PATH. Убедитесь также, что вирус не добавил к ней другие ка-
талоги, которым там нечего делать. Вирусы нередко делают именно так,
но при должной бдительности этого легко избежать. Помните, что любой
компьютер в вашей сети может быть заражен (любыми традиционными
способами, из которых самым распространенным является запуск инфи-
Знание типичных механизмов вирусных атак 261

цированного почтового вложения), и тогда вирус легко скопирует инфи-


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

В куче фрагментов
Последнее, о чем я расскажу, — об объекте фрагмента документа (scrap
object). Существует два типа соответствующих файлов (рис. 4). Оба типа
очень опасны, поскольку могут инкапсулировать исполняемый код в
составном документе OLE (compound OLE document). Чтобы понять ме-
ханизм их работы, в свободное время изучите параметры реестра, управ-
ляющие операциями open для этих файлов. Создать исполняемый код для
таких фрагментов несложно (см, статьи на смежную тематику, перечислен-
ные в аннотации).

Shortcut into a document;


5 gap
MeiosoH Excel SLK Data Inpoit Format
SLLFfc
Visual Studio SolutiOi
|м| SHD AU Fonral Sound

IGS 'SHB'

• ';*>); Shsitseiap object handl»- .

Рис. 4. Фрагменты документов

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

Вторая причина тоньше. Как оказалось, расширения SHS и SHB всегда


скрываются Explorer — пусть даже в настройках Windows вы задали ото-
бражение расширений всех файлов. Дело в том, что разделы реестра для
этих типов файлов включают недокументированный параметр «Kever-
ShowExt». Он переопределяет глобальные настройки Windows. Так что ав-
тор вируса может создать такой объект, присвоить ему значок, соответст-
вующий файлу с изображением, и переименовать его в нечто вроде «По-
смотри на эту забавную KapTHUKy.jpg». Реальное имя файла будет, конечно,
«Посмотри на эту забавную KapTHHKy.jpg.shs», но неискушенному пользо-
вателю файл покажется обычным графическим, содержащим какую-ни-
будь картинку. Когда возникнут подозрения, что этот файл вовсе не содер-
жит никакого изображения, ущерб уже будет нанесен. Поэтому удалите из
обоих разделов параметры «NeverShowExt». Тогда вы сможете быстрее
распознать эти опасные фрагменты.

Лучшая оборона — нападение


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

На рис, 5 показан один из многих возможных вариантов решения этой


проблемы. Я создал приложение на Visual Basic .NET, в котором реали-
зовал механизм,, предотвращающий запуск неблагонадежных программ и
распространение вируса по системе или всей сети. Прежде чем объяс-
нять, как оно работает, замечу, что это не полностью законченное при-
ложение (например, в нем нужно расширить обработку ошибок) и оно не
претендует на непогрешимость. Это лишь отправная точка, отталкиваясь
от которой вы сможете обдумать превентивные меры по защите от ви-
русов.

Рис. 5. Защита от запуска инфицированных файлов


bnports System.Security.Cryptography
Imports Microsoft. Win32
Imports System.Text
Imports System.10

Module Verify&ce

см. след. стр.


Знание типичных механизмов вирусных атак 263

Рис. 5. Защита от запуска инфицированных файлов


Private Function 8yteArrayToString{ByVal DataQ As Byte) As String
Din Counter As Integer

Din SB As New Stri*igBijilder(Data.Length}


For Counter = 0 To Data.Length - 1
SB.ftppenoX&ataCCounteO)
next

Return SB.ToString
End Function

Private Function Co»pareHashValues(ByVal FileName As String) _


As Boolean
Const RegPath Аз String = "SOFTWAflEXVerifyExe"

Bis Я As Registry
Dim RegKey As RegistryKey - R.LocalMachine.OpenSubKey< _
RegPath, True)
If RegKey Is Nothing Then
' flp» первом запуске программы этого раздела нет,
1
так что придется создать его
йедКеу *= R.LoealHaehine.CreateSubKeyCRegPatn)
End If

' Пытаемся считать из реестра ШВ-хэш для указанной программы


Dim ExeWashValue As SyteO ~ RegKey.GetValue(FileName)

If ExeHashValue Is Nothing Then


' Параметр отсутствует. Значит, он или удален, или
' указанная программа никогда не запуекаяась через
' нашу утилиту. В любом случае надо создать параметр
' дпя еявдудацего запуска.
RegKey.SetValue{Filename, HashData< FileName))
' Теперь запускаем саму программу
Call 8пеЩСе*яапсЮ, ApjjWinStyle.NoriaalFaeus)
Else
' Программа уже запускалась через нашу утилиту.
' Надо сравнить хзш из реестра с повторно вычисленным
' для текущего состояния исполняемого файла.
Dim AdHacHashValue As Byte() = HashData(FileSaiBe)
If ByteArrayToStri!i8{EreHashValue) * _
ByteArrayTQString(AdHocHashValue) TJien

см. след. стр.


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

Рис. 5. Защита от запуска инфицированных файлов (продолжение)

' Значения совпадают, поэтому запускаем программу


Snell(CoBroand( ), ApoWiRStyte. NorinalPoc«s)
Else
Nsg8ox("This executable program appears to have _
been tampered with I " & „
vbCrLf & "Please reinstall the program from Its" _
& " original source! ", _
HsgSoxStyle. Critical Or HsgBoxStyle,QKOnly, _
"Potential Tampering Detected*")
Bid If
End If
End FunotiOR

Private Function Haa*iData(ByVal FileName As String) As


' Очень важно, чтобы раздел реестра ShelI\Open\Command
' для каждого типа файгов содержал параметр "S1", передаваемый
' как аргумент. Только тогда пути и инена файдов с гтробелам«
' будут передаваться корректно,
Dim Fs As FileStrea» - Hew FlleStreaffl^ileNaffle, „
Fileftode.Open, FileAccess.Read)
Din Result As 8yte() * New _
NB5CryptoServiceProvider<).CtoHiputeHash(Fs)
Fs.Closa-O
fleturn Result
End

Pufclic Sub Hainf)


If Commando <> "" Then
Din Fl As New FlleIrtfo(Environs6nt.Qet€oBierandLineArgs{1)>
' Если хотите, можете вкявчить дополнительные расширения
" для исполняемых файлов, например ".pit" или ".end",
' Учтите, что функция Shell поддерживает только тиш
' файлов, запускаемых напрямую. Если заменить Shell,
' скажем, вызовом API-функции, то данную технологию
' можно будет применить к любому типу файла, которому
' сопоставлено некоо приложение.
Select Case Fi.Extension.ToLower
Case ".exe", ".com", ".bat"
If Fi.Exist*; Then
Call CoiFipareNashValues(Fi.FijllHa»e)
End If
End Select

см. след. стр.


Знание типичных механизмов вирусных атак 265

Рис, 5. Защита от запуска инфицированных файлов

Else
' Если 8 командной строке не переданы никакие параметры,
' сообщаем об ошибке к выходим. Или же можно использовать
' этот путь для установки корректных параметров
' в Shell\Gpen\Command!
Ms9Box("This application requires that the filename " & _
"and path to an" & vbCrLf & _
"executable program be passed " & _
"as a eoffiBand-line argument", _
HsgBoxStyle.ExclaBatiott Or MsgSoxStyLe.OKOnly, w
"Argument Required")
End If
End Sub

End Module

Я настроил свою утилиту на отслеживание расширений EXE, COM и


ВАТ, но, если хотите, можете расширить этот список другими типами
файлов. Она ищет ссылку на исполняемый файл в определенном месте
реестра. Если такой параметр есть, он содержит MDS-хэш исполняемого
файла (рис. 6). Если соответствующей записи нет, значит, ссылка была
удалена, или с момента установки моей утилиты верификации приложе-
ние не запускалось. В любом случае приложение нужно запустить. Од-
нако сначала следует вычислить MDS-хэш для этого приложения и запи-
сать его в корректном месте реестра. Вообще-то теперь моя программа не
сумеет предотвратить запуск файла, если он был инфицирован еще до
вычисления хэша. Но можно добавить в программу диалог для подтвер-
ждения действия, чтобы пользователь мог сам принять окончательное
решение.
Если запись хэша существует, то для запускаемой программы хэш вычис-
ляется повторно, и два значения сравниваются. Если они совпадают, зна-
чит, с момента первого запуска и вычисления хэша исполняемый файл не
изменялся. Несовпадение указывает на то, что файл был изменен, а это в
свою очередь может означать наличие вируса, поскольку большинство ви-
русов инфицируют исполняемые файлы, изменяя их содержимое. Описа-
ние способов, которыми они это делают, выходит за рамки данной статьи,
но если вы узнаете об изменениях в исполняемом файле, то сможете пре-
дотвратить распространение вируса по своей системе. В этом случае моя
утилита не позволит запустить программу. Она выдаст предупреждение
о возможном заражении данного исполняемого файла.
266 Защита кода и данных

Ш SOFTWARE
Ш 23 АТ7 Те.

fjj* ..JJ ClasHS Ни Ma?saft S{Jt Server s30\Tacis.


msoft Weja> Stude ЛГПр..

DesFipton
G«s
LSH
35 В! 05*1 Ь5 as 13 as -43 d7 ck e? ^ сЗ et S7
5* 2i * 03 И 65 Ж 3S 7s rf Л Оз Оста dc df
. » 58 7rf td 0* c5 5f Й fc 55 72 fO d7 5с и Й
_J £• ^gram Groups . 10 Sf =b 9c *s e8 ji 1 i» ей Те ft eS Id 8112 C3

V _J WVstiK? 3 1

'. . ,ertf|€<e

Рис. 6. MDS-хэш

Я не создава.1 программу установки для своей утилиты, поэтому вам при-


дется установить ее вручную. Для этого измените параметр Shell\Open\
Command реестра для каждого типа файлов, который вы хотели бы кон-
тролировать. Например, чтобы отслеживать изменения в ЕХЕ-файлах, из-
мените значение параметра по умолчанию в разделе HKCR\exefile\Shell\
Open\Command с "%1" %* на «ш/тъ\VerifyExe.exe» "%1" %*.

Разумеется, вместо пути следует подставить полный путь к файлу Verify-


Exe.exe. Это позволит запускать утилиту для всех ЕХЕ-файлов. Затем она
запустит требуемый ЕХЕ-файл, передав ему все ключи и параметры ко-
мандной строки. Внесите в реестр те же изменения для типов файлов
СОМ и ВАТ, а также для любых других, которые вы хотите контролиро-
вать. Возможно, вам захочется встроить эти сопоставления в саму про-
грамму или в программу установки.

Я знаю, о чем вы сейчас думаете. А если вирус учтет такой способ контроля
и удалит из реестра необходимые параметры до инфицирования приложе-
ния? Тогда при следующем запуске приложение сможет выполняться и рас-
пространять вирус. Или же вирус просто удалит сам файл VerifyExe.exe?
Все это возможно, но горькая правда в том, что здесь ничего не поделаешь.
Что бы вы ни делали, вирус всегда может обойти вашу оборону. Конечно, вы
можете изменить исходный код утилиты и размещать хэш в другом разделе
реестра, INI-файле или любом другом месте. Вероятно, тогда вирусам будет
труднее избежать обнаружения, однако любое эффективное решение рано
или поздно само станет мишенью для вирусов. Именно поэтому, как я уже
Знание типичных механизмов вирусных атак 267

говорил, многие современные вирусы пытаются обнаружить и обезвредить


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

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

ДжеЙсон Фишер (Jason Fisher) — технический архитектор, независимый


автор и редактор. По ночам он превращается в бесстрашного супергероя
и безжалостного поборника компьютерной справедливости, который сража-
ется с вирусами, отражает атаки типа DDoS и др'угие попытки вторжений.
В свободное от подвигов время с ним можно связаться по адресу
jasef@swbell.net.
Стэн Зундблат и Пер Зундблат

Шаблоны

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

Архитектура, ориентированная на сервисы (Service-Oriented Architecture,


SOA), рассматривает приложение как сервис, который можно найти
и к которому можно получить доступ через Интернет; при этом не имеет
значения ни платформа, ни язык программирования, на котором написан
компонент-потребитель. Сервис должен быть совершенно автономным,
но не изолированным, поскольку ему придется обслуживать клиентские
запросы. Обслужить запрос или отклонить его, решает сам сервис и никто
другой. Авторы знакомят читателей с концепцией автономии и эмиссаров
и предлагают проектировать приложение как автономию, a UI — в виде
набора эмиссаров, переложив на них как можно больше работы.
В большинстве случаев это поможет проектировать автономию на базе
компонентов, не использующих состояния, что обеспечит высокую
масштабируемость и позволит максимально упростить приложение.

Люди не могут жить в полной изоляции, и разработчики тут не исклю-


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

Публиковалось в MSDN Magazine/tyccKaa Редакция. 2003. № 7 (июль). — Прим. изд.


Архитектура автономного приложения 269

Теперь все иначе. Современным приложениям часто требуется интеграция


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

Ясно, что потребность в кросс-платформенной интеграции нужно учиты-


вать при проектировании и разработке приложений. К счастью, у этой за-
дачи есть решение. Архитектура, ориентированная на сервисы (Service-
Oriented Architecture, SOA), рассматривает приложение как сервис, кото-
рый можно найти и к которому можно получить доступ через Интернет;
при этом не имеет значения ни платформа, ни язык программирования, на
котором написан компонент-потребитель (consumer). Иначе говоря, сер-
вис сам предоставляет сервисы, не зависящие ни от платформы, ни от язы-
ка. И Web-сервисы — лишь один из примеров.

Автономия — что это такое?


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

Пэт Гелланд (Pat Helland), архитектор из Microsoft, начал говорить об ав-


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

Главный ресурс, который автономия хранит, поддерживает и защищает, —


это данные. Данные автономии могут храниться в самых разных источни-
ках. Структурированные данные обычно хранятся в реляционных базах
данных типа SQL Server. Автономия не дает никому прямого доступа к
своим данным, сама выбирая для клиента запрошенные им данные и даже
выполняя для него операции над этими данными. Так или иначе, только
автономия обладает прямым доступом к своим данным.

В буквальном переводе: вотчина, — Прим. перев.


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

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


можно считать, что реальные данные никогда не покидают пределов ав-
тономии — только их копии, «моментальные снимки». Что касается ак-
туальности данных, она имеет место только в пределах автономии. Кли-
енту не следует рассчитывать на получение самой последней версии дан-
ных. Даже в момент получения данные могут потерять актуальность
из-за какой-то транзакции, выполненной над источником данных внут-
ри автономии.

Большинство автономий стремится самостоятельно управлять своими


транзакциями. Автономия ни при каких обстоятельствах не должна разре-
шать посторонним управлять выполняемыми внутри нее транзакциями
(или отдельными частями этих транзакций). И все же автономия может
предоставлять внешнему клиенту сервис, позволяющий ему управлять ча-
стью транзакции, если эта часть относится к так называемой компенси-
рующей транзакции (compensating transaction). Автономия монопольно
управляет своей частью транзакции, но делает это в рамках обслуживания
клиента, который выступает в роли координатора транзакции.
Примером компенсирующей транзакции может быть любая транзакция,
соответствующая второй части спецификации Web Services Transac-
tions — Business Activity (BA). Такая транзакция выполняется по одно-
му из двух сценариев, В первом (нормальном) выполняется набор опе-
раций, указанных в транзакции, а во втором — другой набор операций,
призванных устранить последствия транзакции, уже зафиксированной в
источнике данных автономии. Компенсирующая транзакция вызывается
координатором транзакции, в роли которого может выступать другая ав-
тономия. Она происходит, если после фиксации автономией своей части
транзакции координатор обнаруживает, что какая-то другая часть тран-
закции завершилась неудачей. В таком случае все изменения, внесенные
транзакцией, которая выполнена и зафиксирована автономией, нужно
удалить, даже если их откат невозможен. А это вполне вероятно, так как
блокировка соответствующих записей уже освобождена, и другие тран-
закции могли успеть изменить их состояние. Компенсирующая транзак-
ция исключает такую ситуацию.

Не все типы транзакций поддерживают компенсацию. Большинство авто-


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

Защищенные шлюзы
В какой-то мере приложение, ориентированное на сервисы, безопасно про-
сто по своей природе: единственный способ заставить его выполнить one-
Архитектура автономного приложения 271

рацию в интересах клиента — отправить ему сообщение с запросом на об-


служивание. Выполнить этот запрос или отклонить, решает приложение.

Приложение нередко предоставляет часть своих сервисов всем, кто запро-


сит их. Тогда Web-сервису достаточно делегировать работу подходящему
компоненту, а затем передать клиенту ответ от этого компонента. В других
случаях Web-сервис должен сначала аутентифицировать клиента, чтобы
выяснить, имеет ли он право на запрошенный сервис. Это может быть про-
стая проверка удостоверений в Active Directory или базе данных SQL
только на шлюзе. Или проверка с применением различных технологий за-
щиты, например, олицетворения или делегирования в сочетании с заши-
той на основе ролей на разных уровнях приложения. Не забывайте об
угрозе атак типа «отказ в обслуживании», о хакерах, пытающихся проник-
нуть в систему, и т. п.

В рамках инициативы по повышению безопасности приложений Microsoft


опубликовала ряд документов по этой тематике. Один из самых важных —
«Building Secure ASP.NET Applications» — издан Microsoft Press в этом го-
ду в виде книги. Вы можете скачать документ (http://msdn.microsoft.com/
library/en-us/dnnetsec/html/secnetipMSDN.asp) или купить книгу — в обо-
их источниках вы найдете массу полезных сведений обо всех аспектах за-
щиты Web-приложений.

Архитектурный шаблон
Очевидно, что автономия — это архитектурный шаблон (architecture pat-
tern). Как и в случае других шаблонов, вашим коллегам должно быть по-
нятно, о чем речь, если вы, будучи архитектором ПО, скажете им: «по-мо-
ему, следует проектировать это приложение как автономию». В этом и со-
стоит одно из преимуществ шаблонов: они помогают обсуждать вопросы,
связанные с архитектурой и проектированием, на более высоком уровне
абстракции, не вдаваясь в ненужные на данном этапе детали.

Также ясно, что не все автономии следует проектировать по одному шаб-


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

Эмиссары
Ряд автономий поддерживает UI для предоставляемых ими сервисов.
В таких автономиях может применяться один из шаблонов эмиссаров
(emissary patterns) — эмиссар на основе либо Web Forms, либо Win-
dows Forms. Эмиссар действует подобно торговому агенту или торговцу
недвижимостью и предоставляет пользователю образец данных, позво-
ляя выбрать один из вариантов. Эмиссар также в курсе большинства
принятых в автономии бизнес-правил и может помочь пользователю со-
ставить запрос так, что он с большей вероятностью будет выполнен ав-
тономией.
Эмиссары также способны сохранять состояние пользовательского сеанса.
Типичный пример — Web-форма ASP.NET, которая сохраняет содержимое
корзины покупателя между запросами и тем самым позволяет добавлять
в корзину новые товары и хранить уже добавленные, пока не придет вре-
мя оформления заказа.
Однако автономия не доверяет эмиссару, даже если формально он являет-
ся ее частью. Любой запрос, поступающий через него, проверяется на
предмет безопасности, полноты и корректности заполнения не менее тща-
тельно, чем запросы, поступающие по другим каналам. В этом случае ав-
тономия ведет себя подобно страховой компании, которая проверяет заяв-
ление на выплату страховки, даже если оно подано через агента, оформив-
шего эту страховку. Это разумно, так как эмиссар действует скорее в
интересах клиента, чем автономии.
Как же распределяются обязанности между автономией и ее эмиссарами?
Автономия имеет дело с разделяемыми (общими) данными (shared data).
Это означает, что операции записи должны быть упорядоченными, чтобы
не нарушить целостность данных. Эмиссары, напротив, работают с локаль-
ными копиями эталонных данных только для чтения (образцами данных),
а также с информацией, индивидуальной для каждого пользователя. То
есть эмиссары могут игнорировать проблему параллельного доступа к дан-
ным. Как правило, эмиссары поочередно обслуживают клиентов в демили-
таризованной зоне, тогда как автономия обычно размещается в защищен-
ной интрасети и должна быть всегда доступна клиентам.
Web-фермы обеспечивают едва ли не безграничное масштабирование эмис-
саров. Масштабировать автономию много сложнее, так как ей приходится
обрабатывать разделяемые данные, которые должны быть доступны всем.
Поэтому, переложив больше работы на эмиссаров и тем самым разгрузив
автономию, вы заметно повысите масштабируемость приложения. Вспом-
ним Amazon.com. Навигация по сайту, добавление книг и других товаров
Архитектура автономного приложения 273

в корзину — все это можно отдать на откуп эмиссарам, а автономию мож-


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

Проверка соответствия бизнес-правилам


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

Слабое сопряжение с клиентами


Автономия слабо сопряжена (loosely coupled) со своими клиентами. Обыч-
но соединения между клиентом и автономией вовсе нет, пока клиент не
инициирует его отправкой сообщения. Автономия обрабатывает сообщение
и посылает клиенту ответ, в котором может быть результат запрошенного
действия или сообщение об отказе в обслуживании. В любом случае соеди-
нение между клиентом и автономией разрывается сразу после ответа авто-
номии — по аналогии с обменом сообщениями в HTTP. Как и в HTTP, со-
единение можно оставить открытым на весь сеанс, но обычно оно закрыва-
ется после одного цикла обмена сообщениями.
Слабое сопряжение очень важно для масштабирования, так как сводит к
минимуму время, в течение которого клиент подключен к системе и ис-
пользует ее ресурсы. Именно этим принципом руководствовались проек-
тировщики службы Microsoft Transaction Services (MTS), которая в даль-
нейшем эволюционировала в СОМ+ Component Services, а теперь извест-
на как .NET Enterprise Services.

Web-сервисы на основе XML


Идеи, на которых базируется SOA, — транспарентность размещения и не-
зависимость от платформы и языка программирования, а Web-сервисы —
идеальное средство для реализации этих идей. Поскольку Web-сер-
вис публикуют в виде URL, его реальное размещение может изменяться
274 Защита кода и данных

произвольным образом, если только его провайдер поддерживает действи-


тельную ссылку на этот сервис.

Ну а раз Web-сервисы совершенно не зависят от платформы и языка про-


граммирования, для работы с ними клиентам достаточно поддерживать
SOAP, XML и один из транспортных протоколов. Обычно это HTTP, но го-
дится и SMTP или любой другой стандартный транспортный протокол
Интернета.

Считается, что любая автономия, написанная на .NET-совместимом язы-


ке, должна предоставлять свои данные и функциональность через Web-
сервисы. Сами Web-сервисы не обязательно открывать внешнему миру —
можно ограничить круг их прямых клиентов эмиссарами, известными ав-
тономии.

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


слабого сопряжения (loose coupling) с клиентами. Вместо наборов объек-
тов состояний в автономиях следует применять компоненты сервисов, не
использующие состояния. По возможности надо передавать и обрабаты-
вать данные в том виде, в каком они поступают от клиентов или отправ-
ляются им, т. е. в виде XML-потоков. Этому способствует ,NET Framework
с ее замечательным XML API для транспорта данных через автономию и
для предоставления этих данных клиентам.

Эти API состоят из ADO.NET-объектов, называемых наборами данных


XML (XML datasets). Средства ADO.NET для доступа к данным позволя-
ют без труда выполнять выборку информации из произвольного числа
таблиц базы данных в XML-набор данных. Такие наборы легко транспор-
тируются между компонентами (и даже между процессами и компьютера-
ми) и в конечном итоге доставляются потребителю. Содержимое XML-на-
бора данных можно рассматривать как XML-документ, который крайне
легко передать как ответ Web-сервиса XML. Наконец, хотя набор данных
содержит данные в формате XML, использовать синтаксис XML для их
чтения и записи вовсе не обязательно. Для доступа к его свойствам мож-
но применять обычный объектно-ориентированный синтаксис вроде Trai-
nerData.Trainers(3).LastName.

Порой против представления би;шес-сущностей в виде XML-наборов дан-


ных возражают, приводя примерно такие аргументы: -«клиенту лучше не
знать структуру базы данных». Действительно, было бы весьма опрометчи-
во позволить XML-набору данных копировать структуру базы данных.
Структура этого XML-объекта должна определяться исключительно специ-
фикой запросов. В принципе его структуру несложно определить, не имея
никакого представления о структуре базы данных, так что все наборы дан-
ных приложения можно задать еще до проектирования базы данных.
Архитектура автономного приложения 275

Проектирование XML-наборов данных


в соответствии с их использованием
Рассмотрим реализацию компонента View Upcoming Racedays, отвечаю-
щего за вывод расписания скачек в приложении, которое мы использова-
ли как пример в предыдущих статьях и книгах. Щелкнув ссылку, пользо-
ватель должен получить список предстоящих заездов. Для составления
списка используются поля Date, Track, City и Country.

Зная, какие данные потребуются для составления списка, можно спроек-


тировать соответствующий XML-набор данных. Идеальный проект тако-
го объекта показан на рис. 1. Как видите, он состоит из единственной таб-
лицы — RaceDayTable, которая содержит все нужные данные. За исключе-
нием RaceDayld все поля таблицы отображаются клиенту (рис. 2), а с
помощью поля RaceDayld пользователи могут выбрать интересующий их
день скачек и получить дополнительные сведения на отдельной странице.
Посмотрим, откуда берутся нужные данные. Как показано на рис. 3, на ко-
тором изображена часть модели базы данных, следует обратиться к четы-
рем таблицам, чтобы создать единственную таблицу набора данных.
RacedaysData
RaceDayTable:DataTable

Y
ЯасеОауТаЫе
RaceDayld:lnteger
Date: Date
Rac6TrackName:String
City Name: String
Country Name; String

Рис. 1. XML-набор данных

Ради забавы взгляните на хранимую процедуру, которая с помощью SQL-


команды SELECT за один прием выбирает данные из четырех таблиц ба-
зы данных в один набор данных:
CREATE PROCEDURE GetFutureRacedays AS
SELECT RacingProgramld As RaceDayld, Date, TrackName As RaceTrackName,
CityName, CountryName
FROM RacingPrograms As RP
JOIN RaceTracks As RT ON (RP.RaceTrackld = RT.RaceTrackld)
JOIN Cities As Ci ON (RT.Cityld = Ci.Cityld)
JOIN Countries As Co ON (Ci.CountryCode = Co.CountryCode)
WHERE Date > GetDateO
ORDER BY RacingProgramld
276 Защита кода и данных

Date Tj-ack City Country


SNaw Details' "2Ш2~11-2§" T.a'bv Galopp Stockholm Sweden
Show Details 2002-12-06 Ovrevoll . Oslo Norway
Sr. •;•'.-.' Dat i i S f 2002-12-09 Taby GaAopp' Stockholm Swsd^ |
Show Details 2002-12-13 Jagersro Galopp MalmU Sweden
ЗЙЙ Detail. 2QU2-12-20 T.;ibv Galopp Stockhblm Sweden
Рис. 2, Таблица со сведениями о расписании скачек

Структуры набора данных и баз ы данных в корне различаются. Дело в том,


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

Cityjd City id
TrackName CilyName
CountryCode CountryCode
CountryName

Рис. З. Таблицы базы данных

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

Кстати, реальным источником данных для проблемной области является


база данных. Именно там хранятся структурированные данные. Модель
Архитектура автономного приложения 277

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


это и есть модель проблемной области.

Вот почему набор классов сущностей в приложении тесно коррелирует с


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

В идеале соответствие между объектной и реляционной моделями, пред-


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

Увязка объектов и таблиц


Почему возникает тенденция к такой же нормализации классов сущ-
ностей, как и для таблиц базы данных? В основном из-за увязки объек-
тов в памяти и реляционных таблиц. За редким исключением авторы, ра-
тующие за моделирование проблемной области на основе объектов, ре-
комендуют почти один в один увязывать классы в памяти и таблицы
базы данных.

На рис. 4 показан упрощенный пример того, с чем столкнется клиент в та-


кой ситуации. Кроме ключей и классов наборов, объектная модель явля-
ется точной копией структуры базы данных. Стоит ли говорить, что для за-
полнения этих четырех объектов потребуется отнюдь не один оператор
SELECT? Очевидно и то, что четыре цикла обмена данными с базой зай-
мут больше времени, чем один. Наконец, как быть с главной целью — изо-
лировать клиент от структуры базы данных?

RacingPrograms RaceTracks

Add Adtf Add Add


Remove Remove Remove Remove
Hem Item Hem
1 I I

RacingProgram RaceTrack
i 1
Date TrackName CWyKame CountryName

Рис. 4. Результат прямой увязки объектов и таблиц базы данных


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

Из сказанного должно быть ясно, что предоставление клиентским приложе-


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

Все это особенно важно для приложений, ориентированных на сервисы, —


по крайней мере, для приложений, предоставляющих Web-сервисы. Такие
приложения поддерживают программные интерфейсы, возвращающие
клиентам XML-потоки или XML-документы. Структура XML-документа
определяется XSD-схемой, то же верно для XML-набора данных, который
фактически является XML-документом.

XML-наборы данных
никогда не подключаются к базам данных
Возможно, не все еще знают, что XML-набор данных (Dataset) никогда не
подключается к базе данных — он просто не может этого. Для передачи
данных между XML-набором данных и базой данных нужен какой-нибудь
прокси, сопоставляющий таблицы набора данных с реляционными табли-
цами. Такой объект предоставляет ADO.NET, причем в нескольких верси-
ях, ориентированных на разные СУБД. Называется он адаптером данных
(data adapter) и применяется только для обмена информацией между ис-
точниками и наборами данных. Сам Dataset не имеет ни малейшего пред-
ставления ни о базе данных, ни об операторах SQL, заполняющих его или
передающих данные в источник.

В чем же причина путаницы? Почему многие уверены, что применение


Dataset непременно ведет к зависимости от базы данных? Дело, наверное,
в том, что XML-объект Dataset в каком-то смысле является заменой преж-
них наборов записей (объектов Recordset), применявшихся в класси-
ческом ADO, Remote Data Objects (RDO) и Data Access Objects (DAO).
В каждой из этих моделей Recordset напрямую подключался к источнику
данных, например к реляционной базе данных. Единственное исключе-
ние — ADO-объект Recordset, который умел отсоединяться от базы дан-
ных, а затем вновь подключаться к ней.

XML-наборы данных принципиально отличаются от прежних наборов


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

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

Эта стратегия поможет спроектировать автономию на базе компонентов,


не использующих состояния, что обеспечит высокую масштабируемость и
позволит максимально упростить приложение. Но иногда без компонен-
тов, поддерживающих состояния, все же не обойтись. XML-объект Dataset
в ADO.NET — прекрасная кандидатура на роль транспортера данных в
большинстве приложений. В сочетании с адаптерами данных объекты
Dataset великолепно работают с базами данных. XML-наборы данных пол-
ностью основаны на XML и без проблем пересекают границы между про-
цессами и компьютерами. Они рассчитаны на поддержку связывания дан-
ных (data binding) — механизма, который во многих случаях предпочти-
телен для отображения данных на формах Web Forms и Windows Forms.

Если вы хотите подробнее изучить архитектурные шаблоны Пэта Геллан-


да и его концепции автономий и эмиссаров, послушайте его выступление
по ссылке http://www.microsoft.com/usa/Webcasts/ondemand/892.asp -
вы не зря потратите свое время.

Стэн Зундблат (Sten Sundblad) и Пер Зундблат (Per Sundblad) — совла-


дельцы шведской компании Sundblad & Sundblad ADB-Arkitektur, которая
занимается разработками для .NET и специализируется на архитектуре
и проектировании ПО. Стэн и Пер — авторы книг «Designing for Scalability
with Microsoft Windows DNA» (Microsoft Press, 2000) и «Design Patterns for
Scalable Microsoft .NET Applications-. (Sundblad & Sundblad, 2002). Подроб-
нее об их деятельности можно узнать на сайте http://www.2xSundblad.com.
С ними можно связаться по адресам stens@2xSundblad.com
и pers@2xSundblad.com соответственно.
Михаил Коготков-Лизин

Защищенный вход

Средства безопасной
аутентификации через
Microsoft .NET Passport

Защищенный вход (secure sign-in) — новая функциональность в .NET Passport


версии 2.0, службе единого входа и создания профилей (single sign-in and
profile service). Эта функциональность особенно полезна для сайтов,
содержащих конфиденциальную информацию, и везде, где безопасность
имеет первостепенное значение, в том числе для сайтов банков, медицинских
сайтов и т. д. Защищенный вход не менее безопасен, чем современная
SSL-технология входа на Web-сайты, и практически полностью исключает
возможность атак с повторением пакетов (replay attacks) и по словарю
(dictionary attacks). В этой статье поясняется концепция защищенного входа
и демонстрируется, как с минимальными усилиями реализовать его либо в ASP
с использованием СОМ-объекта Passport.Manager, либо в ASP.NET на основе
.NET-класса Passportfdentity.

Microsoft .NET Passport — это система единого унифицированного входа


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

Защищенный вход — функция Passport, которая обеспечивает более высо-


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

Публиковалось в MSDN Magazine/Русская Редакция. 2002. № 3 (сентябрь). — Прим. изд.


Средства безопасной аутентификации через Microsoft .NET Passport 281

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


вход через Passport так же безопасен, как и любые SSL-технологии (Secure
Sockets Layer) входа, применяемые на Web-сайтах в настоящее время.

Фактически реализация защищенного входа требует применения SSL, спе-


циальных параметров для функций входа через Passport и браузера с под-
держкой HTTPS (этот протокол поддерживается почти всеми современ-
ными браузерами). Разработка сайта с поддержкой защищенного входа не
сложнее, чем обычного сайта, — вам понадобится написать всего несколь-
ко дополнительных строк кода.

В этой статье я сравню разные подходы к регистрации на входе и опишу


операции, нужные для поддержки защищенного входа на сайт. Я рассмот-
рю защищенный вход сквозь призму двух парадигм программирования,
одна из которых основана на СОМ-объекте Passport.Manager, а другая -
на объекте Passport Identity, предоставляемом Microsoft .NET Framework.
В заключение я покажу несколько примеров Web-страниц, рассчитанных
на использование защищенного входа.

Стандартный и защищенный вход


Как уже говорилось, у стандартного входа через Passport и других меха-
низмов проверки есть ряд общих проблем с безопасностью. Во-первых,
уязвимость перед лобовыми атаками, при которых подбираются подходя-
щие учетные данные. Чтобы уменьшить уязвимость перед атаками такого
рода, на серверах входа для .NET Passport реализован механизм замедле-
ния (slow-down), отбивающий охоту к атакам с подбором паролей в по-
пытке угадать верный. После пяти подряд неудачных попыток входа поль-
зователя просят подождать пять минут. Это не блокирует пользователя, но
ставит серьезную преграду на пути у злоумышленников, пытающихся по-
лучить доступ к какой-нибудь учетной записи.

Повторение пакетов — другой тип атаки, при которой перехватывается


транзакция входа по открытому HTTP-соединению. В настоящее время
служба Passport уязвима перед атаками с повторением пакетов даже не-
смотря на шифрование билета (ticket) и профиля, так как обмен ими осу-
ществляется через открытое HTTP-соединение. Перехватив соответствую-
щие пакеты и повторив их, хакер может подменить пользователя на тот пе-
риод, пока не истечет срок действия его билета регистрации (login ticket).
С другой стороны, учетные данные (адрес электронной почты и пароль)
передаются в доменный центр Passport (Passport domain authority) no
HTTPS — так что они надежно защищены.
282 Защита кода и данных

Чтобы избежать автоматической повторной аутентификации (silent re-


authentication), сайты — участники системы Passport (далее — просто сай-
ты-участники) м:огут требовать от пользователя заново вводить свое реги-
страционное имя и пароль независимо от текущего состояния аутентифи-
кации этого пользователя. Заставляя его повторно вводить свои учетные
данные, сайт по сути запрещает доступ любому, кто не знает соответствую-
щего имени и пароля.
Новые функции защищенного входа устраняют уязвимость, присущую
стандартному входу. Методы IsAuthenticated, AuthURL2, LoginUser и
LogoTag2 теперь принимают параметр, позволяющий сайту-участнику
указать один из двух новых уровней защиты аутентификации. Первый
уровень, Secure Channel, требует применения SSL при любой попытке ау-
тентификации. Второй уровень Security Key, требует для регистрации до-
полнительного ввода PIN (личного идентификационного кода).

Уровень Secure Channel


Уровень Secure Channel защиты входа активизирует несколько новых функ-
ций, исключающих возможность анализа пакетов, которые могли бы быть
использованы для атаки с повторением пакетов. На этом уровне защиты
страницы должны передаваться по HTTPS. Это означает, что у вашего сай-
та должны быть подписанные сертификаты, выданные центром сертифика-
ции (certification authority, CA). Если вы предпочли использовать оформле-
ние в едином стиле Passport (cobranding)* на сервере входа и других сете-
вых серверах, соответствующие элементы должны быть доступны и по
HTTPS. Наконец, страница для защищенного входа может быть оформле-
на в едином стиле точно так же, как и страница для стандартного входа.
Функциональность защищенного входа записывает в HTTPS-заголовок
новый защищенный «cookie» с зашифрованным PUID (Passport Unique
ID). HTTPS не позволяет анализаторам сетевых пакетов получать этот
cookie, поэтому его нельзя перехватить и применить для фальсификации
пользователя. Чтобы предотвратить копирование и использование на
других сайтах cookie, передаваемых в заголовках на сервер входа, Pass-
port расшифровывает защищенный cookie и сверяет его PUID с PUID
пользователя в билете. Если они не совпадают или если защищенного
cookie нет, пользователь считается не прошедшим аутентификацию и
должен заново выполнить все этапы передачи учетных данных. Билет и
профиль по-прежнему записываются как cookie в открытом текстовом
формате, чтобы они были доступны тем страницам сайта, на которых
HTTPS не применяется. По завершении защищенного входа Passport

Подробнее об этом понятии см. по ссылке http://msdn.microsoft.com/Iibrary/default.asp7url


Iibrary/en-us/ppsdkl4/cobrand/cobraridtemplate.asp. — Прим. перев.
Средства безопасной аутентификации через Microsoft .NET Passport 283

Manager записывает защищенный cookie в ваш домен в виде HTTPS-


cookie.

.NET Passport API будет по-прежнему извлекать информацию о времен-


ных метках из обычного билета. Однако, указав Passport-функциям входа
параметр SecureLevel, равный 10, вы сообщите, что хотели бы проверить,
применялся ли Secure Channel для последнего входа пользователя. Напри-
мер, если метод IsAuthenticated возвращает True при SecureLevel=10t зна-
чит, при последнем входе был записан защищенный cookie, и PUID в этом
cookie должен соответствовать таковому в незащищенном билете.

Эффективное применение защищенного входа подразумевает, что он запра-


шивается при первоначальной проверке состояния аутентификации, когда
пользователь направляется на сервер входа. В дальнейшем такие проверки
осуществляются методом IsAuthenticated с указанием необходимости про-
верки защищенного cookie, что не требует нового обмена данными клиента
с сервером входа. Даже если кто-то перехватит билет или параметры профи-
ля с вашего сайта или умудрится передавать на сервер входа cookie из заго-
ловков, аутентификация закончится неудачей из-за несоответствия PUID
защищенного «cookie*- и последнего билета.

Такой вариант можно использовать отдельно, без ключа защиты (security


key). Для этого в Passport-функцию входа следует передать параметр
SecureLevel, равный 10. А это в свою очередь потребует перенаправления
URL на основе HTTPS, поскольку в ином случае аутентификация закон-
чится неудачей.

Уровень Security Key


Этот уровень защиты требует PIN и считается достаточно стойким. Допол-
нительную защиту обеспечивает механизм блокировки, активизируемый
после пяти неудачных попыток аутентификации. Блокировка ключа защи-
ты не снимается автоматически: после срабатывания механизма блокиров-
ки пользователь должен пройти процедуру сброса (reset process), снова по-
зволяющую обращаться к защищенным сайтам. PIN обычно используют
для защиты сайтов, имеющих дело с конфиденциальной финансовой или
личной информацией, где предотвращение несанкционированного досту-
па гораздо важнее простого и быстрого входа.

Поддержка Security Key введена в .NET Passport версии 2.0 для сайтов,
требования которых к безопасности выше, чем уровень защиты, обеспечи-
ваемый существующими способами входа через безопасные каналы. Secu-
rity Key включает всю функциональность Secure Channel и ряд дополни-
тельных средств.
284 Защита кода и данных

Когда пользователь впервые пытается получить доступ к сайту, требую-


щему ключ защиты, открывается страница выбора Security Key. Пользо-
ватель должен выбрать четырехзначный ключ защиты (PIN) и три раз-
ные комбинации секретных вопросов и ответов на них. Секретные во-
просы и ответы нужны для смены или восстановления ключа. Если
пользователь забудет PIN, ему придется обратиться к Member Services,
где для восстановления PIN он должен будет ответить на три ранее вы-
бранных секретных вопроса.

Словарь терминов
Здесь поясняется часть терминов, встречающихся в этой статье; тол-
кование остальных терминов можно найти в справочном файле
Passport SDK,
Доменные центр (domain authority) — юридическое лицо, которому
принадлежат домены, используемые для выдачи и обслуживания
учетных записей .NET Passport (например, Hotmail).
Сайт-участник (participatiog site) — любой Web-сайт, на котором
реализован единый вход .NET Passport. Заметьте, что для работы с
большинством функций .NET Passport и для доступа к базовым про-
филям пользователей сайт-участник должен быть зарегистрирован.
Для регистрации необходимо предоставить URL сайта, контактную
информацию, политику конфиденциальности и т. д. Это также озна-
чает, что Microsoft будет периодически проводить аудит вашего сай-
та на соответствие стандартам Passport.
PUID (Passport Unique Identifier) — комбинация двух атрибутов
.NET Passport: MemberlDHigh и MemberlDLow. Результирующее
64-разрядное значение образует PUID, который узлы-участники
должны использовать для индексирования своих баз данных и уни-
кальной идентификации пользователей,
Защищенный вход (secure sign-in) — функциональность .NET Pass-
port версии 2.0 (и выше). Расширяет возможности стандартного
входа и за счет дополнительных средств заметно уменьшает вероят-
ность входа хакеров по учетной записи, взломанной атакой с повто-
рением пакетов или по словарю. Существует два уровня защищен-
ного входа: Secure Channel и Security Key.
Вход (sign-in) — процесс входа в .NET Passport с предоставлением
регистрационного имени и пароля при обращении через браузер на
сервер входа,

см. след, стр.


Средства безопасной аутентификации через Microsoft .NET Passport 285

Словарь терминов (окончание)

Единый вход (single sign-in, SSI) — процесс идентификации пользо-


вателей. В сети ,NET Passport единый вход включает получение и
проверку удостоверений защиты (учетных данных) и дает возмож-
ность пользователю, зарегистрированному в .NET Passport, обра-
щаться к защищенным сайтам-участникам. Под термином «стан-
дартный вход» подразумевается небезопасный вход.
Билет (ticket, или ticket cookie) — специальный cookie, применяе-
мый .NET Passport для защищенной передачи учетных данных и
профилей пользователей на сайты-участники. Билет содержит вре-
менные метки, гш которым сайт-участник и объект Passport Manager
может определять, как допускать пользователя: на сайт (автоматиче-
ский вход или вход вручную). Реальное имя этого cookie — MSP-
Auth. После входа пользователя на сайт-участник действуют два би-
лета: один записывается доменом сайта-участника, а другой — до-
менным центром.
Временн'ое окно (TimeWindow) — период (в секундах), в течение
которого на сервере входа должна была пройти последняя аутенти-
фикация пользователя. Минимизирует вероятность атаки с повторе-
нием пакетов. По истечении интервала TimeWindow удостоверения
защиты пользователя должны быть обновлены автоматически или
вручную (в зависимости от установленных параметров),

Для восстановления PIN электронная почта не используется. Смысл это-


го ключа — защитить сайты от уязвимости базовых удостоверений. По-
скольку большинство пользователей Passport обращаются к электронной
почте по базовым удостоверениям (особенно пользователи Hotmail.com и
MSN.com), передача PIN по электронной почте позволила бы злоумыш-
леннику обойтись без него, что сделало бы бессмысленным применение
ключа защиты.

Плюсы и минусы защищенного входа


Вы можете реализовать защищенный вход на своем сайте, если на нем
уже используются достаточно большие значения TimeWindow и вы не
предполагаете слишком часто проверять состояние аутентификации.
(По умолчанию аутентификация действительна в течение 14 000 секунд,
или 4 часов. Это значение записывается в реестр при установке Passport
SDK.) Более высокие значения TimeWindow увеличивают шансы на
успех атак с повторением пакетов, но повышают производительность
286 Защита кода и данных

сайта, минимизируя частоту перенаправлений на сервер входа. Бы мог-


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

Один из основных недостатков защищенного входа — снижение производи-


тельности. Применение HTTPS увеличивает объем передаваемых данных
из-за обмена ключами, в результате чего формирование страниц замедляет-
ся. Загрузка и визуализация HTTPS-страниц на клиенте также занимает
больше времени, Если на вашем сайте нет совершенно секретных пользо-
вательских данных, доступных после аутентификации через Passport, или
на нем уже установлены малые значения параметра Time Window (чтобы
снизить шансы на успех атак с повторением пакетов), то, вероятно, вам бу-
дет достаточно задействовать серверы стандартного входа по протоколу
HTTP и Passport Manager.

Если вы используете защищенный вход и безопасные проверки состояния


аутентификации, вам не обязательно устанавливать очень короткие интер-
валы Time Window — особенно когда значения этого параметра передают-
ся на сервер входа при вызове методов AuthURL2 или LogoTag2. Защи-
щенный вход, как правило, сводит к нулю шансы на успех при обычных
атаках с повторением пакетов, поэтому постоянная перезапись всех cookie
с шифрованием увеличивает не только нагрузку на серверы сети, но и вре-
мя загрузки страниц вашего сайта из-за частой загрузки, перезаписи и пе-
ренаправления через HTTPS.

На сайтах, где реализуется безопасная аутентификация для пользователей


Passport, требуется внести следующие изменения:

• обновить Passport SDK до версии 2.0 (или выше). Заметьте: текущая


версия Passport SDK — 2.1, и настоятельно рекомендуется применять
именно ее;
• задействовать новый параметр SecureLevel (установив для него значе-
ние от 10 до 100), а также методы IsAuthenticated, AuthURL2, LoginUser
и LogoTag2. Кроме того, рекомендуется задать значение True для пара-
метра bSecure, чтобы изображение на кнопке Sign-in также передавалось
по HTTPS. Это позволит исключить предупреждения об отображении
потенциально опасного информационного наполнения (контента);
• если вы реализуете оформление страниц защищенного входа в едином
стиле (cobranding), создайте соответствующие версии файлов на безо-
пасных серверах (доступных через SSL);
Средства безопасной аутентификации через Microsoft .NET Passport 287

• разместите на сайте минимум одну защищенную страницу (доступную


через SSL), чтобы возвращать ее URL в ответ на запросы защищенно-
го входа;
• соблюдайте рекомендации по реализации функциональности Sign-out
(выхода); подробнее об этом см. в справочной системе Passport SDK.

Защищенный вход и ASP


Главным СОМ-объектом для большинства сайтов, использующих .NET
Passport, является Passport.Manager — серверный объект .NET Passport.
Поддержка защищенного входа означает применение параметра SecureLevel
в четырех функциях: Is Authenticated, AuthURL2, LoginUser и LogoTag2. Эти
же функции чаще всего используются и для стандартного входа,

Вот синтаксис функции IsAuthenticated:

bValue = PassportManager.IsAuthenticated(
[TimeWindow], [ForceLogin], [SecureLevel])

Эта функция возвращает true, если пользователь прошел аутентификацию


и его интервал TimeWindow еще не истек. Если параметр ForceLogin уста-
новлен как True, пользователю придется возвращаться на страницу входа
при каждом обращении к защищенным ресурсам, даже если его интервал
TimeWindow пока не закончился. IsAuthenticated принимает те же пара-
метры, что и остальные перечисленные здесь методы (табл. 1).

AuthURL2 является альтернативой LogoTag2 и отображает ссылку, пере-


направляющую на страницу входа через Passport. Главное назначение этой
функции — взаимодействие с приложениями, поддерживающими Passport,
например с Microsoft Internet Explorer 6.0 в Windows XP:

sValue = PassportManager.AuthURL2([returnURL],
[TimeWindow], [ForceLogin], [coBrandArgs],
[lang_id], [NameSpace], [KPP], [SecureLevel])

Функция LoginUser приводит пользователя прямо на страницу входа


Passport. Регистрация осуществляется выдачей «302 redirect URL» или
инициацией обмена удостоверениями для аутентификации клиента через
.NET Passport (в Windows XP — с участием Credentials Manager). Формат
LoginUser таков:

PassportManager.LoginUser [returnURL], [TimeWindow],


[ForceLogin], [coBrandTemplate], [lang_id],
[NameSpace], [KPP], [SecureLevel], [ExtraParams]
288 Защита кода и данных

LogoTag2 возвращает блок HTML-кода с тэгом image для ссылки на .NET


Passport. Ссылка показывает кнопку Sign-in (если не обнаружен действи-
тельный билет) или Sign-out (если такой билет есть) с текстом на языке,
заданным параметром lang_id:

sValue = PassportManager;LogoTag2([returnURL],
[TimeWindow], [ForceLogin], [coBrandArgs],
[lang_id], [bSecure], [NameSpace], [KPP], [SecureLevel])

Табл. 1. Параметры функций входа

Значение Допустимый
Имя Тип данных по умолчанию интервал Описание

TimeWindow Integer 14 000 20-2 678 400 Интервал (в секун-


дах), в течение
которого должна
была пройти послед-
няя аутентификация
пользователя. Если
оставить TimeWindow
пустым, берется зна-
чение по умолчанию
из реестра
ForceLogin Boolean False True или False Заставляет пользо-
или Integer вателя регистриро-
(для .NET- ваться заново, даже
объекта Pass- если таймаут еще
portldentity) не истек
coBrandArgs String Нет Не применим Аргументы, опреде-
ляющие оформление
сайта в едином стиле
(их рассмотрение
выходит за рамки
этой статьи)
lang_id Integer 1033 -- US Соответствует Язык, на котором
English диапазонам показывается
стандартных страница входа
LCID
bSecure Boolean False True или False Если True, изобра-
жение кнопки Sign-in
тоже передается по
HTTPS

см. след. стр.


Средства безопасной аутентификации через Microsoft .NET Passport 289

Табл. 1. Параметры функций входа (окончание)

Значение Допустимый
Имя Тип данных по умолчанию интервал Описание

NameSpace String Нет Не применим Используется только


в особых случаях (их
рассмотрение выхо-
дит за рамки этой
статьи)
КРР Integer -1 или О О, 1 , 2 , 3 , 4 Используется только
службой Kids Pass-
port (ее рассмотре-
ние выходит за рам-
ки этой статьи).
SecureLevel Integer I 1, 10, 100 Объявляет один из
трех уровней
защиты

ExtraParams String Нет Не применим Строка с парами


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

Параметры функций входа


Два параметра, один из которых не описан в табл. 1, имеют особое отноше-
ние к нашей теме и заслуживают более подробного рассмотрения. Необяза-
тельный параметр returnURL указывает URL, на который сервер входа дол-
жен направлять пользователя по окончании процесса входа. Параметры по
умолчанию Passport-функций по возможности не «зашиваются» в код, а
хранятся в разделе реестра HKEY_LOCAL_MACHINE\SOFTWARE\
Microsoft\Passport. К любой строке, которую вы планируете использовать
как значение returnURL, всегда применяйте метод Server.URLEncode, что-
бы быть уверенным в ее целостности при синтаксическом разборе. Не до-
бавляйте к returnURL больше одной собственной строковой переменной,
избегайте специальных символов, требующих отдельной кодировки, и не
назначайте переменным имена, зарезервированные .NET Passport (t, р и г ) -
Если вы собираетесь задействовать порт с номером, отличным от 80, задайте

10-138
290 Защита иода и данных

его в URL; этот URL должен указывать на конкретный файл, а не просто на


корневой каталог.

Параметр SecureLevel объявляет один из трех возможных уровней защиты


входа через .NET Passport. Его допустимые значения показаны в табл. 2.

Защищенный вход и ASP.NET


.NET Framework предоставляет оболочку СОМ-модуля Passport Authenti-
cation, которая называется (и вполне обоснованно) PassportAuthentication-
Module. Поскольку это оболочка, то, чтобы использовать классы и мето-
ды .NET Passport, вам все равно придется установить Passport SDK. Хотя
Passport SDK версии 1.4 еще поддерживается, рекомендуется устанавли-
вать его версию 2.0 или выше (в Windows XP поддерживается только вер-
сия 2.0 или выше).

Табл. 2. Значения параметра SecureLevel

Значение Описание

О (или не определено) Пользовательский интерфейс входа предоставляется


доменным центром .NET Passport через HTTP
(по умолчанию). Заметьте, что даже с этим парамет-
ром сервер .NET Passport работает с HTTPS, чтобы
можно было записывать защищенные cookie
10 Пользовательский интерфейс входа предоставляется
доменным центром .NET Passport через HTTPS.
Параметр returnURL должен содержать HTTPS URL,
иначе аутентификация закончится неудачей
100 Пользовательский интерфейс входа предоставляется
доменным центром .NET Passport через HTTPS,
а в процессе входа нужно вводить не только пароль,
но и PIN

Passportldentity — это класс, используемый PassportAuthenticationModule.


Чтобы задействовать этот класс, вы должны импортировать в своем коде
пространство имен System.Web.Security. Методы класса Passportldentity
очень похожи на методы объекта Passport. Manager. В некоторых из них у
соответствующего метода Passport имеются необязательные параметры.
Если это целочисленные или булевы параметры, вы можете передавать в
них -1 или True, указывая тем самым, что Passport должен использовать
значение по умолчанию. В случае необязательного строкового параметра
с той же целью можно передать null (в Visual Basic — Nothing).
Средства безопасной аутентификации через Microsoft .NET Passport 291

Разработка на ASP
Теперь сравним простейшие ASP-страницы, реализующие стандартный и
защищенный вход. Все, что они делают, — показывают кнопку Sign-in; по-
сле аутентификации пользователя через Passport эта кнопка меняется на
Sign-out.

Сначала рассмотрим пример страницы стандартного входа. На рис. 1 по-


казан базовый код, необходимый для поддержки Passport на ASP-сайте.
Простейшая страница, которая поддерживает защищенный вход, пред-
ставлена на рис. 2. Обратите внимание, что в этом случае параметров боль-
ше: страница перенаправляется на HTTPS, SecureLevel устанавливается
равным 100, a TimeWindow — 10 000 секунд, что совсем не мало.

Рис. 1, Стандартный вход с применением ASP


<% Dim passportManager
Dim strLogoTag ' в этой строке йудет храниться HTML-код
' кнопки Sign-In, сгенерированный Passport
Dim redirectURL ' URL, на который перенаправляет сервер входа

Set paesportHsFtager = Server,CreateOfeject{"Passpsrt.Manager")

1
Я перенаправляю пользователя обратно на эту страницу,
' а вы можете перенаправлять его на любу» другую
redirectURL * Server.URLEneo3e("http:/7" & _
Request.ServerVarlabIes("SSlVER_HAKE") i _
Request.SefverVariables("SCRIPT_HAHE"))

strtogoTag * passportManager.togoTatj2(redirectURt)
Sesponse.W rite(st rLogoTag)

Рис. 2. Защищенный входе применением ASP

Dl» passportManager
Diffi strLogoTag ' a этой строк» будет храниться HTHt-код
' кнопки Sign-in, сгенерированный Passport
Dim redirectURL ' URL, на который перенаправляет сервер входа
Bim TieeMlindow ' интв^яал, в течение которого действителен
* последний вход пользователя
см. след. стр.
292 Защита кода и данных

Рис- 2. Защищенный вход с применением ASP (окончание)


Dim ForceLojjin ' если таймаут истек, заставить пользователя
' зарегистрироваться заново
Dim bSecure ' если True, то изображение для кнопки
' тоже должно быть передано по HT7PS
Dim SecureL&vel ' объявляет один из трёх уровней защиты

TimeWindow - 10000
ForceLogin = false
bSecure = True ' передача изображения по HTTPS
SecureLevel = TOO ' защищенный вход G вводом PIN

" Создать объект Passport Manager


passportwartaser = Server. CreateJ>bject("Passport.Manager")

" Создать UHL страницы перенаправления.


' Я перенаправляю пользователя обратно на эту страницу,
' а вы можете перенаправлять его на любую другую.
red!rectURl - Server.UrlEneode("https;//" S _
Request.ServerVariables("SEflVER_KAME") & _
Request. SB rverVariablesrsefllPTJiAME"))

' Для упрощения используются только параметры,


1
имеющие отношение к теие статьи
strLogoTafl = passportManager.lQgoTag2{redireetiH!L,
TimeWindow, , , , bSecure, , , SecureLevel)

' Показать пользователю кнопку Sigs-in -


Response.Write(strLogoTag)

Разработка на ASP.NET
А теперь посмотрите, как реализовать защищенный вход в ASP.NET на
основе .NET Framework. Код на рис. 3 почти не отличается от такового для
защищенного входа с применением объекта Passport Manager. К немногим
отличиям относятся директива для импорта пространства имен Sys-
tem. Web.Security и модифицированное объявление переменной passport-
Manager: вместо инициализации passportManager вызовом CreateObject,
теперь вызывается New Passportldentity. Наконец, вы должны указать
Средства безопасной аутентификации через Microsoft .NET Passport 293

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

Рис. 3. Защищенный вход с применением ASP.NET


Imports Systera.Web.Security
Public Glass WebFortBl
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object,
ByVal e As Systesi.E-ventArgs) Handles HyBase.Load

Dim passportHanager As PassportIdentity


Dim strlogoTag As String ' для хранения HTML-кода кнопки
' Sign-in, сгенерированный Passport
Sin redirectURL As String ' URL, на который перенаправляет
' сервер входа
Piia TiraeWindow As Integer ' интервал, в течение которого
' действителен последний вход
1
пользователя
Din ForceLogin As SooleaR ' если таймаут истек, эастааить
' пользователя зарегистрироваться
' заново
Dim bSecure As Integer ' если True,, то изображение
' дня кнопки тоже должно быть
' передано по HTTPS
Dim Securelevel As Integer ' объявляет один из трех
' уровней защиты

TieeWindow * 1QOOO
ForceLogln ~ False
bSecure = True ' передача изображения по HTTPS
SecureLevel = 100 " защищенный вход с вводов РШ

* Создать объект Passport Hanager


passportManageг = Кем PassportltfentityO

' Создать URL страницы перенаправления.


' Я перенаправляю пользователя обратно на эту страницу,
' а вы можете перенаправлять его на любую другу».
redirectURL = Server.UrlEncadeChttps://" & _
Request. S0 rverVariablesC" SERVERJ№ME") &
Request. ServerVsriablesrsCflIPT_KAHE")>

см. след. стр.


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

Рис. 3. Защищенный вход с применением ASP-NET


* Для упрощения используются только параметры,
' имевшие отношение к тане статьи
strUgolag = passportltonager. LogoTag2<redlrectURt,
-1, Nothing, -f. bSecure, Sothirjg, -1, §ee»relevel)

' Показать пользователе кнопку


Response, Wri teCstrtogoTag)
End Sub
End Class

Применение Passport на практике


Простой пример сайта с поддержкой Passport мог бы состоять всего из
двух страниц, например LoginPage.asp и DoBusinessPage.asp. На первой
стра