使用Go构建网络漏洞扫描器
渗透测试使组织能够针对网络中的潜在安全弱点,并在恶意行为者利用漏洞之前提供修复需求。
在本文中,我们将使用Go语言创建一个简单但相当强大的网络漏洞扫描器。Go语言非常适合网络编程,因为它设计时考虑了并发性,并拥有出色的标准库。
1. 项目设置
创建漏洞扫描器
我们希望构建一个简单的CLI工具,能够扫描主机网络,查找开放端口、运行的服务并发现可能的漏洞。扫描器开始时非常简单,但随着我们添加功能将变得越来越强大。
首先,创建一个新的Go项目:
|
|
这将为我们的项目初始化一个新的Go模块,帮助我们管理依赖关系。
配置包和环境
对于我们的扫描器,我们将利用几个Go包:
|
|
这只是我们的初始设置。对于某些初始功能来说已经足够,但我们会根据需要添加更多导入。现在,像net这样的标准库包将处理我们所需的大部分网络功能,sync将处理并发等。
网络扫描的道德考虑和风险
在开始实施之前,我们应该谈谈网络扫描的道德考虑。未经授权的网络扫描或枚举在世界许多地方是非法的,被视为网络攻击的载体。您必须始终遵循这些规则:
- 权限:只扫描您拥有或明确获得扫描权限的网络和系统
- 范围:为扫描定义明确的范围,不要超出
- 时机:不要进行可能导致服务中断或引发安全警报的超速扫描
- 披露:如果发现漏洞,请负责任地向适当的系统所有者报告
- 法律合规:了解并遵守管理网络扫描的当地法律
滥用扫描工具可能导致法律诉讼、系统损坏或意外的拒绝服务。我们的扫描器将包括速率限制等保护措施,但最终责任在于用户是否道德地使用它。
2. 简单端口扫描器
漏洞评估基于端口扫描。我们在这些开放端口上寻找的正是可能存在的易受攻击服务的信息。现在,让我们在Go中编写一个简单的端口扫描器。
端口扫描的低级实现
端口扫描:尝试与目标主机上的每个可能端口建立连接。如果连接成功,端口是开放的;如果失败,端口是关闭的或被过滤的。对于这个功能,Go的net包已经为我们提供了支持。
这是我们简单端口扫描器的版本:
|
|
使用Net包
上面的代码使用了Go的net包,它提供了网络I/O接口和函数。主要部分包括:
net.DialTimeout
:此函数尝试连接到TCP网络地址并带有超时。它返回一个连接和一个错误(如果有)- 连接处理:如果连接没有问题,我们知道它是开放的,我们立即关闭连接以释放资源
- 超时参数:我们指定超时以避免在任何被过滤的开放端口上挂起。两秒是一个好的初始值,但可以根据网络条件进行调整
测试我们的第一次扫描
现在让我们对我们的localhost运行简单的扫描器,那里可能运行着一些服务。
- 将代码保存到名为main.go的文件中
- 使用
go run main.go
运行它
这将显示哪些本地端口是开放的。在正常的开发机器上,您可能有80(HTTP)、443(HTTPS)或任何数量的数据库端口在使用,具体取决于您运行的服务。
您可能得到的一些示例输出:
|
|
这个基本扫描器可以工作,但它有一些很大的缺点:
- 速度:由于顺序扫描端口,速度非常慢
- 信息:只告诉我们端口是否开放,没有服务信息
- 有限范围:我们只扫描前1024个端口
这些限制使我们的扫描器在实际世界中不实用。
3. 从这里改进:多线程扫描
为什么第一个版本慢
我们的第一个端口扫描器可以工作,但速度慢得无法使用。问题是它的顺序方法——一次探测一个端口。当主机有许多关闭/被过滤的端口时,我们浪费时间等待每个端口的连接超时,然后再移动到另一个端口。
为了展示问题,让我们看看基本扫描器的时间:
- 扫描前1024个端口的最坏情况最多需要2048秒(超过34分钟),超时时间为2秒
- 但即使对关闭端口的连接立即失败,由于网络延迟,这种方法也是低效的
这种逐个处理的方法对于任何真正的漏洞扫描工具来说都是一个瓶颈。
添加线程支持
Go在使用goroutine和channel处理并发方面特别出色。因此,我们利用这些功能尝试同时扫描多个端口,从而显著提高性能。
现在,让我们创建一个多线程端口扫描器:
|
|
多线程的结果
现在,让我们看看我们添加到改进扫描器中的性能提升和并发机制:
- Goroutine:为了使扫描高效,我们为需要扫描的每个端口启动一个goroutine,这样在检查一个端口的同时可以同时检查其他端口
- WaitGroup:当我们启动goroutine时,我们希望等待它们完成。WaitGroup帮助我们跟踪所有运行的goroutine并等待它们完成
- 结果通道:我们为所有goroutine的结果创建一个缓冲通道
- 信号量模式:使用通道实现的信号量限制了允许并行运行的扫描数量。这防止我们用太多连接淹没实际目标系统甚至我们自己的机器
- 减少超时:由于我们以并行方式运行许多扫描,我们使用较低的超时
性能差距是显著的。当我们实施这个时,它可以在几分钟内扫描1024个端口,肯定少于半小时。
示例输出:
|
|
多线程方法对于更大的端口范围和多个主机扩展得很好。信号量模式保证我们不会因为扫描超过一千个端口而耗尽系统资源。
4. 添加服务检测
现在我们有了一个快速、高效的端口扫描器,下一步是知道这些开放端口上运行着什么服务。这通常被称为"服务指纹识别"或"横幅抓取",我们连接到开放端口并检查返回的数据的过程。
横幅抓取的实现
横幅抓取是我们打开一个服务并读取它发送给我们的响应(横幅)。这是识别运行内容的好方法,因为许多服务在这些横幅中标识自己。
现在让我们将横幅抓取添加到我们的扫描器中:
|
|
识别运行的服务
我们使用两种主要策略进行服务检测:
- 基于端口的识别:通过映射到常见端口号(例如,端口80是HTTP),我们对服务有一个可能的猜测
- 横幅分析:我们获取横幅文本并查找服务标识符和版本信息
第一个函数grabBanner
尝试从服务获取第一个响应。某些服务如HTTP需要我们发送请求并接收回复,为此我们添加了特定情况的处理。
基本版本检测
版本检测对于识别漏洞很重要。在可能的情况下,我们的扫描器解析服务横幅以提取版本信息:
- SSH:通常以"SSH-2.0-OpenSSH_7.4"的形式提供版本信息
- HTTP服务器:通常在响应头中响应其版本信息,如"Server: Apache/2.4.29"
- 数据库服务器:可能在其欢迎消息中披露版本信息
现在输出为每个开放端口返回更多信息:
|
|
这些增强的信息对于漏洞评估更有价值。
5. 漏洞检测实现
现在我们能够枚举运行的服务及其版本,我们将实施漏洞检测。服务信息将被分析并与已知漏洞进行比较。
编写简单漏洞测试
我们将基于常见服务和版本从已知漏洞形成一个数据库。为简单起见,我们将创建一个代码内的漏洞数据库,尽管在真实场景中,扫描器很可能会查询外部漏洞数据库(如CVE或NVD)。
现在,让我们扩展代码以检测漏洞:
|
|