Hackerman's Hacking Tutorials

The knowledge of anything, since all things have causes, is not acquired or complete unless it is known by its causes. - Avicenna

Dec 22, 2019 - 8 minute read - Comments - Burp Burp extension

Using Mozilla Rhino to Run JavaScript in Java

This post discusses what I learned about executing JavaScript code in Java with Mozilla Rhino. By the end of this post, you will know:

  1. What Rhino is.
  2. How to use Rhino in your Java application (e.g., a Burp extension).
  3. Some tips and tricks when dealing with Rhino.
  4. Alternative options to using Rhino.

Code is at:

What's Mozilla Rhino?

Mozilla Rhino is an open-source implementation of JavaScript in Java. In other words, we can run JavaScript on the JVM.

Beautifying JavaScript with Rhino

As part of a different project, I wanted to beautify JavaScript in Burp. The extension is in Java and I could not find anything that does it in native Java. The closest thing I could find was a java-prettify from Google at https://gerrit.googlesource.com/java-prettify/.

There is a Burp extension named BurpSuiteJSBeautifier that beautifies JavaScript. This extension along with most utilities use an open-source library named js-beautify. I did not try the extension to see if it still works (the last update was more than 6 years ago) but when I modified it in my example application, I got an error.

If your extension is in Python, js-beautify has Python bindings.

For the remainder of the blog, I will work on an example that reads minified JavaScript from a file, beautifies it, and stores it in another file.

Adding Rhino to The Java Application

Let's start with a skeleton project. This is not a Burp extension but we can use the instructions from Developing and Debugging Java Burp Extensions with VisualStudio Code.

Our build.gradle is different this time because we are making a standalone application. The most important parts are:

  • Adding Rhino as a dependency with compile 'org.mozilla:rhino:1.7.11'.
  • Creating the Main-Class attribute to be able to do java -jar whatever.jar.

See the comments to figure out what was changed.

// Apply the application plugin (runs the 'java' plugin implicitly).
apply plugin: 'application'

// Use Maven (because Burp Extender is on Maven)
repositories {
    mavenCentral()
}

dependencies {
    // Add the Burp Extender interface
    compile 'org.mozilla:rhino:1.7.11'
    compile 'commons-io:commons-io:2.6'
}

sourceSets {
    main {
        java {
            // Set the source directory to "src"
            srcDir 'src'
            exclude 'resources/'
        }
    }
    main {
        resources {
            // Set the resource directory to "src/resources"
            srcDir 'src/resources'
        }
    }
}

// Put the final jar file in a different location
libsDirName = '../release'

// This is needed if we want to run the jar with "gradlew run"
// mainClassName = 'beautify.Beautify'

