java的命令执行漏洞揭秘
0x01 前言
在Java中可用于执行系统命令常见的方式有两种,API为:java.lang.Runtime
、java.lang.ProcessBuilder
0x02 java.lang.Runtime
@GetMapping("/runtime/exec")public String CommandExec(String cmd) {Runtime run = Runtime.getRuntime(); //Runtime.getRuntime.execStringBuilder sb = new StringBuilder(); //ProcessBuilder.start()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();}
最基础的Runtime.getRuntime().exec(cmd),直接传入命令即可执行
0x03 java.lang.ProcessBuilder
1. 直接传入参数导致命令执行
@GetMapping("/ProcessBuilder")public String processBuilder(String cmd) {StringBuilder sb = new StringBuilder();try {// String[] arrCmd = {"/bin/sh", "-c", cmd}; //linuxString[] arrCmd = {cmd}; //windows,windows下无需指定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();}
同样也是直接执行命令,不同的是使用的是ProcessBuilder来执行命令。ProcessBuilder传入参数为列表,第一个参数为可执行命令程序,后面的参数为执行的命令程序的参数
2. 传入参数拼接导致命令执行
@GetMapping("/codeinject")public String codeInject(String filepath) throws IOException {String[] cmdList = new String[]{"sh", "-c", "ls -la " + filepath};//String[] cmdList = new String[]{"cmd.exe", "-c", "dir " + filepath};ProcessBuilder builder = new ProcessBuilder(cmdList);builder.redirectErrorStream(true);Process process = builder.start();return WebUtils.convertStreamToString(process.getInputStream());}
获取一个参数filepath,然后通过ProcessBuilder将数组cmdList中 的字符串拼接起来执行命令,由于没有对输入filepath进行过滤,原本用作查看目录下文件的一个功能就会被执行恶意命令。通过Java执行系统命令,与cmd中或者终端上一样执行shell命令,最典型的用法就是使用Runtime.getRuntime().exec(command)
、new ProcessBuilder(cmdArray).start()
拼接后paylaod:http://localhost/codeinject?filepath=/tmp;%20id
3. Host头未过滤命令执行
@GetMapping("/codeinject/host")public String codeInjectHost(HttpServletRequest request) throws IOException {String host = request.getHeader("host");logger.info(host);String[] cmdList = new String[]{"sh", "-c", "curl " + host};ProcessBuilder builder = new ProcessBuilder(cmdList);builder.redirectErrorStream(true);Process process = builder.start();return WebUtils.convertStreamToString(process.getInputStream());}
代码中在header中的host中设置参数且未过滤拼接后传入ProcessBuilder导致命令执行
payload:Host: 192.168.1.4;id && pwd
修复方式:
利用SecurityUtil.cmdFilter来对传入的参数进行过滤,严格限制用户输入只能包含a-zA-Z0-9_-.
字符
public static String cmdFilter(String input) {if (!FILTER_PATTERN.matcher(input).matches()) {return null;}return input;}
0x04 其它方式
ScriptEngineManager
@GetMapping("/jscmd")public void jsEngine(String jsurl) throws Exception{// js nashorn javascript ecmascriptScriptEngine 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
@GetMapping("/vuln/yarm")public void yarm(String content) {Yaml y = new Yaml();y.load(content);}
groovy
@GetMapping("groovy")public void groovyshell(String content) {GroovyShell groovyShell = new GroovyShell();groovyShell.evaluate(content);}
0x05 总结
注意:java的Runtime.getRuntime.exec和ProcessBuilder.start,都是直接启动传入参数对应的进程,如果只是命令执行的部分参数可控,想在java中通过;、|、&等实现命令注入是行不通的, 例如这样传入id && whoami
命令是无法执行的
存在问题的关键词:
Runtime.getRuntime().exec()、new ProcessBuilder().start()、cmd
ScriptEngineManager、Yaml、GroovyShell