Безпека by design. Частина 1. (Роль проектування у безпеці)

4 вересня 2023 2 хвилин Автор: Lady Liberty

Роль Проектування у Забезпеченні Безпеки

У цій книзі тема безпеки розглядається під незвичним кутом. Замість використання класичного підходу, коли безпека знаходиться у центрі уваги, ми вирішили зробити основною темою проектування програмного забезпечення. Спочатку це може прозвучати трохи дивно, але з огляду на те, що вразливості найчастіше викликані поганою архітектурою, обговорення безпеки з погляду проектування набагато привабливіше. Що, якщо суттєвого кількості вразливостей можна було б уникнути, застосовуючи добрі методи проектування та загальноприйняті рекомендації? Це кардинально змінило б наші погляди на розробку ПЗ і стало б приводом для вибору певних архітектурних рішень. Таким чином, дослідження того, як проектування ПЗ пов’язане з безпекою, — головна мета книги.

Це означає, що тут ви не знайдете обговорення таких класичних аспектів безпеки, як переповнення буфера, недоліки криптографічних хеш-функцій або вибір відповідного методу автентифікації. Натомість ви дізнаєтеся, чому деякі архітектурні рішення важливі з точки зору безпеки і як з їх допомогою можна створювати програмне забезпечення з глибоким захистом У першій частині ми поставимо контекст книги. Ви дізнаєтеся, як ми підходимо до безпеки ПЗ, розробки та взаємозв’язку між ними. Ми проаналізуємо, де зазвичай виникають проблеми та що з цим можна зробити. Ці аспекти та приклади того, що ми розуміємо під надійним софтом, будуть розглянуті у  перщій частині. У цьому розділі. Безпека як невід’ємна властивість системи. Проектування та його роль у забезпеченні безпеки. Підвищення безпеки завдяки гарній архітектурі. Захист від атаки Billion Laughs.

Починаємо

Уявіть, що ви починаєте роботу над типовим програмним проектом. Ви збираєте команду розробників, тестувальників і профільних фахівців і починаєте формувати ключові вимоги. Порадившись із зацікавленими сторонами, ми накидали перелік важливих параметрів: продуктивність, безпеку, ремонтопридатність і простоту використання. Як і в багатьох інших програмних проектах, якість є першим пріоритетом, час виходу на ринок дуже важливий, і вам потрібно залишатися в межах виділеного бюджету. Ви вирішуєте бути активними та додаєте завдання безпеки до свого списку завдань, а деякі члени вашої групи вибирають бібліотеки безпеки, які можна використовувати в коді проекту. Після первинного планування ви беретеся за реалізацію завдань і бізнес-функцій. Команда мотивована і дає результати в хорошому темпі.

Ви розумієте, що завжди потрібно піклуватися про безпеку, але це заважає зосередитися на інших завданнях. Крім того, ви все ще витрачаєте більшу частину свого часу на код, який не викликається безпосередньо через Інтернет, тому  бібліотеки веб-безпеки, які ви планували використовувати, не зовсім правильні. У той же час завдання, пов’язані з безпекою, стають все менш важливими, на відміну від бізнес-функцій. Зрештою, час спливає, і якщо вам не вдасться реалізувати функції, які потрібні користувачам, безпека системи не матиме значення. Бізнес-функції приносять прибуток, і жоден користувач не подякує вам за додавання токенів CSRF до форми входу. Крім того, завдання з меншим пріоритетом завжди можна відкласти на потім.

Вам, як розробнику, відповідальність за безпеку здається тягарем, від якого ви воліли б позбутися. Ви вважаєте, що компанія повинна найняти фахівців з безпеки і зробити їх невід’ємною частиною команди розробників. Розробники спеціалізуються на написанні хорошого коду, побудові масштабованих архітектур і використанні безперервної доставки, а не магічних заклинань, які можуть захистити від злих хакерів в чорних балахонах. Ви ніколи не розуміли таємниці, яку породжує безпека, і завжди віддавали перевагу створенню, а не руйнуванню. Проект повинен рухатися вперед, тому ви орієнтуєтеся на найвищі пріоритети і реалізацію нових можливостей.

Через деякий час ваше програмне забезпечення готове до випуску. Майбутнє проекту може розвиватися по-різному. Наприклад, можна провести аудит безпеки і тест на проникнення. Остаточний звіт покаже, що існують деякі серйозні вразливості, які необхідно усунути, перш ніж код може бути розгорнутий в промисловому середовищі. Це затягує вас на тижні або навіть місяці, що в кінцевому підсумку призводить до втрати доходу. Якщо не пощастить,  Для вирішення проблем доведеться переписати всю програму з нуля, і в результаті зацікавлені сторони приймуть рішення закрити ваш проект.

Існує також ймовірність того, що перевірка безпеки ніколи не буде проведена, і ви розгорнете програму в промисловому середовищі. Користувачі почнуть користуватися вашим сервісом, і все буде добре, поки одного разу ви не прочитаєте в новинах, що ваш сервіс зламаний і всі дані просочилися. Користувачі, довіру яких ви завоювали з таким трудом, почнуть залишати ваш сервіс швидше, ніж щури – тонучий корабель.

Тести на проникнення проводяться для виявлення потенційних дірок у безпеці в системі.

  • Чому завдання, пов’язані з безпекою, завжди отримують знижений пріоритет?

  • Чому розробників загалом мало цікавить безпека?

  • Фахівці не втомлюються нагадувати розробникам про безпеку, то чому ж не всі нею займаються?

  • Чому керівники не розуміють, що їхня команда потребує фахівців з безпеки не менше, ніж тестувальників?

Автори книг і експерти давно говорять про те, що варто приділяти більше уваги безпеці. Але, на жаль, ми регулярно бачимо новини про злом різних систем. Щось тут явно не так.

ВАЖЛИВО. Щоб ефективно та легко створювати безпечне програмне забезпечення, вам може знадобитися змінити своє мислення.

Але що, якщо існує інший підхід до безпеки програмного забезпечення, який дозволить уникнути багатьох проблем, які ми бачимо сьогодні в нашій галузі? Ми вважаємо, що для того, щоб ефективно і легко розробляти безпечні програми, вам потрібне мислення, яке може вас не влаштовувати, і яке зосереджується не на безпеці, а скоріше на дизайні.

Спочатку це може здатися нелогічним, але в цьому розділі ви дізнаєтеся, що ми маємо на увазі  під дизайном і  чому це дуже важливо для безпеки. Ми обговоримо деякі недоліки традиційного підходу до безпеки програмного забезпечення і покажемо, як їх подолати на архітектурному рівні. Ми також надамо кілька прикладів того, як ці ідеї застосовуються в реальних умовах, щоб ви познайомилися з деякими концепціями,  які будуть розглянуті в наступних розділах.

Безпека як невід’ємна властивість системи

Безпеку слід розглядати як аспект системи, а не просто ще одну функцію. Однак нерідкі випадки, коли безпеку описують як набір функцій. Відмінність такого підходу полягає в тому, що, навіть якщо ці функції допомагають вирішити певні питання безпеки, це не означає забезпечення безпеки продукту в цілому. Щоб проілюструвати це, почнемо з історичного прикладу. Розглянемо одне з перших відомих нам пограбувань банків і покажіть, що такі функції безпеки, як якісні замки, марні, якщо двері мають слабкі петлі. У цьому прикладі застосовані захисні заходи не запобігли пограбуванню, тому вимоги безпеки не були дотримані.

Пограбування банку Öst-Götha, 1854

Це було 25 травня 1854 року, в ніч незадовго до пограбування шведського банку Ost-Gotha. Капрал і колишній фермер на ім’я Нільс Стрід і його компаньйон, коваль Ларс Екстрем, мовчки йдуть у напрямку банку. Вхідні двері відділення банку замкнена, але ключ висить зовні на цвяху – потрібно лише знати, де шукати.

Керівництво банку також витратило гроші на якісні замки сховища, які практично неможливо зламати. Але ковалю не складає труднощів витягнути петлі і відкрити двері сховища з іншого боку. Цим двом злочинцям вдалося вкрасти все, що зберігалося в банку – 900 000 ріксдалерів (офіційна шведська валюта того часу). Цю суму складно порівняти з сучасними грошима, але її еквівалент становить десь від 5 до 10 мільйонів доларів.

Протягом багатьох років це було одне з найбільших пограбувань за всю історію. Аналогічна сума була вкрадена тільки в 1963 році, під час пограбування поїзда на залізничному мосту Бриджгоу-Брідж в графстві Бакінгемшир, Англія. У Швеції грабіжники залишили після себе одну трехріксдалерскую записку і записку зі смішним римованим віршем:

Крім того, що це цікаве історична подія, дане пограбування цікаво ще й з точки зору безпеки, як в юридичному, так і в технічному сенсі. Юридичний аспект полягає в тому, що він призвів до прийняття нових законів, які диктують певний ступінь захисту банків. Ці закони змусили фінансові установи звернути увагу на безпеку і слідувати деяким рекомендаціям. Перший з них, прийнятий в 1855 році, був одним з найбільш ранніх прикладів регулювання в сфері безпеки. З технічної точки зору грабіжники скористалися слабкими сторонами банку: двері відділення були замкнені, але ключ був погано захований, сховище мало якісні замки, але дверні петлі можна було вирвати.

Ця історія – яскравий приклад організації безпеки у вигляді набору функцій – замків і петель. Якісні замки давали відчуття захищеності, але самі по собі не забезпечували ніякої безпеки. Хорошого замка недостатньо, якщо ключ висить на цвяху або якщо двері в сховище кріпиться на слабких петлях. Замість того, щоб розглядати безпеку як сукупність окремих елементів, краще розглядати її як невід’ємну властивість системи.

Якби керівництво вважало безпеку невід’ємною власністю своєї установи, воно б задавалося питанням: «Як перешкодити людям знімати гроші з банку?». І відповіддю буде не просто замок – заходи безпеки включатимуть зберігання ключа в іншому місці або перевірку на наявність інших способів зламати двері в сховищі. Власники банку могли придумати щось нове, наприклад, сигналізацію. Вони могли б винайти механізми запобігання пограбувань, які з’явилися в наступному столітті, але не покладалися б на єдиний дверний замок.

Тепер давайте повернемося з дев’ятнадцятого століття в сучасний світ розробки програмного забезпечення і подивимося, як ці різні підходи до безпеки співвідносяться з вашими поточними проектами. У наступному розділі ми покажемо вам, як перестати розглядати безпеку як окремий елемент і почати розглядати її як внутрішню властивість системи.

Елементи безпеки та безпеки в цілому

Програмне забезпечення часто описують як набір функцій, тобто те, що можна зробити з певним продуктом. Наприклад: це програма, яка дозволяє обмінюватися списками покупок; це сайт, куди можна завантажувати фотографії, щоб інші люди могли їх переглядати та коментувати; Це програма для створення презентацій. Цей вид опису також використовується у формальних контекстах.

Багато методологій фокусуються на тому, що повинна робити система, тобто на функціональній стороні. Rational Unified Process (RUP) як і раніше має великий вплив на розробку програмного забезпечення і фокусується на функціональності у вигляді  варіантів використання. Інші міркування, такі як час відгуку або необхідна потужність, потрапляють у другорядну категорію додаткових специфікацій. У agile-спільноті домінуючим форматом опису того, що потрібно зробити під час наступного спринту (або еквівалента), є історія користувача щось на зразок цього: «Я, користувач такого-то, хочу таку-то можливість, щоб отримати таку-то вигоду». З огляду на такий акцент на можливості (те, що робить система), не дивно, що безпеку часто описують подібним чином: нам потрібна сторінка входу, нам потрібен модуль для виявлення шахрайства, нам потрібно вести журнал.

Фахівці з безпеки Джон Вілландер і Йенс Густавссон провели дослідження про те, як люди описують і визначають безпеку. Вони відібрали основні програмні ініціативи, що фінансуються з бюджету. Було встановлено, що в 78% випадків безпека була безпосередньо пов’язана з можливостями.

Звичайно, деякі функції безпеки приносять користь, як видиму, так і приховану. Прикладом видимих переваг є якісний механізм аутентифікації, що дозволяє клієнтам бути впевненими в тому, що їх доступ і спілкування захищені. Але проблема в тому, що описуючи безпеку як набір функцій, ми часто забуваємо про головне. Спробуємо перефразувати історію користувача, пов’язану з безпекою, щоб безпека стала невід’ємною властивістю системи.

Уявіть собі сайт зберігання фотографій з механізмом аутентифікації. Якщо спробувати втиснути цей механізм в формат функціональної історії користувача, то вийде приблизно таке: «Як користувачеві, мені потрібна сторінка входу, яка дозволить мені отримати доступ до завантажених фотографій».

Незважаючи на те, що безпека описується як  можливість, більшість зацікавлених сторін стурбовані нею в цілому. Якщо реалізувати тільки цей функціонал, то можна досягти мети, поставленої в історії сторінки входу. Однак наявність однієї лише сторінки входу не забезпечує необхідну безпеку. Той факт, що ніхто насправді не хоче таку сторінку, може здатися очевидним,  Але історії користувачів, орієнтовані на функції, траплялися багато разів.

Уявіть, що ви і ваша команда створюєте сторінку входу. Після аутентифікації користувач перенаправляється на свої фотографії, серед яких  є дуже незграбна поза.jpg. Користувач може натиснути на посилання, щоб завантажити зображення. Ускладнимо ситуацію і уявимо, що у іншого користувача є пряме посилання на цю фотографію (рис. 1.1). Як вам це подобається? Ви реалізували свій план, так як у вас є сторінка входу з описаним функціональність, але не помітили чогось важливого, чи не так?

Озираючись назад, ми розуміємо, що ціллю не була сторінка входу як така. Мета полягала в тому, щоб тільки власник міг переглядати і завантажувати зображення і ніхто інший. Сторінка входу потрібна лише для дотримання цього правила. Користувач не повинен мати можливість отримати зображення в обхід цієї сторінки.

Тепер ви можете перефразувати своє бажання наступним чином: “Як користувачеві, мені потрібно, щоб доступ до моїх зображень був доступний через сторінку входу, і щоб ці зображення залишалися приватними”. Це формулювання відображає занепокоєння, яке зацікавлені сторони спочатку висловили щодо сторінки входу.

Краще взагалі не згадувати сторінку входу: “Я, як користувач, хочу, щоб доступ до всіх моїх завантажених зображень був захищений автентифікацією, а зображення залишалися приватними”.

Сенс бажання користувача полягав не в тому, щоб мати елемент безпеки, а задовольнити певну вимогу – в даному випадку конфіденційність (приховане сховище). При реалізації цієї історії важливо було розуміти, що зміни коду, який застосовується тільки до одного способу доступу до  зображень, недостатньо. Всі шляхи доступу повинні бути захищені, пропустіть тільки один і вимога не буде виконана.

