Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2025-59059-Misattributed-RCE-in-Apache-Ranger-Static-Analysis-Correction — CVE-2025-59059: Misattributed RCE in Apache Ranger Static Analysis Correction | Kitploit
Tools/GitHubGitHub/pl4tyz/cve-2025-59059-misattributed-rce-in-apache-ranger-static-analysis-correction
Static AnalysisVulnerability AnalysisCode AnalysisExploitationWeb SecurityLearning & Education
GitHubpl4tyz/cve-2025-59059-misattributed-rce-in-apache-ranger-static-analysis-correction

CVE-2025-59059-Misattributed-RCE-in-Apache-Ranger-Static-Analysis-Correction

CVE-2025-59059: Misattributed RCE in Apache Ranger Static Analysis Correction

View Repository
155 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2025-59059: Misattributed RCE in Apache Ranger a correction

CVE: CVE-2025-59059
Affected versions: Apache Ranger <= 2.7.0
Fixed in: Apache Ranger 2.8.0
CVSS (official): 9.8 Critical
Actual severity (argued): ~6.0 Medium — see analysis below


Preface

this write-up isnt about a new exploit or a working PoC. the point here is to correct whats wrong with the public disclosure on CVE-2025-59059 specifically two things: the wrong class got named as the vulnerable component, and the CVSS score doesnt reflect the actual exploitability constraints. everything here is based on static code analysis of the Apache Ranger 2.7.0 source and the patch diff that went into 2.8.0.


What the advisory says

the official advisory reads:

"Remote Code Execution Vulnerability in NashornScriptEngineCreator is reported in Apache Ranger versions <= 2.7.0."

this is wrong, or at least misleading. NashornScriptEngineCreator is not the vulnerable class. if anything its the better hardened of the two Nashorn-related components in the codebase — which we'll get into.


Background: Nashorn in Apache Ranger

Apache Ranger uses JavaScript expressions to evaluate row-level filter policies. these are rules that decide whether a given user gets access to a specific record in a dataset. to actually run those expressions on the JVM, Ranger embeds a JavaScript engine. in versions <= 2.7.0 that engine is Nashorn, Oracles built-in JS engine that shipped with JDK 8 through 14.

one thing the advisory completly omits — and this matters a lot for scoping impact — is that jdk.nashorn.api.scripting.NashornScriptEngineFactory is not part of Rangers source code at all. its a JDK built-in. it was included in JDK 8, deprecated in JDK 11, and fully removed in JDK 15. so this vuln is only reachable if youre running Ranger on JDK 8 through 14. anything on JDK 15+ isnt affected because Nashorn simply doesnt exist there, and ScriptEngineUtil just falls through to GraalJS or JavaScriptEngineCreator silently.

two classes in Ranger use Nashorn. they treat it very differently.


The two Nashorn-related classes

1. NashornScriptEngineCreator — the named but safer one

located at:

root@kitploit:~
agents-common/src/main/java/org/apache/ranger/plugin/util/NashornScriptEngineCreator.java
root@kitploit:~
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;
        }
    }
}

this class actually does three things to harden the engine:

  • --no-java — cuts off direct access to the java.* namespace from inside scripts
  • --no-syntax-extensions — disables non-standard Nashorn syntax
  • RangerClassFilter — blocks all Java class access at the ClassFilter level, returns false for everything

is it bulletproof? no. Nashorn ClassFilter bypasses exist through reflection chains and java.lang.invoke tricks. but its meaningfully more locked down than what we find in the other class.


2. RecordFilterJavaScript the actual vulnerable class

located at:

root@kitploit:~
plugin-nestedstructure/src/main/java/org/apache/ranger/authorization/nestedstructure/authorizer/RecordFilterJavaScript.java
root@kitploit:~
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);
        ...
    }
}

compare this to NashornScriptEngineCreator and the difference is pretty obvious:

  • no --no-java flag java.* namespace is fully open to scripts
  • no --no-syntax-extensions
  • the only "security" is one string check: filterExpr.contains("this.engine")
  • filterExpr gets concatenated directly into the script that Nashorn evaluates

because --no-java isnt passed, the java.* namespace is fully accessible. you dont need any bypass technique at all:

root@kitploit:~
var runtime = java.lang.Runtime.getRuntime();
runtime.exec("id");

no this.engine anywhere in there. the blacklist is completely irrelevent to this attack path.

whats interesting is that the developers clearly knew about at least one bypass pattern. the test file TestRecordFilterJavaScript.java has this:

root@kitploit:~
RecordFilterJavaScript.filterRow("user",
    "this.engine.factory.scriptEngine.eval('java.lang.Runtime.getRuntime().exec(\"/Applications/Spotify.app/Contents/MacOS/Spotify\")')",
    ...);

