1. Вы находитесь в архивной версии форума xaker.name. Здесь собраны темы с 2007 по 2012 год, большинство инструкций и мануалов уже неактуальны.

Пуленепробиваемый C#: основные правила создания безопасного кода

  1. ozs
    Введение

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

    [​IMG]

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

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

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


    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 — позволяет отключать механизм управления доступом (становится возможным использование закрытых членов).



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

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

    [​IMG]
    Утилита графической настройки прав доступа к коду 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{...}
    [​IMG]


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

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

    Код:
    [assemblyiFilelOPermissionAttribute (SecurityAction.RequestMinimum, Wrlte="C:\\test.tmp")]
    [assembly:РеrmissionSet(SecurityAction.RequestOptional. Unrestricted=false)]
    ... SecurityAction.RequestRefused ...
    В этом примере системе сообщается, что код не должен запускаться, пока не получит разрешение на запись в C:\test.tmp. Если одна из политик безопасности не предоставляет такое разрешение, генерируется исключение PolicyException, и код не запускается. Ты должен убедиться в том, что коду выдается нужное разрешение, и тогда тебе не придется беспокоиться об ошибках из-за нехватки разрешений. Кроме того, здесь система уведомляется о том, что дополнительные разрешения нежелательны. Иначе код получит все разрешения, предусмотренные политикой безопасности. Лишние разрешения не принесут вреда, но, если в системе безопасности есть какая-то ошибка, уменьшение числа разрешений, выдаваемых коду, может прикрыть брешь в защите. Таким образом, если код обладает разрешениями, которые ему не нужны, возможны проблемы с безопасностью. Еще один способ ограничить количество привилегий, предоставляемых коду — явно перечислить разрешения, от которых следует отказаться. Отказ от разрешений осуществляется объявлением необязательности разрешений и исключением конкретных разрешений из запроса.


    Заключение


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

    (c)xakep.ru
     
    1 человеку нравится это.