java安全
基础
反射关键四个点
获取类
getclass:存在实例,获取类
class:传入类,只获取class对象
forname:通过名字获取类
内部类
若c1类中编写c2类,则编译的时候会构造出c1$c2.class
同时forClass获取内部类也是用$
可变长函数
在java底层都相当于数组
动态代理
语法
- ClassLoader
- 需要代理的对象
- 实现了InvocationHandler接口的对象(实现)
反射函数
forName
当forName函数第二个参数为ture的时候,会进行类初始化
如下代码先执行static 最后执行构造函数
static {} 就是在“类初始化”的时候调⽤的,⽽ {} 中的代码会放在构造函数的 super() 后⾯,但在当前构造函数内容的前⾯
那么,若有函数
则可以已通过构造一个恶意类,使它的static函数被虚拟机类初始化时能够getshell
newInstance
调用无参构造,或者在构造器上面调用构造函数
无参构造函数
方法不能私有
如上 Runtime类构造函数私有,报错
(单例模式:对于Web应用来说,数据库连接只需要建立一次,开发者可以将数据库连接使用的类的构造函数设置为私有,然后编写一个静态方法来获取)
则换成Runtime.getRuntime()获取对象
getMethod
获取类的公有方法,由于java支持重载,则需要传入两个参数
- 函数名
- 参数列表
例如
invoke
执行方法 ,第一个参数
- 普通方法,传入对象
- 静态方法,传入类
第二个参数是传入方法的参数
综上所述,
获取类,获取方法
利用获取的静态方法传入类调用构造函数,得到对象,然后invoke调用方法
getConstructor
没有无参构造,也没静态调用构造
接受构造函数列表类型,返回引用了该构造函数的java.lang.reflect.Constructor
对象,然后用newinstance将他实例化
想调用ProcessBuilder第二个构造函数的时候由于是变长参数,又因为newInstance函数本身接收的是一个可变长参数,则传入一个二维数组
getDeclared
- 私有方法调用
- 不继承父类
- 需要setAccessible(ture)
getRuntime
Runtime类中的静态方法,可以省略实例化的过程,invoke直接放入一个类即可,如果不使用它则需要通过getConstructor来实例化对象
实例
由于Runtime类不可直接序列化,所以通过Class类以及强转得到
首先获取class原型对象(可序列化)
然后通过getMethod获取了getRuntime和exec方法
通过invoke调用getRuntime方法来获取runtime对象,然后同样调用exec方法
RMI
结构
如下
- 继承Remote并且定义函数的接口
- 实现此接口的类
- 实例化上面的类后绑定,创建Registry
LocateRegistry.createRegistry(1099);创建了Registry
Naming.bind绑定到将一个对象和一个名字(hello)绑定在一起
客户端
利用接口,Naming.loockup查找并且创建对象
RMI Registry就像⼀个⽹关,他⾃⼰是不会执⾏远程⽅法的,但RMI Server可以在上⾯注册⼀个Name
到对象的绑定关系;RMI Client通过Name向RMI Registry查询,得到这个绑定关系,然后再连接RMI
Server;最后,远程⽅法实际上在RMI Server上调⽤。
tcp连接两次,第一次根据绑定的ip查询Registry,第二次接受序列化数据,反序列化后得到目标ip和端口
安全性
Naming下的list和lookup方法
此处还存在一个codebase漏洞,反序列化时发现一个对象,那么就会去自己的CLASSPATH下寻找想对应的类;如果在本地没有找到这个类,就会去远程加载codebase中的类。
gadget
CC1
1 |
|
在Java 8u71以后的版本AnnotationInvocationHandler新建了一个LinkedHashMap对象,并将原来的键值添加进去,导致CC1 CC3不可用
Runtime是内置类,需要获取.class后getMethod获取getRuntime方法
TransformedMap用来修饰一个map,map被修改键值的时候会回调,所以构造outputMap,被修改键值的时候触发ChainsfromedMap
AnnotationInvocationHandler类构造方法私有,需要反射获取,它的价值是修改map的键值,代替了demo中的put
关于AnnotationInvocationHandler类的构造
所以构造的时候传入了Rentention类,这个类有一个value方法,所以给innerMap传入一个value键
lazymap:链子中代替了transformMap,他的get方法找不到参数的时候会调用transfrom,很巧的是可以利用动态代理调用AnnotationInvocationHandler#invoke去调用lazymap#get
AnnotationInvocationHandler#readObject中的value被设置,但是可以用constanTransformer来绕过,强制返回一个Runtime.class
AnnotationInvocationHandler#readObject–>lazymap#get–>transfrom
CC3
1 |
|
同cc1,限制于8u71
绕过了InvokerTransformer
注意!下图的transformers未修改
CC6
1 |
|
为了解决sun.reflect.annotation.AnnotationInvocationHandler#readObject在8u71后的变化
又回到了如何调用LazyMap#get的问题,这里利用了TiedMapEntry
TiedMapEntry的构造函数传入一个map和一个key,他的getValue方法调用的了map的get方法并且传入
key
同时,直接构造的话会让最后的get进不去,outputMap会多上一个键,需要手动remove
CC5
1 |
|
CC shiro
1 |
|
shiro在链子中存在数组会报错
首先,从动态加载字节码的知识可知,我们可以如上构造exp
但是此时仍然是一个数组,有没有办法跳过ConstantTransformer呢?
TiedMapEntry的构造函数传入一个map和一个key,他的getValue方法调用的了map#get–>transfrom(key)
CC2
1 |
|
在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有以下两
个分⽀版本:
- commons-collections:commons-collections
- org.apache.commons:commons-collections4
commons-collections4
直接CC6的decorate方法改成lazymap()即可
同时还出现了CC2和CC4链子
!!CC2利用了!!
- java.util.PriorityQueue:存在反序列化入口,可以调用到java.util.Comparator对象
- org.apache.commons.collections4.comparators**.TransformingComparator存在compare方法**
- compare方法调用了transfrom
随后
- CC3.2.2增加了序列化检查
- CC4.1直接禁止了部分transfromer类序列化
CB
1 |
|
CB库的getProperty会调用相应javaBean的getter方法来获取对应的属性,同时支持递归
我们从CC2中知道,PriorityQueue可以调用到java.util.Comparator对象
CB中存在org.apache.commons.beanutils.BeanComparator#compare:当比较的参数存在值的时候会通过getProperty寻找属性
而getProperty会递归调用getter方法
而在TemplatesImpl中存在getOutputProperties
1 |
|
则只需要让o1为TemplatesImpl,property为OutputProperties
就可以通过getter调用到TemplatesImpl#getOutputProperties动态加载字节码
Shiro-CB
1 |
|
- serialVersionUID不同
- CB缺乏CC依赖(BeanComparator构造函数调用了org.apache.commons.collections.comparators.ComparableComparator)
问题1:手动设置UID即可
问题2:通过String.CASE_INSENSITIVE_ORDER获取CaseInsensitiveComparator类,他
- 实现 java.util.Comparator 接口
- 实现 java.io.Serializable 接口
- Java、shiro或commons-beanutils自带,且兼容性强
原生链
1 |
|
7u21
AnnotationInvocationHandler#equalsImpl调用了所有方法
这里可以想到动态字节码
又根据他的invoke方法,只需要被动态代理的对象被调用equals方法且只有一个参数
这里涉及到两个方法的区别,java.util.PriorityQueue调用的是compareTo
为了调用到equals方法,想到了不能让键重复的集合
在HashSet#readObject –> HashSet#put中
其中的key是我们传入的可控的对象,而进入到这一步要求proxy对象的“哈希”,等于TemplateImpl对象的“哈希”。
trick
反序列化逻辑如:HashMap中的元素去重,分别是proxy和TemplatesImpl,获取hashcode()成功触发equals(),调用到代理的equalsImpl,然后遍历this.type的方法也就是TemplatesImpl的方法
Hessian
Fastjson
JdbcRowSetImpl
1 |
|
a对象缓存绕过,b对象设置autoCommit导致反序列化时设置反射属性
com.sun.rowset.JdbcRowSetImpl.setAutoCommit() –>com.sun.jndi.rmi.registry.RegistryContext.lookup()
- 较低版本JDK(JDNI)
- 出网
TemplateImpl
1 |
|
构建了一个恶意类,然后base64编码
需要开启Feature.SupportNonPublicField
,实战中不适用
BasicDataSource
利用的是BCEL
- 不需要出网,不需要开启特殊的参数,适用范围较广
- 目标需要引入tomcat依赖,虽说比较常见,但也是一种限制
ROME
1 |
|
用于处理XML数据的库
EqualBeans(ObjectBean)
利用hashmap
hashmap#readObject–>this.map#hashcode(EqualBeans#hashcode)– >EqualBeans#toString
HashTable
绕过hashmap
HashTable#readObject–>this.key#reconstitutionPut–>this.key#hashcode(EqualBeans#hashcode)– >EqualBeans#toString
BadAttributeValueExpException
cc链里面调用到toString的方法
二次反序列化
RMIConnector
1 |
|
RMIConnector#findRMIServerJRMP会将参数进行反序列化
找到调用它的函数
- path开头/stub
- 截取后面的为base64参数传入
最后找到connect
SignedObject
Jackson
writeValueAsString是jackson序列化自带的入口,在调用该方法的过程中将会通过遍历的方法将bean对象中的所有的属性的getter方法进行调用
而在POJONode父类的父类中中有toString方法,可以触发二次反序列化
类加载器
http协议使用loader类,不以/结尾则识别为JAR文件,剩下的都是file协议
顺序:
- 寻找已加载以及父加载器
- 根据url指定方式读取加载字节码
- 处理字节码称为类
defineclass不会触发类初始化,如果想要利用他执行命令需要设法触发构造函数
加载字节码
URLclassloader:远程加载字节码
ClassLoader#defineClass:直接加载字节码,需要触发构造函数
TemplatesImpl:底层调用了defineClass,字节码必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 子类
BCEL ClassLoader:java8u251之前
其他
基础概念
reference引用:传入类名,工厂名,工厂位置
又由于JNDI可以绑定引用对象,就可以在工厂位置中嵌入恶意代码实现诸注入
在本地开一个存在恶意TestRef的服务器
逻辑:JNDI服务器绑定了恶意远程对象,客户端调用lookup去查找的时候会触发恶意代码
同时JNDI也可以去打RMI
高版本的JDK禁止了调用远程服务器,则需要寻找本地可利用的恶意工厂类(tomcat8的beanfactory反射调用EL表达式)
fastjson反序列化漏洞
解析json字符串的fastjson库提供了一个将字符串根据@type类型解析为对象的功能
在处理完字符串之后需要获取类的反序列化器进行反序列化
最早的版本存在黑名单,但是只过滤某些有bug的类
自定义的类之后会来到
会通过一个beaninfo的build方法获取这个类的信息
遍历三次,分别获取setter,属性,getter(逻辑大概是既然需要处理一个类,那么就需要得到这个类的属性并且知道怎么将他的属性赋值)
getter需要满足条件(返回值是特定的类)
若某个属性只有get没有set,反序列化的时候会通过getOnly属性来让他调用get方法
这个过程中以后一个asmEnable属性,关闭的时候会调用JavaBeanDeserializer,开启则调用自定义的asmFactory作为反序列化,为了调试代码需要把它关闭掉
怎么把它关掉呢?第一次获取setter的时候只允许传入一个参数,则不考虑,第三次获取getter的时候可以利用与普通序列化漏洞的区别
CTF
网鼎杯2020thinkjava
拿到class文集爱你,放进idea的out目录下反编译
跟进一下函数,存在注入
此处利用到jdbc注入
#既是sql注释符,又是url中的锚点,jdbc类似url解析
由于#后的字符被忽略,前面的数据正常解析
#后的数据带入数据库,单引号闭合后注入
同时发现数据库默认myapp
这里可以利用swaggerUI
于是构造
1 |
|
后面都是常规的sql注入 得到用户账密后返回一段cookie,rO0AB开头,怀疑反序列化
使用bp插件探测java ROME链子
反弹shell poc
1 |
|
curl和ping外带的poc
1 |
|
网鼎杯2020青龙组filejava
下载路径目录穿越
/usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml
然后目录穿越到
/usr/local/tomcat/webapps/ROOT/WEB-INF/classes下去下载class文件
后面是一个xml的cve
[CISCN 2023]deserbug
main函数接受bugstr base解码后反序列化
Myexcept类存在
联想到CC3