长亭百川云 - 文章详情

原创 | 浅谈Spring actuator env endpoint的一个误区

SecIN技术平台

57

2024-07-13

Spring Boot Actuator 是 Spring Boot 提供的一个管理和监控 Spring Boot 应用程序的插件。Actuator 可以提供有关应用程序的健康状况、度量、日志记录和其他信息,可以通过 HTTP 或 JMX 等不同方式进行访问。

/env 是 Spring Boot Actuator 的一个端点,用于显示当前应用程序的环境属性。该端点返回一个 JSON 格式的响应,其中包含有关应用程序环境的详细信息,例如操作系统、Java 运行时环境、应用程序配置属性等等。

关于env端点的一个误区

默认情况下,actuator的env端点是不支持POST请求修改操作的。但是可通过配置开启env端点的post请求,参考

https://spring.io/blog/2020/03/05/spring-cloud-hoxton-service-release-3-sr3-is-available:

PS:actuator 1.x只需要引入对应的springcloud即可默认开启了。

在引入springcloud后,会依赖一个组件叫 SnakeYAML 其可以触发yaml反序列化,利用actuator的/env POST 功能可以将spring.cloud.bootstrap.location属性的值为恶意yml配置文件的地址,使用/refresh接口刷新后即可触发远程加载yaml 实现urlclassloader达到RCE的效果。

针对SpringBoot-Actuator-SnakeYAML-RCE的情况,很多时候会选择关闭掉env端点来规避风险。

在actuator 1.x,/env端点可以由如下属性进行关闭:

endpoints.env.enabled=false

在spring boot 2.0以后,actuator默认只开启了info和health端点,要想使用其他的端点,需要在application.yml相关配置文件中打开,并且所有的端点都以默认的路径/actuator/开始,如果需要独立关闭/env端点的话可以通过如下属性进行操作:

management.endpoint.env.enabled=false

这里存在一个误区,在actuator 1.x中,很多时候大家

都认为endpoints.env.enabled=false后就env POST

端点就没有暴露出去了,规避了风险,实际上简单的

通过配置关闭env端点并不能解决问题。

具体原理

针对前面的情况,看一下具体的原理(在spring boot 2.0以后,actuator默认只开启了info和health端点,这里只讨论1.x的情况):

2.1、env端点的注册

以spring-boot-actuator-1.5.1.RELEASE版本为例:

首先通过actuator访问/mappings查看env端点具体的方法调用,可以看到是通过

endpointHandlerMapping进行处理的:

访问/beans节点,查看处理类

endpointHandlerMapping的具体实现:

可以看到endpointHandlerMapping对应的类为

org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping

初始化的configuration类为

org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.class:

首先查看

EndpointWebMvcManagementContextConfiguration的具体实现

可以看到其会通过调用endpointHandlerMapping方法来处理,这里首先会获取容器中的MvcEndpoint接口实现类,然后实例化EndpointHandlerMapping:

MvcEndpoints初始化代码如下:

  • 查找spring的beanfactory中所有的MvcEndpoint类和Endpoint类

  • 如果某个MvcEndpoint的getEndpointType方法的返回结果和某个Endpoint的class相冲突,则选取MvcEndpoin

  • 最后将查找选取的结果放在MvcEndpoints的endpoints方法中并返回

通过断点调试,可以看到当前环境endpoints有21个:

继续调试,后面会调用对应Endpoint的构造方法来实现映射

(EnvironmentMvcEndpoint也在21个endpoints里):

以EnvironmentMvcEndpoint为例,查看具体的实现:

org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint#value

这个方法实际上就是/env/{name:.*}这个路由的具体调用:

其中@ActuatorGetMapping 

就是@RequestMapping的封装,被该注解标注的方法,其请求方式为get,产生的数据格式为

application/vnd.spring-boot.actuator.v1+json

和application/json

具体定义如下:

查看EnvironmentMvcEndpoint的构造方法,直接调用直接父类构造函数

(EnvironmentMvcEndpoint的父类是

EndpointMvcAdapter):

在EndpointMvcAdapter中,

org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter#invoke实际上就是/env路由的具体调用:

EndpointMvcAdapter,它代理了MvcEndpoint,实现其invoke方法。

这里的delegate是EnvironmentEndpoint,所以实际调用时,最终调用的是

org.springframework.boot.actuate.endpoint.EnvironmentEndpoint#invoke:

到这里大概可以知道EnvironmentMvcEndpoint实际上就是把EnvironmentEndpoint通过HTTP服务暴露,然后通过/env和/env/{name:.*}两个请求路由进行访问。

2.2、env端点生效

前面提到会调用对应Endpoint的构造方法来实现映射,对应的方法里有一个注解

