Безопасность в Java

Рубрика: Java | 18 August 2008, 10:48 | juriy

Безопасность в Java это один из “козырей” платформы. Контроль за исполняемым кодом с точки зрения доступа к ресурсам и действиям это одновременно высоко востребованная и достаточно сложная функциональность.

До последнего времени я не интересовался безопасностью. Я знал, в общих чертах, что могут а чего не могут апплет и мидлет, знал о том что есть “песочница” и что выходить за нее не стоит. И этого вполне хватало. Исследовать вопросы безопасности ближе меня побудило обсуждение моей заметки “Создание Pluggable решений на Java”. Ведь когда вы сами предоставляете среду для исполнения плагинов вы не можете игнорировать вопросы безопасности (конечно, кроме случаев, когда вы – единственный автор плагинов к своей-же системе). Последней каплей стала наглядная демонстрация того, как при помощи интроспекции плагин может получить доступ к объектам ядра приложения и выполнить с ними любое действие.


Как и все мои заметки, эта ни в коем случае не притендует на полноту. Это скорее небольшое введение в куда более обширную тему, приправленное несколькими практическими примерами.

Введение.
Как известно, в виртуальной машине класс определяется своим полным именем и экземпляром ClassLoader’а которым он был загружен. Так, для VM классы с абсолютно одинаковыми именами, загруженные разными ClassLoader’ами будут разными: к примеру, попытка привести один из объект такого класса к другому обречена на провал.

Перед тем, как обсуждать безопасность необходимо рассмотреть несколько базовых понятий из этой области.

В модели безопасности Java с любым классом кроме ClassLoader’а связана еще одна сущность: code base. Попросту – местро откуда класс был загружен (URL ресурса или ссылка на локальный ресурс). Кроме того, у класса может быть сертификат – “честное слово” какой-нибудь организации или человека, что код класса будет вести себя хорошо. Code location и сертификаты (если есть) вместе образуют code source.

Давайте посмотрим на code source какого-нибудь класса. Для этого достаточно выполнить вот такой-вот фрагмент кода:

	System.out.println(Test.class.getProtectionDomain().getCodeSource());

[/java]

В моем случае я вижу такие строки:

(file:/C:/apps/eclipse/workspace/Test/bin/ )

Permission – (разрешение, право) это класс, который идентифицирует доступность какого-нибудь действия. К примеру FilePermission – контролирует доступность операций над частью файловой системы. Вот простой пример:

	Permission p = new FilePermission("temp/*", "read");

Созданный объект – разрешение на чтение (но не на запись) любых файлов в дериктории temp.

Как видно, permission состоит из двух частей – имени (в FilePermission имя – это путь к папке) и списка действий.

Code Source и коллекция разрешений – образуют домен безопасности (protection domain). С каждым классом ассоциируется домен безопасности (не каждый класс, правда, сможет его посмотреть).

Последний из ключевых классов модели – SecurityManager. Его задача – давать окончательный ответ на вопрос “а стоит ли разрешить действие”, которое пытается выполнить класс.

Типичный пример использования SecurityManager’а выглядит так:

SecurityManager security = System.getSecurityManager();
if (security != null)
	security.checkPermission(permToCheck);

Метод checkPermission отбросит SecurityException, если операция запрещена.

Есть несколько нюансов, которые необходимо понимать при использовании SecurityManager’а. Самый основной: безопасность проверяется в некотором контексте. Попробую объяснить простыми словами: SecurityManager проверяет не просто доступность действия, он исследует все классы из текущего стека на предмет соответствия политике безопасности. Если хотя-бы один из классов не имеет достаточных прав – будет выброшен SecurityException.

Для тех кто любит вникать в детали: SecurityManager, естественно, не занимается проверкой безопасности единолично. Для проверки всего контекста используется AccessController, который в свою очередь передает работу AccessControllerContext. AccessControllerContext инкапсулирует контекст вызова (стек) и именно он проводит окончательную проверку:

	for (int i=0; i< context.length; i++) {
    if (context[i] != null &&  !context[i].implies(perm)) {
	...
	throw new AccessControlException("access denied "+perm, perm);
    }
}

