Spring Boot + Jetty:阮严灵丰丰甲来/会被识别成../

1
2
3
4
获取类的⽅方法: forName
实例例化类对象的⽅方法: newInstance
获取函数的⽅方法: getMethod
执⾏行行函数的⽅方法: invoke

static会在加载类的时候自动调用,可以把我们的恶意类加载到目标机器执行命令

1
2
3
4
5
6
7
8
9
10
11
12
public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/success"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}

newInstance

1
2
3
4
class.newInstance() 的作用就是调用这个类的无参构造函数
如果不成功有两个原因:
1.使用的类没有无参钩造函数
2.使用的类构造函数是私有的

java序列化数据识别

十六进制通常以 aced 开头(ACED)

base64后通常开头为:rO0AB

方法调用

我们正常执行方法是 [1].method([2], [3], [4]…) ,其实在反射里就是method.invoke([1], [2], [3], [4]…) 。下面两种等价,第一个用了强制类型转换,第二个没有

1
2
3
4
5
6
7
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();


Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(
Arrays.asList("calc.exe")));

可变长参数

对于可变长参数,Java其实在编译的时候会编译成一个数组,也就是说,如下这两种写法在底层是等价
的(也就不能重载):

1
2
public void hello(String[] names) {}
public void hello(String...names) {}

RMI

RMI Registry就像⼀一个⽹网关,他⾃己是不会执行远程⽅法的,但RMI Server可以在上面注册一个Name到对象的绑定关系;RMI Client通过Name向RMI Registry查询,得到这个绑定关系,然后再连接RMIServer;最后,远程方法实际上在RMI Server上调用。

server

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
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer {
public interface IRemoteHelloWorld extends Remote {

public String hello() throws RemoteException;
}

public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
protected RemoteHelloWorld() throws RemoteException {
super();
}

public String hello() throws RemoteException {
System.out.println("call from");
return "Hello world";
}
}

private void start() throws Exception

{
RemoteHelloWorld h = new RemoteHelloWorld();
LocateRegistry.createRegistry(1099);
Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
}

public static void main(String[] args) throws Exception {
new RMIServer().start();
}
}

Client

1
2
3
4
5
6
7
8
9
10
11

import java.rmi.Naming;

public class TrainMain {
public static void main(String[] args) throws Exception {
RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming.lookup("rmi://127.0.0.1:1099/Hello");
String ret = hello.hello();
System.out.println(ret);
}
}

Java对远程访问RMI Registry做了限制,只有来源地址是localhost的时候,才能调用rebind、bind、unbind等方法。

所以我们在客户端不能使用这种语句

1
2
RemoteHelloWorld h = new RemoteHelloWorld();
Naming.rebind("rmi://192.168.135.142:1099/Hello", h);

列出所有绑定对象

1
String[] s = Naming.list("rmi://127.0.0.1:1099");

codebase

codebase 是一个地址,告诉 java虚拟机 去哪里搜索类,和CLASSPATH差不多(现在一般通过**-classpath 或 -cp 参数**:在运行 java 命令时直接指定,优先级最高

如果某一端在反序列化时发现一个对象就会去CLASSPATH下寻找类,如果找不到就会远程加载codebase中的类,如果codebase被我们控制就可以加载恶意类

目前满足以下条件的RMI服务器才能被攻击

1
2
3
1.安装并配置了SecurityManager

2.Java版本低于7u21、6u45,或者设置了 java.rmi.server.useCodebaseOnly=false

因为官方在Java 7u21、6u45版本的时候改了一个默认配置

1
将 java.rmi.server.useCodebaseOnly 的默认值由 false 改为了 true

在java.rmi.server.useCodebaseOnly 配置为 true 的情况下,Java虚拟机将只信任预先配置好的codebase ,不再支持从RMI请求中获取

hashcode为0的字符串

1
f5a5a608

执行命令

1
2
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
1
Runtime.getRuntime().exec("ls");