Spring Cloud Function RCE分析

2022年3月28日 324点热度 0人点赞 0条评论

星期五实验室

阅读须知
星期五实验室的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息造成的直接或间接后果和损失,均由使用者本人负责。

星期五实验室拥有对此文章的修改、删除和解释权限,如转载或传播此文章,需保证文章的完整性,未经授权,不得用于其他。


背景介绍

Spring Cloud Function 是一个具有以下特点的项目:

  • • 通过函促进业务逻辑的实现。

  • • 将业务逻辑的开发生命周期与任何特定的运行时目标分离,以便相同的代码可以作为 Web 端点、流处理器或任务运行。

  • • 支持跨无服务器提供商的统一编程模型,以及独立运行(本地或在 PaaS 中)的能力。

  • • 在无服务器提供程序上启用 Spring Boot 功能(自动配置、依赖注入、指标)。

  • 它抽象出所有传输细节和基础设施,允许开发人员保留所有熟悉的工具和流程,并专注于业务逻辑。

图片

环境搭建

GitHub仓库:

https://github.com/Uranusboy/spring-cloud-function-rce

环境搭建直接看图

图片
图片

这样就行了,然后function的版本是3.2.2,同时我们还需要配置一下application.properties 文件

Configure Spring Cloud Function for Rest API
Spring Function comes with functionRouter that can route requests to different Beans based on predefined routing expressions. Let’s configure it to lookup our function Beans by HTTP method and path, create a new application.properties file under src/main/resources/application.properties with the following content:

翻译


为 Rest API 配置 Spring Cloud Function
Spring Function 附带可以根据预定义的路由表达式functionRouter将请求路由到不同的路由。Beans让我们配置它以通过 HTTP 方法和路径查找我们的函数 Beans,在下面创建一个新application.properties文件src/main/resources/application.properties ,内容如下:

spring.main.banner-mode=offspring.cloud.function.definition=functionRouterspring.cloud.function.routing-expression=headers['httpMethod'].concat(' ').concat(headers['path'])spring.cloud.function.scan.packages=org.localstack.sampleproject.api

我们的项目中只配置definition就可以了:

spring.cloud.function.definition:functionRouter