that test exists to confirm this.engine gets blocked. what wasnt blocked is the direct java.* path, which doesnt need this.engine at all.


The entry point

RecordFilterJavaScript.filterRow() is only called from one place:

root@kitploit:~
plugin-nestedstructure/src/main/java/org/apache/ranger/authorization/nestedstructure/authorizer/NestedStructureAuthorizer.java
root@kitploit:~
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 comes from result.getFilterExpr() thats the JavaScript expression stored in a Ranger row filter policy. an attacker with policy admin privileges can set that expression to whatever they want. when any user then hits a resource governed by that policy, hasAccessToRecord() fires, pulls filterExpr from the policy store, and filterRow() hands it to Nashorn with basically no sandboxing.

attack chain:

root@kitploit:~
Policy admin access
       |
       v
Create/modify a row filter policy on a nestedstructure resource
       |
       v
filterExpr set to: java.lang.Runtime.getRuntime().exec("...")
       |
       v
Any user accesses the resource → hasAccessToRecord() triggered
       |
       v
filterExpr flows into filterRow() → Nashorn evaluates it → RCE

The patch diff

the fix is tracked as RANGER-4076: Remove Nashorn Script Engine, commited December 8, 2025 by Kishor Gollapalliwar. pulled from the Apache mailing list commit archive. three files changed, 27 insertions, 81 deletions.

root@kitploit:~
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(-)

File 1: NashornScriptEngineCreator.java deleted entirely

root@kitploit:~
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;
-        }
-    }
-}

File 2: ScriptEngineUtil.java Nashorn removed from the engine creator chain

root@kitploit:~
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;

File 3: RecordFilterJavaScript.java the real fix

this is where the actual security change is. the direct NashornScriptEngineFactory call gets replaced with GraalJS, and SecurityFilter loses its ClassFilter role entirely gets demoted to a plain class with just the string check.

root@kitploit:~
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");
         }
     }

few things worth calling out from this diff:

  • SecurityFilter is no longer a ClassFilter loses its engine-level hook and becomes just a regular class with only the string check left
  • Nashorn imports are gone, replaced with ScriptEngineManager
  • GraalJS runs in nashorn-compat mode for backward compat with existing policy expressions
  • polyglot.js.allowHostAccess is set to true this is a tradeoff for backward compat, it relies on GraalJS's own sandbox rather than a ClassFilter. worth keeping an eye on if you care about the fix quality

the patch makes it clear that RecordFilterJavaScript was the actual security target. deleting NashornScriptEngineCreator was cleanup, not the fix.


Why the CVE named the wrong class

honestly its not that hard to see how this happened. NashornScriptEngineCreator is sitting right in agents-common, its the most obviously named Nashorn class in the whole codebase, and a basic grep for Nashorn would surface it immediately. RecordFilterJavaScript on the other hand lives in plugin-nestedstructure which is a separate submodule, and theres nothing in the class name that hints at Nashorn. if someone did a quick triage without actually following the code path they'd probably land on NashornScriptEngineCreator and stop there.

thats appears to be what happened here. the vulnerable class and the class that got named in the advisory are two different things.


Why the CVSS 9.8 doesnt hold up

a 9.8 implies network reachable, no auth required, no user interaction needed. the reality is quite different:

FactorReality
Authenticationrequires policy admin privileges in Ranger
Plugin requirementplugin-nestedstructure is not enabled by default
JDK constraintonly exploitable on JDK 8–14, JDK 15+ is unaffected
Network exposureFOFA fingerprinting returned ~35 publicly exposed Ranger instances

none of that is reflected in the advisory. to actually exploit this you need policy admin access, which is a significant privilege level inside a Ranger deployment. this isnt unauthenticated RCE. factoring in the auth requirement and the non-default plugin, something in the 6.0–7.0 range would be a more honest score.


Summary

Advisory claimActual finding
Vulnerable classNashornScriptEngineCreatorRecordFilterJavaScript
Attack typeUnauthenticated RCEAuthenticated RCE (policy admin required)
Affected JDKNot specifiedJDK 8–14 only
Plugin requiredNot specifiedplugin-nestedstructure (non-default)
CVSS9.8 Critical~6.0 Medium (argued)
Internet exposureNot specified~35 instances (FOFA)

the vuln is real and upgrading to 2.8.0 is the right call. but the advisory gets the class wrong and the severity score doesnt reflect whats actually required to exploit this.


References

  • Apache Ranger 2.8.0 release: https://ranger.apache.org/download.html
  • Patch commit (RANGER-4076): https://gitbox.apache.org/repos/asf/ranger.git
  • CVE entry: https://www.cve.org/CVERecord?id=CVE-2025-59059
  • Apache mailing list thread: https://lists.apache.org/thread/z47q86rho80390lf2qcmoc2josvs0gtv
Download Tool