небезпечна безпечна JAVA h2>
Кріс Касперски p>
Безпека
java-технологій виявилася вирішальним аргументом при просуванні до сфери
корпоративних enterprise-додатків з конкурентом в особі з #. Однак прикордонна
смуга, що відокремлює рекламний маркетинг від реального життя, виявилася досить
тернистою. Java багато обіцяє, але кожен раз відкладає виконання своїх
обіцянок на невизначений термін. Розглянемо модель безпеки java на макро-,
мікрорівнях і проаналізуємо сильні і слабкі сторони цієї технології. p>
В
1994 порядком розрісся колектив програмістів зробив спробу
проникнення на ринок Web-додатків. Вони сфокусувалися на питаннях
безпеки і підготували спеціальну редакцію мови HotJava (в «дівоцтві»
WebRunner), призначену для вбудовування в браузери, яка згодом стала доступною для
публічного скачування в 1995-му. Спроба виявилася успішною. І з моменту
підтримки HotJava браузером Netscape Web-серфінг перестав бути безпечним, а
сам браузер перетворився в один з основних об'єктів хакерських атак. Незважаючи на
це, Java просочилася практично в усі сфери ринку і сьогодні зустрічається
повсюдно: від стільникових телефонів до enterprise-серверів і суперкомп'ютерів. p>
Впровадження
Java-технологій зазвичай відбувається під егідою гасел про підвищення безпеки,
а всі дірки списуються на помилки реалізації конкретних віртуальних машин.
Однак справжня причина в тому, що Java вразливою на концептуальному рівні.
Втім, у конкурентів справи йдуть не краще і основний суперник Java-C #
містить ще більше лазівок, деколи навмисне привнесених розробниками для
досягнення більшої продуктивності на шкоду безпеці. Безпека --
досить абстрактне поняття, що не піддається вимірюванню та не має числового
подання, в той час як тести продуктивності - потужне маркетингове
засіб. Зрозуміло, це ще не означає, що Java і С # повинні бути з ганьбою
викинуті на смітник історії. Достатньо знати шляхи досягнення безпеки і
мати на увазі пастки, які чатують на шляху. p>
Багатолика Java h2>
Перш
ніж приступати до обговорення системи безпеки Java-додатків, проведемо
вододіл між Java-технологіями і однойменною мовою програмування, за
яким, власне кажучи, і асоціюється торгова марка Java. Загальновідомо,
що Java є інтерпретується мовою, але він суттєво відрізняється від
більшості інших різних мов, таких, наприклад, як Perl, PHP або
Python. Якщо в Реrl'е інтерпретації піддається безпосередньо сам вихідний
код, то програма, написана на Java, транслюється в байт-код, що виконується на
віртуальної Java-машини (далі-JVM). p>
Згідно
термінології, запропонованої компанією Sun, реалізатор JVM називається клієнтом, і
будь-який клієнт має право виконувати байт-код так, як йому заманеться
(природно, в рамках специфікації JVM). Поряд з програмними реалізаціями
JVM існують і апаратні, що демонструють продуктивність, нітрохи не
поступається (а часто навіть перевершує в силу грамотній оптимізації
байт-коду JVM) чистому машинному коду з процесором сімейства х86, Alpha та ін
З іншого боку, велику популярність завоювали JIТ-компілятори
(Just-ln-Time-компілятори), на льоту транслюють байт-код в «нативний»
(native) машинний код відповідного процесора і поєднують високу
швидкодію з дешевизною реалізації. p>
Таким
чином, Java-додатки являють собою двійкових файлів, що не мають нічого
спільного з вихідними текстами, складеними на мові Java. Байт-код віртуальної
машини надає досить багатий набір інструкцій, описаний в
специфікаціях на JVM, що дозволяє стороннім розробникам створювати свої
власні транслятори, що працюють з відмінними від Java мовами
програмування. Так, вже з'явилися і завоювали популярність Жасмин
(Java-асемблер), Ephedra (компілятор, що транслює Сі/Сі + + програми в
байт-код JVM), Component Pascal (компілятор, що транслює Pascal і Oberon
програми в байт-код JVM). Є транслятори і для інших мов: Ада,
Бейсік, Форт, Кобол і т. д. p>
Java
являє собою об'єктивно-орієнтована мова програмування з суворим
контролем типів, що виконуються на рівні JVM, що забезпечує захист як від
«Нечесних» трансляторів (не дотримуються оригінальної специфікації), так і
від прямої модифікації байт-коду в hex-редакторі. У той же самий час такий
підхід суттєво ускладнює трансляцію Сі-програм, відомих своїм
нецензурним кастингом (від англійського «to cast» - явне перетворення типів) і
вільним поводженням з вказівниками. Говорячи про безпеку Java, головним чином
зосередимося нa JVM, оскільки системи контролю, вбудовані безпосередньо в
мова програмування Java, працюють лише на стадії трансляції, страхуючи
Java-програмістів від ненавмисним помилок, але не рятують від
цілеспрямованої атаки на байт-код. p>
Концептуальна модель безпеки h2>
Ніщо
не працює так, як планувалося запрограмувати (перший закон
програмування). Ніщо не програмується так, як має працювати (наслідок
першого закону програмування). Звідси випливає, що машинна програма
виконує те, що ви наказали їй робити, а не те, що б ви хотіли, щоб вона
робила. p>
Достаток
переповнюють буферів в Сі-програми (що призводять до можливості віддаленого
захоплення управління системою) носить фундаментальний характер, обумовлений
природою самої мови програмування. Сі підтримує масиви лише формально,
і реально програмістам доводиться працювати не з масивами, а з покажчиками на
області пам'яті невідомої довжини. Мова не виконує жодного контролю кордонів
буферів, цілком покладаючись на програмістів, а тим, як відомо, властиво
помилятися. Саме тому принципова можливість створення безпечних
програм на Сі практично ніколи не досягається в конкретних реалізаціях,
найчастіше створюються в жорстких часових рамках і протестованих на рівні
«Якщо запускається і не падає, значить працює». Ще ні один великий проект,
написаний на Сі/Сі + +, не уникнув помилок проектування. Достатньо взяти
SendMail або IE і підрахувати кількість дірок, знайдених за час їх
існування. p>
Java
в цьому сенсі виглядає досить привабливо. Вбудований контроль типів знімає з
програміста тягар постійних перевірок кордонів масивів, роблячи їх переповнення
достатньо малоймовірним подією. Автоматичний збирач сміття знижує
актуальність проблеми витоків ресурсів і появи «висячих» покажчиків, хоча
це дістається дорогою ціною - зниженням продуктивності і неможливістю
створювати додатки реального часу. До того ж підчистка сміття звільняє
лише ресурси, що йдуть з області видимості, але не здатна запобігти
«Локальні» витоку пам'яті, які часто-густо ризикують обернутися
глобальними. Достатньо, наприклад, пам `ятi у нескінченному циклі аж
до повного її вичерпання. Візьмемо для наочності FireFox, істотна частина
якого написана з використанням Java, і порівняємо його з Оперою, реалізованої
на Сі + +. Лавиноподібний зростання дірок, що виявляють у FireFox'e переконливо
доводить, що Java сам по собі від помилок проектування ніяк не рятує.
Надійність програми в першу чергу залежить від професіоналізму її
творців, а вже потім від властивостей вибраної мови програмування. Створювати
надійну програму можна і на Сі + +, Опера-найкраще тому підтвердження. Це не
тільки один з найшвидших, але і один з самих надійних браузерів на
сьогоднішній день. Складається парадоксальна ситуація. При всій ненадійності
мови Сі/Сі + +, написані на нім програми, як правило, набагато надійніше своїх
Java-побратимів, хоча за логікою все має бути навпаки. Причина в тому, що
більшість старих (і досвідчених) програмістів, що освоїли Сі/Сі + +, не бачать
ніяких причин для переходу на Java-платформу, переважно обирану
молодими (більше недосвідченими) програмістами. І той факт, що додаток написано
на Java, ще не гарантує його надійності. p>
Але
залишимо ненавмисним помилки в стороні і перейдемо до аналізу цілеспрямованих
атак на байт-код. p>
мікрорівень
JVM являє собою віртуальну машину з вбудованим контролем типів, прямим
аналогом якої є «залізні» процесори з тегова архітектурою
(наприклад, наш вітчизняний Ельбрус) - «заповітна» мрія теоретиків від
програмування, абстрагуються від реальних концепцій. На макрорівні,
дійсно, можна працювати з об'єктами, не замислюючись про їх внутрішньому
поданні, але на мікрорівні неминуче доводиться стикатися з
фізичними обмеженнями об'єктивно-орієнтованого підходу. Для досягнення
прийнятною ефективності у виконавчу машину доводиться включати
«Нечесні» механізми, що працюють в обхід зазначеної системи типів.
Стосовно до JVM-це прямі виклики машинного коду і клас sun.misc.Unsafe,
реалізує небезпечні методи роботи з пам'яттю - getLong (читання подвійного
слова з пам'яті по заданому адресу) і putLong (запис подвійного слова на згадку
по заданому адресу). p>
Почнемо
з прямого виклику машинного коду, що є документованої особливістю
JVM, принаймні в її реалізаціях від Sun аж до версії 1.5.6 (починаючи з
1.5.6 можливість зателефонувати машинного коду нібито виключена й інформацію
доводиться добувати шляхом «зворотного проектування»). З кожним методом класу
пов'язана спеціальна структура, одним з полів якої є вказівник на
машинний код (точніше, псевдоуказатель). Якщо він дорівнює нулю, то виконується
«Рідної» байт-код, розташований у хвості структури, у противному випадку
управління передається по псведоуказателю. Спочатку цей механізм замислювався
для виклику внутрішніх RTL-функцій, критичних до продуктивності, і для
компіляції в пам'ять JIТ-трансляторами. p>
Виходить,
що в Java спочатку була присутня діра в безпеці. Адже будь-який
зловмисник запросто може впровадити в байт-код справжній машинний код,
робить все що завгодно. Насправді в Sun зовсім не дурні сидять: перед
запуском Java-додатки середовище виконання ретельно перевіряє байт-код,
відкидаючи власні класи з ненульовим покажчиком. Динамічна
перевірка менш педантична, і, хоча безпосередня модифікація покажчика на
машинний код за допомогою методу putLong в більшості випадків відловлюється
середовищем виконання, байт-код, відкомпілювати в пам'ять, може безперешкодно
«Хачіть» покажчики на свій розсуд. І середовище виконання виявляється не в
стані відрізнити «чесну» модифікацію покажчика, виконану
JIТ-компілятором, від «нечесної». P>
Втім,
навіть не вдаючись до машинного коду з одними лише методами getLong/putLong,
можна істотно похитнути модель безпеки Java, довільним чином
модифікуючи внутрішні дані класів і міняючи типи змінних разом з
атрибутами класів (public, final і т. п.). Що дозволяє реалізувати той самий
«Нецензурний кастинг», що приводить до помилок переповнення (до умисним,
зрозуміло) і можливості віддаленого захоплення управління машиною з передачею управління
на shell-код (тільки для функцій, компільованих в пам'ять). Важливо зрозуміти,
що методи getLong/putLong є не функціями, що поставляються разом з
бібліотекою часу виконання, а командами JVM. Тобто заблокувати їх виклик
напряму не вийде, а якщо б і вийшло, багато штатні бібліотеки тут же
б відмовили в роботі. Уявити набір інструкцій виконавчої машини без
можливості низькорівневою роботи з окремими осередками пам'яті - не можна! А раз
так, у нас є всі підстави вважати, що інструкції getLong/putLong-це
надовго (якщо не назавжди) і тому варто придивитися до них уважніше. p>
Макрорівень h2>
Починаючи
з версії 1.0, в JVM з'явилася метафора «пісочниці» (sandbox) - ізольованою
середовища, в яку поміщаються потенційно небезпечні програми (наприклад,
Web-додатки, отримані з ненадійних вузлів). Песочница як би відрізана від
файлової системи і може спілкуватися тільки з тим вузлом, з якого було додано
дана Java-додаток. «Начебто» - бо не існує ні однієї реалізації
JVM, що відповідає цій вимозі не тільки на папері. Ряд атак на IE і FireFox
як раз заснований на можливості прориву за межі «пісочниці» і перезапису
локальних файлів. p>
Рішення
проблеми полягає у запуску IE/FireFox від імені користувача, якому
недоступні ніякі файли, крім тих, що потрібні для роботи браузера. Тим не
менше атакуючому як і раніше доступні cookies, кеш сторінок та інші дані,
витік яких вкрай небажана, а в деяких випадках неприпустима і спричиняє
до втрати контролю над своїми обліковими записами. Тому багато компаній відмовляються
від Java, забороняючи виконання Java-додатків в браузері. p>
Гірше
з Java-додатками, що знаходяться на локальному диску. Вони за замовчуванням
вважаються безпечними і їм доступні всі ресурси JMV, у тому числі файли, мережеві
з'єднання і т. д. Створення комп'ютерного вірусу на Java не тільки можливо, але
і не сильно відрізняється від створення вірусів, написаних на інших мовах
програмування (Сі, Паскалі, Асемблері). Сказане відноситься і до Web-сторіночкам,
збереженим на диск. При наступному відкритті вони вже вважаються «безпечними»
з усіма наслідками, що випливають звідси наслідками. Тобто для успішної атаки
зловмиснику досить заманити жертву на сторінку зі шкідливим
Java-додатком і мотивувати зберегти дані на диск для подальшого
запуску. Взагалі-то, при бажанні налаштування браузералег-ко змінити, але тоді
перестануть працювати і все дійсно безпечні програми, які потребують
доступ до файлів/мережевим з'єднанням. p>
Усвідомлюючи
ущербність запропонованої моделі безпеки, компанія Sun вже в JVM 1.1 ввела
підтримку електронного підпису, завдяки якій шкідливий код втратив все
шанси. Але знову тільки на папері, а в реальному житті ... Щоб не втрачати
сумісність з вже написаним програмним забезпеченням, перевірка цифрового
підпису за замовчуванням була або вимкнено, або при завантаженні непідписаного
Java-додатки видавала запит на підтвердження, на який більшість
користувачів відповідала ствердно. Java-скриптів цифровий підпис взагалі
ніяк не торкнулася, і при відкритті збереженої Web-сторінки з диска вони
як і раніше отримували всі права. p>
В
наступної версії віртуальної машини політика безпеки була переглянута і
істотно розширена. Поділ на «ненадійні» і «надійні» додатка зникло,
поступившись місцем прав доступу. Тепер програми могли звертатися тільки до
визначеного переліку ресурсів, що задається адміністратором системи, що само
по собі величезний прогрес, оскільки концепція «все або нічого» (тобто
«Пісочниця» або «жива» середа) виявилася вкрай негнучкій. Важко уявити
собі повноваге додаток, задовольняються «пісочницею». З іншого боку,
якщо делегувати всім Java-додатків права доступу до всіх ресурсів, то про
який «безпеки» може йти мова. p>
Введення
селективного контролю за ресурсами зажадало реалізації «контексту
виконання »- перевіряючи права доступу об'єкта до ресурсу, JVM змушена
аналізувати не тільки даний об'єкт, а й попередні елементи стека викликів,
надаючи доступ тоді і тільки тоді, коли за потрібне правом володіють всі
об'єкти в стеку (у термінології Sun це називається принципом мінімізації
привілеїв). Принцип мінімізації привілеїв, як легко побачити, набуває
протиріччя з принципом інкапсуляції. Об'єкт too, що спирається на об'єкт bar, в
«Правильних» ООП-мовами не знає про внутрішній устрій bar, і тому bar
може (при необхідності) користуватися ресурсами, недоступними для too.
Класичним прикладом тому є системний виклик операційної системи,
здійснюваний прикладною програмою. Об'єкт «файл», що має прямий доступ до
диску, надає іншим об'єктам набір методів для
створення/видалення/читання і запису файлів, гарантуючи, що ніякий інший об'єкт
не зруйнує дані на диску. Якщо ж дотримуватися принципу мінімізації привілеїв,
то прямий доступ до диска необхідно надати всім об'єктам, що абсурдно.
Іншими словами, якщо об'єктно має право викликати даний метод об'єкта bar з
заданими аргументами, то bar зобов'язаний обслужити виклик, інакше
довелося б враховувати можливий граф викликів об'єктів, що вимагає величезних
витрат пам'яті і процесорного часу. Механізм, реалізований у JVM, обходить
цю проблему шляхом створення привілейованого інтервалу, при виконанні
якого контекст (тобто попередні виклики об'єктів) ігнорується, в результаті
чого стає можливим поява програм, нарушающ?? х делеговані їм
права доступу (не важливо, свідомо чи ні). На це ще можна було б закрити
очі, якби не ваговитість реалізації та високі накладні витрати. Як
жінка не може бути «трохи» вагітної, так і система не буває
«Практично» безпечною. P>
Висновок h2>
Незважаючи
на недоліки, властиві Java, слід визнати, що подібного рівня
захищеності на сьогоднішній день не забезпечує ні одна з мов
програмування. Можна довго критикувати Java, але нових інструментів від цього
не додасться. Проте користувачам завжди слід пам'ятати, що вибір
програмного забезпечення повинен здійснюватися на підставі реальних даних про
їх надійності, а не тільки тому, що вони написані на Java. Програмістам ж
не слід забувати про те, що Java лише зменшує ймовірність появи
деяких помилок проектування, а не виключає їх. p>
Верифікатор h2>
Верифікатор
байт-коду - невід'ємна частина JVM, він перевіряє кожну виконувану інструкцію
віртуальної машини (у тому числі і додану динамічно) для запобігання
надходження завідомо некоректної інформації. Вважається, що Верифікатор
запобігає наступні операції: p>
--
підробка покажчиків, наприклад, одержання покажчика як результат виконання
арифметичної операції (хоча інструкції getLong/putLong дозволяють звертатися до
будь-якій комірці пам'яті і Верифікатор їм не перешкода); p>
--
порушення прав доступу до компонентів класів, зокрема, надання
змінної, забезпеченою описувачем final (інструкції gettong/putLong без праці
обходять це обмеження); p>
--
використання об'єктів в якому-небудь іншій якості, наприклад, застосування до
об'єкту методу іншого класу (інструкції getLong/putLong дозволяють обійти
систему контролю типів). Фактично, Верифікатор вирішує більш скромні завдання,
перешкоджаючи: p>
--
викликом методів об'єктів з неприпустимим набором параметрів; p>
--
викликом інструкцій JVM з неприпустимим набором параметрів; p>
--
некоректної операції з JVM-регістрами; p>
--
переповнення або вичерпання стека. Для досягнення максимальної
продуктивності Верифікатор здійснює ряд припущень, пом'якшуючи перевірку
«Мертвого» коду (коду, що на думку верифікатори ніколи не отримає
управління). А реалізація верифікатори в JIТ-компіляторах з динамічної
(виконуваної на кожному кроці) і зовсім вироджується в статичну (виконувану до
компіляції). Зокрема, перевірка кордонів масивів, віднімає багато часу,
опускається кожного разу, коли JIТ-компілятор вважає, що порушення доступу на
даній ділянці коду свідомо не відбувається. Порівнюючи реалізації JVM від Sun і
Microsoft, не можна не помітити, що реалізація Microsoft працює істотно
швидше, а реалізація Sun виконує набагато більше перевірок. А реалізація JVM
корпорації IBM забезпечує досить високу продуктивність без
істотного збитку для безпеки. p>
Список літератури h2>
IT
спец № 07 ЛИПЕНЬ 2007 p>