安全矩阵

 找回密码
 立即注册
搜索
查看: 4328|回复: 0

Java命令注入原理结合Java Instrument技术(FreeBuf首发)

[复制链接]

991

主题

1063

帖子

4315

积分

论坛元老

Rank: 8Rank: 8

积分
4315
发表于 2020-6-17 14:38:55 | 显示全部楼层 |阅读模式
本帖最后由 gclome 于 2020-6-17 14:41 编辑

原文链接:Java命令注入原理结合Java Instrument技术(FreeBuf首发)

一、前言

命令注入:恶意用户构造恶意请求,对一些执行系统命令的功能点进行构造注入,从而达到执行命令的效果。


二、演示环境搭建

这里采用springboot+swagger搭建一个模拟的web环境:启动成功后访问:http://localhost:8090/swagger-ui.html#/command

主要有三个接口:

  1. 1 /command/exec/string 主要实现Runtime.getRuntime().exec() 入参为String
  2. 2 /command/exec/array 主要实现Runtime.getRuntime().exec()入参为String[]
  3. 3 /command/processbuilder 主要实现ProcessBuilder 入参为List
复制代码

源码:https://gitee.com/cor0ps/java-range.git

这里举个访问一个栗子:



三、java执行系统命令的方法
  1. - java.lang.Runtime.getRuntime().exec()
  2. - java.lang.ProcessBuilder
  3. - com.jcraft.jsch.ChannelExec
复制代码
特殊情况下method.invoke()也是执行命令,后续反序列化会细说。前置条件:如果我们需要执行系统管道(|)、;、&&等,我们必须要创建shell来执行命令[2]。

file:///C:\Users\asus\AppData\Roaming\Tencent\Users\2594738584\QQ\WinTemp\RichOle\`B{O)(M38T]766)@N$E${M5.png

















3.1 Runtime


主要分为两大类,第一类入参为String类型,第二类入参为String[]类型
  1. public Process exec(String command);
  2. public Process exec(String cmdarray[]);
复制代码
我们先分析第一种情况,入参String:
  1. @ApiOperation(value = "命令执行", notes = "exec接受string参数")
  2.   @PostMapping(value = "/exec/string", produces = MediaType.APPLICATION_JSON_VALUE)
  3.   public ResponseResult execString(@RequestBody PathInfo path) throws IOException {
  4.   String cmdStr;
  5.   //1.日志注入 2.path本身校验防跨目录等等
  6.   logger.info("Runtime.getRuntime().exec args:" + path);
  7.   if(path.getType()==1)
  8.   {
  9.       cmdStr = "/bin/sh -c" + path;
  10.   }else {
  11.       cmdStr = "ping " + path;
  12.   }
  13.   String result=ShellExcute.Exec(cmdStr);
  14.   // p.getInputStream();
  15.   if (result != null) {
  16.   return new ResponseResult<>(result, "执行成功", 200);
  17.   }
  18.   //System.out.println(result);
  19.   return new ResponseResult<>("result is null", "执行成功", 200);
  20.   }
复制代码
这里先分析下源码,分析发现代码会进入到exec(String command, String[] envp, File dir)函数中:
  1. public Process exec(String command, String[] envp, File dir)
  2.   throws IOException {
  3.   if (command.length() == 0)
  4.       throw new IllegalArgumentException("Empty command");
  5.   StringTokenizer st = new StringTokenizer(command);
  6.   String[] cmdarray = new String[st.countTokens()];
  7.   for (int i = 0; st.hasMoreTokens(); i++)
  8.       cmdarray[i] = st.nextToken();
  9.   return exec(cmdarray, envp, dir);
  10.   }
复制代码
这里关注下StringTokenizer类及skipDelimiters方法
  1. public StringTokenizer(String str) {
  2.   this(str, " \t\n\r\f", false);
  3.   }
复制代码
这里会对传入的字符串进行处理,重点处理空格,\t\n\r\f字符,然后调用exec(cmdarray, envp, dir),继续跟踪发现最终调用ProcessBuilder:
  1. public Process exec(String[] cmdarray, String[] envp, File dir)
  2.   throws IOException {
  3.   return new ProcessBuilder(cmdarray)
  4.       .environment(envp)
  5.       .directory(dir)
  6.       .start();
  7.   }
复制代码

从这上面两段代码中我们可以知道:
  1. 1、string入参被转化成string[];^_^
  2. 2、Runtime最终执行在ProcessBuilder中,参数为String可变类型
复制代码
3.2 ProcessBuilder