Щоб отримати реальну безпеку, потрібно перестати думати про неї як про набір можливостей. Безпеку слід розглядати як вимогу, яка охоплює різні функції.

Категорії вимог безпеки: CIA-T

Ми вже згадували конфіденційність як аспект безпеки. Але безпека полягає не тільки в тому, щоб приховати речі від сторонніх очей. У цій книзі ми поговоримо про інші аспекти. Для початку давайте перерахуємо терміни, пов’язані з питаннями безпеки.

У класичній інформаційній безпеці зазвичай є три основні вимоги: конфіденційність, цілісність, доступність (ЦРУ).

  1. Конфіденційність. Найчастіше згадується під час обговорення безпеки й у тому, щоб приховувати речі, які мають бути публічними. Одним із прикладів конфіденційної інформації є ваша медична картка.

  2. Цілісність. Означає, що у випадках, коли це важливо, інформація не повинна змінюватися або зміни повинні проводитись певними схваленими способами. Прикладом цілісності є підрахунок результатів голосування. Безпека у цьому контексті забезпечує захист від маніпуляцій. з голосами.

  3. Доступність. Це означає, що дані повинні бути завжди під рукою і доступ до них повинен надаватися своєчасно. Пожежникам потрібно знати, що горить, і ця інформація їм потрібна негайно. Якщо вони отримають її із затримкою, може виявитися надто пізно, і вимога до безпеки не буде виконана.

Для будь-якої частини даних всі три фактори можуть бути важливими, але найчастіше їх порушення має різні наслідки. Взяти, наприклад, медичну карту. Розголошення вашої історії хвороби (порушення конфіденційності) викличе у вас роздратування або навіть гнів. Якщо є помилки в даних (порушення цілісності), це може бути небезпечно для вашого здоров’я. Якщо ви перебуваєте в машині швидкої допомоги, а медичної карти немає (порушення доступності), наслідки можуть бути фатальними.

Або візьміть свої банківські записи. Якщо ви не можете дізнатися свій баланс (наявність) при оплаті рахунків, це дратує. Якщо баланс став відомий стороннім (конфіденційність), це привід для гніву. Але якщо ваші пенсійні заощадження раптом випарувалися (цілісність), це вже катастрофа.

Пізніше до цих трьох факторів приєднався ще один:  простежуваність (T) – це необхідність знати, хто і коли отримував доступ або змінював дані. Після деяких скандалів у фінансовому секторі та охороні здоров’я це стало важливим питанням. Цей вид аудиту є невід’ємною частиною регламенту Європейського Союзу, який називається «Загальний регламент захисту даних» (General Data Protection Regulation,  GDPR) і які набули чинності у 2018 році. Наприклад, відповідно до GDPR, доступ до персональних даних повинен відстежуватися і зберігатися в постійному контрольному журналі. Ми будемо використовувати терміни «конфіденційність», «цілісність», «доступність» і «відстеження» на сторінках цієї книги, щоб пояснити, про яку безпеку йде мова.

Турбота про питання безпеки, а не окремих її елементів, значно покращує якість системи, але в той же час ставить розробників в скрутне становище: як забезпечити безпеку в програмному забезпеченні, яке ви пишете? Непросто переконатися у відсутності помилок. Для цього розробники повинні постійно дбати про безпеку. Але є ще один спосіб зробити це, і це включити безпеку в робочий процес і процес проектування.

Що таке дизайн?

Написання софта – далеко не тривіальне завдання. Розробник повинен володіти навичками в широкому спектрі областей. Очікується, що ви будете володіти знаннями в різних областях, від мов програмування та алгоритмів до системної архітектури та гнучких методологій. Ці дисципліни пронизують різні області знань і можуть помітно відрізнятися один від одного, але при їх обговоренні постійно згадується один і той же термін –  «дизайн». Яке значення ми вкладаємо в це слово?

Ми вважаємо, що термін «дизайн», як правило, використовується досить вільно і може означати різні речі в залежності від того, про кого і про що ви говорите. На нашу думку, дизайн є надзвичайно важливим поняттям у розробці програмного забезпечення. Тому цілком логічно почати з власного визначення дизайну, яке буде застосовуватися в подальшому. Розуміння цього допоможе вам зорієнтуватися в обговореннях та концепціях, представлених тут.

При розробці програмного забезпечення постійно доводиться приймати рішення про те, який код потрібно написати для вирішення існуючих завдань. Ви обираєте синтаксис, конструкції та алгоритми, структуруєте свій код і визначаєте напрямок виконання. Якщо ви використовуєте об’єктно-орієнтований підхід, вам потрібно буде вирішити, як ви хочете, щоб виглядала ваша об’єктна модель і як її елементи будуть взаємодіяти між собою. Якщо застосовувати функціональний стиль програмування,

Потрібно визначити, яку поведінку слід передавати як функції і переконатися, що вони чисті, без побічних ефектів. Всі ці рішення можна вважати частиною дизайну.

В ході написання коду велика увага приділяється тому, як буде представлена бізнес-логіка – функціональність, яка робить програмне забезпечення унікальним. Ви гарненько подумаєте, як реалізувати цю логіку і як зробити її обслуговування прозорим і легким. Якщо ваші обов’язки мають якесь відношення до моделювання домену, ви витратите багато часу на розробку та оптимізацію своєї моделі, а також на планування того, як вона повинна бути виражена в коді. Навіть якщо реалізована логіка не складніше регулярного умовного виразу, все одно потрібно прийняти усвідомлене рішення. Наприклад, ви можете розглянути такі аспекти, як читабельність або продуктивність, і, залежно від ваших уподобань, вибрати, як вираз буде виглядати в коді. Спираючись на свій досвід і знання, ви свідомо приймаєте рішення, які підходять для створюваного вами програмного забезпечення. Таке прийняття рішень можна розглядати як частину процесу проектування.

У міру розвитку вашої кодової бази ви розбиваєте свій код на пакети або модулі, щоб зробити його зрозумілішим і простішим у використанні, отримуючи при цьому бажані функції, такі як узгодженість і слабке з’єднання. Можна застосовувати такі прийоми і поняття, як інтерфейси, інверсія залежностей і незмінність, намагаючись при цьому не порушувати принцип підстановки Ліскова. Ви також можете спробувати вилучити та ізолювати частину функціоналу, щоб зробити його більш наочним або полегшити тестування. Таким чином, ви пишете код і виконуєте рефакторинг — редизайн, який призводить до поліпшення вашого додатка.

Якщо ваш код взаємодіє з іншим програмним забезпеченням (наприклад, ви розробляєте сервіс в архітектурі мікросервісу), вам доведеться подумати про те, як визначити публічний API вашого сервісу, щоб він працював безперебійно, був простим у використанні та підтримував керування версіями. Також слід звернути увагу на те, як забезпечити його стабільність і чуйність при взаємодії з іншими сервісами і як домогтися прийнятного часу безвідмовної роботи. На більш високому рівні, напевно, необхідно враховувати той факт, що сервіс повинен вписуватися в загальну архітектуру системи. Всі різні рішення, які ви приймаєте, є частиною одного цілого – процесу проектування вашого програмного забезпечення. Всі види діяльності, описані раніше, пов’язані з кодуванням.

Ми сказали, що всі вони є частиною процесу проектування, але якщо трохи подумати, які з них пов’язані з дизайном, а які ні?

  • Чи є планування API та аналіз архітектури системи типовими прикладами проектування?

  • Чи можна моделювання предметної галузі вважати проектуванням?

  • Чи відноситься до проектування вибір між тим, використовувати чи ні ключове слово final в оголошенні поля об’єкта?

