
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: