Apache Flink CVE-2020-17518/17519 读写反序列化
又是URL编码的问题
# CVE-2020-17519 任意读文件
1 分析
原理在于两次url解码
org.apache.flink.runtime.rest.handler.router.RouterHandler#channelRead0
RouterHandler类是路由核心类,用于处理路由的整体交互走向。QueryStringDecoder类是自实现的解码类,在qsd.path()
中首次进行url解码。
decodeComponent()进行解码,大致逻辑就是截取%
之后的内容进行解码
解码之后为/jobmanager/logs/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd
在request解析完method、path和参数之后,进行this.router.route()
。
其中this.router存放了所有的路由,通过http method进行键值对匹配。意思就是GET请求对应什么路由,全拿出来。
在this.router.route()
中router是取出来所有的GET请求的路由
decodePathTokens()以/
进行路径分割,并进行了第二次url解码
拿到tokens之后进行router.route(path, path, queryParameters, tokens)
在pattern.match(pathTokens, pathParams)
这个方法中是通过已知的GET method的路由遍历进行正则匹配(一句话就是匹配路由)。
其中pattern的值是jobmanager/logs/:filename
,其中:filename
是变量值。
而我们的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取出
两次url编码之后../
目录穿越,造成任意文件读取。
2 修复
简单粗暴,通过getName()获取文件名
# CVE-2020-17518 任意文件上传
所有的url请求都会经过Handler进行处理,路由随意即可触发。
文件上传位于org.apache.flink.runtime.rest.FileUploadHandler#channelRead0
中。
其中fileUpload和filename均可控,造成跨目录
# RCE
1 自身功能rce
这个鬼东西本身无鉴权,并且可以通过上传jar包执行,传个jar包上去runtime.exec就行。
|
|
2 反序列化RCE
还有一种值得学习的反序列化RCE的方式。
org.apache.flink.runtime.rest.handler.job.JobSubmitHandler#loadJobGraph
直接将上传文件进行反序列化。当以post方式访问到/v1/jobs
时,会路由到此。
根据官方文档本地构造请求包
|
|
其中request值为json数据,指定从上传数据包中取得反序列化的对象
|
|
构造请求这个地方卡了我半天,自己真是个傻逼,不知道看文档。
然后再看org.apache.flink.api.common.state.StateDescriptor#readObject
其自实现了反序列化流程。
继承TypeSerializer有很多实现,其中org.apache.flink.api.java.typeutils.runtime.PojoSerializer#deserialize(org.apache.flink.core.memory.DataInputView)
存在Class.forname第二个参数为true,可以静态代码块执行。
那么转变思路即为先上传恶意class到classpath中,然后通过反序列化触发static代码块的rce。
classpath如图
将编译好的Exec.class上传到bin或者lib目录下,反序列化触发就行了。
|
|
构造poc的时候在org.apache.flink.api.common.state.StateDescriptor#readObject
中首先要绕过hasDefaultValue。我用的是ValueStateDescriptor类来初始化赋值。
直接贴poc吧。
|
|
将a.ser的内容放到http请求的file_0字段
收到curl请求
实战中注意的是Class.forname()
一个类只能被加载一次,第二次rce的时候需要更换Exec的类名。
# 参考
- https://ci.apache.org/projects/flink/flink-docs-stable/ops/rest_api.html
- https://www.anquanke.com/post/id/227668
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
如果你觉得这篇文章对你有所帮助,欢迎赞赏或关注微信公众号~