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

 

Введение

Увы и ах — технологии .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).

 

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

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

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

 

Выдача разрешений

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

• Зона локального компьютера (например, C:\app.exe) является полностью доверяемой. Предполагается, что пользователи помещают на свой компьютер только код, которому они доверяют, и что большинство пользователей не собираются разбивать свой жесткий диск на области с разной степенью доверия. По существу, этот код может делать все что угодно, поэтому от злонамеренного кода, находящегося в этой зоне, никакой защиты нет. Честно говоря, по моему скромному мнению, именно это предположение является одной из огромных дыр в архитектуре безопасности Windows, что приводит к появлению таких извратов, как UAC, DEP, рандомизация стека, сандбоксов и пр.

  • Зона интернета (например, http://www.microsoft.com). Коду из этой зоны предоставляется очень ограниченный набор разрешений, который не опасно предоставить даже злонамеренному коду. Обычно этому коду нельзя доверять, поэтому его можно безопасно выполнять только с очень узкими разрешениями, с которыми он не сумеет нанести ущерб:
  • WebPermission — доступ к серверу сайта, с которого получен код;
  • FileDialogPermission — доступ только к файлам, специально указанным пользователем;
  • IsolatedStorageFilePermission — постоянное хранилище, ограниченное пределами веб-сайта;
  • UlPermission — возможность записи информации в окно пользовательского интерфейса.
  • Зона интрасети (например \\UNC\share).

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

  • FilelOPermission — доступ только для чтения к файлам каталога, из которого загружен код;
  • WebPermission — доступ к серверу, с которого загружен код;
  • DNSPermission — допускается разрешение DNS-имен в IP-адреса;
  • FileDialogPermission — доступ только к файлам, специально указанным пользователем;
  • Isolated StorageFilePermission — постоянное хранилище (с меньшими ограничениями);
  • UlPermission — код может свободно использовать собственные окна верхнего уровня.
  • Зона ограниченных сайтов, код из которой выполняется только с минимальными разрешениями.

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

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

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

 

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

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

[Security Permission]:

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

[ReflectionPermission]:

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

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

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

Иногда приходится ограничивать доступ к методам, которые не предназначены для открытого использования, но все равно должны быть объявлены как открытые. Например, у тебя есть некий интерфейс, вызываемый вашими же 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, и код не запускается. Ты должен убедиться в том, что коду выдается нужное разрешение, и тогда тебе не придется беспокоиться об ошибках из-за нехватки разрешений.

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

 

Заключение

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

 

Links

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

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии