前言
我目前真不知道实战这个用的多不多,但是确实就是说这个,好像攻击的方法确实就是,比较繁琐复杂然后条件苛刻加上危害(不是指对说rce的这种危害,而是把别人服务打烂要坐牢)大
然后我们简单可以实战利用的方法和比较复杂的方法都来看一下
简单利用方法:https://rivers.chaitin.cn/blog/cqv1snh0lne0chgvdm2g
快1000star的项目:https://github.com/LandGrey/spring-boot-upload-file-lead-to-rce-tricks
Spring Boot Fat Jar 写文件getshell :
https://landgrey.me/blog/22/
但是上面那个网站我在26年10/23打不开呀
https://cangqingzhe.github.io/2021/04/16/Spring-Boot-Fat-Jar-%E5%86%99%E6%96%87%E4%BB%B6getshell%E6%8A%80%E6%9C%AF%E5%88%86%E6%9E%90/
还有
https://cloud.tencent.com/developer/article/2069794
和
https://mp.weixin.qq.com/s/n8RW0NIllcQ0sn3nI9uceA
常规简单打法
路径穿越+文件上传Get Shell
Nmap快速全端口扫描发现开放22,80和3306端口。访问目标,使用hfinger扫描发现Web应用使用了 Spring Boot 和 Ueditor ,同时在留言页面发现有个上传图片功能
抓包发现上传采用自定义API实现,没有任何限制。但是访问正常图片、txt文件、html文件都可以,访问jsp时却返回404

这个一看就是spring boot 的项目
经过查询可能的原因是:
- 缺少依赖:如
spring-boot-starter-tomcat和javax.servlet-api - 配置错误:
application.properties或application.yml中的JSP配置可能不正确 - 文件位置不正确:JSP文件必须放在正确的目录下,通常是
/src/main/webapp/WEB-INF/views/
访问 index.jsp 也返回404,应该是使用了默认的配置没有解析jsp。难道煮熟的鸭子就这么飞了?
俗话说只有不努力的黑客,没有攻不破的系统,继续尝试采用目录穿越上传到其它目录,发现报错信息返回了网站绝对路径,可以看出有tomcat(其实Spring Boot默认内置了Tomcat Web容器)

根据暴露的网站绝对路径,直接构造4个 ../ 将文件上传到网站根目录下,利用Tomcat进行解析jsp成功
然后替换为Webshell即可拿下目标
这个可以尝试下吧
charsets.jar
之前在审计的过程中发现,很多JAVA开发的网站经常使用springboot,而springboot又是可以独立于tomcat部署web项目的,而且在不做特殊配置的情况下,也并不解析jsp文件,当时也在想,如果在渗透的过程中发现了一个任意文件上传漏洞,并且目标网站没有内置jsp或者jspx的解析引擎,我该如何getshell?
以我目前的知识积累来看,在linux下且高权限的情况下,可以通过写入计划任务反弹shell。在windows下高权下可以通过写自启动的方式getshell,但是使用这些方式显然要求都比较高,一方面是需要高权限,另一方面在windows下可能有些上传点并不支持上传exe或者dll,也需要等到程序下次启动时才能上线,但是对于服务器来讲,可能管理员很久也不上来操作,这显然也是不行的。后来也想过替换jar包的方式getshell,但想到jar在运行期间替换也是不可行的。最近刚好看到Spring Boot Fat Jar 写文件漏洞到稳定 RCE 的探索,作者通过分析JDK中自带且在运行期间默认没有加载的JAR完成了getshell的操作,我本着学习的态度写下了这个文章
一个类的生命周期主要经过下面这几个阶段,我们主要关注的是类加载阶段

类加载阶段主要做了什么?
1 | 1、通过一个类的全限定名来获取定义此类的二进制字节流。 |
可以通过什么途径加载class文件?
JAVA中获取class文件的方式并不单一,可以通过下面的方式获取class文件,当然比较常用的方式还是通过jar包或者本地的class文件获取
- 从本地系统中直接加载
- 通过网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 从专有数据库中提取.class文件
- 将Java源文件动态编译为.class文件
并不需要等到某个类被使用时才去加载,,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它。比如当虚拟机启动时,会去加载JAVA_HOME/jre/lib/下的rt.jar下的.class文件文件。我们编写一个简单类测试下
1 | public class LoadTest { |
在启动时加入-XX:+TraceClassLoading参数即可获得加载信息,可以看到加载了JAVA_HOME/lib/rt.jar的多个类,并没有加载其他JAVA_HOME/jre/lib/目录下其他jar包下的类

由于我们需要测试的是springboot打包的WEB项目,所以也可以搭建一个简单的springboot web项目,看看会加载哪些JAR?我编写了一个空的springBoot web项目,经过测试会在启动是加载下面的4个JDK自带的jar,可以看到这里明显是加载了charsets.jar,似乎和LandGrey文章里提到的不太一样

经过问题排查,在使用maven编译打包是,会生成一个xml文件,在这个配置文件中有GBK编码的设置

当使用maven编译跳过测试打包时,则不会生成surefire-reports目录,下面再测试下是否加载了charsets.jar。经过测试还是会加载charsets.jar,可能再下面的几个依赖包中都有设置字符集的操作吧

显然这几个文件在程序运行中想要替换是不行的,会拒绝访问,没有加载的JAR则可以直接替换
使用作者在github上提供的测试代码来分析是否加载了charsets.jar,发现在springboot程序启动的过程中还是会open下面的三个系统jar,所以理论上是不可以替换这三个jar文件的

这样测试的结果并不准确,使用handle.exe可以看到程序依赖的所有jar文件

这些jar文件显然也不能直接覆盖利用,但在linux下执行并不会加载charsets.jar文件,所以作者说到的覆盖charsets.jar这种思路目前只适用于Linux场景,不过问题不大,一般Linux跑spingboot项目会遇到的比较多

和windows类似,直接通过查看系统调用可以看到具体依赖哪些jar文件

虽然Linux下确实没有加载Charsets.jar,但是我还有下面的疑问
已经Opened过的jar在Linux下能否覆盖?
经过测试,在Linux下即使程序正在运行中,也可以直接通过上传覆盖系统的jar文件

既然在linux中可以在运行时覆盖系统的jar包,我们能不能直接覆盖一个已经被load的类?虽然确实可以这么做,但是由于springboot项目在启动时已经将ri.jar或者jsse.jar中的class加载到虚拟机了,所以即使在后面需要使用这个类,会直接从JVM虚拟机中直接加载执行,而不会再将这些类重新Load
springboot启动会不会将jar中的所有类全部加载?
经过测试,springboot并不会在启动时将某个加载的jar的所有class类进行加载,以jfr.jar为例,也仅仅加载了jdk.jfr.internal.EventWriter,这个也非常容易理解,如果每次需要加载某个class都需要将对应jar里的所有class都加载一遍,对JVM来说也是一个很大的性能损耗
通过上面的分析和测试,无论springboot在Linux中是否加载了Charsets.jar文件,我们都可以找一个不常用的类来触发我们的payload
JAVA类的初始化
前面我们大致了解了类的加载,但是加载了类并不代表我们的逻辑被执行,所以还要了解一下类的初始化阶段?
类的初始化主要做什么?
执行< clinit >(静态变量及其赋值语句、静态代码块、静态方法)
虚拟机会保证在子类< clinit >的执行之前,父类的< clinit >()先执行
什么时候会触发类的初始化?
下面的几种情况会进行类的初始化
1 | 使用new关键字实例化对象 |
类初始化注意事项
同一个类加载器下,一个类型只会被初始化一 次。
通过上面的分析,我们可以尝试将恶意代码放到某个没有被load的static代码块中,只要找到触发这个类完成初始化的方法,即可执行我们的恶意代码
主动触发的初始化
在org.springframework.web.accept.HeaderContentNegotiationStrategy#resolveMediaTypes中,会去接收Accept参数

在org.springframework.util.MimeTypeUtils#parseMimeTypeInternal中会对accapt请求头的内容进行解析。将;后的内容放到LinkedHashMap中

在org.springframework.util.MimeType#MimeType(java.lang.String, java.lang.String, java.util.Map<java.lang.String,java.lang.String>)中解析parameters的值,并调用了org.springframework.util.MimeType#checkParameters

在org.springframework.util.MimeType#checkParameters中会去检查key是否为charset如果是则通过Charset.forName(value);加载charsets.jar中的某个类

当某个类没有加载过时,会执行这个类的static静态代码块

所以可以选择某个不太常用的编码方式的static静态代码块进行替换,来执行我们的恶意代码
1 | package sun.nio.cs.ext; |
编译文件后,将编译好的Big5_HKSCS.class文件放到charsets.jar中,上传修改过的jar文件,由于charsets.jar文件比较大3M左右,所以上传时间会比较长

最终通过设置Accept头触发恶意代码

总的来说这种利用这种方式需要一些条件,利用成本也比较高。
- 需要以高权限的方式开启springboot
- 跨任意目录上传
- 对于charsets.jar来说windows启动会加载并占用,无法在运行期间覆盖,所以并不适用
但charsets.jar也有着诸多缺点。
1,需root权限。不过好在现在docker/k8s横行,springboot服务大多数都是root权限。
2,charsets.jar加载仅一次机会。如果之前有用户使用过charset=GBK之类的加载过charsets.jar,不管是正常还是恶意的,该方法都会失效。如果该方式已经失效的情况下,你写的charsets.jar又不完整,还可能会导致服务挂掉。
3,完整的charsets.jar比较大,不过测试时不需要完整charsets.jar。
4,jdk目录需要猜测,需要字典。
5,仅jdk8或者以下适用。jdk9开始使用了模块化,不再存在charsets.jar
classes
jdk8在加载类时,还会从/usr/lib/jvm/java-8-openjdk-amd64/jre/classes/去找这个类,因此只需要向这里写一个Evil.class,再想办法触发即可。
比如如果存在反序列化入口,可以class Evil implements Serializable,然后反序列化这个类,如果存在fastjson1.2.68入口,用如下payload触发
1 | {"@type":"java.lang.AutoCloseable","@type":"Evil"} |
实际效果如下


优点如下
1,多次机会写入,Evil写坏了就写Evil2/Evil3
2,写入文件不大。
缺点如下
1,需root权限。
2,jdk目录需要猜测。
3,仅jdk8或者以下适用,jdk9的ClassLoader变动,不再尝试载入该文件夹。
4,默认不存在classes目录,需要创建
5,需触发入口,不像charsets.jar那样可以header触发
classes+SPI机制
这个方法解决了classes的缺点5,可以在不改动charsets.jar的情况下,利用classes/META-INF/services/java.nio.charset.spi.CharsetProvider文件指向classes/Evil.class,来完成charset=Evil触发Evil.class

但同时它也多了一个缺点,charset=Evil第二次不会触发,需要不断变化charset=Evil111
总结
https://mp.weixin.qq.com/s/n8RW0NIllcQ0sn3nI9uceA
这个文章还讲了tomcat-docbase
方法加fastjson io链历史
我这里就先不写这个了
- charsets.jar JDK8,Bootstrap 类加载器加载该 jar。覆盖 jar 植入恶意字符集类,请求头 Accept 里指定 charset 触发加载。优点:header 直接触发;缺点:需要 root,一次性,类加载后失效,JDK9 + 模块化失效,容易把服务搞崩。
- jre/classes 写 class JDK8 加载时会扫描 jre/classes 目录。写入恶意 class,靠反序列化入口触发。优点:文件小,可以换多个恶意类反复打;缺点:root 权限,需要额外触发入口,不能 header 触发。
- classes+SPI 写 jre/classes,配合 SPI 配置文件注册自定义字符集,用 charset 参数触发。优点:不用篡改 charsets.jar;缺点:root,每个字符集名字只能触发一次。
- tomcat-docbase(重点) SpringBoot 内置 Tomcat 在 /tmp 生成随机名 tomcat-docbase 目录,其内 WEB-INF/classes 可被线程上下文类加载器 TCCL 加载。Fastjson 加载类用的就是 TCCL,适合搭配 fastjson。优点:不需要 root,JDK8、11 都支持;缺点:目录随机,需要先爆破路径,必须 TCCL 触发,普通 ObjectInputStream 反序列化不行
我是先搞定前三个吧