
CVE-2025-59059: Attribuzione errata di RCE nella correzione dell'analisi statica di Apache Ranger
CVE: CVE-2025-59059
Versioni interessate: Apache Ranger <= 2.7.0
Corretto in: Apache Ranger 2.8.0
CVSS (ufficiale): 9.8 Critico
Gravità effettiva (argomentata): ~6.0 Media — vedi analisi sotto
questo articolo non riguarda un nuovo exploit o un PoC funzionante. il punto è correggere ciò che non va nella divulgazione pubblica di CVE-2025-59059, in particolare due cose: è stata indicata la classe sbagliata come componente vulnerabile, e il punteggio CVSS non riflette i reali vincoli di sfruttabilità. tutto ciò si basa sull'analisi statica del codice sorgente di Apache Ranger 2.7.0 e sul diff della patch integrata in 2.8.0.
l'avviso ufficiale recita:
"Remote Code Execution Vulnerability in NashornScriptEngineCreator è segnalata in Apache Ranger versioni <= 2.7.0."
questo è sbagliato, o quantomeno fuorviante. NashornScriptEngineCreator non è la classe vulnerabile. semmai è quella più robusta tra i due componenti Nashorn presenti nel codice — come vedremo.
Apache Ranger utilizza espressioni JavaScript per valutare le policy di filtro a livello di riga. si tratta di regole che decidono se un determinato utente può accedere a un record specifico in un dataset. Per eseguire effettivamente tali espressioni sulla JVM, Ranger incorpora un motore JavaScript. nelle versioni <= 2.7.0 quel motore è Nashorn, il motore JS integrato di Oracle fornito con JDK 8 fino a 14.
una cosa che l'avviso omette completamente — e questo conta molto per la delimitazione dell'impatto — è che jdk.nashorn.api.scripting.NashornScriptEngineFactory non fa affatto parte del codice sorgente di Ranger. è un componente integrato del JDK. è stato incluso in JDK 8, deprecato in JDK 11 e rimosso completamente in JDK 15. quindi questa vulnerabilità è sfruttabile solo se si esegue Ranger su JDK 8–14. qualsiasi cosa su JDK 15+ non è interessata perché Nashorn semplicemente non esiste, e ScriptEngineUtil passa silenziosamente a GraalJS o JavaScriptEngineCreator.
due classi in Ranger utilizzano Nashorn. lo trattano in modo molto diverso.
NashornScriptEngineCreator — quella nominata ma più sicuraposizionata in:
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 bloccato: tentativo di usare la classe Java {}", className);
return false;
}
}
}
questa classe in realtà fa tre cose per indurire il motore:
--no-java — taglia l'accesso diretto al namespace java.* dagli script--no-syntax-extensions — disabilita le estensioni sintattiche non standard di NashornRangerClassFilter — blocca ogni accesso alle classi Java a livello di ClassFilter, restituisce false per tuttoè a prova di proiettile? no. esistono bypass di Nashorn ClassFilter attraverso catene di reflection e trucchi con java.lang.invoke. ma è significativamente più bloccato rispetto a ciò che troviamo nell'altra classe.
RecordFilterJavaScript — la classe effettivamente vulnerabileposizionata in:
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) {
// controlla solo questa specifica stringa
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("impossibile elaborare l'espressione di filtro per motivi di sicurezza...");
}
// istanzia Nashorn direttamente — senza --no-java, senza --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);
...
}
}
confrontandola con NashornScriptEngineCreator la differenza è piuttosto evidente:
--no-java: il namespace java.* è completamente aperto agli script--no-syntax-extensionsfilterExpr.contains("this.engine")filterExpr viene concatenato direttamente nello script che Nashorn valutapoiché --no-java non è passato, il namespace java.* è completamente accessibile. non è necessaria alcuna tecnica di bypass:
var runtime = java.lang.Runtime.getRuntime();
runtime.exec("id");
nessun this.engine qui. la blacklist è completamente irrilevante per questo percorso d'attacco.
ciò che è interessante è che gli sviluppatori erano chiaramente a conoscenza di almeno un modello di bypass. il file di test TestRecordFilterJavaScript.java contiene quanto segue:
RecordFilterJavaScript.filterRow("user",
"this.engine.factory.scriptEngine.eval('java.lang.Runtime.getRuntime().exec(\"/Applications/Spotify.app/Contents/MacOS/Spotify\")')",
...);
quel test esiste per confermare che this.engine viene bloccato. ciò che non è stato bloccato è il percorso diretto java.*, che non necessita affatto di this.engine.
RecordFilterJavaScript.filterRow() viene chiamato solo da un punto:
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 proviene da result.getFilterExpr(): è l'espressione JavaScript memorizzata in una policy di filtro righe di Ranger. un attaccante con privilegi di amministratore delle policy può impostare quell'espressione a piacere. quando un qualsiasi utente accede successivamente a una risorsa governata da quella policy, hasAccessToRecord() si attiva, recupera filterExpr dall'archivio delle policy e filterRow() la passa a Nashorn praticamente senza sandboxing.
Catena d'attacco:
Accesso come amministratore delle policy
|
v
Creare/modificare una policy di filtro righe su una risorsa nestedstructure
|
v
filterExpr impostata a: java.lang.Runtime.getRuntime().exec("...")
|
v
Qualsiasi utente accede alla risorsa → hasAccessToRecord() attivato
|
v
filterExpr fluisce in filterRow() → Nashorn la valuta → RCE
la correzione è tracciata come RANGER-4076: Remove Nashorn Script Engine, commit effettuato l'8 dicembre 2025 da Kishor Gollapalliwar. recuperato dall'archivio dei commit della mailing list Apache. tre file modificati, 27 inserimenti, 81 cancellazioni.
commit 923a8473de2985cd389d45062cd4717d5ca13235
Author: Kishor Gollapalliwar
AuthorDate: Mon Dec 8 14:55:56 2025 +0530
RANGER-4076: Rimuovere 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 eliminato completamentediff --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(): impossibile creare il motore tipo {}", 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 bloccato: tentativo di usare la classe Java {}", className);
- return false;
- }
- }
-}
ScriptEngineUtil.java Nashorn rimosso dalla catena dei creatori di motoridiff --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 — la vera correzionequi è dove avviene il vero cambiamento di sicurezza. la chiamata diretta a NashornScriptEngineFactory viene sostituita con GraalJS, e SecurityFilter perde completamente il suo ruolo di ClassFilter – viene declassato a una classe normale con solo il controllo sulla stringa.
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("impossibile elaborare l'espressione di filtro...");
}
- 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(): impossibile creare il motore tipo {}", "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");
}
}
alcune cose degne di nota da questo diff:
SecurityFilter non è più un ClassFilter – perde il suo hook a livello di motore e diventa solo una classe normale con il solo controllo sulla stringaScriptEngineManagernashorn-compat per la compatibilità con le espressioni di policy esistentipolyglot.js.allowHostAccess è impostato su true – questo è un compromesso per la compatibilità, si affida alla sandbox di GraalJS piuttosto che a un ClassFilter. vale la pena tenerlo d'occhio se ci interessa la qualità della correzionela patch chiarisce che RecordFilterJavaScript era il bersaglio di sicurezza effettivo. eliminare NashornScriptEngineCreator è stata una pulizia, non la correzione.
onestamente non è difficile capire come sia successo. NashornScriptEngineCreator si trova proprio in agents-common, è la classe Nashorn più ovvia in tutto il codebase, e una semplice grep per Nashorn la farebbe emergere immediatamente. RecordFilterJavaScript invece si trova in plugin-nestedstructure, che è un sottomodulo separato, e non c'è nulla nel nome della classe che suggerisca Nashorn. se qualcuno ha fatto un triage rapido senza seguire effettivamente il percorso del codice, probabilmente si sarebbe fermato su NashornScriptEngineCreator.
sembra essere ciò che è successo qui. la classe vulnerabile e la classe nominata nell'avviso sono due cose diverse.
un 9.8 implica accessibile in rete, nessuna autenticazione richiesta, nessuna interazione con l'utente. la realtà è ben diversa:
| Fattore | Realtà |
|---|---|
| Autenticazione | richiede privilegi di amministratore delle policy in Ranger |
| Plugin richiesto | plugin-nestedstructure non è abilitato per impostazione predefinita |
| Vincolo JDK | sfruttabile solo su JDK 8–14, JDK 15+ non è interessato |
| Esposizione di rete | Il fingerprinting FOFA ha restituito circa 35 istanze Ranger pubbliche |
niente di tutto ciò è riflesso nell'avviso. per sfruttare effettivamente questa vulnerabilità è necessario l'accesso come amministratore delle policy, che è un livello di privilegio significativo all'interno di un deployment Ranger. non si tratta di RCE non autenticato. considerando il requisito di autenticazione e il plugin non predefinito, un punteggio nell'intervallo 6.0–7.0 sarebbe più onesto.
| Affermazione dell'avviso | Riscontro effettivo | |
|---|---|---|
| Classe vulnerabile | NashornScriptEngineCreator | RecordFilterJavaScript |
| Tipo di attacco | RCE non autenticato | RCE autenticato (amministratore policy richiesto) |
| JDK interessato | Non specificato | Solo JDK 8–14 |
| Plugin richiesto | Non specificato | plugin-nestedstructure (non predefinito) |
| CVSS | 9.8 Critico | ~6.0 Medio (argomentato) |
| Esposizione Internet | Non specificata | ~35 istanze (FOFA) |
la vulnerabilità è reale e l'aggiornamento alla 2.8.0 è la scelta giusta. ma l'avviso sbaglia la classe e il punteggio di gravità non riflette ciò che è effettivamente necessario per sfruttarla.