
CVE: CVE-2025-59059
影響を受けるバージョン: Apache Ranger <= 2.7.0
修正版: Apache Ranger 2.8.0
CVSS(公式): 9.8 Critical
実際の深刻度(主張): ~6.0 Medium — 分析は以下を参照
この記事は新たなエクスプロイトや動作する PoC についてのものではない。ここでの目的は、CVE-2025-59059 に関する公開情報の誤りを訂正することだ。具体的には2点: 脆弱性コンポーネントとして誤ったクラスが指定されたこと、CVSS スコアが実際の悪用可能性の制約を反映していないこと。ここで述べる内容はすべて、Apache Ranger 2.7.0 ソースコードと 2.8.0 に取り込まれたパッチ差分の静的コード解析に基づく。
公式アドバイザリには次のように記載されている:
"NashornScriptEngineCreator のリモートコード実行の脆弱性が Apache Ranger バージョン <= 2.7.0 で報告されています。"
これは誤りであり、少なくとも誤解を招く。NashornScriptEngineCreator は脆弱性のあるクラスではない。どちらかと言えば、コードベース内の Nashorn 関連コンポーネントのうち、より堅牢化されている方だ — これについては後述する。
Apache Ranger は、行レベルフィルターポリシーを評価するために JavaScript 式を使用する。これらは、特定のユーザーがデータセット内の特定のレコードにアクセスできるかどうかを決定するルールだ。それらの式を JVM 上で実際に実行するために、Ranger は JavaScript エンジンを組み込んでいる。バージョン <= 2.7.0 では、そのエンジンは Nashorn だ。これは JDK 8 から 14 に同梱されていた Oracle の組み込み JS エンジンである。
アドバイザリが完全に省略している点 — そして影響範囲の評価において非常に重要な点 — は、jdk.nashorn.api.scripting.NashornScriptEngineFactory が Ranger のソースコードには一切含まれていないことだ。これは JDK の組み込み機能である。JDK 8 に含まれ、JDK 11 で非推奨となり、JDK 15 で完全に削除された。つまり、この脆弱性に到達できるのは JDK 8 から 14 で Ranger を実行している場合のみだ。JDK 15+ では Nashorn が存在しないため影響を受けず、 は黙って GraalJS または にフォールバックする。
ScriptEngineUtilJavaScriptEngineCreatorRanger 内で Nashorn を使用するクラスは2つあり、その扱いは大きく異なる。
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;
}
}
}
このクラスは実際にはエンジンを堅牢化するために3つのことを行っている:
--no-java — スクリプト内から java.* 名前空間への直接アクセスを遮断--no-syntax-extensions — 標準外の Nashorn 構文を無効化RangerClassFilter — ClassFilter レベルで全ての Java クラスアクセスをブロックし、すべてに対して false を返す完璧か? 否。Nashorn の ClassFilter バイパスは、リフレクションチェーンや java.lang.invoke のトリックを通じて存在する。しかし、もう一方のクラスにあるものと比べれば、意味のあるレベルでロックダウンされている。
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) {
// only checks for this one specific string
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...");
}
// instantiates Nashorn directly — no --no-java, no --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-extensions なしfilterExpr.contains("this.engine")filterExpr は Nashorn が評価するスクリプトに直接連結される--no-java が渡されていないため、java.* 名前空間には完全にアクセスできる。バイパス技術は一切必要ない:
var runtime = java.lang.Runtime.getRuntime();
runtime.exec("id");
この中に this.engine はどこにもない。ブラックリストはこの攻撃経路に対して完全に無関係だ。
興味深いのは、開発者が少なくとも1つのバイパスパターンを明確に認識していたことだ。テストファイル TestRecordFilterJavaScript.java には次の記述がある:
RecordFilterJavaScript.filterRow("user",
"this.engine.factory.scriptEngine.eval('java.lang.Runtime.getRuntime().exec(\"/Applications/Spotify.app/Contents/MacOS/Spotify\")')",
...);
このテストは this.engine がブロックされることを確認するために存在する。ブロックされなかったのは、this.engine を一切必要としない直接の java.* パスだ。
RecordFilterJavaScript.filterRow() が呼び出される場所は1箇所のみ:
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
nestedstructure リソースに対する行フィルターポリシーを作成/変更
|
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 メーリングリストのコミットアーカイブから取得した。変更ファイルは3つ、挿入27行、削除81行。
commit 923a8473de2985cd389d45062cd4717d5ca13235
Author: Kishor Gollapalliwar
AuthorDate: Mon Dec 8 14:55:56 2025 +0530
RANGER-4076: Remove Nashorn Script Engine
.../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 エンジン生成チェーンから Nashorn を削除diff --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 {
- private static final String SCRIPT_ENGINE_CREATOR_NASHHORN =
- "org.apache.ranger.plugin.util.NashornScriptEngineCreator";
private static final String SCRIPT_ENGINE_CREATOR_GRAAL =
"org.apache.ranger.plugin.util.GraalScriptEngineCreator";
private static final String SCRIPT_ENGINE_CREATOR_JS =
"org.apache.ranger.plugin.util.JavaScriptEngineCreator";
- private static final String[] SCRIPT_ENGINE_CREATORS = new String[] {
- SCRIPT_ENGINE_CREATOR_NASHHORN,
- SCRIPT_ENGINE_CREATOR_GRAAL,
- SCRIPT_ENGINE_CREATOR_JS
- };
+ private static final String[] SCRIPT_ENGINE_CREATORS = new String[] {
+ SCRIPT_ENGINE_CREATOR_GRAAL,
+ SCRIPT_ENGINE_CREATOR_JS
+ };
@@ -108,9 +107,7 @@ private static void initScriptEngineCreator(String serviceType) {
} catch (Throwable t) {
boolean logWarn;
- if (creatorClsName.equals(SCRIPT_ENGINE_CREATOR_NASHHORN)) {
- logWarn = JVM_MAJOR_CLASS_VERSION < JVM_MAJOR_CLASS_VERSION_JDK15;
- } else if (creatorClsName.equals(SCRIPT_ENGINE_CREATOR_GRAAL)) {
+ if (creatorClsName.equals(SCRIPT_ENGINE_CREATOR_GRAAL)) {
logWarn = JVM_MAJOR_CLASS_VERSION >= JVM_MAJOR_CLASS_VERSION_JDK15;
} else {
logWarn = true;
RecordFilterJavaScript.java 本当の修正ここが実際のセキュリティ変更箇所だ。直接の NashornScriptEngineFactory 呼び出しは GraalJS に置き換えられ、SecurityFilter は ClassFilter としての役割を完全に失い、文字列チェックのみを残した通常のクラスに格下げされている。
diff --git a/plugin-nestedstructure/src/main/java/org/apache/ranger/authorization/nestedstructure/authorizer/RecordFilterJavaScript.java
--- a/plugin-nestedstructure/.../RecordFilterJavaScript.java
+++ b/plugin-nestedstructure/.../RecordFilterJavaScript.java
@@ -18,13 +18,16 @@
-import jdk.nashorn.api.scripting.ClassFilter;
-import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngineManager;
+import java.util.HashMap;
+import java.util.Map;
@@ -54,8 +57,25 @@
if (securityFilter.containsMalware(filterExpr)) {
throw new MaskingException("cannot process filter expression...");
}
- NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
- ScriptEngine engine = factory.getScriptEngine(securityFilter);
+ ClassLoader clsLoader = Thread.currentThread().getContextClassLoader();
+ ScriptEngineManager mgr = new ScriptEngineManager(clsLoader);
+ ScriptEngine engine = mgr.getEngineByName("graal.js");
+
+ if (engine != null) {
+ try {
+ Map<String, Boolean> graalVmConfigs = new HashMap<>();
+ graalVmConfigs.put("polyglot.js.allowHostAccess", Boolean.TRUE);
+ graalVmConfigs.put("polyglot.js.nashorn-compat", Boolean.TRUE);
+
+ Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
+ bindings.putAll(graalVmConfigs);
+ engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
+ } catch (Throwable t) {
+ logger.debug("RecordFilterJavaScript.filterRow(): failed to create engine type {}", "graal.js", t);
+ }
+ }
@@ -83,12 +103,8 @@
- static class SecurityFilter implements ClassFilter {
- @Override
- public boolean exposeToScripts(String s) {
- return false;
- }
-
+ static class SecurityFilter {
boolean containsMalware(String filterExpr) {
return filterExpr.contains("this.engine");
}
}
この差分から指摘しておく価値のある点がいくつかある:
SecurityFilter はもはや ClassFilter ではない — エンジンレベルのフックを失い、文字列チェックだけが残った通常のクラスになるScriptEngineManager に置き換えられたnashorn-compat モードで動作するpolyglot.js.allowHostAccess は true に設定されている — これは後方互換性のためのトレードオフであり、ClassFilter ではなく GraalJS 自身のサンドボックスに依存している。修正の品質を気にするなら注目しておく価値があるこのパッチは、RecordFilterJavaScript が実際のセキュリティ修正対象であったことを明確に示している。NashornScriptEngineCreator の削除は整理整頓であって、修正ではない。
正直なところ、どうしてこうなったかは理解しやすい。NashornScriptEngineCreator は agents-common 内にあり、コードベース全体で最も明白に Nashorn と名付けられたクラスであり、Nashorn を基本的な grep で検索すれば即座に表面化する。一方、RecordFilterJavaScript は別のサブモジュールである plugin-nestedstructure 内にあり、クラス名には Nashorn を連想させるものは何もない。コードパスを実際に追わずに簡単なトリアージを行えば、おそらく NashornScriptEngineCreator にたどり着いてそこで止まるだろう。
ここで起きたのはまさにそれのようだ。脆弱性のあるクラスと、アドバイザリで名前が挙げられたクラスは別物だ。
9.8 は、ネットワークから到達可能、認証不要、ユーザー操作不要を意味する。現実はかなり異なる:
| 要素 | 現実 |
|---|---|
| 認証 | Ranger のポリシー管理者権限が必要 |
| プラグイン要件 | plugin-nestedstructure はデフォルトでは有効化されていない |
| JDK 制約 | JDK 8–14 でのみ悪用可能、JDK 15+ は影響なし |
| ネットワーク露出 | FOFA フィンガープリンティングで公開された Ranger インスタンスは約35件 |
これらのいずれもアドバイザリには反映されていない。実際に悪用するにはポリシー管理者アクセスが必要であり、これは Ranger デプロイメント内では相当高い権限レベルだ。これは認証なしの RCE ではない。認証要件と非デフォルトのプラグインを考慮すると、6.0〜7.0 程度がより誠実なスコアだろう。
| アドバイザリの主張 | 実際の調査結果 | |
|---|---|---|
| 脆弱性のあるクラス | NashornScriptEngineCreator | RecordFilterJavaScript |
| 攻撃タイプ | 認証なし RCE | 認証済み RCE (ポリシー管理者が必要) |
| 影響を受ける JDK | 指定なし | JDK 8–14 のみ |
| 必要なプラグイン | 指定なし | plugin-nestedstructure (非デフォルト) |
| CVSS | 9.8 Critical | ~6.0 Medium (主張) |
| インターネット露出 | 指定なし | 約35インスタンス (FOFA) |
脆弱性は実在し、2.8.0 へのアップグレードが正しい対応だ。しかし、アドバイザリはクラス名を誤っており、深刻度スコアも実際に悪用するために必要な条件を反映していない。