@ConditionalOnEnabledEndpoint:

该注解会通过

org.springframework.boot.actuate.condition.OnEnabledEndpointCondition

类进行处理。

首先是getMatchOutcome方法:

主要做了如下处理:

  • 获得@ConditionalOnEnabledEndpoint配置的Endpoint名字和

    enabledByDefaultenabled的值

  • 调用determineEndpointOutcome方法,获得endpoints.{name}.enabled的配置并返回,例如env端点的话会读取endpoints.env.enabled的值

  • 读取endpoints.enabled的配置,如果没有配置的话,则默认匹配

也就是说@ConditionalOnEnabledEndpoint注解满足如下条件时或生效:

  • endpoints. env.enabled=true

  • endpoints.enabled=true

  • 未配置

    endpoints.enabled,endpoints.env.enabled时默认生效

同样是之前的环境,在application.properties进行如下配置关闭env端点:

endpoints.env.enabled=false

通过断点调试,可以看到当前环境endpoints 从21个减少到了20个,EnvironmentMvcEndpoint没有了:

2.3、env如何支持POST

根据前面的分析,通过@ActuatorGetMapping注解仅仅实现了GET请求的调用,并未实现POST。看看具体的env POST是如何实现的。

同样的通过actuator访问/mappings查看env端点具体的方法调用,可以看到是通过

org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint#value处理的,查看具体的实现:

以spring-cloud-context-1.1.3为例:

org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint#value方法通过@RequestMapping标记并且限制了只能通过POST请求:

同时EnvironmentManagerMvcEndpoint实现了MvcEndpoint接口

在MvcEndpoints初始化时也能获取到:

EnvironmentManagerMvcEndpoint是在

org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration中注入

这里是通过@ConditionalOnProperty注解来判断的(@ConditionalOnProperty可以通过配置文件中的属性值来判定是否被注入,这里是endpoints.env.post.enabled,matchIfMissing属性为true时,配置文件中缺少对应的value或name的对应的属性值,也会注入成功):

到这里其实已经大概知道具体的原因了。实际上env POST和env GET在endpointHandlerMapping处理时并不是同一个endpoint。

所以当通过endpoints.env.enabled=false关闭env GET时,env POST 端点

(EnvironmentManagerMvcEndpoint)仍因为默认配置注入到环境中。

根据前面的分析,这里尝试在application.properties中进行如下配置:

endpoints.env.post.enabled=false

但是env GET可以正常访问,验证了前面的猜想:

查看调试信息,EnvironmentManagerMvcEndpoint没有注入,但是代表env GET实现的EnvironmentMvcEndpoint注入成功并且获取到了,所以env GET可以访问:

其他

对于actuator 2.x生态,设计上会有区别。

以spring-cloud-context-2.2.1.RELEASE为例,其env POST的实现主要在

WritableEnvironmentEndpointAutoConfiguration:

这里不再是EnvironmentManagerMvcEndpoint,而是

WritableEnvironmentEndpoint:

而POST的实现主要是通过@EndpointWebExtension注解扩展现有的Endpoint(ReadOperation、@WriteOperation和@DeleteOperation每个注解的方法都有对应操作对象,根据操作对象、Endpoint对象、Endpoint名称在RequestMappingInfo进行处理,然后对外提供服务):

那么在2.x中,实际上只需要关闭env端点,即可禁用env POST了,例如如下配置,首先暴露endpoint,然后禁用env:

management.endpoints.web.exposure.include=*

此时再次尝试请求env POST,返回404 status:

修复措施

根据前面的分析,env端点POST支持主要是通过spring-cloud-context组件控制的。在1.x中需要额外的注意。

当引入了spring-cloud-starter-config或

spring-cloud-context 大于等于 2.2.2.RELEASE时:

<dependency>

若未配置

management.endpoint.env.post.enabled=true

则不支持post请求,此时无影响。

但当版本为2.2.1.RELEASE及以下时候,则可以直接请求POST:

<dependency>

此时如果需要关闭,通过配置对应的属性进行处理:

  • actuator 1.x
endpoints.env.post.enabled=false
  • actuator 2.x
management.endpoint.env.post.enabled=false

同时关闭env POST和GET后,访问会返回404,达到预期的效果,从一定程度规避了env POST端点的风险:

往期推荐

原创 | 2023年第八届上海市大学生网络安全大赛 / 磐石行动 漏洞挖掘 Workthrough(上)

原创 | 2023年第八届上海市大学生网络安全大赛 / 磐石行动 漏洞挖掘 Workthrough(下)

原创 | 2023 CISCN 第十六届全国大学生信息安全竞赛初赛 WriteUp

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2