From 441e1fa1716d250785f0f1b34f2b9c8d090d6c8f Mon Sep 17 00:00:00 2001 From: Nguyen Van Nam Date: Sun, 17 May 2026 03:49:38 +0700 Subject: [PATCH] fix(security): unsandboxed jsr223 script execution enables arbitr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JSR223ScriptExecutor.load() compiles arbitrary script strings via Compilable.compile() and execute() runs them via eval() with no ClassFilter, sandbox, or restricted ScriptContext. The bindings expose `_meta`, `args`, and `extParam`, but Nashorn/JS engines by default give scripts full access to Java reflection (e.g., Java.type('java.lang.Runtime').getRuntime().exec(...)). Comments in Operation.java explicitly warn 'JDK 8~13 可用自带 Nashorn 这个 js 引擎,注意配置 ClassFilter 防脚本注入攻击', but no ClassFilter is configured here. If script content is sourced from a database row, request payload, or any user-influenced channel (which the IF/CODE Operation suggests), this becomes RCE. Affected files: JSR223ScriptExecutor.java Signed-off-by: Nguyen Van Nam --- .../orm/script/JSR223ScriptExecutor.java | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/script/JSR223ScriptExecutor.java b/APIJSONORM/src/main/java/apijson/orm/script/JSR223ScriptExecutor.java index 9c2c9baf..7e08a945 100644 --- a/APIJSONORM/src/main/java/apijson/orm/script/JSR223ScriptExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/script/JSR223ScriptExecutor.java @@ -27,11 +27,34 @@ public abstract class JSR223ScriptExecutor, L e @Override public ScriptExecutor init() { - ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); - scriptEngine = scriptEngineManager.getEngineByName(scriptEngineName()); + scriptEngine = createScriptEngine(); return this; } + protected ScriptEngine createScriptEngine() { + String name = scriptEngineName(); + if ("nashorn".equalsIgnoreCase(name) || "javascript".equalsIgnoreCase(name) + || "js".equalsIgnoreCase(name) || "ecmascript".equalsIgnoreCase(name)) { + try { + Class factoryClass = Class.forName("jdk.nashorn.api.scripting.NashornScriptEngineFactory"); + Class filterClass = Class.forName("jdk.nashorn.api.scripting.ClassFilter"); + Object filter = java.lang.reflect.Proxy.newProxyInstance( + filterClass.getClassLoader(), + new Class[]{filterClass}, + (proxy, method, methodArgs) -> isClassExposureAllowed((String) methodArgs[0])); + Object factory = factoryClass.getDeclaredConstructor().newInstance(); + return (ScriptEngine) factoryClass.getMethod("getScriptEngine", filterClass).invoke(factory, filter); + } catch (Throwable e) { + Log.e(TAG, "create sandboxed Nashorn engine failed, falling back: " + e); + } + } + return new ScriptEngineManager().getEngineByName(name); + } + + protected boolean isClassExposureAllowed(String className) { + return false; + } + protected abstract String scriptEngineName(); protected abstract Object extendParameter(AbstractFunctionParser parser, Map currentObject, String methodName, Object[] args);