Последнее о чем нужно знать на этом этапе – понятие “политики безопасности”. Политика безопасности сопостовляет Code Source’ы (что за код выполяется) Principal’ы (кто выполняет код) c наборами разрешений. В Java политика реализуется при помощи классов наследников java.security.Policy (сам класс – абстрактный). В JDK есть только один наследник Policy: PolicyFile. Как и следует из названия PolicyFile в качестве источника информации о правах использует файл. Файл с описанием политики по умолчанию находится в JRE_HOME/lib/security/java.policy.
Описание формата файла выходит за рамки этой заметки. Его легко можно найти в сети.

В самом простом варианте, файл состоит из блоков grant, в которых описаны разрешения в формате
permission <Класс Permission> “<имя Permission’a>”, “<значение>”

К примеру:

grant {
	// allows anyone to listen on un-privileged ports
	permission java.net.SocketPermission "localhost:1024-", "listen";
};

Такой блок позволяет “слушать” порты от 1024-го и выше. Соответственно, порт 125 “услышать” не выйдет.

Теперь, когда основные понятия благополучно введены, самое время немного позабавиться “практикой”.

Нарушая правила.
Первое, чем мы займемся – попробуем выполнить действие, которое должно быть “запрещено”, и посмотрим, что будет. Итак, поскольку код выше я скопировал прямо из java.policy, Security Maneger не должен позволить мне открыть порт 125.

Что-ж попробуем выполнить такой код:

try {
	new ServerSocket(124);
} catch (IOException e) {
	System.out.println("Could not listen on port: 124");
	System.exit(-1);
}
System.out.println("Listening OK.");

Код выполняется нормально. Как такое может быть? Ведь в файле с описанием политики явно сказано “можно слушать от 1024-го порта и выше”. Нюанс, который в свое время убил мне пару десятков нервных клеток: по умолчанию, в Java SE не установлен Securitiy Manager. Соответственно, вообще никакие проверки не производятся. “Включить” Security Manager можно двумя способами.

Способ первый:
Исполнить

System.setSecurityManager(new SecurityManager());

Способ второй:
Запустить программу таким вот образом:

java -Djava.security.manager -Djava.security.policy==someURL SomeApp

(второй параметр можно опустить, он указывает расположение Policy файла).

Когда SecurityManager установлен, код не выполняется: SecurityException подробно объясняет нам, почему именно:

Exception in thread "main" java.security.AccessControlException: access denied
 (java.net.SocketPermission localhost:124 listen,resolve)

Это означает, что SecurityManager на месте и исполняет свои обязанности.

Вот и все, что я хотел рассказать в этой заметке. В следующей заметке я постараюсь “починить” дыру в безопасности Pluggable приложения, и покажу каким образом можно реализовать свой собсвенный Permission.

Напоследок, пару ссылок по теме:

http://java.sun.com/j2se/1.5.0/docs/guide/security/index.html
http://java.sun.com/j2se/1.5.0/docs/guide/security/PolicyFiles.html
http://www.onjava.com/pub/a/onjava/2007/01/03/discovering-java-security-requirements.html

Комментариев: 13

13 Responses to “Безопасность в Java”

Комментарии:

  1. Iggi

    Спасибо, интересно. Только стоит прогнать текст через спеллчекер – “безопасность” пишется без “т”.

  2. Alexander

    очень интересная заметка, спасибо! :)

  3. juriy

    2 Iggi: спасибо, что обратили внимание. Действительно, не прав. Исправлено.

  4. Степан

    Это очень круто, но практически этим почти никто не пользуется. Мало кому это надо, наверное.

  5. Vadim Voituk

    Степан,

    Во-первых, количество задач и областей в которых применяется Java, настолько велико, что говорить “это нигде не юзается и нафиг кому надо” как минимум недальновидно.
    Для примера то же самое я слышал и Groovy/Grails/Scala – а ведь даже вы вроде как Scala и Groovy используете.

    Во-вторых, статья очень доходчиво обьясняет ЧТО и КАК происходит внутри JVM, и понятна даже тем, кто в недри Java никогда не углублялся. Как минимум для общего образования – mustread.

  6. Farcaller

    Долго думал, каким образом можно слушать 124-й порт в любом случае, если low-id порты только для root’а. Потом понял, что пример видимо запускался на виндовой машинке :)

  7. juriy

    Ну да. Чтобы пример был немного более жизненным, можно 124 заменить на 80 и попробовать со включенным Security Manager’ом запустить безобидный Jetty.

  8. Chabster

    > Кроме того, у класса может быть сертификат – “честное слово” какой-нибудь организации или человека, что код класса будет вести себя хорошо.

    Это в корне неверно. Сертификат лишь подтверждает, что код принадлежит организации или человеку. И позволяет дополнительно дать такому коду набор разрешений. Т.е. “атрибутами” кода, которые влияют на набор разрешений, являюся
    1) code base
    2) сертификат

    Почему не упомянул о doPrivileged? AllPermission? JAAS?

    Есть один важный нюанс всей этой схемы безопасности жабы – большая часть кода реализована в “пользовательском” режиме. Поэтому, можно смело приготовиться к наследованию от SecurityManager, переопределению checkPermission, блужданию по контексту (стеку) для реализации своих Permission. Кроме того, это означает некоторые “тормоза” при проверках. А учитывая тяготение к очень глубоким стек трейсам… ну, вы поняли.

    Недостатки:
    1) тормоза
    2) необходимость извращаться с реализацией checkPermission (т.е. логика проверки Permission перемещается из наследника Permission в SecurityManager – какого?)
    3) отсутствие запрещающих Permission (есть только разрещающие)
    4) отсутствие декларативной модели

    Последнее. Специально для юниксоидов. Вы, почему-то, все дружно руководствуетесь лозунгом “винда – говно”, хотя в вопросе ничерта не понимаете. Я могу на контроллере домена задать политику резервирования порта и одним щелчком мышки распостранить его на все тазики в сети. При этом правило 1024 портов давно признано устаревшим и будет убрано.

  9. juriy

    >> Сертификат лишь подтверждает, что код принадлежит организации или человеку. И позволяет дополнительно дать такому коду набор разрешений

    Формально, совершенно верно. Фактически, вы “позволяете” исполнять только те фрагменты кода, которые пренадлежат компаниям, которым вы доверяете. К примеру, я смело исполню апплет со “всеми привелегиями” от Sun, но засомневаюсь о целесообразности исполнять апплет от “Vasya Inc”. Но и Sun и Vasya своими сертификатами утверждают “мой код не удалит тебе все текстовые файлы на диске С, да и скриншот рабочего стола или содержисое папки TopSecret по сети я не буду передавать, хотя могу”

    >> Почему не упомянул о doPrivileged? AllPermission? JAAS?
    Чтобы кратко подать основные понятия из темы и подготовить себе почву для второй заметки.

    >> Есть один важный нюанс всей этой схемы безопасности жабы – большая часть кода реализована в “пользовательском” режиме. Поэтому, можно смело приготовиться к наследованию от SecurityManager, переопределению checkPermission, блужданию по контексту (стеку) для реализации своих Permission.

    Можно подробнее? Или ссылку на материал.

  10. Chabster

    > Но и Sun и Vasya своими сертификатами утверждают “мой код не удалит тебе все текстовые файлы на диске С, да и скриншот рабочего стола или содержисое папки TopSecret по сети я не буду передавать, хотя могу”
    Опять неверно, или ты неправильно выразился, или я неправильно понял.

    Сертификат ничего не утверждает о коде (о том, что он сделает или не сделает), просто позволяет ссылаться на код не по codebase, а по имени сертификата в хранилище сертификатов. Если ты доверяешь корпорации Sun и веришь, что их код не удалит файлы на диске С – даешь такому коду все разрешения. С другой стороны, зачем тогда давать разрешения на вещи, которых код не сделает по твоему мнению?

    Гораздо полезнее (и в этом состоит безопрасность кода жабы – только разрешающая модель) давать разрешение на вещи, которые код сделает и ты на это согласен. Все остальное по умолчанию запрещено.

    Подробнее про реализацию своих Permission? Подумай минут пять над тем, как запретить чтение текстового файла, если в нем сожержиться слово, например, .NET или C#. :)

  11. rety

    2 Chabster.
    Мда.. Ты прежде чем писать сам разберись в своих мыслях, а то мало того что не понимаешь о чем речь, так и остальных своими бессмысленными комментариями путаешь.

  12. gb

    А будет ли продолжение статей про безопастность. Хотелось бы узнать больше о permissions, credentials, principals и т.д. и т.п. Когда нужно создавать свои экземпляры объектов, как их потом использовать, для чего их использовать. Еще хотелось бы про poliсy файлы, что-там к чему и за что отвечает. Было бы идеально, если бы все это на примере небольшого веб приложения.

  13. Almaz

    Посоветуйте книги на русском(или на английском на худой конец) про безопасноть в Java