星期五实验室
Spring Cloud Function 是一个具有以下特点的项目:
-
• 通过函促进业务逻辑的实现。
-
• 将业务逻辑的开发生命周期与任何特定的运行时目标分离,以便相同的代码可以作为
Web端点、流处理器或任务运行。 -
• 支持跨无服务器提供商的统一编程模型,以及独立运行(本地或在
PaaS中)的能力。 -
• 在无服务器提供程序上启用
Spring Boot功能(自动配置、依赖注入、指标)。 -
它抽象出所有传输细节和基础设施,允许开发人员保留所有熟悉的工具和流程,并专注于业务逻辑。

GitHub仓库:
https://github.com/Uranusboy/spring-cloud-function-rce
环境搭建直接看图


这样就行了,然后function的版本是3.2.2,同时我们还需要配置一下application.properties 文件
翻译
=off=functionRouter=headers['httpMethod'].concat(' ').concat(headers['path'])=org.localstack.sampleproject.api
我们的项目中只配置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进行了调用:public 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 { // Functionresult = this.invokeFunction(convertedInput);}return result;}
SimpleFunctionRegistry#apply() 调用了本类的doApply()方法:public 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这一个方法中。
MediaType.MULTIPART_FORM_DATA_VALUE })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环节了。
SPEL 的RCE猜也能猜到,至于为什么写在header中,认真看完前面分析的也应该能懂了。Java RCE的洞就那几种,变过来变过去都是亘古不变。害!FRIDAY LAB
星期五实验室成立于2017年,汇集众多技术研究人员,在工业互联网安全前瞻技术研究方向上不断进取。星期五实验室由海内外知名高校的学院精英及来自于顶尖企业的行业专家组成,且大部分人员来自国际领先、国内知名的黑客战队——浙大AAA战队。作为木链科技专业的技术研发团队,星期五实验室凭借精湛的专业技术水平,为产品研发提供新思路、为行业技术革新探索新方向。





扫二维码|关注我们
星期五实验室
FRIDAY LAB
文章评论