// Create a task for bundling all dependencies into a jar file.
task bigJar(type: Jar) {
    // Make an executable jar that can be executed with "java -jar"
    manifest {
        attributes(
                'Main-Class': 'beautify.Beautify'
        )
    }
    // Bundle all dependencies together in one jar file.
    baseName = project.name + '-all'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

beautify.js

There are different versions of the beautifier. We can use it as Node or Python package and there is a web version in a stand-alone JavaScript file. This is the version used by the BurpSuiteJSBeautifier extension and we will use it. Get it from https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.10.2/beautify.js and add it to the resources directory.

Reading Resource Files

src\Beautify\beautify.java will be our main class. Inside, we create a couple of helper utils. For example, to load a file from the jar's resource, we use this:

public static String getResourceFile(Class cls, String name) throws IOException {
    InputStream in = cls.getResourceAsStream(name);
    String content = IOUtils.toString(in, "UTF-8");
    in.close();
    return content;
}

If you are already using the Apache Commons IO library then this function is not an overhead. The following utility function does the same but without the extra dependency.

public static String getResourceFile(String name) throws IOException {
    InputStream in = BurpExtender.class.getResourceAsStream(name); 
    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
    
    StringBuffer buf = new StringBuffer();
    String tmpStr = "";

    while((tmpStr = reader.readLine()) != null) {
        buf.append(tmpStr);
    }
    in.close();
    return buf.toString();
}

Beautify Function

The other utility function is beautify. It uses beautify.js to beautify the JavaScript code in the input. We will follow the Mozilla embedding tutorial at https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Embedding_tutorial.

We create a context and enter it.

public static String beautify(String uglyJS) throws IOException {
    // Enter a context.
    Context cx = Context.enter();

We can set the optimization level. The optimization levels are explained at https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Optimization. I am not sure if we will need it here.

// Set optimization.
// cx.setOptimizationLevel(-1);

Create standard objects.

// Initialize standard objects.
Scriptable scope = cx.initSafeStandardObjects();

Moving forward, we can add scripts to the scope with cx.evaluateString.

Follow the BurpSuiteJSBeautifier extension source code at

Tl;DR, we need to add a global variable to our scope because the js_beautify function is added to the global variable. See the last few lines of beautify.js in the following snippet:

var js_beautify = legacy_beautify_js;
/* Footer */
if (typeof define === "function" && define.amd) {
    // Add support for AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- )
    define([], function() {
        return { js_beautify: js_beautify };
    });
} else if (typeof exports !== "undefined") {
    // Add support for CommonJS. Just put this file somewhere on your require.paths
    // and you will be able to `var js_beautify = require("beautify").js_beautify`.
    exports.js_beautify = js_beautify;
} else if (typeof window !== "undefined") {
    // If we're running a web page and don't have either of the above, add our one global
    window.js_beautify = js_beautify;
} else if (typeof global !== "undefined") {
    // If we don't even have window, try global.
    global.js_beautify = js_beautify; // <----- HERE
}

Now we need to read beautify.js with getResourceFile and add it to the scope.

// Read the jsbeautify.js file.
String jsbeautifyFile = getResourceFile(Beautify.class, "/beautify.js");

cx.evaluateString(scope, "var global = {}; "+jsbeautifyFile, "global", 0, null);

Note the initial forward slash in the file name /beautify.js. This wasted a few hours of my life.

Next is what wasted a few more hours of my life. In both the Burp extension and the Stack Overflow solution, the function is retrieved from the scope directly like this:

// Solution: https://stackoverflow.com/a/16338524 -- doesn't work
Object fjsBeautify = scope.get("js_beautify", scope);

This does not work here. I am not sure why. It might be a JavaScript version issue. Even calling it with global.js_beautify does not work either.

Instead, what I did was add a new script to scope.

// Add our own export.
cx.evaluateString(scope, "var js_beautify = global.js_beautify;", "export", 0, null);

We can get this new function with:

// Get the function.
Object fjsBeautify = scope.get("js_beautify", scope);

We can follow the rest of the tutorial to call the function. The input to the function is the uglyJS string:

// Check to see if we got the correct function?
if (!(fjsBeautify instanceof Function)) {
    System.out.println("js_beautify is undefined or not a function.");
    // System.out.println(fjsBeautify.toString());
    
} else {
    Object functionArgs[] = { uglyJS };
    // Object functionArgs[] = { "var x='1234';var y='4444';var z='3123123';" };
    Function f = (Function)fjsBeautify;
    Object result = f.call(cx, scope, scope, functionArgs);
    Context.exit();
    return Context.toString(result);
}
// We should throw an exception here in production code.
Context.exit();
return null;

The next utility function takes two file paths, reads the first one, beautifies it and stores it in the second path.

public static void beautifyFile(String inFilePath, String outFilePath) throws IOException {
    // Read the file.
    File inFile = new File(inFilePath);
    String fileContent = FileUtils.readFileToString(inFile, "UTF-8");

    File outFile = new File(outFilePath);
    try {
        String beautified = beautify(fileContent);
        FileUtils.writeStringToFile(outFile, beautified, "UTF-8");
    } catch (Exception e) {
        // TODO: handle exception
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        System.out.println(sw.toString());
    }
}

Time to tie everything together. As an example, we want to beautify the cookiebanner.min.js at:

    public static void main(String[] args) {

        try {
            beautifyFile("cookiebanner.min.js", "cookie-beautified.js");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Done");
    }

Build the project. The jar will be created in the release directory. Download the cookiebanner.min.js file and store it in the same directory. Next, we can run the jar file java -jar test-jsbeautify-all.jar.

After a few seconds, the cookie-beautified.js is created.

Beautified JavaScript Beautified JavaScript

To speed up things a little bit, we can modify beautify.js to call evaluateString once.

var global = {};

// Beautify.js

var js_beautify = global.js_beautify;

Re-using the scope

If we want to beautify a bunch of JavaScript files, we can reuse this scope instead of creating this every time. This is what the BurpSuiteJSBeautifier extension does.

Precompiling to Bytecode

It's also possible to compile the string into a class file and then execute it. To make a class file, we can do it programmatically with Context.compileString. The result is a Script that can be executed with exec. In this case, exec will the equivalent of evaluateString.

So for beautify.js we can also use:

Script scr = cx.compileString(jsbeautifyFile, "beautify.js", 0, null);
scr.exec(cx, scope);
Object fjsBeautify = scope.get("beautify", scope);

Creating Class Files from JavaScript Files

We can create a class file from a JavaScript file using the Rhino jar.

  1. Download the Rhino jar file to a path.
    1. E.g., rhino-1.7.11.jar from https://github.com/mozilla/rhino/releases/tag/Rhino1_7_11_Release.
  2. Run the following command.
    1. java -cp rhino-1.7.11.jar org.mozilla.javascript.tools.jsc.Main beautify.js
  3. Load the resulting class file and use objects/functions/etc.

See the options at:

I have not been able to load the compiled strings in Rhino and execute them yet.

js_beautify Options

The js_beautify function has a second optional parameter. This is a JSON string with options. See an example at:

Alternatives

Instead of using Rhino, it's possible to call js-beautify via the command line. This method requires the https://www.npmjs.com/package/js-beautify to be installed globally or your Java code should point to node_modules/.bin/js-beautify.

Then we can call js-beautify -f inputfile -o output-file.

Another option is to create an executable using https://github.com/zeit/pkg. This means we do not need to install the package. The executable size on Windows is around 40 MBs.

These two might be better your use case. However, that means you have to create the dependencies yourself and or ship them with your app.

What Did We Learn Here Today?

  1. How to add Rhino to your project.
  2. How to read resources inside jar files.
  3. How to run a JavaScript function in Rhino.
  4. Troubleshooting tips and tricks.