Куленепробивний C# основні правила створення безпечного коду
Зміст статті
На жаль, технології .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) дозволяє перевіряти, чи весь код у стеку має необхідні дозволи на виконання будь-яких дій.
Ці дві технології пов'язані між собою концепцією дозволів.
Mscorcfg.msc
Плануємо бойові дії
Є таке японське прислів'я: "Виходь з дому так, ніби він оточений тисячею ворогів". Зрозуміло, що в часи феодальної Японії, коли туди-сюди бігали самураї, воювали між собою і шукали інших пригод, це прислів'я було актуальним. Сьогодні, дозволю собі помітити, це прислів'я буде справедливим і для твого коду - якщо ти думаєш, що твій код нікому нафіг не здався, ти глибокопомиляєшся.
Якщо твій код — частина програми, яка не викликається іншим кодом, то його захист простий і спеціального програмування не вимагає. Але врахуй, що він може бути викликаний зловмисним кодом. Хоча захист з прав доступу коду перешкоджає доступу зловмисного коду до ресурсів, він все одно здатний вважати значення полів або властивостей, які, можливо, містять цінну інформацію.
Ihre ausweiss, bitte!: видача дозволів
Захист на основі ознак базується на припущенні, що високий рівень довіри (з широкими повноваженнями) присвоюється лише коду, що заслуговує на це самої довіри, а зловмисний код є "малодовірним" або взагалі не має дозволів. Відповідно до стандартної політики в .NET Framework дозволи видаються на основі зон (так, як вони визначені в Microsoft Internet Explorer). Нижче наведено спрощений опис цієї політики "за умовчанням":
Залежно від способу розгортання, твій код може отримувати різні дозволи. Перед випуском коду у світ переконайся, що йому надаються дозволи, достатні для нормальної роботи. Продумуючи захист коду від атак, подивися, звідки може бути завантажений атакуючий код, і як він може отримати доступ до твого коду.
Не влазь — уб'є!
Які дозволи мають потенційну небезпеку? Для виконання деяких захищених операцій .NET Framework надає дозволи, що потенційно дозволяють обійти систему захисту. Ці небезпечні дозволи слід надавати тільки коду, що заслуговує на довіру, і лише за абсолютної необхідності. Зазвичай, якщо зловмисний код отримує такі дозволи, захиститися не можна. До небезпечних дозволів належать:
- Unmanaged Codeдозволяє керованому коду викликати некерований код, що часто дуже небезпечно;
- Skip Verification - код може робити будь-що без будь-якої верифікації;
- ControlEvidence - управління ознаками дозволяє обдурити систему захисту;
- ControlPolicy — можливість змінювати безпекову політику дозволяє відключити захист;
- SerializationFormatter — за рахунок серіалізації можна обійти керування доступом;
- ControlPrincipal - можливість вказувати поточного користувача дозволяє обходити захист на основі ролей;
- ControlThread - можливість маніпуляцій з потоками небезпечна, так як з ними пов'язаний стан захисту;
- 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.
[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:РеmissionSet(SecurityAction.RequestOptional. Unrestricted=false)] . SecurityAction.RequestRefused .
Висновок
Уфф! Про безпеку твого коду можна говорити нескінченно. У рамках цієї статті я постарався згадати лише найважливіше і, на мій погляд, цікаве. Іншими словами, те, що має допомогти тобі зробити свої програми непробивними. Загалом, нехай буде зтобою Сила!