Java命令注入_Java命令注入原理并结合Java Instrument技术

原标题:Java命令注入原理并结合Java Instrument技术

一、前言

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

二、演示环境搭建

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

a1746bf82e28879c55ab227ffe645ef6.png

主要有三个接口:

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

源码:https://gitee.com/cor0ps/java-range.git这里取访问一个栗子:

84eb6bd2d30bd24ec0813360650dc4a7.png

三、java执行系统命令的方法 - java.lang.Runtime.getRuntime.exec - java.lang.ProcessBuilder - com.jcraft.jsch.ChannelExec

特殊情况下method.invoke也是执行命令,后续反序列化会细说。前置条件:如果我们需要执行系统管道(|)、;、&&等,我们必须要创建shell来执行命令。java shell

符号&形式 说明 cmd1|cmd2 |管道表示前一个命令执行的结果重定向给后一个命令,不管cmd1是否成功,cmd2都会执行 cmd1;cmd2 多语句的分隔符 cmd2&&cmd2 逻辑操作符,两个为真时返回真 cmd1||cmd2 逻辑操作符,测试条件有一个为真返回真 {cmd2,cmd2} 花括号扩展,扩展参数列表,命令依次进行扩展 `cmd2,cmd2` 反引号的功能是命令替换,将反引号中的字符串做为命令来执行 $(cmd2,cmd2) 用于变量替换,换句话说就是取变量的值 3 Runtime

30e59c07796827569f9c038045f625db.png

主要分为两大类,第一类入参为String类型,第二类入参为String[]类型

public Process exec(String command); public Process exec(String cmdarray[]);

我们先分析第一种情况,入参String:

@ApiOperation(value = "命令执行", notes = "exec接受string参数") @PostMapping(value = "/exec/string", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseResult execString(@RequestBody PathInfo path) throws IOException { String cmdStr; //1.日志注入 2.path本身校验防跨目录等等 logger.info("Runtime.getRuntime.exec args:" + path); if(path.getType==1) { cmdStr = "/bin/sh -c" + path; }else { cmdStr = "ping " + path; } String result=ShellExcute.Exec(cmdStr); // p.getInputStream; if (result != null) { return new ResponseResult<>(result, "执行成功", 200); } //System.out.println(result); return new ResponseResult<>("result is null", "执行成功", 200); }

这里先分析下源码,分析发现代码会进入到exec(String command, String[] envp, File dir)函数中:

public Process exec(String command, String[] envp, File dir) throws IOException { if (command.length == 0) throw new IllegalArgumentException("Empty command"); StringTokenizer st = new StringTokenizer(command); String[] cmdarray = new String[st.countTokens]; for (int i = 0; st.hasMoreTokens; i++) cmdarray[i] = st.nextToken; return exec(cmdarray, envp, dir); }

这里关注下StringTokenizer类及skipDelimiters方法

public StringTokenizer(String str) { this(str, " \t\n\r\f", false); }

这里会对传入的字符串进行处理,重点处理空格,\t\n\r\f字符,然后调用exec(cmdarray, envp, dir),继续跟踪发现最终调用ProcessBuilder:

public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException { return new ProcessBuilder(cmdarray) .environment(envp) .directory(dir) .start; }

从这上面两段代码中我们可以知道:

1、string入参被转化成string[];^_^ 2、Runtime最终执行在ProcessBuilder中,参数为String可变类型 四、ProcessBuilder

025801c43f09869e1a1bd9a8af159092.png

主要也分为两大类,第一类入参为List类型,第二类入参为String可变参数类型(可以0到多个Object对象,或者一个Object[])

public ProcessBuilder(List command); public ProcessBuilder(String... command);

跟踪ProcessBuilder(String… command),发现入参将转化为List类型

public ProcessBuilder(String... command) { this.command = new ArrayList<>(command.length); for (String arg : command) this.command.add(arg); }

进入start方法中,发现存在prog变量,为cmdarray[0]的值,就是/bin/sh或者ping;如果security不为null,就会进入checkExec。最终进入ProcessImpl。

return ProcessImpl.start(cmdarray, environment, dir, redirects, redirectErrorStream);

进入之后发现可以看到最终调用的java.lang.UNIXProcess这个类执行命令,(和windows下的代码不相同),这里执行什么命令根据cmdarray[0] 来判断,最后调用forkAndExec ^3,来为命令创建环境等操作。从3和 4知道,Linux环境最终的执行都是java.lang.UNIXProcess类,那么我们可以使用类似百度OpenRASP的java Instrument技术,监控cmdarray参数,不用每次调试。百度的或者某为的都比较庞大复杂,可以使用我这个轻巧简单,可拓展。

if ("java.lang.UNIXProcess".equals(className)) { try { ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer)); CtBehavior[] ctBehaviors = ctClass.getDeclaredConstructors; for (CtBehavior cb : ctBehaviors) { //System.out.println("UNIXProcess:" + cb.getName); if (cb.getName.equals("UNIXProcess")) { String src="{" + "String prog_1=new String($1);" + "String cmd_1=new String($2);" + "System.out.println(\"unixprocess_result:\"+prog_1+\" \"+cmd_1);" + "}"; cb.insertBefore(src); } } bytesCode = ctClass.toBytecode; } catch (IOException e) { e.printStackTrace; } catch (CannotCompileException e) { e.printStackTrace; } }

unixprocess_result为识别关键字,方便后续搜索用户执行的命令。开启方式,在VM Options添加如下语句:-javaagent:”/path/agent.jar” 一定要加下双引号,不然会出现异常

0ba9d326ade4551e4a1a3f6a9cdb40af.png

运行结果如下:

Instrument Agent start!

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

五、分析结果

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

{"path":"echo \"xxxx\t\n\r\f\">/tmp/xxx" ,"type":1}

/command/exec/array请求对应的入参是String[]类型,发送如下body:

{"path":"echo \"xxx\">/tmp/yyy" ,"type":1}

依次发送上面的请求,得到的结果如下:

#string类型会被StringTokenizer过滤 unixprocess_result:prog:/bin/sh cmd:-cecho"xxxx">/tmp/xxx 未执行成功 #string[]类型没有过滤 unixprocess_result:prog:/bin/sh cmd:-cecho "xxx">/tmp/yyy 执行成功

java instrument使用运行结果:

ae4df5bb8de4cb21e8b4ae10c5d0c700.png

看了 l1nk3r关于使用编码,linux下可以用bash的base64编码来解决这个特殊字符的问题。这里的利用条件一定要是这个入参String完全可控,或者存在参数注入。

/bin/sh -c {echo,dG91Y2glMjAvdG1wL3p6eno=}|{base64,-d}|{bash,-i}

我们运行下这个绕过的方法:运行结果如下:成功执行:

参考

*本文作者:buglab,转载请注明来自FreeBuf.COM返回搜狐,查看更多

责任编辑:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值