Сегодня мы поговорим о том, о чем вспоминают обычно в последнюю очередь — о
безопасности твоих приложений. Ведь ты же не хочешь читать о том, что в творении
рук твоих — супернавороченной программе — нашли уязвимости, которые поставят под
угрозу работу каких-нибудь важных организаций? Ладно бы, если это простая фирма,
а если банк? Или атомная электростанция?

 

Введение

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

У общеязыковой исполняющей среды (common language runtime, CLR) в .NET
Framework есть своя модель безопасного выполнения кода, независимая от
ограничений операционной системы, в которой она работает. Более того, в отличие
от старой модели защиты на основе участников безопасности (principal-based
security), CLR реализует политику, исходя из того, откуда поступает код, а не из
того, кто являет ся его пользователем. Эта модель защиты по правам доступа кода
(code access security) имеет больший смысл в современных условиях, поскольку
немалая часть кода устанавливается через интернет, и даже доверенный
пользователь (trusted user) не знает, какой код действительно безопасен. Все это
реализовано в пространстве имен System.Security. "Ээээээ, так ты об этом…" —
разочарованно вздохнет читатель, который, наверняка, вдоль и поперек изучил все
те фичи, которые .NET предлагает программисту для реализации его злобных
замыслов. Спешу огорчить — о System.Security мы сегодня как раз разговаривать не
будем. Это скучно :). Вместо этого мы попробуем взглянуть на проблему
"безопасного кода" с другой стороны — с точки зрения того, в чьи хорошие (или не
очень) руки он попадет. Вне зависимости от того, что предоставляет интерфейс
твоей программы: просто складывает два числа или же управляет атомной
электростанцией.

 

Что может CLR?

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

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

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

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

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


Mscorcfg.msc

 

Планируем боевые действия

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

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

 

Ihre ausweiss, bitte!: выдача разрешений

Защита на основе признаков базируется на предположении, что высокий уровень
доверия (с широкими полномочиями) присваивается лишь коду, заслуживающему этого
самого доверия, а злонамеренный код является "малодоверяемым" или вообще не
имеет разрешений. В соответствии с политикой по умолчанию в .NET Framework
разрешения выдаются на основе зон (так, как они определены в Microsoft Internet
Explorer). Ниже приведено упрощенное описание этой политики "по умолчанию":

  • Зона локального компьютера (например, C:\app.exe) является
    полностью доверяемой. Предполагается, что пользователи помещают на свой
    компьютер только код, которому они доверяют, и что большинство пользователей
    не собираются разбивать свой жесткий диск на области с разной степенью
    доверия. По существу, этот код может делать все что угодно, поэтому от
    злонамеренного кода, находящегося в этой зоне, никакой защиты нет. Честно
    говоря, по моему скромному мнению, именно это предположение является одной
    из огромных дыр в архитектуре безопасности Windows, что приводит к появлению
    таких извратов, как UAC, DEP, рандомизация стека, сандбоксов и пр.
  • Зона интернета (например, http://www.microsoft.com). Коду из этой
    зоны предоставляется очень ограниченный набор разрешений, который не опасно
    предоставить даже злонамеренному коду. Обычно этому коду нельзя доверять,
    поэтому его можно безопасно выполнять только с очень узкими разрешениями, с
    которыми он не сумеет нанести ущерб:
  • WebPermission — доступ к серверу сайта, с которого получен код;
  • FileDialogPermission — доступ только к файлам, специально
    указанным пользователем;
  • IsolatedStorageFilePermission — постоянное хранилище,
    ограниченное пределами веб-сайта;
  • UlPermission — возможность записи информации в окно
    пользовательского интерфейса;
  • Зона интрасети (например \\UNC\share). Код из этой зоны
    выполняется с чуть большими разрешениями, чем код из интернета, но среди них
    все равно нет таких, которые предоставляли бы широкие полномочия;
  • FilelOPermission — доступ только для чтения к файлам каталога, из
    которого загружен код;
  • DNSPermission — допускается разрешение DNS-имен в IP-адреса;
  • FileDialogPermission — доступ только к файлам, специально
    указанным пользователем;
  • Isolated StorageFilePermission — постоянное хранилище (с меньшими
    ограничениями);

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

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

 

Не влезай — убьет!

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

[Security Permission]:

  • Unmanaged Code — позволяет управляемому коду вызывать неуправляемый код,
    что зачастую весьма опасно;
  • Skip Verification — код может делать что угодно без всякой верификации;
  • ControlEvidence — управление признаками позволяет обмануть систему
    защиты;
  • ControlPolicy — возможность изменять политику безопасности позволяет
    отключить защиту;
  • SerializationFormatter — за счет сериализации можно обойти управление
    доступом;
  • ControlPrincipal — возможность указывать текущего пользователя позволяет
    обходить защиту на основе ролей;
  • ControlThread — возможность манипуляций с потоками опасна, так как с
    ними связано состояние защиты;

[ReflectionPermission]:

  • MemberAccess — позволяет отключать механизм управления доступом
    (становится возможным использование закрытых членов).
 

Защита доступа к методам

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


Утилита графической настройки прав доступа к коду GuiCaspol

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

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

Аналогичным образом декларативная защита позволяет контролировать
наследование классов. С помощью 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 ( SecurityAction . LinkDemand , PublicKey="...hex...",Name="App1",
Version="0. 0.0.0")]
public class MyClass

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

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

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

5. В период выполнения Appl имеет необходимую подпись со строгим именем и
может использовать MyClass. В этом примере для защиты API-элемента применяется
атрибут LinkDemand.

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

[System.Security.Permissions. PermissionSetAttribute(System.Security.
Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")]
[System.Security.Permissions. PermissionSetAttribute(System.Security.
Permissions.SecurityAction.LinkDemand, Name="FullTrust")]
public class YourClass{...}


Утилита ручной настройки прав доступа к коду

 

Приемы безопасного кодинга

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

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

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

 

Заключение

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

 

WWW

blogs.msdn.com,
www.eggheadcafe.com,
bytes.com — на этих
сайтах ты сможешь найти россыпи интересной информации о технологиях .NET и популярных
языках.

Оставить мнение

Check Also

WWW: Carbon — сервис для создания идеальных скриншотов кода

В теории сделать скриншот кода, чтобы кому-то показать, — задача несложная. В реальности р…