java javascript nashorn

java - Asegurar la ejecución de Nashorn JS



javascript (8)

Agregado en 1.8u40 , puede usar el ClassFilter para restringir qué clases puede usar el motor.

Aquí hay un ejemplo de la documentación de Oracle :

import javax.script.ScriptEngine; import jdk.nashorn.api.scripting.ClassFilter; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; public class MyClassFilterTest { class MyCF implements ClassFilter { @Override public boolean exposeToScripts(String s) { if (s.compareTo("java.io.File") == 0) return false; return true; } } public void testClassFilter() { final String script = "print(java.lang.System.getProperty(/"java.home/"));" + "print(/"Create file variable/");" + "var File = Java.type(/"java.io.File/");"; NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); ScriptEngine engine = factory.getScriptEngine( new MyClassFilterTest.MyCF()); try { engine.eval(script); } catch (Exception e) { System.out.println("Exception caught: " + e.toString()); } } public static void main(String[] args) { MyClassFilterTest myApp = new MyClassFilterTest(); myApp.testClassFilter(); } }

Este ejemplo imprime lo siguiente:

C:/Java/jre8 Create file variable Exception caught: java.lang.RuntimeException: java.lang.ClassNotFoundException: java.io.File

¿Cómo puedo ejecutar de forma segura algún código JS provisto por el usuario utilizando Java8 Nashorn?

El script extiende algunos cálculos para algunos informes basados ​​en servlets. La aplicación tiene muchos usuarios diferentes (no confiables). Los scripts solo deben poder acceder a un objeto Java y a los que devuelven los miembros definidos. Por defecto, los scripts podrían crear una instancia de cualquier clase usando Class.forName () (usando .getClass () de mi objeto suministrado). ¿Hay alguna forma de prohibir el acceso a cualquier clase de Java que no haya sido explícitamente especificada por mí?


Hace un tiempo atrás hice esta pregunta en la lista de correo de Nashorn :

¿Hay recomendaciones para la mejor manera de restringir las clases que los scripts de Nashorn pueden crear en una lista blanca? ¿O es el enfoque el mismo que cualquier motor JSR223 (cargador de clases personalizado en el constructor ScriptEngineManager)?

Y obtuve esta respuesta de uno de los desarrolladores de Nashorn:

Hola,

  • Nashorn ya filtra las clases, solo las clases públicas de paquetes no confidenciales (los paquetes se enumeran en la propiedad de seguridad package.access, también conocida como "sensible"). La verificación de acceso al paquete se realiza desde un contexto sin permisos. es decir, solo se permite cualquier paquete al que se pueda acceder desde una clase sin permiso.

  • Nashorn filtra el acceso reflexivo y jsr292 de Java, a menos que el script tenga RuntimePermission ("nashorn.JavaReflection"), el script no podrá hacer una reflexión.

  • Los dos anteriores requieren la ejecución con SecurityManager habilitado. Bajo ningún administrador de seguridad, el filtrado anterior no se aplicará.

  • Puede eliminar la función Java.type global y el objeto Paquetes (+ com, edu, java, javafx, javax, org, JavaImporter) en el alcance global y / o reemplazar aquellos con cualquier función de filtrado que implemente. Debido a que, estos son los únicos puntos de entrada al acceso a Java desde el script, personalizando estas funciones => filtrando el acceso de Java desde los scripts.

  • Hay una opción no documentada (ahora se usa solo para ejecutar pruebas test262) "--no-java" de shell nashorn que hace lo anterior por usted. Es decir, Nashorn no inicializará los enlaces de Java en el ámbito global.

  • JSR223 no proporciona ningún enganche basado en estándares para pasar un cargador de clases personalizado. Es posible que esto deba solucionarse en una (posible) futura actualización de jsr223.

Espero que esto ayude,

-Sundar


He investigado formas de permitir que los usuarios escriban un script simple en un arenero que permita el acceso a algunos objetos básicos proporcionados por mi aplicación (de la misma manera que funciona Google Apps Script ). Mi conclusión fue que esto es más fácil / mejor documentado con Rhino que con Nashorn. Usted puede:

  1. Defina un obturador de clase para evitar el acceso a otras clases: http://codeutopia.net/blog/2009/01/02/sandboxing-rhino-in-java/

  2. Limite el número de instrucciones para evitar endess-loops con observeInstructionCount: http://www-archive.mozilla.org/rhino/apidocs/org/mozilla/javascript/ContextFactory.html

Sin embargo, tenga en cuenta que esto no es suficiente para los usuarios que no son de confianza, ya que aún pueden (por accidente o a propósito) asignar una gran cantidad de memoria, lo que hace que su JVM genere un OutOfMemoryError. Todavía no he encontrado una solución segura para este último punto.


La mejor manera de asegurar una ejecución JS en Nashorn es habilitar SecurityManager y permitir que Nashorn niegue las operaciones críticas. Además, puede crear una clase de monitoreo que verifique el tiempo de ejecución del script y la memoria para evitar bucles infinitos y outOfMemory. En caso de que lo ejecute en un entorno restringido sin posibilidad de configurar SecurityManager, puede pensar en utilizar el ClassFilter de Nashorn para denegar el acceso total / parcial a las clases de Java. Además de eso, debe sobrescribir todas las funciones críticas de JS (como quit () etc.). Eche un vistazo a esta función que gestiona todos estos aspectos (excepto la gestión de memoria):