Якщо ви запитаєте десятьох людей, які види діяльності з розробки програмного забезпечення вважаються дизайном, ви, швидше за все, отримаєте десять різних відповідей. Як явні приклади багато хто, ймовірно, наведе моделювання домену, планування API, застосування шаблонів проектування та архітектуру системи, частково тому, що це більш традиційний погляд на те, що таке дизайн. Лише деякі скажуть, що процес проектування програмного забезпечення серйозний Думаючи про те, як написати  оператор if або  цикл for.

Отже, що вважається частиною процесу проектування? Відповідь проста: все, що пов’язано з розробкою. Системний або програмний компонент в результаті проектування можна назвати стабільним (тобто функціонуючим, і це не означає, що він перестає розвиватися) тільки після його реалізації і розгортання в реальних умовах. Іншими словами, моделі доменів, модулі, API і шаблони проектування так само важливі в дизайні,  А також оголошення полів і методів, якщо операторів,  і хеш-таблиць. Все це впливає на стабільність кінцевого продукту.

Всі ці аспекти розвитку мають дещо спільне: вони вимагають усвідомленого прийняття рішень. Будь-яка діяльність, пов’язана з усвідомленим прийняттям рішень, є частиною процесу розробки програмного забезпечення. Це, в свою чергу, означає, що дизайн грає роль керівного принципу, на якому створюється система, і вона може бути застосована на всіх рівнях, від коду до архітектури.

У цьому розділі ви дізналися, як слід розглядати дизайн програмного забезпечення і який сенс ми вкладаємо в нього в цій книзі. Далі ми поговоримо про традиційний підхід до безпеки програмного забезпечення і деяких його недоліках.

Традиційний підхід до безпеки програмного забезпечення та його недоліки

Спостерігаючи за тим, що відбувається в сфері розробки програмного забезпечення, ми зрозуміли, що багато хто приходить до такого висновку: щоб уникнути вразливостей, слід віддавати безпеці найвищий пріоритет при написанні коду. Кожен, хто бере участь в цьому процесі, повинен бути належним чином навчений і досвідчений в області безпеки програмного забезпечення. Назвемо цей погляд традиційним підходом  для безпеки програмного забезпечення. Як правило, вона включає в себе певні завдання і дії, які повинні виконати розробники (рис. 1.2).

Розробники повинні бути знайомі з міжсайтовими сценаріями (XSS), розуміти вразливості низькорівневих протоколів і знати напам’ять список ризиків безпеки OWASP[1]. Тестувальники повинні бути знайомі з основними методами тестування на проникнення, а експерти домену повинні мати можливість обговорювати відповідні проблеми та приймати рішення щодо безпеки програмного забезпечення.

Недоліком такого підходу є те, що з ряду причин він дозволяє легко створювати програмне забезпечення, досить безпечне, щоб протистояти суворій реальності промислових умов. Якби він був успішним, уразливості були б менш поширеними, і нам не довелося б мати справу з величезними дірками в безпеці, викликаними однією і тією ж вразливістю знову і знову. Давайте детальніше розглянемо деякі слабкі сторони цього підходу, щоб краще зрозуміти, чому він не дає бажаних результатів і чому ми вважаємо, що він має кращу альтернативу.

Уявіть, що у вас є простий об’єкт домену, який представляє користувача в типовому веб-застосунку, і ім’я користувача відображається на сторінці. Це досить простий об’єкт, який містить лише ідентифікатор та ім’я користувача. Але, незважаючи на таке спрощення, наш досвід показує, що такі об’єкти часто можна зустріти в коді. Реалізація показана в Лістингу 1.1.

Якщо ви подивитеся на цей вигляд користувача, ви можете побачити потенційні проблеми з безпекою. Наприклад, будь-яке значення рядка приймається як ім’я користувача, що робить можливими атаки XSS. XSS-атака виникає, коли зловмисник  використовує веб-додаток для відправки шкідливого коду іншому користувачеві. Цей код, наприклад, може бути у формі клієнтського скрипту в JavaScript. Якщо зловмисник вводить щось на кшталт  <script>alert(42);</script> в якості логіна під час реєстрації в сервісі, браузер може вивести вікно з цифрою 42 в якості імені користувача пізніше, коли це ім’я відображається на якійсь веб-сторінці програми

Щоб пом’якшити цю вразливість за допомогою традиційного підходу, ви можете додати явну перевірку введення, орієнтовану на безпеку. Перевірка даних, наприклад, може бути реалізована у вигляді фільтрів, які аналізують дані всіх форм у веб-додатку і переконуються, що він не містить шкідливого XSS-коду. Але те ж саме можна було б зробити безпосередньо в класі доменів. Якщо ви надаєте перевагу перевірці введених даних у класі User , дивіться в Списку 1.2, як це може виглядати.

У цьому списку ви можете побачити, як ми включаємо гіпотетичну бібліотеку безпеки, яка дозволяє перевіряти рядки на можливі атаки XSS. Ми також вирішили переконатися, що жоден з параметрів конструктора не є нульовим, щоб зробити перевірку ще кращою.

Цей спосіб захисту програмного забезпечення широко поширений, але має кілька проблем, серед яких наступні:

  • Розробнику доводиться безпосередньо займатися вразливістю і водночас зосереджуватися на вирішенні бізнес-завдань.

  • Кожен розробник має бути спеціалістом з безпеки.

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

Розберемо кожен із цих пунктів і подивимося, що з ними не так.

Безпека вимагає особливої уваги

Перша проблема полягає в тому, що ми окремо зосереджуємося на безпеці. Коли розробник пише код, його основна увага завжди зосереджена на функціональності, яку він намагається реалізувати. Якщо йому доведеться ще й подбати про безпеку, це може відвернути його від основних обов’язків. У разі виникнення такого конфлікту безпека завжди залишається на другому плані. Причин тому кілька, про які ми детально поговоримо в підрозділі 1.4.2.

Кожен повинен бути професіоналом безпеки

Наступна проблема полягає в тому, що кожен розробник повинен бути професіоналом з безпеки, але не кожен має можливість або бажання спеціалізуватися в цій області, подібно до того, як не кожен може бути фахівцем з продуктивності JVM або користувацького досвіду. І якщо розробники не дуже добре обізнані про безпеку, програмне забезпечення, яке вони створюють, відображатиме їхній рівень знань та навичок.

У майбутньому розробникам, можливо, знадобляться глибокі знання про безпеку програмного забезпечення, так само, як вміння писати хороші модульні тести є більш-менш обов’язковим в наші дні. Але зараз стан нашої промисловості такий, що такі очікування дещо нереалістичні.

Ви повинні знати про всі уразливості, навіть ті, які на даний момент невідомі

Навіть якщо ваше програмне забезпечення написано командою фахівців з безпеки, вам все одно потрібно прийняти той факт, що створення контрзаходів можливо тільки для тих вразливостей, про які ви вже знаєте. Однак ви повинні мати глибоке розуміння не тільки величезної кількості векторів атаки, з якими ви знайомі, але і вразливостей, про які ніхто не знає. Ви повинні осягнути незрозуміле, так би мовити. Коли ви усвідомите цю дилему, для вас стане очевидним, що вона створює додаткові труднощі при написанні захищеного програмного забезпечення.

Підхід «безпека перш за все» існує з незапам’ятних часів, і ми всі його використовували. Іноді його застосування було успішним, але часто ми відчували, що чогось не вистачає, і повинен бути інший, кращий спосіб створення безпечних додатків. Ми віримо, що дизайн сприяє успішному написанню дійсно безпечного коду. І, зосередившись на дизайні, ви зможете уникнути багатьох недоліків, властивих підходу, який ми розглянули в цьому розділі.

 Забезпечення безпеки за допомогою проектування

Ми не стверджуємо, що безпека важлива, і що ви повинні пам’ятати про це при розробці програмного забезпечення. Однак ми вважаємо, що замість використання традиційного підходу до безпеки ми можемо звернутися до альтернативи, яка дає такі ж або навіть кращі результати, коли мова йде про те, як буде виглядати готовий проект (рис. 1.3).

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

Забезпечення початкового захисту користувача

Давайте повернемося до прикладу  класу User в  попередньому розділі і подивимося, як ми могли б підійти до його реалізації з акцентом на дизайн. По-перше, вам потрібно запитати експертів вашого домену про значення імені користувача в контексті поточного додатка (рисунок 1.4).

Обговоривши це питання, ви приходите до висновку, що ім’я користувача повинно містити тільки символи [A-Za-z0-9_-] і складатися  з 4-40 символів. Це продиктовано тим, що вважається звичайним іменем користувача в створюваному додатку. Ви виключаєте < символи і > не  тому, що вони можуть бути частиною атаки XSS у випадку, якщо ім’я користувача відображається в браузері. Швидше,  У цьому прикладі ви вирішили, що  символи < і > не можуть бути частиною правильного імені, тому виключили їх.

Невелике дослідження з експертами домену дозволило отримати більш глибоке розуміння поточного контексту і, як наслідок, розробити більш точне визначення імені користувача. Лістинг 1.3 показує новий  клас User.

Лістинг 1.3. Клас користувача з обмеженнями домену.

Тепер, коли ви дивитеся на  клас User, ви бачите багато логіки, пов’язаної з іменем користувача. Цей факт, а також те, що вам довелося досить детально обговорювати ім’я користувача з експертами домену, є ознакою того, що ім’я користувача має бути явно представлено в моделі домену. Частково це пов’язано з важливістю цього поняття, але воно не менш важливо  що вилучення логіки відбувається за принципом високої узгодженості.

Маючи це на увазі, ви можете винести логіку в окремий  клас Username, який інкапсулює всі ваші знання про ім’я користувача. Новий клас застосовує всі правила домену на етапі створення. Цей новий об’єкт називається  примітивом домену  (або примітивом домену, докладніше про нього в главі 5). Лістинг 1.4 показує, як  виглядатиме  клас User при переміщенні коду в новий клас Ім’я користувача.

Лістинг коду 1.4. Клас користувача з об’єктом значення домену.

Зосередившись на дизайні, ви змогли дізнатися більше подробиць про користувача та його ім’я. Це дозволило створити більш точну модель домену. Ви також помітили, що концепції поточної моделі стали настільки важливими, що їх потрібно переміщати в окремі об’єкти. В результаті ви стали глибше розуміти свій домен і при цьому убезпечили себе від уразливості XSS,  які ми обговорювали раніше:  <script>alert(42);</script> більше не можна ввести, оскільки це недійсне ім’я користувача. А про безпеку ви ще навіть не замислювалися! Приділивши певну увагу, обмеження на імена користувачів, ймовірно, можна зробити ще жорсткішими, але дизайн залишиться на першому плані.

ПРИМІТКА. Чітка спрямованість на дизайн дозволяє писати більш безпечний код, ніж традиційний підхід до безпеки програмного забезпечення.

Отже, ми познайомилися з недоліками традиційного підходу і побачили, як застосувати дизайн для створення безпечного програмного забезпечення. Деякі поняття, розглянуті в цьому розділі, будуть детально розглянуті в главі 3. Він представить основні концепції доменного дизайну, які мають відношення до цієї книги. Потім, у розділах 4 і 5, ми опишемо фундаментальні конструкції програмування, які допомагають досягти безпеки. Тепер давайте розглянемо переваги безпеки за допомогою проектування і пояснимо, чому цей метод, на наш погляд, дає кращі результати в порівнянні з традиційним підходом до безпеки програмного забезпечення.

 Переваги проектно-орієнтованого підходу

На прикладі простого  класу User  було показано, як дизайн може підвищити безпеку процесу розробки. Ми також відзначили, що, зосередившись на дизайні, можна досягти рівня безпеки програмного забезпечення, який дорівнює, а іноді і перевершує традиційний підхід. Але на якій підставі можна сказати, що цей підхід краще традиційного?

Ми вважаємо, що якщо основна увага в розробці приділяється дизайну, безпека може перетворитися з нав’язаної вимоги на природний аспект процесу. Ми також переконані, що це дозволяє не тільки подолати або уникнути багатьох недоліків традиційного підходу, а й отримати певні переваги.

Основні причини цього наступні.

  • Проектування ПЗ – головне, що цікавить розробників, воно лежить в основі їх умінь. Це дозволяє легко впровадити концепцію безпеки через проектування.

  • Якщо зосередитися на проектуванні, питання, які стосуються бізнесу та безпеки, матимуть рівний пріоритет в очах як розробників, так і фахівців у предметній галузі.

  • Вибираючи відповідні концепції проектування, фахівці, які не мають відносини до безпеки можуть писати безпечний код.

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

Давайте більш детально обговоримо обґрунтування цих переваг. У наступному розділі ви дізнаєтеся, чому ми вважаємо, що розробка, орієнтована на дизайн, краща за традиційний підхід.

Дизайн є природним аспектом розробки програмного забезпечення

Розробників програмного забезпечення з перших днів вчать, що хороший дизайн має важливе значення. Створення добре розробленого додатка, який виконує свою роботу, – це те, чим можна пишатися. Це робить дизайн природним аспектом розробки програмного забезпечення.

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

Якщо ви зосередитеся на дизайні, безпека починає турбувати всіх оточуючих, а не тільки фахівців. Такий підхід усуває конфлікт між бізнес-функціями і питаннями безпеки, так як вони зливаються в єдине ціле. Це знижує когнітивне навантаження на розробника і дозволяє уникнути одного з недоліків традиційного підходу.

Питанням бізнесу та безпеки надається однаковий пріоритет

Одним з недоліків традиційного підходу є те, що безпека вважається окремим видом діяльності. В результаті безпека починає вступати в конфлікт з іншими важливими аспектами, яким ви намагаєтеся присвятити час, такими як бізнес-функції, масштабованість, тестованість, ремонтопридатність і т. д. Завдання, пов’язані з безпекою, додаються до загального списку завдань і розставляються пріоритети щодо решти необхідної функціональності.

У процесі визначення пріоритетів немає жодних вказівок на те, що безпека повинна займати особливе місце серед інших завдань. Навпаки, часто можна спостерігати, як безпека не отримує уваги, на яку вона заслуговує.

Причин тому кілька.

  • І розробники, і бізнес-фахівці погано розуміють безпеку.

  • З причин, розглянутих раніше, розробники найчастіше вважають, що безпека – це не їхня справа.

  • Навіть якщо ви добре знаєтеся на сфері безпеки, нею дуже легко знехтувати на користь функціональних можливостей, ставлячись до неї так, ніби її можна відкласти на потім.

Ідея впровадження безпеки не відразу, а через деякий час має один нюанс: ця ідея може виявитися нереалістичною, якщо необхідні аспекти безпеки вимагають кардинальних змін в архітектурі. Це схоже на те, чому затримати впровадження масштабованості або відмовитися від державного сховища зазвичай важко або неможливо.

Зосередившись на дизайні та знаннях домену (як у попередньому прикладі з класом User), ви уникли кількох ситуацій, коли безпека мала бути пріоритетнішою за інші заплановані завдання. Тепер вам не доведеться вибирати між елементами безпеки та можливостями для бізнесу. Ви просто впроваджуєте функції, специфічні для вашого домену.

Наостанок відзначимо, що акцент на дизайн робить безпеку більш доступною не тільки для фахівців, але і для інших зацікавлених сторін. Це результат того, що вам легше обговорювати завдання, з якими ви стикаєтеся, бачити цінність, яку вони приносять, і розставляти пріоритети, якщо ці завдання пов’язані з вашою предметною областю, а не з окремими вразливостями.

Професіонали, які не займаються безпекою, пишуть безпечний код без додаткових зусиль

Ще одна цікава перевага проектного підходу до безпеки полягає в тому, що будь-хто може написати захищений код без додаткових зусиль. І справа не в тому, що вони аналізують вектори атак і думають про те, як шкідливі дані можуть вплинути на систему, скоріше, небезпечні концепції просто не потрапляють в архітектуру. Щоб проілюструвати це, розглянемо  клас Username  Зі списку 1.4, інваріанти якого гарантують, що приймаються лише дійсні імена користувачів.

Чи виправдано використовувати цей складний тип замість звичайного рядка?

При спілкуванні з профільними експертами більшість розробників визнають важливість максимально точного представлення бізнес-концепцій. Ім’я користувача – це не просто довільна послідовність будь-яких символів, це чітко визначене поняття з певним значенням і роллю в предметній області. Представлення його у вигляді стандарту Рядковий клас буде не тільки поганим, але і абсолютно неправильним дизайнерським рішенням. І розуміння цього автоматично підштовхує розробника до вибору точних і правильних реалізацій, незалежно від його досвіду або зацікавленості в безпеці.

Фокус на домені автоматично дозволяє уникнути дірок у безпеці

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

Якщо ви подивитеся на  ім’я користувача в списку 1.4, основна мета інваріантів полягає не в захисті імені користувача від зловмисної ін’єкції, а в передачі дійсного значення імені користувача. В результаті будь-яке шкідливе введення, яке не відповідає цьому визначенню, відхиляється, а ім’я користувача стає безпечним через зосередженість на домені, а не тому, що ми подбали про безпеку. Це знижує ризик помилок безпека у вашому коді, а в деяких випадках може захистити вас від дірок у сторонніх продуктах.

Поєднання різних підходів

Як згадувалося раніше, якщо ви додасте більш традиційну та цілеспрямовану проблему безпеки до акценту дизайну, ваш код стане ще більш безпечним. Це важливе зауваження, адже підвищена увага до дизайну хоч і забезпечує високий ступінь безпеки, але ніколи не охоплює всіх вимог безпеки системи (а це не є метою такого підходу). При створенні програмного забезпечення завжди необхідно проводити тестування на проникнення і активний аналіз конкретних векторів атак і вразливостей. Навіть якщо зосередження уваги на домені зробило  приклад імені користувача безпечним, ви  все одно повинні виконувати правильне кодування виводу, коли воно відображається на веб-сторінці. Зосередившись на дизайні і в той же час застосовуючи універсальний підхід до безпеки, ви можете створювати по-справжньому безпечні продукти.

Ми розглянули досить великий матеріал, але нам здається, що перш ніж  пояснювати, як все це  реалізується, потрібно зрозуміти, чому. Ви дізналися, що таке дизайн і основні принципи, що лежать в основі ідеї, що підвищена увага до питань дизайну може допомогти вам розробити безпечне програмне забезпечення. Ви також бачили простий приклад того, як це працює. У наступному розділі ми розглянемо трохи складніше Кейс, який ще раз проілюструє підвищення безпеки за допомогою дизайну.

Strings, XML та атака мільярда сміху

При розробці програмного забезпечення часто доводиться приймати рішення про те, як будуть представлені дані. На жаль, зазвичай перевага віддається надмірно узагальненим типам даних. Наприклад, представлення номера телефону у вигляді рядка спочатку може здатися зручним, але з точки зору безпеки це катастрофа, так як рядок може містити практично будь-які дані, не тільки те, що ми очікуємо. Але розробники все ж віддають перевагу рядкам,  І, як показано в Лістингу 1.5, захист від неправильного введення часто забезпечується тільки на рівні імені змінної. Метод реєстрації  приймає номер телефону, але його аргумент має тип String, що означає, що це може бути що завгодно!

Природно, це не допоможе запобігти неправильне введення. Рішення полягає у використанні строгих типів доменів із правилами, як показано раніше у  прикладі User та Username. Але строгі типи – це тільки половина успіху.

Якщо ви подивитеся  на Username, то помітите, що логіка в конструкторі містить перевірки notBlank  і довжини, які  виконуються перед застосуванням регулярного виразу. Це надзвичайно важливо з точки зору безпеки, і в розділі 4 ми пояснимо чому.

Тим часом просто майте на увазі, що перевірки повинні проводитися в такому порядку.

  • Перевірка довжини. Чи є довжина введення в очікуваному діапазоні?

  • Лексична перевірка вмісту. Чи містить коректні символи введення і чи має він відповідне кодування?

  • Синтаксична перевірка. Чи правильний формат у вводі?

Поки що ми розглядали лише прості перевірки, але це не означає, що їх не можна використовувати в більш складних ситуаціях. Щоб проілюструвати це, давайте розглянемо приклад, який дозволить вам навчитися безпечно поводитися з XML. На перший погляд вона має мало спільного з попередніми прикладами, але ви побачите, що ті ж принципи дозволяють звести парсинг до звичайної перевірки введення. Давайте розберемося з обробкою XML.

XML

XML (Extensible Markup Language) схожий на рядок, оскільки він може представляти майже будь-який тип даних.  Завдяки цьому його часто використовують для проміжного представлення даних в ході взаємодії між системами. На жаль, мало хто здогадується, що нормалізація подання даних – далеко не єдине застосування XML.

По суті, XML є повноцінною мовою, заснованим на SGML (Standard Generalized Markup Language). Це означає, що XML має багато функцій, які не цікавлять більшість розробників. В результаті використання цього формату вносить багато ризиків безпеки для програмного забезпечення. Давайте проілюструємо це на прикладі  атаки «Мільярд сміху», який використовує розширюваність сутностей XML на етапі аналізу. Це послужить фундаментом для вивчення безпечної переробки

XML. Але перш ніж ми перейдемо до деталей, давайте згадаємо, як працюють внутрішні сутності XML.

Короткий огляд внутрішніх об’єктів XML

Внутрішні сутності XML є важливими конструкціями, які дозволяють створювати прості скорочення в XML. Вони визначені в розділі Document Type Definition (DTD) і записуються як <!ENTITY name “value”>.

Лістинг 1.6 показує простий приклад сутності, яка є абревіатурою для цієї книги.

Коли аналізатор зустрічається  з титульною сутністю, він розширює абревіатуру та підставляє значення, вказане в DTD. Результатом є розширений блок XML без скорочень.

Це показано в лістингу 1.7.

Можливість розгортання сутностей досить корисна, але, на жаль, її можна використовувати для атак. Давайте подивимося, як його експлуатує атака «Мільярд сміху».

Атака мільярда сміху

