Безопасность реализации на языке Java

Безопасность реализации на языке JavaВ предыдущей статье мы говорили о безопасном проектировании на языке Java, а в этой статье продолжим разговор, но уже о безопасной реализации. Одно дело — создать язык, который не даст вам навредить самому себе; другое — создать язык, благодаря которому другие не будут приносить вред вам.

Инкапсуляция данных — это принцип сокрытия данных и поведения их внутри класса; это важная часть объектно-ориентированного проектирования. Она помогает вам писать чистое, модульное программное обеспечение. Во многих языках, однако, видимость элементов данных — просто часть связи между программистом и компилятором. Это вопрос семантики, а не утверждение о действительной безопасности данных в контексте выполнения программной оболочки.

Когда Бьёрн Страуструп выбрал ключевое слово private для обозначения скрытых классов в С++, скорее всего, он думал о защите разработчика от беспорядочных деталей кода другого разработчика, а не о проблемах защиты классов и объектов этого разработчика от атак чьих-то вирусов и троянских коней. Произвольный подсчет и арифметика указателей в языке С или С++ делают обычным делом нарушение доступов к классам без нарушения правил языка. Посмотрите на следующий код:

  1. // код С++
  2. class Finances {
  3. private:
  4. char
  5. creditCardNurnber[16] ;
  6. . . .
  7. } ;
  8. main ( ) {
  9. Finances finances;
  10. // изобретение указателя, чтобы заглянуть в класс
  11. char *cardno = (char * ) &finances;
  12. printf (”Card Nurnber = ‰ . 16s\n», cardno) ;

В этом фрагменте на языке С++ мы написали некий код, который нарушает инкапсуляцию класса Finances и вытягивает некую секретную информацию. Такой тип махинаций — злоупотребление нетипизированным указателем — невозможен в языке Java. Если этот пример кажется нереалистичным, обратите внимание на то, как он важен для защиты базовых (системных) классов выполняемой программы от подобных видов атак. Если ненадежный код может повредить компоненты, которые предоставляют доступ к реальным ресурсам, таким как файловая система, сеть или система управления окнами, у него действительно есть все шансы украсть номера вашей кредитной карточки.

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

Модель безопасности Java
Модель безопасности Java

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

Верификатор байт-кода Java является фиксированной частью системы выполнения Java. Загрузчики классов и диспетчеры безопасности (или, если быть более точными, политика безопасности), однако, являются компонентами, которые могут выполняться по-разному в разных приложениях, таких как серверы или веб-браузеры. Все эти три компонента должны функционировать должным образом, чтобы обеспечивать безопасность среды Java.

Верификатор

Первая линия защиты языка Java — байт-кодовый верификатор. Верификатор считывает байт-код перед его запуском и убеждается, что он работает хорошо и подчиняется общим правилам языка Jаvа. Надежный компилятор Java не производит код, который ведет себя по-другому. Однако какой-то злой человек может преднамеренно собрать плохой байт-код Java. Работа верификатора заключается в том, чтобы обнаружить это.

После того как код был проверен, он считается застрахованным от невнимательности или злоумышленных ошибок. Например, проверенный код не может пропустить ссылки или нарушить разрешения к доступу к объекту. Он не может выполнять нелегальных приведений типов или использовать объект не предусмотренными путями. Он даже не может вызвать определенные типы внутренних ошибок, такие как переполнение или опустошение внутреннего стека. Эти фундаментальные гарантии лежат в основе безопасности Java.

Вы, возможно, поинтересуетесь, не подразумевается ли этот тип безопасности в большинстве интерпретируемых языков? Что ж, хотя и правда вряд ли сможете разрушить интерпретатор Basic поддельной строчкой кода Basic, помните, что защита во многих интерпретируемых языках происходит на высоком уровне. Вероятно, эти языки имеют тяжеловесные интерпретаторы, которые выполняют большую часть работы, поэтому они непременно более медленные и громоздкие. В то же время байт-код Java является относительно простым набором инструкций низкого уровня. Способность статически проверять байт-код Java перед выполнением позволяет интерпретатору Java работать на полной скорости с полноq безопасностью после, без дорогих проверок времени выполнения. Это было одним из базовых нововведений Java.

Верификатор — это тип математического «доказывания теорем». Он проходит через байт-код Java и применяет простые, индуктивные правила для выявления определенных аспектов того, как себя поведет байт-код. Такой тип анализа возможен, поскольку компилируемый байт-код Java содержит намного больше информации о типе, чем объектный код другого языка этого типа. Байт-код также должен следовать нескольким дополнительным правилам, которые упрощают его поведение. Во-первых, большинство инструкций байт-кода работает только с индивидуальными типами данных. Например, в стековых операциях есть отдельные инструкции для ссылок объекта и для каждого из числовых типов в Java. Проще говоря, для перемещения каждого типа значения в локальную переменную и из нее инструкции.

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

Поскольку операция всегда производит известный тип, можно определить типы всех элементов стека и локальные переменные в любой момент в будущем, посмотрев на исходное состояние. Коллекция всей этой информации типов в любое предоставленное время называется состоянием типов стека; это то, что Java старается проанализировать перед запуском приложения. Язык Java ничего не знает о действительных значениях стека и элементах переменных в данное время; он только знает, что это за элементы. Однако этой информации достаточно, чтобы задействовать правила безопасности и удостовериться в том, что объекты не управляются нелегально.

Чтобы сделать реальным анализ типа состояний стека, Java налагает дополнительное ограничение на то, как выполняются инструкции байт-кода Java: все пути к одной точке в коде должны достигаться одним и тем же состоянием типа.

Загрузчики классов

Язык Java добавляет второй уровень защиты при помощи загрузчиков классов. Загрузчик класса ответственен за доставку байт-кода для классов Java в интерпретатор. Каждое приложение, которое загружает классы из сети, должно использовать загрузчик класса, чтобы справиться с этой задачей.

После того как класс загружен и прошел через верификатор, он остается связанным со своим загрузчиком классов. В результате классы эффективно секционируются в отдельные пространства имен, основанные на их происхождении. Когда загрузчик класса ссылается на другое имя класса, местоположение нового класса предоставляется исходным загрузчиком класса. Это означает, что классы, полученные из специфического источника, ограничены до взаимодействия только с другими классами, полученными из того же места. Например, веб-браузер с запущенным приложением Java может использовать загрузчик классов, чтобы создать отдельное пространство для всех классов, загруженных с определенного URL-адреса (единого указателя ресурсов). Сложная система безопасности, основанная на криптографически обозначенных классах, также может быть применена с использованием загрузчиков классов.

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

Диспетчеры безопасности

Диспетчер безопасности отвечает за принятие решений по безопасности на уровне приложения. Диспетчер безопасности является объектом, который может быть установлен приложением для ограничения доступа к ресурсам системы. К диспетчеру безопасности обращаются каждый раз, когда приложение пытается получить доступ к таким элементам, как файловые системы, сетевые порты, внешние процессы и система управления окнами; диспетчер безопасности может разрешить и запретить запрос.

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

Диспетчер безопасности работает совместно с инспектором доступа, что позволяет вам применять политику безопасности на высоком уровне редактированием декларативного файла политики безопасности. Политика доступа может быть простой или сложной как специфический гарант безопасности приложения. Иногда достаточно просто запретить доступ ко всем ресурсам или общим категориям услуг, таким как файловая система Java или сеть. Но также возможно принимать сложные решения, основанные на информации высокого уровня. Например, веб-браузеры с запущенным приложением Java могут использовать политику доступа, которая позволяет пользователям определять, насколько можно доверять приложению или которая разрешает или запрещает доступ к специфическим ресурсам от случая к случаю. Конечно, это предполагает, что браузер может определить, какому приложению можно доверять. Скоро мы обсудим, как эта проблема решается через подпись кода.

Целостность диспетчера безопасности основана на защите, предоставленной на низких уровнях модели безопасности Java. Без гарантий, предоставленных верификатором и загрузчиком классов, утверждения о высоком уровне безопасности ресурсов системы являются бессмысленными. Безопасность, обеспеченная верификатором байт-кода Java, означает, что интерпретатор не может быть поврежден или разрушен и что java-код должен использовать компоненты по назначению. Это в свою очередь означает, что загрузчик классов может гарантировать, что приложение использует системные классы ядра Java и эти классы являются единственным способом доступа к базовым системным ресурсам. С такими ограничениями можно централизовать контроль над этими ресурсами на высоком уровне, с диспетчером безопасности и определяемой пользователем политикой. В следующей статье мы поговорим о приложении и безопасности на уровне пользователя.

С Уважением, МониторБанк

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *