Может ли синглетон быть дырой в безопасности (Java) ?
Шо же получается по факту. Подключив свой класс к уже роботающей системе используя рефлексию можно наделать много плохих вещей особенно в том случае если можно получить инстанс какихто важних сучностей (которые кста часто в синглетоны пихают). А синглетон дает нам етот инстанс. Так что я меняю свое мнение. Слишком много минусов получается для етого паттерна. Жаль переголосовать нельзя.
ЗЫ. на выходных хочу поекспериментировать с ломанием синглетонов рефлексией. Может что интересного получится.
Все средства для того, чтобы "ломать синглотоны" в ней, конечно, есть. Проблема только в том, в каких ситуациях это будет разрешено исполняющей системой.
Итак, пусть есть простой класс-синглтон.
public class SimpleSingleton{
private static SimpleSingleton self = new SimpleSingleton();
private SimpleSingleton(){
}
public static SimpleSingleton getInstance(){
return self;
}
}
И есть код в другом классе, работающий с этими синглтонами.
Задача: - создать в этом коде два объекта класса-синглтона.
Простейший случай:
import java.lang.reflect.*;
public class SingletonTest {
public static void main(String[] args) {
try {
// Первый объект создается самым типичным способом;
SimpleSingleton firstCopyOfSingleton = SimpleSingleton.getInstance();
//----------------------------------------------------------------------
// Второй объект создается через рефлексию.
Class SingletonClass = SimpleSingleton.class;
//Получаем закрытый конструктор без параметров
Constructor<SimpleSingleton> constr = SingletonClass.getDeclaredConstructor();
/* Ключевой момент - отключается проверка доступа к конструктору
* (Иначе вызвать его не получиться, т.е. он private).
* В этом месте программа выдаст SecurityException, если менеджер безопасности
* для приложения установлен, и данное действие запрещено. *
*/
constr.setAccessible(true);
SimpleSingleton secondCopyOfSingleton = constr.newInstance();
System.out.println(firstCopyOfSingleton == secondCopyOfSingleton);
} catch (InstantiationException ex) {
ex.printStackTrace();
} catch (IllegalAccessException ex) {
ex.printStackTrace();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (InvocationTargetException ex) {
ex.printStackTrace();
} catch (NoSuchMethodException ex) {
ex.printStackTrace();
} catch (SecurityException ex) {
ex.printStackTrace();
}
}
}
Нетрудно убедиться, что программа выдаст на консоль - false.
Т.е. у нас получилось создать ДВА объекта-синглтона - получилось потому, что менеджер безопасности по умолчанию не установлен.
Прежде всего его нужно установить. Можно сделать это из программы, вызвав в main(...) -
Можно потребовать исполнение приложение с его использованием от JVM - запуская приложение с ключем -Djava.security.manager.
Дальше - нужно, чтобы операции с рефлексией (только потенциально опасные, конечно) запрещались диспетчером защиты. Для этого в файле jre_home/lib/security/java.policy в блок привилегий добавим -
grant {
...
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
...
}
Cнова запускаем код, приведенный выше - получаем следующее
java.security.AccessControlException: access denied (java.lang.reflect.ReflectPermission suppressAccessChecks)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:342)
at java.security.AccessController.checkPermission(AccessController.java:556)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:550)
[color=red]at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:125)
at reflectiontest.SingletonTest.main(SingletonTest.java:25) [/color]
[olorin@varjag ~]$
Это, конечно, очень краткое и неполное описание того, как разрешается/запрещается колдовство над классами. Любым поправкам буду рад, т.к. с этом аспектом Java на практике почти не сталкивался. Попозже, может, найду еще какие-нибудь тонкости.
* (Иначе вызвать его не получиться, т.е. он private).
* В этом месте программа выдаст SecurityException, если менеджер безопасности
* для приложения установлен, и данное действие запрещено. *
*/
constr.setAccessible(true);
не проканает изза етого менеджера безопасности (про него я еще не узнавал) то всеровно мы легко и просто получаем инстанс сущности и каверкаем ее через публичные set методы (если они конечно есть).
Что получается. Чтоб такого не случилось надо глобальный обект сделать локальной переменной функции main.
ЗЫ. После прочтения второго поста по поводу менеджера безопасности и запрета рефлексии.
А может она мне самому пригодится ? Можно отключить ее для чужого класса а себе оставить ?
Эээ, не понял? Если переменная
имеет открытые сетеры, мы конечно можем ее повредить - но это же не нормальная реализация синглтона. Можно обратиться не к конструктору, а к этому полю напрямую - ну так для этого все равно проверяются ReflectPermission.
А может она мне самому пригодится ? Можно отключить ее для чужого класса а себе оставить ?
Хорстманн пишет, что для привилегии ReflectPermission не задается, сущность, для которой проверяется привилегия - т.е. как я пока понял, менеджер защиты, работающий по файлам политики, через
может либо запретить, либо разрешить такое вообще. Для разграничния на уровне классов видимо, придется реализовывать свой диспетчер защиты, если такое вообще можно сделать. Как время будет (сессия ;)), проверю.
P.S. А как ты собрался это использовать? Тут же речь идет о работе в рамках одного приложения, ты что, работаешь с какими-то библиотеками (предоставляешь библиотеку), активно использующими рефлексию для потенциально запрещенных вещей? Поделись ;)
Вероятно, кстати, что такое используется в IDE. Но в их исходниках меня копаться пока не тянет ;)
Я не о том. Я не о создании нового обекта а о роботе с етим единственным синглетоном.
Припустим у синглетона есть приватное поле "важная_опция" и публичные методы get/set"важная_опция". Припустим злоумышленник не может создать нового инстанса, но он может получить существующий вызвав getInstance, после чего вызывать set"важная_опция". Без инстанса не получится вызвать нестатический метод set"важная_опция".
Если сделать не синглетоном, ему придется доставать инстанс из поля какогото другого обекта-держателя. Для етого нужен и инстанс держателя. И так далее вверх по структуре классов со звязями "содержит в себе". Посему я и говорю что самый верхний обект который содержит в себе другие логично делать не полем, а локальной переменной функции main. Вот такой у меня бред в голове.
Будем читать.
Припустим у синглетона есть приватное поле "важная_опция" и публичные методы get/set"важная_опция". Припустим злоумышленник не может создать нового инстанса, но он может получить существующий вызвав getInstance, после чего вызывать set"важная_опция".
Без инстанса не получится вызвать нестатический метод set"важная_опция".
Если сделать не синглетоном, ему придется доставать инстанс из поля какогото другого обекта-держателя. Для етого нужен и инстанс держателя. И так далее вверх по структуре классов со звязями "содержит в себе". Посему я и говорю что самый верхний обект который содержит в себе другие логично делать не полем, а локальной переменной функции main. Вот такой у меня бред в голове.
Погоди. Что за гипотетический злоумышленник-то? Откуда начинается его "атака" и какова ее цель? Опиши уж тогда.
И еще - это ты сейчас про работу именно в Java говоришь, или в общем?
В общем случае, кстати, приложение совершенно необязательно имеет какой-то главный класс и точку входа в него - main.
В общем случае, кстати, приложение совершенно необязательно имеет какой-то главный класс и точку входа в него - main.
Да говорю именно о ней. Возможно етот розговор лутше в твой роздел перетащить, хотя ето всетаки про синглетон.
Просто хочу сказать что синглетон может быть дырой в безопасности.
Виноват. Действительно думаю о своей задаче. Она для меня суто тренировочная. Хочу попрактиковаться перед тем как идти роботать с етим языком. Суть дела такова.
Шахматная доска. На ней можно играть разные игры. Есть класс ИГРА, класс (или интерфейс) ИГРОК. Я делаю класс ИГРА. Ктото реализирует интерфейс ИГРОК. Реализация подключается к примеру с помощю учазания пути к jar-файлу. Про биндинг я еще не читал (медленно учусь по своим причинам). Я вызываю его функцию "сделать_ход". Я не хочу анализировать его код на безопасность, но не хочу чтоб он мог сделать чтото плохое. В даном случае смохлевать в игре. Ну вот такой собственно случай. Реализации пока никакой. Только идея.
Какой это еще "мой" раздел? :)
Ну продолжу высказываться, если зарвемся и слишком далеко от синглтонов отойдем - пусть Green перенесет в Java.
Виноват. Действительно думаю о своей задаче. Она для меня суто тренировочная. Хочу попрактиковаться перед тем как идти роботать с етим языком. Суть дела такова.
Шахматная доска. На ней можно играть разные игры. Есть класс ИГРА, класс (или интерфейс) ИГРОК. Я делаю класс ИГРА. Ктото реализирует интерфейс ИГРОК. Реализация подключается к примеру с помощю учазания пути к jar-файлу. Про биндинг я еще не читал (медленно учусь по своим причинам). Я вызываю его функцию "сделать_ход". Я не хочу анализировать его код на безопасность, но не хочу чтоб он мог сделать чтото плохое. В даном случае смохлевать в игре. Ну вот такой собственно случай. Реализации пока никакой. Только идея.
Что ты подразумеваешь под "жульничеством"? Запуск кода стирания файла, например - это сюда входит?
Еще - какая у тебя связь ИГРЫ и ИГРОКА на уровне классов?
P.S. Что-то да, от синглтонов мы отдалились...
Security Restrictions
Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.
Exposure of Internals
Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.
То есть если ты имеешь синглтон или имеешь просто какой-то объект - ето не важно. Если ты изменишь какое-то его скрытое поле, то за ето будешь отвечать только ты. Таким образом я считаю что ни синглтон ни рефлекшен ето не дыры в безопасности - ето средства. Пользоваться ими или нет - ето уже ваша прерогатива :)
Неа. Хотя ето тоже былобы не плохо,, но я не знаю как такео запретить.
В даном случае да. Хотя я говорил. Задача тренировочная. Хочу сделать на уровне класов, потом через HTTP, ВебСервис. Может еще чтото попробую. Я не говорю что ето правильные решения но учится то на чемто надо.
Теперь про конкретный случай. Интерфейс ИГРОК. В нем метод который совершает ход. Ему передается состояние на игровом поле. Он возвращает ход. Сам обект игрока жывой на протяжении всей игры. Вызов хода игрока из метода класса ИГРА. Естественно реализацыю ИГРОКА делают неизвесные мне люди. Ето не новая идея и такие програмы уже есть. Загружается класс реализацыи игрока ... ну пускай пока рефлексией (я пока ни до чего умнее не дочитался но решения полутше наверно есть)
Что я думаю. Я не профи и сжульничать наверно ктото сможет. Ну всмысле какаято реализация игрока может обмануть мой класс ИГРА. Поетому играем игру с двумя игроками 2 раза. Один раз запрашиваем ходы у игроков и пишем их в лог. Ну например в БД. Потом проганяем игру еще раз по логу но уже без игроков и повторно проверяем соблюдение всех правил.
Ето хорошо, но хочется также какможно больше обезопасится от игроков, при етом как можно меньше им запрещая. Ну ето чисто для спортивного интереса.
public static void main(String[] args){
SystemRunable mainRunable = new SystemRunable();
mainRunable.run();
}
}
* делаю так как бы хотел чтоб было но пока с синглетонами).
* Фактически должен поддерживаться интерфейс IRunabe
* но для упрощения упустим */
public class SystemRunable {
/* Еще бы пригодились init/destroy но их тоже упустим
* */
public int run(Object... params){
System.out.println(Singleton.getInstance().getData());
try {
Class cl = Class.forName("Crack");
Object crack = cl.newInstance();
if (ICrack.class.isInstance(crack)){
((ICrack)crack).crack();
}
else {
System.out.println("ICrack not supported.");
return 2;
}
} catch (Exception e){
System.out.println(e.getMessage());
return 1;
}
System.out.println(Singleton.getInstance().getData());
return 0;
}
}
* */
public class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
/* Надо искаверкать ету строчку
* */
private String data;
private Singleton(){
this.data = "Data is GOOD :)";
}
public String getData(){
return this.data;
}
/* В большинстве случаев не удастса избежать методов
* меняющих состояние екземпляра
* */
public void setData(String data){
this.data = data;
}
}
* */
public interface ICrack {
void crack();
}
/* Реализация ламалки
* */
public class Crack implements ICrack{
public void crack(){
try {
Class cl = Class.forName("Singleton");
Method mGetInstance = cl.getDeclaredMethod("getInstance");
/* Вот то о чем я говорил.
* Можна достать инстанс синглетона и испортить его
* */
Object singleton = mGetInstance.invoke(null);
Method mSetData = cl.getDeclaredMethod("setData", String.class);
mSetData.invoke(singleton, "Data is BAD :(");
System.out.println(">:-[");
} catch (Exception e){
System.out.println(e.getMessage());
return;
}
}
}
Чтото типа такого. Второй екземпляр не делается но существующий калечится. И ни одного обращения к прайвету. Будете говорить "Ну дик метод set есть". А куда я без него. Что мне самому себе руки связывать :)
Привожу только изменения.
* Фактически должен поддерживаться интерфейс IRunabe
* но для упрощения упустим */
public class SystemRunable {
private Context context = new Context();
/* Еще бы пригодились init/destroy но их тоже упустим
* */
public int run(Object... params){
PsevdoSingleton pSingleton = context.getPsevdoSingleton();
System.out.println(pSingleton.getData());
try {
Class cl = Class.forName("Crack");
Object crack = cl.newInstance();
if (ICrack.class.isInstance(crack)){
((ICrack)crack).crack();
}
else {
System.out.println("ICrack not supported.");
return 2;
}
} catch (Exception e){
System.out.println(e.getMessage());
return 1;
}
System.out.println(pSingleton.getData());
return 0;
}
}
* Можно ли ето щитать фабрикой при final ?
* Можно ли ето щитать контекстом ?
* */
public class Context {
private final PsevdoSingleton pSingleton = new PsevdoSingleton();
public final PsevdoSingleton getPsevdoSingleton(){
return pSingleton;
}
}
* */
public class PsevdoSingleton {
/* Надо искаверкать ету строчку
* */
private String data;
public PsevdoSingleton(){
this.data = "Data is GOOD :)";
}
public String getData(){
return this.data;
}
/* В большинстве случаев не удастса избежать методов
* меняющих состояние екземпляра
* */
public void setData(String data){
this.data = data;
}
}
Если при таком подходе можно изменить поле синглетона то дыра наверно не в синглетоне а в рефлексии.
Мне кажется что твой пример синглтона не очень-то логичный. Представь ситуацию когда у тебя запущено несколько потоков? И им нужно всем использовать один бин сущности (синглтон). Как ты будешь им его надавать? Мне кажется что при таком раскладе тебе придётся предоставлять каждому потоку один и тот же контекст (доступ к твоему синглтону ты даешь только через контекст).
Вот мне интересно как ты будешь надавать всем потокам один контекст?!
Если же ты будешь делать разные контексты, то твой синглтон в контексте уже не будет синглтоном ;)
Вот мне интересно как ты будешь надавать всем потокам один контекст?!
Если же ты будешь делать разные контексты, то твой синглтон в контексте уже не будет синглтоном ;)
Ето проблемы контекста.
// Убрал final у поля.
private PsevdoSingleton pSingleton = new PsevdoSingleton();
public final PsevdoSingleton getPsevdoSingleton(){
return pSingleton;
}
public final void setPsevdoSingleton(........){
.........
}
}
Лутше конструктор сделать, передавать ему другой контекст и без setPsevdoSingleton обойтись. Но поскольку ты хотел доступ к прайветам то ето меня не спасет. Я тебя ограничевать не стану :)
ЗЫ. Ступил. Ето проблема не только Context, а еще и той сущности которая над SystemRunable стоять будет. Но ета проблема решается.
Как ето сделать я подумаю дома.
С точки зрения гарантии общей безопасности, можно заставить авторов реализаций подписывать jar-ники (если реализации игровой логики поставляются в них). Вот это может быть очень эффективно.
В даном случае да. Хотя я говорил. Задача тренировочная. Хочу сделать на уровне класов, потом через HTTP, ВебСервис. Может еще чтото попробую. Я не говорю что ето правильные решения но учится то на чемто надо.
Тут есть некоторая разница. Для десктопных Java-приложений менеджер безопасности по дефолту не ставится (а если его включить, то опять же по дефолту правомерность операций именно рефлексии не проверяется, как я показывал выше) - а вот в контейнарах сервлетов и серверах приложений, уверен, он есть - все таки это промышленная сфера программирования. И рефлексия там может быть ограничена.
Потом надо будет проверить тот код внутри какого-нибудь сервлета.
Что я думаю. Я не профи и сжульничать наверно ктото сможет. Ну всмысле какаято реализация игрока может обмануть мой класс ИГРА. Поетому играем игру с двумя игроками 2 раза. Один раз запрашиваем ходы у игроков и пишем их в лог. Ну например в БД. Потом проганяем игру еще раз по логу но уже без игроков и повторно проверяем соблюдение всех правил.
Если ход запрещен, но я с помощью рефлексии его делаю - он может и не записаться в лог (если, например, ты логируешь из метода, меняющего что-то в параметрах игры - то я могу это поменять напрямую, не вызвав метод, точно так же я могу обойти любых слушателей таких изменений и т.п.). И начиная с некоторого момента лог будет просто битый - т.е. ход следующего игрока может быть признан некорректным - по "официальным" данным, хотя по создавшейся "скрытым образом во время игры" ситуации - он допустим. Т.е. ты можешь сделать вывод, что игра, начиная с некоторого момента, "сдохла". Как предполагаешь отлавливашь такие ситуации?
biolash - Ты совершенно прав в своем первом посте - что сам по себе синглтон не является дырой в защите - т.е. является ей не больше, чем любой другой паттерн, полагающийся на сокрытие данных в ситуации, когда инкапсуляция нарушается "хирургическим методом".
А потому мы, видимо, разбиваем рассуждение на две ветки:
1. Особенности безопасности данных (в том числе разные проявления и последствия нарушения инкапсуляции) при рефлексии, и способы запрета таких нарушений. Можно попробовать взломать фабрику или какие другие паттерны, которые предложит Rebbit или еще кто-нибудь :).
2. Особенности работы синглтонов в многопоточных средах и обсуждения решения Rebbit'a в его проекте.
Какие будут мнения?
Тогда можно уже и разобрать и покомментировать код выше.
Ну и где ето я инкапсуляцию нарушил ? Я ж прайветов не трогал вовсе.
Или под "хирургическим методом" ты имееш ввиду просто рефлексию.
Про направление розговора: решения в моем проекте ето дело тридцатое. Проекта то нет да и если будет то мелочный. Я просто думаю, могу ли я сделать програму в которую пущу еще чей то код и не буду бояться что она изменит состояние моих обектов несанкционировано. Доступ к внешним ресурсам (файли, БД, железо меня в данный момент не интересуют. Только мои обекты). И смогу ли я с такой системой удобно роботать.
При етом я не хочу запрещать рефлексию как такую, но доступ к прайветам конечно запрещу. Если есть такая техническая возможность.
Обыкновенно, менеджер безопасности служит для того, чтобы программа из непроверенного источника не могла повредить машине пользователя. Соответственно, под это заточена модель безопасности. В твоей ситуации, получается, что ты хочешь обезопасить одну часть своей программы от другой. Соответственно, ты никак не можешь повлиять на политику исполнения Java-приложений, поскольку не имеешь прав на той машине (пользователя), где они будут исполняться. Думаю, кастомный менеджер безопасности единственно тебе поможет - и еще, уместны будут проверки всевозможные этих самых источников кода (подписание jar-ников, X509 сертификаты, еще что-то, быть может). Подробней трудно сказать мне.
Ну такое на мишине клиента я делать не собираюсь. Только на моей машине. На моем сервере.
Собственно я только что для себя сформулировал что меня интересует.
Смогу ли я сделать то что сказал в теме про синглетоныGreen ? Смогу ли я сделать так чтоб небыло глобальных данных вовсе? Только точка входа. Смогу ли я так роботать ? Защитит ли ето мои обекты от рефлексии ?
Во втором моем примере я не могу повредить своих обектов потомучто не могу до них добраться. Не знаю как получить инстанс. Могу создать новые, но зачем. Наверно могу визвать main повторно, не пробовал. Тоже хочу от етого зачититься.
Почему я толком не могу сказать что буду делать с многопоточностю. Потому что не могу сунуть контекст или другой обект содержащий в себе контекст в поле потока. Его оттуда достать легко если не сделать поле потока приватным и не защитить его менеджером безопасности. :)
А я biolashу обещал доступ к прайветам. (Я с ним вчера по аське поспорил что он не поломает).
Собственно я только что для себя сформулировал что меня интересует.
Смогу ли я сделать то что сказал в теме про синглетоныGreen ? Смогу ли я сделать так чтоб небыло глобальных данных вовсе? Только точка входа. Смогу ли я так роботать ? Защитит ли ето мои обекты от рефлексии ?
Да я тебе говорю, можно сделать простейшее веб-приложения, у которого вообще не будет метода main. Один сервлет, отображенный на какой-то URI. Выдающий что-то по запросу. Не вижу связи пока между точками входа и безопасностью доступа к синглтону. Пример приведи тогда уж.
Во втором моем примере я не могу повредить своих обектов потомучто не могу до них добраться. Не знаю как получить инстанс. Могу создать новые, но зачем. Наверно могу визвать main повторно, не пробовал. Тоже хочу от етого зачититься.
Это пример с фабрикой? А почему нельзя их повредить-то? По крайней мере когда ты убрал финал у поля, хранящего инстанс у контекста. Что мешает загрузить класс этого контекста, и из него вытащить инстанс? В точности так же, как это делается для обычной реализации синглтона.
Почему я толком не могу сказать что буду делать с многопоточностю. Потому что не могу сунуть контекст или другой обект содержащий в себе контекст в поле потока. Его оттуда достать легко если не сделать поле потока приватным и не защитить его менеджером безопасности. :)
А я biolashу обещал доступ к прайветам. (Я с ним вчера по аське поспорил что он не поломает).
Что именно сломать надо? :) Может, я тоже присоединюсь. Заодно вдруг, найдем дыру в JVM, чем Гослинг не шутит.
Про точку входа ето я к слову. Просто хочу без глобальных переменных. Чтоб никто к моим обектам не добрался ;)
Ето в моих постах.
№12 я елементарно поломал синглетон. Просто и некрасиво.
№13 Изменения к псту №12. Переписал без синглетона. Нет синглетона и нет глобальных даных. Как достать инстанс из контекста ?
Инстанс синглетона можно достать с самого класса
[quote=Rebbit]
........................
Object singleton = mGetInstance.invoke(null);
[/quote]
Етот null там не потому что параметров нет у гетИнстанс, а потому что метод статический. А метод Контекста гетПсевдоСинглтон не статический.
В Псевдосинглетоне строчку поменять. Буду рад если поломаете.
Я о JNI не знаю. Надо почитать. Но если есть такая возможность то впринцыпе можно тогда получить адресс объекта Class и сканировать кучу для поиска екземпляра етого класа. Полагаю что обект должен иметь ссылку на свой класс для поддержки виртуальных методов. Во всяком случае в .NET так и есть. Как в Java не знаю.
Я не собираюсь вызывать чужой код из своего в любом более-менне серезном проекте. Ето плохо и опасно. Но я хочу уметь делать такое. Ты можеш сказать что ето безполезная трата времени :). Но такой уж я есть. Хочу уметь и все.
Dynamic Linkage. Загрузка по запросу и выполнение байткода класса, реализующего известный клиенту интерфейс. Т.е. как раз твой случай :).
Так вот, в области безопасности он примерно следующее говорит.
Если вероятность неправильного хода событий мала, и возможный ущерб незначителен - то не следует принимать спец. мер по обеспечению безопасности подобных решений. (Думается, это твой случай, с точки зрения практики)
В противном случае, при необходимости дополнительных мер по безопасности, общая стратегия основана на определении уровня доверия к поставщику, которая использует стандартные механизмы безопасности платформы Java - цифровые подписи поставщика на jar-файле, например.