CVE: CVE-2025-59059
受影响版本: Apache Ranger <= 2.7.0
修复版本: Apache Ranger 2.8.0
CVSS(官方): 9.8 严重
实际严重性(争论): ~6.0 中等 — 见下方分析
本文并非关于新漏洞或可用PoC。重点在于纠正CVE-2025-59059公开披露中的错误,具体有两方面:错误地将错误类列为脆弱组件,且CVSS评分未能反映实际可利用性限制。所有分析均基于Apache Ranger 2.7.0源代码的静态分析及2.8.0版本的补丁差异。
官方公告称:
"Apache Ranger <= 2.7.0版本中存在NashornScriptEngineCreator的远程代码执行漏洞。"
这是错误的,或至少具有误导性。NashornScriptEngineCreator并非脆弱类。相反,它可能是代码库中两个Nashorn相关组件中防护较好的一个——下文将详细说明。
Apache Ranger使用JavaScript表达式评估行级过滤策略。这些策略决定给定用户是否有权访问数据集中的特定记录。为了在JVM上实际运行这些表达式,Ranger嵌入了JavaScript引擎。在<= 2.7.0版本中,该引擎是Nashorn,即Oracle随JDK 8至14内置的JS引擎。
公告完全忽略了一点——且这对影响范围界定非常重要——jdk.nashorn.api.scripting.NashornScriptEngineFactory并非Ranger源代码的一部分。它是JDK内置的。它包含在JDK 8中,在JDK 11中弃用,并在JDK 15中完全移除。因此,该漏洞仅在Ranger运行于JDK 8至14时方可利用。任何JDK 15+版本均不受影响,因为Nashorn根本不存在,ScriptEngineUtil会静默回退到GraalJS或JavaScriptEngineCreator。
Ranger中有两个类使用了Nashorn,但处理方式截然不同。
NashornScriptEngineCreator——被点名但更安全的类位于:
agents-common/src/main/java/org/apache/ranger/plugin/util/NashornScriptEngineCreator.java
public class NashornScriptEngineCreator implements ScriptEngineCreator {
private static final String[] SCRIPT_ENGINE_ARGS = new String[] {
"--no-java", "--no-syntax-extensions"
};
@Override
public ScriptEngine getScriptEngine(ClassLoader clsLoader) {
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
ret = factory.getScriptEngine(SCRIPT_ENGINE_ARGS, clsLoader, RangerClassFilter.INSTANCE);
...
}
private static class RangerClassFilter implements ClassFilter {
@Override
public boolean exposeToScripts(String className) {
LOG.warn("script blocked: attempt to use Java class {}", className);
return false;
}
}
}
该类实际上通过三种方式加固引擎:
--no-java — 切断脚本对java.*命名空间的直接访问--no-syntax-extensions — 禁用非标准的Nashorn语法RangerClassFilter — 在ClassFilter级别阻止所有Java类访问,对所有请求返回false它是否无懈可击?不是。通过反射链和java.lang.invoke技巧存在Nashorn ClassFilter绕过。但相比另一个类,它的防护性明显更强。
RecordFilterJavaScript——实际脆弱类位于:
plugin-nestedstructure/src/main/java/org/apache/ranger/authorization/nestedstructure/authorizer/RecordFilterJavaScript.java
public class RecordFilterJavaScript {
static class SecurityFilter implements ClassFilter {
@Override
public boolean exposeToScripts(String s) {
return false;
}
boolean containsMalware(String filterExpr) {
// 仅检查这一特定字符串
return filterExpr.contains("this.engine");
}
}
public static boolean filterRow(String user, String filterExpr, String jsonString) {
SecurityFilter securityFilter = new SecurityFilter();
if (securityFilter.containsMalware(filterExpr)) {
throw new MaskingException("cannot process filter expression due to security concern...");
}
// 直接实例化Nashorn——无--no-java,无--no-syntax-extensions
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
ScriptEngine engine = factory.getScriptEngine(securityFilter);
String script = " jsonAttr = JSON.parse(jsonString); " + filterExpr;
Bindings bindings = engine.createBindings();
bindings.put("jsonString", jsonString);
bindings.put("user", user);
boolean hasAccess = (boolean) engine.eval(script, bindings);
...
}
}
与NashornScriptEngineCreator对比,差异显而易见:
--no-java标志 — java.*命名空间对脚本完全开放--no-syntax-extensionsfilterExpr.contains("this.engine")filterExpr直接被拼接进Nashorn执行的脚本中由于未传递--no-java,java.*命名空间完全可访问。你无需任何绕过技巧:
var runtime = java.lang.Runtime.getRuntime();
runtime.exec("id");
其中完全没有this.engine。黑名单对这一攻击路径完全无效。
有趣的是,开发人员显然至少知道一种绕过模式。测试文件TestRecordFilterJavaScript.java包含:
RecordFilterJavaScript.filterRow("user",
"this.engine.factory.scriptEngine.eval('java.lang.Runtime.getRuntime().exec(\"/Applications/Spotify.app/Contents/MacOS/Spotify\")')",
...);
该测试旨在确认this.engine被阻止。但未阻止的是直接的java.*路径,而该路径根本不需this.engine。
RecordFilterJavaScript.filterRow()仅从一个地方被调用:
plugin-nestedstructure/src/main/java/org/apache/ranger/authorization/nestedstructure/authorizer/NestedStructureAuthorizer.java
private boolean hasAccessToRecord(String schema, String user, ..., String jsonString, ...) {
RangerAccessResult result = plugin.evalRowFilterPolicies(request, null);
if (result.isRowFilterEnabled()) {
String filterExpr = result.getFilterExpr();
ret = RecordFilterJavaScript.filterRow(user, filterExpr, jsonString);
}
return ret;
}
filterExpr来自result.getFilterExpr(),即存储在Ranger行过滤策略中的JavaScript表达式。拥有策略管理权限的攻击者可以将该表达式设置为其所需内容。当任何用户随后访问受该策略保护的资源时,hasAccessToRecord()被触发,从策略存储中取出filterExpr,然后filterRow()将其交给Nashorn,几乎没有任何沙箱。
攻击链:
策略管理权限
|
v
在嵌套结构资源上创建/修改行过滤策略
|
v
filterExpr设置为: java.lang.Runtime.getRuntime().exec("...")
|
v
任何用户访问资源 → hasAccessToRecord()被触发
|
v
filterExpr流入filterRow() → Nashorn执行 → RCE
修复跟踪为RANGER-4076: Remove Nashorn Script Engine,于2025年12月8日由Kishor Gollapalliwar提交。来自Apache邮件列表提交归档。三个文件变更,27处插入,81处删除。
commit 923a8473de2985cd389d45062cd4717d5ca13235
Author: Kishor Gollapalliwar
AuthorDate: Mon Dec 8 14:55:56 2025 +0530
RANGER-4076: 移除Nashorn脚本引擎
.../plugin/util/NashornScriptEngineCreator.java | 67 ----------------------
.../ranger/plugin/util/ScriptEngineUtil.java | 7 +--
.../authorizer/RecordFilterJavaScript.java | 34 ++++++++---
3 files changed, 27 insertions, 81 deletions(-)
NashornScriptEngineCreator.java 完全删除diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/NashornScriptEngineCreator.java
deleted file mode 100644
index b890fe85d..000000000
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/NashornScriptEngineCreator.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.apache.ranger.plugin.util;
-
-import jdk.nashorn.api.scripting.ClassFilter;
-import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
-
-public class NashornScriptEngineCreator implements ScriptEngineCreator {
-
- private static final String[] SCRIPT_ENGINE_ARGS = new String[] {
- "--no-java", "--no-syntax-extensions"
- };
- private static final String ENGINE_NAME = "NashornScriptEngine";
-
- @Override
- public ScriptEngine getScriptEngine(ClassLoader clsLoader) {
- ScriptEngine ret = null;
- if (clsLoader == null) {
- clsLoader = getDefaultClassLoader();
- }
- try {
- NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
- ret = factory.getScriptEngine(SCRIPT_ENGINE_ARGS, clsLoader, RangerClassFilter.INSTANCE);
- } catch (Throwable t) {
- LOG.debug("NashornScriptEngineCreator.getScriptEngine(): failed to create engine type {}", ENGINE_NAME, t);
- }
- return ret;
- }
-
- private static class RangerClassFilter implements ClassFilter {
- static final RangerClassFilter INSTANCE = new RangerClassFilter();
-
- @Override
- public boolean exposeToScripts(String className) {
- LOG.warn("script blocked: attempt to use Java class {}", className);
- return false;
- }
- }
-}
ScriptEngineUtil.java 从引擎创建者链中移除Nashorndiff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/ScriptEngineUtil.java
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/ScriptEngineUtil.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/ScriptEngineUtil.java
@@ -28,10 +28,9 @@
public class ScriptEngineUtil {