Простота атаки «Мільярд сміху» порівнянна лише з її ефективністю. Основна ідея полягає в експлуатації розширюваності сутностей XML шляхом створення рекурсивного визначення, яке займає величезну кількість пам’яті в результаті розгортання. Лістинг 1.8 показує приклад атаки, заснованої на невеликому блоці XML, який займає менше 1 КБ. Через це він проходить більшість перевірок валідації, які аналізують розмір або довжину. Коли аналізатор завантажує XML-документ,  lol9 розширюється на десять  екземплярів lol8, які потім розширюються на сто  екземплярів lol7,  і так далі, в результаті чого утворюється мільярд рядків  lol, які займають кілька гігабайт пам’яті.

Очевидно, що сутності поводяться не так, як ми очікували, але сам факт того, що вони є частиною мови XML, робить будь-який аналізатор вразливим до такого роду атак. Наш досвід показує, що найкращим захистом від цієї проблеми є поєднання правильної конфігурації парсера і перевірки лексичного контенту. Спочатку налаштуємо аналізатор XML.

XML Конфігурація аналізатора

Щоб сутності не розгорталися, потрібно розуміти, від яких параметрів залежить, як вони поводяться при аналізі. А без розуміння внутрішньої будови аналізатора це стає дивно складним завданням, так як кожен аналізатор поводиться по-різному. Щоб зрозуміти це, можна звернутися до зовнішніх ресурсів, таким як OWASP.

Лістинг 1.9 показує приклад конфігурації аналізатора, яка, дотримуючись інструкцій OWASP, намагається уникнути розширення сутності. Тут вибираються досить агресивні варіанти, так як відключається практично все, що пов’язано з сутностями. Наприклад, заборона  використання doctype  ускладнює атаки на сутності, але в той же час впливає на загальну зручність використання XML. У таких ситуаціях проблеми безпеки часто протиставляються потребам бізнесу, і якщо ви вирішили послабити конфігурацію, вам потрібно розуміти пов’язані з цим ризики.

Зміна конфігурації аналізатора є рекомендованим підходом, але це відчувається дуже ризиковано. Наприклад, що буде, якщо зміниться внутрішня реалізація аналізатора або ви забудете про якийсь параметр? Ці побоювання виправдані, тому рекомендується застосовувати додатковий рівень безпеки – дизайн.

Рішення задачі за допомогою проектування

Нам здається, що перш ніж підходити до проблеми «Мільярда сміху» з дизайнерської точки зору, необхідно відмовитися від думки про те, що першопричина криється в механізмі розширення сутності. Справа в тому, що ця функція відповідає специфікації XML і не є результатом ретельної реалізації аналізатора. Звідси випливає, що ми маємо справу не зі структурною проблемою з форматом XML, а скоріше з проблемою перевірки вхідних даних на стороні одержувача. Це, в свою чергу, означає, що шкідливий блок XML, такий як код Billion Laughs, повинен бути відхилений приймаючою системою ще до того, як він буде проаналізований. Звучить заманливо, але чи практичне таке рішення? Ми впевнено відповідаємо: так. Запам’ятайте другий пункт в списку, який визначає порядок етапів перевірки – виконання лексичного розбору. На перший погляд це може здатися складною операцією, але насправді це не так вже й складно. Це всього лише процес перетворення потоку символів в послідовність лексем без аналізу їх значення і порядку (це завдання аналізатора).

Лексичний контент-аналіз може бути реалізований різними способами. Лістинг 1.10 показує приклад аналізу XML за допомогою простого API для XML (SAX). Використання парсера для лексичного розбору може здатися нелогічним, але інструмент SAX цілком підійде для цього, оскільки генерує подію при виявленні токена в потоці даних. Ці події потім можна використовувати для аналізу вмісту (якщо нас зацікавив synsing) або, як у цьому випадку, відхилення XML-документа з сутностями. Щоб досягти цього, коли сутність знайдена,  метод startEntity класу ElementHandler  негайно викидає виняток і перериває аналіз.

Лістинг 1.10. Простий лексичний аналізатор для пошуку сутностей XML

Результат відповідає нашим очікуванням, але основна мета лексичного аналізу полягає не тільки у відмові від сутностей. Цей процес також повинен гарантувати, що XML-документ має всі необхідні елементи, інакше немає сенсу його розбирати. Щоб проілюструвати це, уявіть, що у нас є  об’єкт клієнта у форматі XML, з рівно одним номером телефону, однією адресою електронної пошти та однією домашньою адресою, як показано у списку 1.11. Обов’язкові номер телефону та домашня адреса, а адреса електронної пошти – ні. Починати парсинг цього XML-документа має сенс тільки в тому випадку, якщо він містить всі необхідні елементи. Будь-які інші поєднання елементів будуть некоректними з точки зору бізнес-вимог, і лексичний аналізатор повинен їх відкинути. Це схоже на те, як ми відхилили неправильне введення у прикладі імені користувача.

Для того, щоб XML-документ містив всі необхідні елементи, нам потрібен більш просунутий  клас ElementHandler. Але перш ніж вдаватися в подробиці, слід пам’ятати, що лексичний розбір дозволяє дізнатися тільки про існування елементів, але ніяк не про їх значення, порядку розміщення або дублювання. Тому існує ризик, що невірний клієнтський XML-блок пройде лексичну перевірку  (наприклад, з кількома номерами телефонів або домашніми адресами). І хоча це виглядає як дефект, така поведінка – саме те, чого ми очікуємо. Справа в тому, що лексичні перевірки в поєднанні з семантичними неминуче перетворюються в парсинг, повертаючи нас туди, де ми почали.

Маючи це на увазі, розглянемо оновлену версію  ElementHandler у списку 1.12. Дана реалізація має кілька цікавих моментів, на які варто звернути увагу. Перш за все, кожен раз, коли метод startElement спрацьовує,  він звіряється з колекцією, яка зберігає всі необхідні елементи. Виглядає досить просто, але є один неочевидний нюанс, який легко не помітити. Для лексичного розбору важливо тільки наявність елементів,  тому нам потрібен механізм для забезпечення того, щоб всі необхідні елементи були присутні в XML-документі принаймні в одному екземплярі. Для цього кожен виявлений елемент видаляється зі згаданої колекції. І неважливо, чи є цей елемент обов’язковим, головне, що його більше не буде в зборі. Після розбору потоку даних до кінця лексичний аналізатор повинен переконатися, що всі необхідні елементи знайдені, для цього в методі endDocument перевіряє,  чи не порожній збірник.

Другий нюанс, про який варто згадати, – це вибір ліберальної стратегії пошуку, яка ігнорує будь-які необов’язкові елементи. Може здатися, що це потенційний дефект, так як блок  XML замовника  не відповідає вимогам аналізатора, але це усвідомлене рішення. Відповідно до закону Постеля і шаблону проектування Tolerant Reader, коли системи взаємодіють один з одним, реалізація прийому даних повинна бути ліберальною, а реалізація відправки даних – ліберальною.  консервативний. Це дозволяє уникнути деяких труднощів в інтеграції систем, так як приймаюча сторона ігнорує зміни в полях даних. В результаті, ігноруючи всі необов’язкові елементи, ми зробили лексичний аналіз стійким до незначних змін даних, таких як оновлення додаткового елемента в блоці XML клієнта.

Це, безсумнівно, ускладнює реалізацію XML-блоку Billion Laughs, але що, якщо потрібні сутності? Чи зробить це лексичний аналіз марним? Або, можливо, існує спосіб прийняття суб’єктів, який захищає від атак розширення? Дійсно, такий спосіб є, але щоб дізнатися, що це таке, потрібно підійти до проблеми з іншого боку.