public static Object javascriptSafeEval(HashMap<String, Object> parameters, String algorithm, boolean enableSecurityManager, boolean disableCriticalJSFunctions, boolean disableLoadJSFunctions, boolean defaultDenyJavaClasses, List<String> javaClassesExceptionList, int maxAllowedExecTimeInSeconds) throws Exception { System.setProperty("java.net.useSystemProxies", "true"); Policy originalPolicy = null; if(enableSecurityManager) { ProtectionDomain currentProtectionDomain = this.getClass().getProtectionDomain(); originalPolicy = Policy.getPolicy(); final Policy orinalPolicyFinal = originalPolicy; Policy.setPolicy(new Policy() { @Override public boolean implies(ProtectionDomain domain, Permission permission) { if(domain.equals(currentProtectionDomain)) return true; return orinalPolicyFinal.implies(domain, permission); } }); } try { SecurityManager originalSecurityManager = null; if(enableSecurityManager) { originalSecurityManager = System.getSecurityManager(); System.setSecurityManager(new SecurityManager() { //allow only the opening of a socket connection (required by the JS function load()) @Override public void checkConnect(String host, int port, Object context) {} @Override public void checkConnect(String host, int port) {} }); } try { ScriptEngine engineReflex = null; try{ Class<?> nashornScriptEngineFactoryClass = Class.forName("jdk.nashorn.api.scripting.NashornScriptEngineFactory"); Class<?> classFilterClass = Class.forName("jdk.nashorn.api.scripting.ClassFilter"); engineReflex = (ScriptEngine)nashornScriptEngineFactoryClass.getDeclaredMethod("getScriptEngine", new Class[]{Class.forName("jdk.nashorn.api.scripting.ClassFilter")}).invoke(nashornScriptEngineFactoryClass.newInstance(), Proxy.newProxyInstance(classFilterClass.getClassLoader(), new Class[]{classFilterClass}, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("exposeToScripts")) { if(javaClassesExceptionList != null && javaClassesExceptionList.contains(args[0])) return defaultDenyJavaClasses; return !defaultDenyJavaClasses; } throw new RuntimeException("no method found"); } })); /* engine = new jdk.nashorn.api.scripting.NashornScriptEngineFactory().getScriptEngine(new jdk.nashorn.api.scripting.ClassFilter() { @Override public boolean exposeToScripts(String arg0) { ... } }); */ }catch(Exception ex) { throw new Exception("Impossible to initialize the Nashorn Engine: " + ex.getMessage()); } final ScriptEngine engine = engineReflex; if(parameters != null) for(Entry<String, Object> entry : parameters.entrySet()) engine.put(entry.getKey(), entry.getValue()); if(disableCriticalJSFunctions) engine.eval("quit=function(){throw ''quit() not allowed'';};exit=function(){throw ''exit() not allowed'';};print=function(){throw ''print() not allowed'';};echo=function(){throw ''echo() not allowed'';};readFully=function(){throw ''readFully() not allowed'';};readLine=function(){throw ''readLine() not allowed'';};$ARG=null;$ENV=null;$EXEC=null;$OPTIONS=null;$OUT=null;$ERR=null;$EXIT=null;"); if(disableLoadJSFunctions) engine.eval("load=function(){throw ''load() not allowed'';};loadWithNewGlobal=function(){throw ''loadWithNewGlobal() not allowed'';};"); //nashorn-polyfill.js engine.eval("var global=this;var window=this;var process={env:{}};var console={};console.debug=print;console.log=print;console.warn=print;console.error=print;"); class ScriptMonitor{ public Object scriptResult = null; private boolean stop = false; Object lock = new Object(); @SuppressWarnings("deprecation") public void startAndWait(Thread threadToMonitor, int secondsToWait) { threadToMonitor.start(); synchronized (lock) { if(!stop) { try { if(secondsToWait<1) lock.wait(); else lock.wait(1000*secondsToWait); } catch (InterruptedException e) { throw new RuntimeException(e); } } } if(!stop) { threadToMonitor.interrupt(); threadToMonitor.stop(); throw new RuntimeException("Javascript forced to termination: Execution time bigger then " + secondsToWait + " seconds"); } } public void stop() { synchronized (lock) { stop = true; lock.notifyAll(); } } } final ScriptMonitor scriptMonitor = new ScriptMonitor(); scriptMonitor.startAndWait(new Thread(new Runnable() { @Override public void run() { try { scriptMonitor.scriptResult = engine.eval(algorithm); } catch (ScriptException e) { throw new RuntimeException(e); } finally { scriptMonitor.stop(); } } }), maxAllowedExecTimeInSeconds); Object ret = scriptMonitor.scriptResult; return ret; } finally { if(enableSecurityManager) System.setSecurityManager(originalSecurityManager); } } finally { if(enableSecurityManager) Policy.setPolicy(originalPolicy); } }

La función actualmente utiliza el Thread stop () en desuso. Una mejora puede ejecutarse JS no en un subproceso sino en un proceso separado.

PD: aquí Nashorn se carga a través de la reflexión, pero el código de Java equivalente también se proporciona en los comentarios


Por lo que puedo decir, no se puede la caja de arena Nashorn. Un usuario que no es de confianza puede ejecutar las "Funciones incorporadas adicionales de Nashorn" enumeradas aquí:

https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/shell.html

que incluyen "quit ()". Lo he probado sale completamente de la JVM.

(Aparte de eso, en mi configuración, los objetos globales, $ ENV, $ ARG, no funcionaron, lo cual es bueno).

Si me equivoco al respecto, alguien, por favor, deje un comentario.


Puede crear fácilmente un ClassFilter que permita un control detallado de qué clases de Java están disponibles en JavaScript.

Siguiendo el ejemplo de los documentos de Oracle Nashorn :

class MyCF implements ClassFilter { @Override public boolean exposeToScripts(String s) { if (s.compareTo("java.io.File") == 0) return false; return true; } }

He envuelto esto en algunas otras medidas en una pequeña biblioteca hoy: Nashorn Sandbox (en GitHub). ¡Disfrutar!


Se puede usar una biblioteca externa de sandbox si no desea implementar su propio ClassLoader & SecurityManager (esa es la única forma de sandbox por ahora).

He intentado "The Java Sandbox" ( http://blog.datenwerke.net/p/the-java-sandbox.html ) aunque es un poco rudo en los bordes, pero funciona.


Yo diría que anular el cargador de clases de la clase suministrada es la forma más fácil de controlar el acceso a las clases.

(Descargo de responsabilidad: no estoy muy familiarizado con la versión más reciente de Java, por lo que esta respuesta puede ser obsoleta)