分析
根据传出来的poc我们可以知道漏洞触发点是由于SPEL触发的,所以我们看下源码,其实他的触发点就是如下函数:
RoutingFunction#functionFromExpression() 
private FunctionInvocationWrapper functionFromExpression(String routingExpression, Object input) {          //解析SPEL表达式,我们可以看到传入进来的是一个routingExpression字符串    Expression expression = spelParser.parseExpression(routingExpression);    if (input instanceof Message) {      input = MessageUtils.toCaseInsensitiveHeadersStructure((Message<?>) input);    }    //了解SpEL rce的应该一眼就能看出此处就是触发点    String functionName = expression.getValue(this.evalContext, input, String.class);        ...........  }


我们知道触发点后看看那里调用了functionFromExpression方法,我们查找后发现就在同类下的route方法,我们看代码:


/*   * - Check if `this.routingCallback` is present and if it is use it (only for Message input)   * If NOT   * - Check if spring.cloud.function.definition is set in header and if it is use it.(only for Message input)   * If NOT   * - Check if spring.cloud.function.routing-expression is set in header and if it is set use it (only for Message input)   * If NOT   * - Check `spring.cloud.function.definition` is set in FunctionProperties and if it is use it (Message and Publisher)   * If NOT   * - Check `spring.cloud.function.routing-expression` is set in FunctionProperties and if it is use it (Message and Publisher)   * If NOT   * - Fail   */private Object route(Object input, boolean originalInputIsPublisher) {    FunctionInvocationWrapper function = null;    if (input instanceof Message) {        Message<?> message = (Message<?>) input;        if (this.routingCallback != null) {            FunctionRoutingResult routingResult = this.routingCallback.routingResult(message);            if (routingResult != null) {                if (StringUtils.hasText(routingResult.getFunctionDefinition())) {                    function = this.functionFromDefinition(routingResult.getFunctionDefinition());                }                if (routingResult.getMessage() != null) {                    message = routingResult.getMessage();                }            }        }        if (function == null) {            if (StringUtils.hasText((String) message.getHeaders().get("spring.cloud.function.definition"))) {                function = functionFromDefinition((String) message.getHeaders().get("spring.cloud.function.definition"));                if (function.isInputTypePublisher()) {                    this.assertOriginalInputIsNotPublisher(originalInputIsPublisher);                }            }            //这里我们可以看到他是从header中获取到key=spring.cloud.function.routing-expression的value值,然后将值传递给functionFromExpression(),然后解析            else if (StringUtils.hasText((String) message.getHeaders().get("spring.cloud.function.routing-expression"))) {                function = this.functionFromExpression((String) message.getHeaders().get("spring.cloud.function.routing-expression"), message);                ...............            }            else if (StringUtils.hasText(functionProperties.getRoutingExpression())) {                ...............            }            else if (StringUtils.hasText(functionProperties.getDefinition())) {                ...............            }            else {                ............            }        }    }    else if (input instanceof Publisher) {        .................    }    else {        .................    }    return function.apply(input);}//不重要代码皆省略

接下来就是看谁调用了route方法,发现是重写的apply方法对route进行了调用
@Overridepublic Object apply(Object input) {    return this.route(input, input instanceof Publisher);}


继续找我们发现
SimpleFunctionRegistry#doApply() 对apply进行了调用,这层调用的话是将target对象强转成了RoutingFunction的父类Function,因为有继承重写的缘故在里面,所以调用apply时就会调用重写后的具体实现。
Object doApply(Object input) {    Object result;    input = this.fluxifyInputIfNecessary(input);    Object convertedInput = this.convertInputIfNecessary(input, this.inputType);    if (this.isRoutingFunction() || this.isComposed()) {        //调用apply,由于Java的特性会调用Function子类RoutingFunction中重写的apply方法        result = ((Function) this.target).apply(convertedInput);    }    else if (this.isSupplier()) {        result = ((Supplier) this.target).get();    }    else if (this.isConsumer()) {        result = this.invokeConsumer(convertedInput);    }    else { // Function        result = this.invokeFunction(convertedInput);    }    return result;}


继续向上找找到是
SimpleFunctionRegistry#apply() 调用了本类的doApply()方法
@Overridepublic Object apply(Object input) {    if (logger.isDebugEnabled() && !(input  instanceof Publisher)) {        logger.debug("Invoking function " + this);    }    //调用同类中的doApply()    Object result = this.doApply(input);    if (result != null && this.outputType != null) {        result = this.convertOutputIfNecessary(result, this.outputType, this.expectedOutputContentType);    }    return result;}

到这其实基本上就快到我们的目标位置了,也就是source位置:
SimpleFunctionRegistry#apply()
 方法是被下面的函数调用的:
FunctionWebRequestProcessingHelper#processRequest()
public static Publisher<?> processRequest(FunctionWrapper wrapper, Object argument, boolean eventStream) {    FunctionInvocationWrapper function = wrapper.getFunction();    HttpHeaders headers = wrapper.getHeaders();    Message<?> inputMessage = argument == null ? null : MessageBuilder.withPayload(argument).copyHeaders(headers.toSingleValueMap()).build();    if (function.isRoutingFunction()) {      function.setSkipOutputConversion(true);    }    Object input = argument == null ? "" : (argument instanceof Publisher ? Flux.from((Publisher) argument) : inputMessage);    //调用了SimpleFunctionRegistry#apply()方法    Object result = function.apply(input);    .....................  }


再往上就是终点了,因为在这一层我们就把我们需要的东西全部获取到了,就是FunctionController 这个一看就知道是什么意思了吧。这里先给大家放个图,大家看看怎么才能使post提交走到第一个form这一个方法中。
图片

他的具体代码也很简单,给大家看看。
@PostMapping(path = "/**", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE,      MediaType.MULTIPART_FORM_DATA_VALUE })  @ResponseBody  public Object form(WebRequest request) {    FunctionWrapper wrapper = wrapper(request);    //为了能够执行到我们的FunctionWebRequestProcessingHelper.processRequest()方法,那么我们的请求就不能是StandardMultipartHttpServletRequest的子类    if (((ServletWebRequest) request).getRequest() instanceof StandardMultipartHttpServletRequest) {      ...............            //存在多个return语句    }    return FunctionWebRequestProcessingHelper.processRequest(wrapper, wrapper.getParams(), false);  }

到这,整个分析就结束了,我们可以发现其实通过代码我们知道了请求方法,payload放置的位置,下面就是简单的poc环节了。

POC
图片

比较简单,如果了解SPEL 的RCE猜也能猜到,至于为什么写在header中,认真看完前面分析的也应该能懂了。

总结
Java RCE的洞就那几种,变过来变过去都是亘古不变。害!


FRIDAY LAB

星期五实验室成立于2017年,汇集众多技术研究人员,在工业互联网安全前瞻技术研究方向上不断进取。星期五实验室由海内外知名高校的学院精英及来自于顶尖企业的行业专家组成,且大部分人员来自国际领先、国内知名的黑客战队——浙大AAA战队。作为木链科技专业的技术研发团队,星期五实验室凭借精湛的专业技术水平,为产品研发提供新思路、为行业技术革新探索新方向。


图片
图片
图片
图片
图片

扫二维码|关注我们

星期五实验室
FRIDAY LAB

40080Spring Cloud Function RCE分析

这个人很懒,什么都没留下

文章评论