主要也分为两大类,第一类入参为List类型,第二类入参为String可变参数类型(可以0到多个Object对象,或者一个Object[])
  1. public ProcessBuilder(List command);
  2. public ProcessBuilder(String... command);
复制代码
跟踪ProcessBuilder(String… command),发现入参将转化为List类型
  1. public ProcessBuilder(String... command) {
  2.   this.command = new ArrayList<>(command.length);
  3.   for (String arg : command)
  4.       this.command.add(arg);
  5.   }
复制代码
进入start()方法中,发现存在prog变量,为cmdarray[0]的值,就是/bin/sh或者ping;如果security不为null,就会进入checkExec()。最终进入ProcessImpl。
  1. return ProcessImpl.start(cmdarray,
  2.                                environment,
  3.                                dir,
  4.                                redirects,
  5.                                redirectErrorStream);
复制代码
进入之后发现可以看到最终调用的java.lang.UNIXProcess这个类执行命令,(和windows下的代码不相同),这里执行什么命令根据cmdarray[0] 来判断,最后调用forkAndExec ,来为命令创建环境等操作。从3和 4知道,Linux环境最终的执行都是java.lang.UNIXProcess类,那么我们可以使用类似百度OpenRASP的java Instrument技术,监控cmdarray参数,不用每次调试。百度的都比较庞大复杂,可以使用我这个轻巧简单,可拓展。
  1. if ("java.lang.UNIXProcess".equals(className)) {
  2.       try {
  3.           ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
  4.           CtBehavior[] ctBehaviors = ctClass.getDeclaredConstructors();
  5.           for (CtBehavior cb : ctBehaviors) {
  6.              //System.out.println("UNIXProcess:" + cb.getName());
  7.               if (cb.getName().equals("UNIXProcess")) {
  8.                   String src="{" +
  9.                           "String prog_1=new String($1);" +
  10.                           "String cmd_1=new String($2);" +
  11.                           "System.out.println("unixprocess_result:"+prog_1+" "+cmd_1);" +
  12.                           "}";
  13.                   cb.insertBefore(src);
  14.               }
  15.           }
  16.           bytesCode = ctClass.toBytecode();
  17.   } catch (IOException e) {
  18.       e.printStackTrace();
  19.   } catch (CannotCompileException e) {
  20.       e.printStackTrace();
  21.   }
  22.   }
复制代码
unixprocess_result为识别关键字,方便后续搜索用户执行的命令。开启方式,在VM Options添加如下语句:-javaagent:"/path/agent.jar" 一定要加下双引号,不然会出现异常。


运行结果如下:
  1. Instrument Agent start!
复制代码

日志出现上面字段,表示我们成功运行,那么可以继续下步测试。


四、分析结果

Runtime.getRuntime.exec入参String和String[]对比:/command/exec/string请求对应的入参是String类型,发送如下body:

  1. {"path":"echo "xxxx\t\n\r\f">/tmp/xxx" ,"type":1}
复制代码
/command/exec/array请求对应的入参是String[]类型,发送如下body:

  1. {"path":"echo "xxx">/tmp/yyy" ,"type":1}
复制代码
依次发送上面的请求,得到的结果如下:
  1. #string类型会被StringTokenizer过滤
  2. unixprocess_result:prog:/bin/sh cmd:-cecho"xxxx">/tmp/xxx 未执行成功
  3. #string[]类型没有过滤
  4. unixprocess_result:prog:/bin/sh cmd:-cecho "xxx">/tmp/yyy 执行成功
复制代码
java instrument使用运行结果:


看了 l1nk3r关于使用编码,linux下可以用bash的base64编码来解决这个特殊字符的问题。这里的利用条件一定要是这个入参String完全可控,或者存在参数注入。
/bin/sh -c {echo,dG91Y2glMjAvdG1wL3p6eno=}|{base64,-d}|{bash,-i}
我们运行下这个绕过的方法:


运行结果如下(这可以监控linux系统中的命令执行,应该能看到解码后的内容):

成功执行:


javaagent源码:https://gitee.com/cor0ps/Agent.git

参考
  1.     1.https://mp.weixin.qq.com/s/ZS-hA03ykKleDjgN8oWZDw

  2.     2.https://alvinalexander.com/java/java-exec-system-command-pipeline-pipe

  3.     3.https://blog.csdn.net/GV7lZB0y87u7C/article/details/79860776

  4.     4.https://www.cnblogs.com/rickiyang/p/11336268.html

  5.     5.https://docs.huihoo.com/javaone/2015/CON3597-Having-Fun-with-Javassist.pdf
复制代码


















回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-3-29 02:42 , Processed in 0.017848 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表