2.2 弱口令扫描器
弱口令扫描器用来检测系统中是否存在弱口令。常用的开源弱口令检测工具有Hydra和Medura等。
本节将介绍如何开发一款简单的弱口令扫描器,此扫描器可用来检测FTP、SSH、MySQL、Redis、MSSQL、PostgreSQL和MongoDB等服务是否存在弱口令,也可以增加其他服务的扫描插件。
2.2.1 弱口令扫描器插件的实现
弱口令扫描器的代码结构如图2-9所示。
●图2-9 弱口令扫描器的代码结构
● cmd包为命令行入口的实现。
● logger包为log模块的实现。
● models包为扫描器数据结构的实现。
● plugins包为扫描插件包。
● util包为工具函数的实现,如读取文件、扫描任务调度等。
● vars包定义了项目中用到的全局变量。
弱口令扫描器的原理是使用服务的客户端与服务器建立连接后,用常见的弱口令字典中的用户名和密码不断地尝试登录,如果能登录成功,说明存在弱口令。
以SSH弱口令扫描器为例,每次尝试一个密码对,代码如下所示:
以上代码只实现了一个最简单的SSH弱口令扫描器,功能较为完善的弱口令扫描器应该可以支持扫描多种服务,支持用户名与密码字典,所以需要将其设计成兼容多种服务的数据结构:
● Service用来将需要扫描的服务的相关字段传给相关的扫描模块,比如127.0.0.1:2222| ssh| username:password,表示用username:password这个密码对去尝试IP为127.0.0.1,端口为2222的SSH服务。
● ScanResult为扫描结果,其中Service表示扫描目标,Result是一个布尔型的变量,表示是否有弱口令。
● IpAddr为待扫描的服务列表,如127.0.0.1:2222 | ssh,可以告诉扫描器待扫描的IP与端口是什么服务。
为了以后方便增加对其他服务的扫描,这里将每种服务的扫描器设计为插件式,需要时增加插件即可。
1.SSH弱口令扫描插件
在前面最简单的SSH弱口令扫描器的基础上封装一个函数,传入的参数为待扫描的数据models.Service,返回的参数为扫描结果models.ScanResult与error。利用上面的数据结构,最终封装的SSH扫描函数如下所示:
其他服务的扫描函数都可以定义为type ScanFunc func(service models.Service) (result models.ScanResult, err error),然后将这些函数放入一个ScanFuncMap map[string]ScanFunc中,程序就可以动态地根据不同的服务调用相应服务的扫描插件了。
2.FTP弱口令扫描插件
FTP弱口令扫描的功能是用一个第三方库github.com/jlaffaye/ftp来实现的,实现与SSH扫描函数相同的函数原型func(service models.Service)(result models.ScanResult, err error)即可,最终完成的FTP弱口令扫描插件的代码如下所示:
3.MySQL弱口令扫描插件
MySQL弱口令扫描的功能是利用Go语言的第三方包xorm实现的,实现与SSH扫描功能相同的函数原型func(service models.Service) (result models.ScanResult, err error)即可,MySQL弱口令扫描插件的详细代码如下所示:
4.Redis弱口令扫描插件
Redis弱口令扫描插件与SSH、MySQL弱口令扫描插件的实现类似,只要实现相关的函数原型func(service models.Service)(result models.ScanResult, err error)即可,需要注意的是Redis的账户只有密码,没有用户名,Redis弱口令扫描插件的详细代码如下所示:
5.MSSQL弱口令扫描插件
MSSQL弱口令扫描的功能也是用第三方包xorm实现的,实现与MySQL扫描功能相同的函数原型func(service models.Service)(result models.ScanResult, err error)即可,MSSQL弱口令扫描插件的详细代码如下所示:
6.PostgreSQL弱口令扫描插件
PostgreSQL弱口令扫描的功能是调用github.com/lib/pq包来完成的,按前面的函数签名再实现一个func ScanPostgres(service models.Service) (result models.ScanResult, err error)函数即可,PostgreSQL弱口令扫描插件的详细代码如下所示:
7.MongoDB弱口令扫描插件
MongoDB的弱口令扫描插件是用gopkg.in/mgo.v2包开发的,还是实现与前面相同签名的函数,最终完成的MongoDB弱口令扫描插件的详细代码如下所示:
2.2.2 弱口令扫描器插件注册
前面已经按统一的函数原型完成了SSH、MySQL、Redis等服务的弱口令扫描器的函数,接下来可以将这几个函数放入一个map中,以实现在扫描的过程中自动根据不同的服务选择不同的扫描函数的功能。详细的代码如下所示:
2.2.3 弱口令扫描器任务执行功能的实现
接下来实现扫描器入口及执行扫描任务的功能,详细的流程如下。
● 通过ipList文件读取扫描任务。
● 读取用户名与密码字典。
● IP与端口的有效性测试。
● 生成扫描任务。
● 调度扫描任务。
● 保存扫描结果。
● 输出扫描结果。
1.通过ipList文件读取扫描任务
ipList文件中包含了需要扫描的IP、端口以及该端口的服务类型。
对于标准的端口的协议,程序可以自动判断其类型;对于非标准的端口的协议,需要在待扫描的IP列表中显式地标注出服务的类型,端口与服务类型之前用|分割。格式范例如下所示。
以下为将ipList解析为[]IpAddr的函数,通过标准库的bufio包逐行读取,然后用strings包的Split分割,详细代码如下所示:
2.读取用户名与密码字典
读取用户名与密码的功能也是通过bufio包完成的,分别将用户名与密码字典的内容按行分割;然后逐行读取,删除字符前面的空格,并过滤空字符串;最后将读取到的内容保存到一个[]string中,代码如下所示:
3.IP与端口的有效性测试
若将不存在的IP或不开放的端口传给扫描器,扫描器也会对此IP与端口进行弱口令尝试,这相当于在做无用功,所以在正式生成扫描任务之前有必要将无效的IP与端口排除掉,以免影响扫描效率。
最简单的方法是利用TCP全连接扫描的方式事先将所有端口扫描一遍,过滤出有效的扫描列表,详细的代码如下所示:
CheckAlive为检测端口是否有效的函数,它将扫描列表中的所有端口通过并发扫描确认一遍,最后只将有效的端口返回。
4.生成扫描任务
待扫描的任务列表使用了3个嵌套的循环,用ipList、用户名和密码的值初始化一个models.Services结构,如service :=models.Service{Ip: addr.Ip, Port: addr.Port, Protocol: addr.Protocol, Username: user, Password: password},并将models.Service结构保存到一个[]models.Service切片中,详细的代码如下所示:
5.调度扫描任务
扫描任务的处理是通过缓存型的channel与sync.WaitGroup配合实现的生产者-消费者完成的。
taskChan为一个大小与并发数相同的缓存型channel,生产者源源不断地将任务写入这个channel中,每个协程负责从channel读取任务并消化。
调度扫描任务的代码如下所示:
6.保存扫描结果
将扫描结果进行保存,crackPassword的作用是从taskChan中不断地读取扫描任务,然后根据协议内容从plugins.ScanFuncMap中获取相应的处理函数,执行扫描操作。
因为每个函数的原型是相同的,返回的值也是相同的(resultScanResult, err error),而SaveResult函数的输入参数正好是(resultScanResult, err error),所以可以直接将fn(task)作为SaveResult的参数,代码如下所示:
hash.CheckTashHash是个sync.map,目的是防止密码破解出来后继续提交破解请求,浪费破解资源与时间。
个别服务的库没有提供超时功能,扫描线程可能会卡住,导致扫描时间过长,为了利用sync.WaitGroup,这里提供了一个统一的超时功能waitTimeout,可以为所有的扫描插件统一添加一个超时控制机制,详细的代码如下所示:
7.输出扫描结果
在调度扫描任务的RunTask函数中,所有扫描任务执行完毕会执行以下几行代码:
以上3行代码的作用如下。
● 将扫描结果保存到一个DB文件中,DB文件的格式为github.com/patrickmn/go-cache库定义的格式,ResultTotal的具体实现如下所示:
● 打印扫描结果的状态信息,如扫描用时、扫描得到的有效弱口令的总数等,代码如下所示:
● 将结果导出到一个txt文件中,方便查看,代码如下所示:
2.2.4 弱口令扫描器命令行的实现
通过前面的步骤已经完成了弱口令扫描器的扫描插件、扫描任务调度等功能,接下来需要再提供一个命令行入口来控制这些模块。
命令行参数的解析还是用前面提到的github.com/urfave/cli库来实现,弱口令的命令行参数如下:
● --ip_list表示待扫描的ipList。
● --user_dict表示用户字典。
● --pass_dict表示密码字典。
● --timeout表示每个连接的超时时间。
● --scan_num表示扫描的并发数。
● --debug表示debug模式是否开启。
根据以上预设的命令行参数,定义相应的cli.Command对象,详细代码如下所示:
util.Scan为Scan对象的Action,对命令行传入的参数进行处理,如果传入了具体的参数就把vars包中定义的默认值替换掉,代码如下所示:
前面已经定义了命令行对象,现在只要在main中加入以下代码就可以很方便地使用了,完整代码如下所示:
将弱口令扫描器进行编译,直接运行后会输出命令行参数使用说明,如图2-10所示。
●图2-10 弱口令扫描器命令行参数
2.2.5 弱口令扫描器测试
在ip_list.txt写入需要扫描的ipList,然后启动程序即可扫描,默认会从ipList中读取待扫描的IP列表,用-timeout与-scan_num可以分别指定超时时间与扫描的并发数,如图2-11所示。
●图2-11 弱口令扫描器测试
需要注意的是,如果目标IP太少,不要把协程数设置得过大,以免连接数过多,造成目标服务器卡顿,导致最终的结果发生漏报的情况。