
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 が存在しないため影響を受けず、ScriptEngineUtil は黙って GraalJS または JavaScriptEngineCreator にフォールバックする。
Ranger 内で 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 完全削除