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) { } } }
|
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 2
| Class clazz = Class.forName("java.lang.ProcessBuilder"); ((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
|
1
| Runtime.getRuntime().exec("ls");
|