Застосування операційних обмежень

Лексичний аналіз і конфігурація аналізатора борються з розширеннями атаками, сліпо відхиляючи блоки XML з сутностями, незалежно від того, чи є вони шкідливими. Такий підхід має недолік: він працює тільки в ситуаціях, коли сутності XML неприпустимі. У всіх інших випадках потрібне інше рішення. Отже, давайте ще раз подивимося на XML-блок Billion Laughs і спробуємо зрозуміти, де криється реальна небезпека.

Основною проблемою є розширення сутностей, але саме по собі це не робить сутності небезпечними для аналізу. Справжня проблема полягає в споживанні великої кількості пам’яті. Це означає, що сам аналіз XML не є небезпечним — проблема полягає в розмірі кінцевого XML-документа. Як практичне рішення, ми можемо дозволити розширення сутності, але обмежити процес аналізу (наприклад, використання лімітів пам’яті або квот), щоб запобігти надмірному споживанню ресурсів.

Однак вибір такого підходу не захищає автоматично від вичерпання ресурсів. Навіть якщо один процес синтаксичного аналізу споживає пам’ять в допустимих межах (тому що він буде примусово завершений при перевищенні лімітів), паралельне виконання декількох таких процесів може викликати ситуацію, схожу на атаку «Мільярда сміху». Уявімо, наприклад, що процеси парсингу йдуть паралельно і кожен з них споживає максимальну кількість ресурсів. При цьому загальна кількість займаних ресурсів буде прямо пропорційно кількості процесів, що істотно навантажить систему. Таким чином, будь-яка частина системи, яка потребує тих самих ресурсів (наприклад, процесор або пам’ять), буде порушена. Це говорить про архітектуру, в якій синтаксичний аналіз проводиться ізольовано, так як це знижує ризик каскадних збоїв. Про це ми поговоримо більш детально при обговоренні шаблону дизайну перегородки в главі 9.

Накладення операційних обмежень здається життєздатним рішенням, але використання лексичного аналізу та переналаштування аналізатора все ще має сенс. Фактично, вибір архітектури, яка включає всі стратегії, робить систему ще більш стійкою до атак розширення, що підводить нас до наступної теми забезпечення глибокої безпеки.

Забезпечення глибокої безпеки

Більшість розробників віддадуть перевагу боротьбі з атакою розширення сутності лише за допомогою конфігурації. Саме по собі це рішення не є помилковим, але воно схоже на будівництво паркану навколо будинку, в якому двері не замкнені. Будинок залишається в безпеці, поки хтось не проб’є паркан. Це вкрай небажано. Очевидним рішенням є замикання дверей і,  Є можливість встановити сигналізацію всередині. Ось що означає глибока безпека. Кілька рівнів захисту значно ускладнюють успішну атаку, навіть якщо зловмиснику вдається обійти один з них.

Щоб краще проілюструвати реалізацію принципу глибокої безпеки, порівняємо архітектуру, спрямовану на захист від XML-блоку Billion Laughs з будинком. Після налаштування парсера ми звели надійний паркан навколо будинку. Іноді, якщо ви не можете відхилити всі типи сутностей, цей захист є занадто жорстким. У таких ситуаціях краще не позбавлятися від усього паркану відразу, а постаратися зрозуміти, який тип сутності нам потрібен. Ви можете послабити конфігурацію, щоб вона дозволяла лише сутності певних типів (наприклад, лише внутрішні сутності). Це не ідеальне рішення, але воно дозволяє зберегти паркан навколо будинку.

Лексичний розбір гарантує, що XML-документ, який отримує аналізатор, містить необхідні елементи. Ми ніби впускаємо в будинок тільки тих людей, у яких є ключ. В результаті набір XML-блоків, які вимагають парсингу, значно зменшується — аналізуватимуться лише ті, які відповідають бізнес-вимогам. Це дуже ускладнює зловмисну експлуатацію аналізатора, оскільки вектор атаки тепер обмежений блоками XML, що містять необхідні елементи. А як щодо сутностей? Що робити, якщо нам потрібно їх взяти?

Саме для цього і призначений останній рівень захисту. Якщо ви застосуєте операційні обмеження до процесу аналізу, ви можете розслабити лексичний аналіз і передати сутності аналізатору XML — це як відкрити вікно на другому поверсі. Операційні обмеження гарантують, що процес аналізу ніколи не займе занадто багато ресурсів – як сторожовий пес у будинку.

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

Резюме

  • Безпеку краще розглядати як вимоги, які потрібно задовольнити, а не як набір можливостей, які потрібно реалізувати.

  • Забезпечувати безпеку, приділяючи їй першочергову увагу, непрактично. Натомість краще знайти методи проектування, які дозволяють прийнятиНайбезпечніші рішення.

  • Будь-який вид діяльності, що має на увазі свідоме прийняття рішень, слід вважати частиною процесу проектування ПЗ.

  • Проектування – це керівний принцип, що визначає пристрій системи та способи її застосування на всіх рівнях, від коду до архітектури.

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

  • Змістивши свою увагу у бік проектування, ви зможете досягти високої ступеня безпеки, і при цьому вам не доведеться постійно про неї думати.

  • Наголошуючи на проектуванні, ви можете створювати більш безпечний код в порівнянні з традиційним підходом до безпеки програмного забезпечення.

  • Будь-який синтаксичний аналізатор XML уразливий для атак на сутності, оскільки сутності є частиною мови XML.

  • Використання загальних типів для подання конкретних даних дозволяє проявитися потенційним дірам у безпеці.

  • Вибір конфігурації синтаксичного аналізатора XML вимагає його розуміння внутрішньої реалізації.

  • Забезпечення безпеки на етапі проектування сприяє створенню глибокого, багатошарового захисту.

Ми використовували матеріали з книгиБезпека by design”, які  написали Дэн Берг Джонсон, Дэниел Деоган, Дэниел Савано.

Інші статті по темі
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 2. (Антракт: анти-“Гамлет”)
У цій статті описується роль глибокого моделювання у забезпеченні безпеки інформаційних систем та бізнес-цілісності. Розглядаються ризики поверхового моделювання, які можуть призвести до недостатнього рівня захисту та дефективної безпеки.
483
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 3. (Основні концепції предметно-орієнтованого проектування)
Основні Концепції Предметно-Орієнтованого Проектування полягає в поясненні важливих концепцій цього підходу, включаючи абстракцію, глибокий аналіз предметної області та покращення процесів розробки. Виокремлені аспекти покращення якості та продуктивності роботи, що досягаються завдяки використанню предметно-орієнтованого проектування.
603
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 4. (Концепції програмування, що сприяють безпеки)
Застосування цих концепцій у програмуванні сприяє підвищенню рівня інформаційної безпеки. Вони допомагають уникнути загроз та вразливостей, забезпечуючи захист даних та надійність програмних продуктів.
496
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 5. (Доменні притиви)
У цій частині. Як доменні примітиви допомагають писати безпечний код. Боротьба з витоком даних за допомогою об'єктів одноразового читання. Поліпшення сутностей з допомогою доменних примітивів. Ідеї, запозичені із аналізу помічених даних.
486
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 8. (Роль процесу доставки коду у безпеці)
У цій частині. Модульні випробування, спрямовані на безпеку. Перемикачі функціональності з погляду безпеки. Написання автоматизованих тестів безпеки. Чому важливими є тести доступності. Як неправильна конфігурація призводить до проблем безпеки.
525
Знайшли помилку?
Якщо ви знайшли помилку, зробіть скріншот і надішліть його боту.