
CVE-2025-59059: تصحيح التحليل الساكن لثغرة RCE المنسوبة بشكل خاطئ في Apache Ranger
CVE: CVE-2025-59059
الإصدارات المتأثرة: Apache Ranger <= 2.7.0
تم الإصلاح في: Apache Ranger 2.8.0
CVSS (رسمي): 9.8 حرجة
الخطورة الفعلية (وفق الحجة): ~6.0 متوسطة — انظر التحليل أدناه
هذه المقالة ليست عن استغلال جديد أو PoC عملي. الهدف هنا هو تصحيح ما هو خاطئ في الإفصاح العام عن CVE-2025-59059، وتحديدًا أمران: تمت تسمية الفئة الخاطئة كمكوّن معرّض للثغرة، ودرجة CVSS لا تعكس قيود قابلية الاستغلال الفعلية. كل ما هو مذكور هنا مبني على تحليل ثابت لشفرة Apache Ranger 2.7.0 المصدرية وفرق التصحيح (patch diff) الذي دخل في الإصدار 2.8.0.
النشرة الأمنية الرسمية تقول:
"تم الإبلاغ عن ثغرة تنفيذ الأكواد عن بُعد في NashornScriptEngineCreator في Apache Ranger الإصدارات <= 2.7.0."
هذا خطأ، أو على الأقل مضلل. NashornScriptEngineCreator ليست الفئة المعرّضة للثغرة. إن وُجد شيء، فهي الأكثر تحصينًا بين المكوّنين المرتبطين بـ Nashorn في قاعدة الشيفرة — وسنصل إلى ذلك.
يستخدم Apache Ranger تعبيرات JavaScript لتقييم سياسات تصفية الصفوف (row-level filter policies). وهي قواعد تحدّد ما إذا كان مستخدم معيّن سيحصل على حق الوصول إلى سجلّ محدّد داخل مجموعة بيانات. لتشغيل تلك التعبيرات فعليًا على JVM، يضمّن Ranger محرّك JavaScript. في الإصدارات <= 2.7.0 يكون هذا المحرك هو Nashorn، محرّك JS المدمج من Oracle والذي جاء مع JDK 8 حتى 14.
أمر واحد تحذفه النشرة تمامًا — وهو أمر مهم جدًا لتحديد نطاق الأثر — هو أن jdk.nashorn.api.scripting.NashornScriptEngineFactory ليس جزءًا من شيفرة Ranger المصدرية إطلاقًا. إنه مكوّن مدمج في JDK. فقد كان مشمولًا في JDK 8، وأصبح مهملًا (deprecated) في JDK 11، وأُزيل كليًا في JDK 15. لذلك لا يمكن الوصول إلى هذه الثغرة إلا إذا كنت تشغّل Ranger على JDK 8 حتى 14. أي شيء يعمل على JDK 15+ غير متأثر لأن Nashorn ببساطة غير موجود هناك، وScriptEngineUtil يمرّر بصمت إلى GraalJS أو JavaScriptEngineCreator.
هناك فئتان في Ranger تستخدمان Nashorn، وتتعاملان معه بطريقة مختلفة جدًا.
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;
}
}
}
تقوم هذه الفئة فعليًا بثلاثة أشياء لتقوية المحرك:
--no-java — يقطع الوصول المباشر إلى فضاء الأسماء java.* من داخل السكربتات--no-syntax-extensions — يعطّل صيغة Nashorn غير القياسيةRangerClassFilter — يمنع كل الوصول إلى فئات Java على مستوى ClassFilter، ويعيد false لكل شيءهل هي منيعة تمامًا؟ لا. توجد طرق لتجاوز ClassFilter في Nashorn عبر سلاسل reflection وحيل 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-extensionsfilterExpr.contains("this.engine")filterExpr يُدمج مباشرة في السكربت الذي يقيّمه Nashornنظرًا لأن --no-java لا يُمرَّر، فإن فضاء الأسماء java.* يمكن الوصول إليه بالكامل. لا تحتاج إلى أي تقنية تجاوز إطلاقًا:
var runtime = java.lang.Runtime.getRuntime();
runtime.exec("id");
لا يوجد this.engine في أي مكان هنا. والقائمة السوداء غير ذات صلة إطلاقًا بمسار الهجوم هذا.
المثير للاهتمام أن المطوّرين كانوا يعرفون بوضوح نمط تجاوز واحدًا على الأقل. ملف الاختبار TestRecordFilterJavaScript.java يحتوي على هذا:
RecordFilterJavaScript.filterRow("user",
"this.engine.factory.scriptEngine.eval('java.lang.Runtime.getRuntime().exec(\"/Applications/Spotify.app/Contents/MacOS/Spotify\")')",
...);
هذا الاختبار موجود للتأكيد على أن this.engine يتم حظره. أما ما لم يتم حظره فهو المسار المباشر java.*، الذي لا يحتاج إلى this.engine إطلاقًا.
RecordFilterJavaScript.filterRow() تُستدعى من مكان واحد فقط:
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()، أي التعبير البرمجي JavaScript المخزّن في سياسة تصفية صفوف في Ranger. يمكن للمهاجم الذي يملك صلاحيات إدارة السياسات (policy admin) ضبط هذا التعبير على ما يشاء. عندما يصل أي مستخدم إلى مورد محكوم بتلك السياسة، يتم تشغيل hasAccessToRecord()، وتسحب filterExpr من مخزن السياسات، وتسلمها filterRow() إلى Nashorn دون أي عزل فعلي تقريبًا.
سلسلة الهجوم:
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
يُتتبَّع الإصلاح باسم RANGER-4076: Remove Nashorn Script Engine، وتم تنفيذه في 8 ديسمبر 2025 بواسطة Kishor Gollapalliwar. سُحب من أرشيف رسائل الالتزامات في القائمة البريدية لـ Apache. ثلاثة ملفات تغيّرت، 27 إضافة، 81 حذفًا.
commit 923a8473de2985cd389d45062cd4717d5ca13235
Author: Kishor Gollapalliwar
AuthorDate: Mon Dec 8 14:55:56 2025 +0530
RANGER-4076: Remove Nashorn Script Engine