jdk8+spring原生场景任意写文件rce

  1. 1. jdk8+spring原生场景任意写文件rce
    1. 1.1. 写文件
    2. 1.2. 思路
    3. 1.3. 实践
    4. 1.4. 拓展

jdk8+spring原生场景任意写文件rce

逛了一下三梦师傅的博客看到的,写一篇笔记。

写文件

springboot把所有资源打包进jar,我们无法在运行时往classpath等目录写入内容。但是我们可以往系统的classpath目录写内容,即 JDK HOME

思路

jvm不会把JDK HOME下的jar文件一次性全部加载进来。配合任意写文件的漏洞,我们可以替换JDK HOME目录下的系统 jar 文件,再主动触发 jar 文件里的类初始化来达到执行任意代码的方法。

覆盖哪一个jar文件呢?

如果程序代码中没有使用Charset.forName("GBK")类似的代码,就不会加载到/jre/lib/charsets.jar

所以

覆盖charsets,jar即可,接下来要解决的是如何主动加载这个jar。

org.springframework.web.accept.HeaderContentNegotiationStrategy#resolveMediaTypes

1649144875511.png

spring都会尝试解析请求头中的ACCEPT,进入parseMediaTypes,经过一系列调用会来到MimeTypeUtils#parseMimeTypeInternal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
private static MimeType parseMimeTypeInternal(String mimeType) {
int index = mimeType.indexOf(';');
String fullType = (index >= 0 ? mimeType.substring(0, index) : mimeType).trim();
if (fullType.isEmpty()) {
throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
}

// java.net.HttpURLConnection returns a *; q=.2 Accept header
if (MimeType.WILDCARD_TYPE.equals(fullType)) {
fullType = "*/*";
}
int subIndex = fullType.indexOf('/');
if (subIndex == -1) {
throw new InvalidMimeTypeException(mimeType, "does not contain '/'");
}
if (subIndex == fullType.length() - 1) {
throw new InvalidMimeTypeException(mimeType, "does not contain subtype after '/'");
}
String type = fullType.substring(0, subIndex);
String subtype = fullType.substring(subIndex + 1);
if (MimeType.WILDCARD_TYPE.equals(type) && !MimeType.WILDCARD_TYPE.equals(subtype)) {
throw new InvalidMimeTypeException(mimeType, "wildcard type is legal only in '*/*' (all mime types)");
}

Map<String, String> parameters = null;
do {
int nextIndex = index + 1;
boolean quoted = false;
while (nextIndex < mimeType.length()) {
char ch = mimeType.charAt(nextIndex);
if (ch == ';') {
if (!quoted) {
break;
}
}
else if (ch == '"') {
quoted = !quoted;
}
nextIndex++;
}
String parameter = mimeType.substring(index + 1, nextIndex).trim();
if (parameter.length() > 0) {
if (parameters == null) {
parameters = new LinkedHashMap<>(4);
}
int eqIndex = parameter.indexOf('=');
if (eqIndex >= 0) {
String attribute = parameter.substring(0, eqIndex).trim();
String value = parameter.substring(eqIndex + 1).trim();
parameters.put(attribute, value);
}
}
index = nextIndex;
}
while (index < mimeType.length());

try {
return new MimeType(type, subtype, parameters);
}
catch (UnsupportedCharsetException ex) {
throw new InvalidMimeTypeException(mimeType, "unsupported charset '" + ex.getCharsetName() + "'");
}
catch (IllegalArgumentException ex) {
throw new InvalidMimeTypeException(mimeType, ex.getMessage());
}
}

关键是return new MimeType(type, subtype, parameters);,解析了ACCEPT后生成MimeType实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public MimeType(String type, String subtype, @Nullable Map<String, String> parameters) {
Assert.hasLength(type, "'type' must not be empty");
Assert.hasLength(subtype, "'subtype' must not be empty");
checkToken(type);
checkToken(subtype);
this.type = type.toLowerCase(Locale.ENGLISH);
this.subtype = subtype.toLowerCase(Locale.ENGLISH);
if (!CollectionUtils.isEmpty(parameters)) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ENGLISH);
parameters.forEach((parameter, value) -> {
checkParameters(parameter, value);
map.put(parameter, value);
});
this.parameters = Collections.unmodifiableMap(map);
}
else {
this.parameters = Collections.emptyMap();
}
}

进入checkParameters

1649145758822.png

看到了Charset.forName

举例来说:

1
Accept: text/plain, */*; q=0.01

会执行

1
Charset.forName("0.01")

实践

Landgrey大佬的项目地址

https://github.com/LandGrey/spring-boot-upload-file-lead-to-rce-tricks

也为我们准备好了jar文件,直接用就行

1649147979495.png

之后发包

1
Accept: text/html;charset=GBK

1649148071224.png

看到log文件创建就说明成功

拓展

https://threedr3am.github.io/2021/04/14/JDK8%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99%E5%9C%BA%E6%99%AF%E4%B8%8B%E7%9A%84SpringBoot%20RCE/

三梦师傅给了另外的一种思路,相比于覆盖charset.jar来说更稳定

1649148259925.png

是从Charset.forName的原理上来探究,最后找到了lookupViaProviders(charsetName)利用SPI机制加载类。