whitelabel error page SpEL RCE漏洞复现

漏洞成因

正常情况下访问/article并输入数字型id即可获取文章内容,但如果传入了spel表达式,则会导致转到错误页面同时对spel表达式内容进行解析并反应在错误页面中。

debug过程

同样从DispatcherServlet.doDispatch()函数开始,很快就定位到了关键类:即PropertyPlaceholderHelper类。
在DispatcherServlet中由于以下判定报错

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
...
		if (asyncManager.isConcurrentHandlingStarted()) {
    
    
		                    return;
		                }
...
}

报错内容为NumberFormatException,即将输入的值转化为数字错误。

先看PropertyPlaceholderHelper.parseStringValue()方法

rotected String parseStringValue(String strVal, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
    
    
        StringBuilder result = new StringBuilder(strVal);
        int startIndex = strVal.indexOf(this.placeholderPrefix);

        while(startIndex != -1) {
    
    
            int endIndex = this.findPlaceholderEndIndex(result, startIndex);
            if (endIndex != -1) {
    
    
                String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                String originalPlaceholder = placeholder;
                if (!visitedPlaceholders.add(placeholder)) {
    
    
                    throw new IllegalArgumentException("Circular placeholder reference '" + placeholder + "' in property definitions");
                }

                placeholder = this.parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                //这里传入
                String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                if (propVal == null && this.valueSeparator != null) {
    
    
                    int separatorIndex = placeholder.indexOf(this.valueSeparator);
                    if (separatorIndex != -1) {
    
    
                        String actualPlaceholder = placeholder.substring(0, separatorIndex);
                        String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                        propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                        if (propVal == null) {
    
    
                            propVal = defaultValue;
                        }
                    }
                }

                ......
        }

        return result.toString();
    }

之后就走会返回view,而view中包含了 ${timestamp}、${error}、${status}、${message},view会通过循环遍历判断值是否以 “${” 开头
在这里插入图片描述

只要是 “${” 开头,就会进入spel表达式执行阶段。但当我们把message的value设置为payload,也是 ‘${’ 开头的,则也会执行payload,他没有对可控参数message的值进行校验。

之后通过ErrorMvcAutoConfiguration.resolvePlaceholder()方法来对spel表达式解析并获取值。
在这里插入图片描述
这一步即获取status的值。而当到达payload时,则会解析成Runtime类并执行exec方法,这一步为反射获取到Runtime实例
在这里插入图片描述

总结

由于传入的值会判断是否为数字,如果不为数字则会报错,走入报错页面,而报错页面是包含了一些spel表达式的,所以会对报错页面内的spel表达式进行循环查找并解析出来,最终渲染给页面。但出问题的原因是,${messgae}的值如果也是一个spel表达式,那么它会继续循环解析这个表达式,从而达到了命令注入,也即未对用户可控参数进行校验。

修复建议

跟其原因,是未对id进行spel方向的过滤。因此我总结建议为以下几点:

  1. 对id进行过滤,设置黑白名单,过滤如 ${ 等的值
  2. 升级框架版本
  3. 打补丁
  4. 自定义报错页面

猜你喜欢

转载自blog.csdn.net/qq_40519543/article/details/121403143