背景

尝试写一些能用的东西。

  1. 尝试过MASSCAN + NMAP + 漏洞检测的模式,MASSCAN 漏报概率有点高就放弃了。
  2. 做详细报告不合适,但是可以用来做大致,快速的梳理

原理

NMAP【自定义/默认扫描】 -> 资产报告【生成表格】-> 漏洞扫描【常见漏洞扫描】 -> 整理【生成最终表格】

编写过程

文件读取/配置

使用config.json进行文件配置

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
type configuration struct {
Email string
Key string
Attribute string
}

func init() {
fmt.Println(banner)
// 加载配置文件内容
file, err := os.Open("config.json")
if err != nil {
fmt.Println("配置文件读取错误 !")
os.Exit(0)
}
defer file.Close()
decoder := json.NewDecoder(file)
conf := configuration{}
err1 := decoder.Decode(&conf)
if err1 != nil {
fmt.Println("编码错误 !请检查json文件格式 !")
os.Exit(0)
}
email = conf.Email
key = conf.Key
attribute = conf.Attribute
}

读取要进行扫描的数据 iplist.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func ReadLines(path string) ([]string, int, error) {
file, err := os.Open(path)
if err != nil {
return nil,0, err
}
defer file.Close()

var lines []string
linecount :=0
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
linecount++
}
return lines,linecount,scanner.Err()
}

调用NMAP

使用nmap来命令行调用nmap,

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
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Equivalent to `/usr/local/bin/nmap -p 80,443,843 google.com facebook.com youtube.com`,
// with a 5 minute timeout.
scanner, err := nmap.NewScanner(
nmap.WithTargets("121.36.140.230"),
nmap.WithPorts("1-100"),
nmap.WithContext(ctx),
)
if err != nil {
log.Fatalf("unable to create nmap scanner: %v", err)
}
result, _, err := scanner.Run()
if err != nil {
log.Fatalf("unable to run nmap scan: %v", err)
}
// Use the results to print an example output
for _, host := range result.Hosts {
if len(host.Ports) == 0 || len(host.Addresses) == 0 {
continue
}
fmt.Printf("Host %q:\n", host.Addresses[0])
for _, port := range host.Ports {
fmt.Printf("\tPort %d/%s %s %s\n", port.ID, port.Protocol, port.State, port.Service.Name)
}
}
fmt.Printf("Nmap done: %d hosts up scanned in %.2f seconds\n", len(result.Hosts), result.Stats.Finished.Elapsed)
}

决定要使用的常用命令参数:

1
nmap -sS -Pn -p 1433,445,135,5985,3389,22,1521,3306,6379,5432,389,25,110,143,443,5900,21,873,27017,23,3690,1099,5984,5632,80-100,7000-10000,13389,13306,11433,18080 -n --open --min-hostgroup 1024 --min-parallelism 1024 --host-timeout 30 -T4 -v

参数解释:

  1. -sS : Nmap默认使用TCP方式扫描,需要完成完整的三次握手,很费时,所以这里可以采用-sS让Nmap使用SYN方式扫描。
  2. -Pn : 绕过防Ping(准确的说是不进行主机Ping发现)
  3. -p : 指定扫描端口
  4. -n : 不进行DNS解析(反向解析IP地址到域名)
  5. –open : 只输出检测状态为open的端口,即开放的端口(输出结果的优化)
  6. –min-hostgroup 1024 : 调整并行扫描组的大小
  7. –min-parallelism 1024 : 调整探测报文的并行度
  8. –host-timeout 30 : 检测超时的跳过(单位是秒 一般我们设置为30毫秒)
  9. -T4 : 设置时间模板,总共有T0-T5,比较适中的是T4
  10. -v : 打印详细扫描过程

进行构造,这些参数的函数在nmap库中已经写好了,直接调用就是,但是这里又个问题待解决:

  1. 如何能够 “灵活” 使用命令参数?

目前使用的参数函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Nmaptimeout)*time.Minute)
defer cancel()
// 新建一个扫描类
scanner, err := nmap.NewScanner(
nmap.WithTargets(host),
nmap.WithPorts(port),
nmap.WithContext(ctx),
nmap.WithSYNScan(),
nmap.WithOpenOnly(),
nmap.WithSkipHostDiscovery(),
nmap.WithDisabledDNSResolution(),
)
...

结果处理

在每扫描一个结果后,这里使用excelize 来针对数据生成表格处理.创建一个表格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 生成excel表格等待结果写入
func ExcleCreate(filename string) {
f := excelize.NewFile()
f.SetCellValue("Sheet1", "A1", "IP")
f.SetCellValue("Sheet1", "B1", "端口")
f.SetCellValue("Sheet1", "C1", "状态")
f.SetCellValue("Sheet1", "D1", "协议")
//设置宽度
f.SetColWidth("Sheet1", "A", "B", 30)
f.SetColWidth("Sheet1", "C", "G", 15)
if err := f.SaveAs(filename); err != nil {
fmt.Println("生成表格错误 !")
}
}

利用循环,将每次的结果写入数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 写入 excle 
func excle360(IP string, port string, xieyi string, ipopen string, listnumber int ) {

f, err1 := excelize.OpenFile(Nmapresultfile)
if err1 != nil {
fmt.Println("1")
}

list1 := fmt.Sprintf("A%d", listnumber+2)
list2 := fmt.Sprintf("B%d", listnumber+2)
list3 := fmt.Sprintf("C%d", listnumber+2)
list4 := fmt.Sprintf("D%d", listnumber+2)

f.SetCellValue("Sheet1",list1,IP)
f.SetCellValue("Sheet1",list2,port)
f.SetCellValue("Sheet1",list3,xieyi)
f.SetCellValue("Sheet1",list4,ipopen)


if err := f.SaveAs(Nmapresultfile); err != nil {
fmt.Println(err)
}
}

但是这里出现了一个问题,我们呢是按照循环的number来作为根据进行写入数据的,但是这个函数我们是并发的,导致数据会重复覆盖,之类后还是采用txt来进行存储,在nmap扫描完毕后,再转换成excle,解决方案,先存储在一个函数/文件中,再进遍历写入文档:

1
2
3
4
5
6
7
8
9
10
11

// 用于写入数据
func tracefile(str_content,name string) {

fd,_:=os.OpenFile(name,os.O_RDWR|os.O_CREATE|os.O_APPEND,0644)

fd_content:=str_content
buf:=[]byte(fd_content)
fd.Write(buf)
fd.Close()
}

分析nmap数据

根据服务进行分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// 用于筛选服务类型
func Checkmodels(aliveIpList []Service) (weakpasswordaliveIpList,httpliveIpList,vulnerabillityaliveIpList,otheraliveIpList []Service){
for _, aliveIpListmodel := range aliveIpList {
if aliveIpListmodel.Protocol == "SSH" || aliveIpListmodel.Protocol == "FTP" || aliveIpListmodel.Protocol == "REDIS"{
weakpasswordaliveIpList = append(weakpasswordaliveIpList, aliveIpListmodel)
} else if aliveIpListmodel.Protocol == "HTTP" {
httpliveIpList = append(httpliveIpList, aliveIpListmodel)
} else if aliveIpListmodel.Protocol == "MICROSOFI-DS"{
vulnerabillityaliveIpList = append(vulnerabillityaliveIpList, aliveIpListmodel)
} else {
otheraliveIpList = append(otheraliveIpList, aliveIpListmodel)
}
}
return weakpasswordaliveIpList, httpliveIpList, vulnerabillityaliveIpList, otheraliveIpList
}

可插拔式设计

第一种是学习大佬写的工具x-crack的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type ScanFunc func(service models.Service) (err error, result models.ScanResult)

var (
ScanFuncMap map[string]ScanFunc
)

func init() {
ScanFuncMap = make(map[string]ScanFunc)
ScanFuncMap["FTP"] = ScanFtp
ScanFuncMap["SSH"] = ScanSsh
ScanFuncMap["SMB"] = ScanSmb
ScanFuncMap["MSSQL"] = ScanMssql
ScanFuncMap["MYSQL"] = ScanMysql
ScanFuncMap["POSTGRESQL"] = ScanPostgres
ScanFuncMap["REDIS"] = ScanRedis
ScanFuncMap["ELASTICSEARCH"] = ScanElastic
ScanFuncMap["MONGODB"] = ScanMongodb
ScanFuncMap["SNMP"] = ScanSNMP
}

第二种是使用golang接口,进行可插拔的设计

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
30
31
32
33
package models

var Plugins map[string]Need

func init() {
/* 设定容器的容量 */
Plugins = make(map[string]Need)
}

// 设定插在此容器的插件的样子
type Need interface {
/* 它必须告诉容器它是否生病 */
Flag() string
/* 它必须得有启动方法 */
Start(ServiceWeakPassword)
}

// 启动这个容器中所有的插件
func Start(IP ServiceWeakPassword) {
for _, plugin := range Plugins {
/* 查看插件是否是启用状态 */
// plugin.Flag() 这里的判断可以改成判断运行的模块
if IP.Server.Protocol == plugin.Flag() {
// 这里之前是go 并发的
plugin.Start(IP)
//fmt.Printf("加载 %s\n", name)
}
}
}
// 插件做完之后必须得插入到容器中
func Regist(name string, plugin Need) {
Plugins[name] = plugin
}

增加插件

根据设计的可插拔设计,进行插件的增加,插拔接口这里也是分开的,这里用ftp扫描为例子:

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
30
31
32
33
34
35
36
37
38
39
40
package ftpweakpassword


import(
"fmt"
"time"
"NmapScan/functions"
"NmapScan/plugins"
"github.com/jlaffaye/ftp"
)


func init() {
p_1 := plugin_1{}
/* 与容器进行连接 */
plugins.Regist("ftp弱口令扫描 插件", p_1)
}

type plugin_1 struct {
}

// 告诉插件我没生病
func (this plugin_1) Flag() string {
return "FTP"
}

// 告诉插件我是干啥的
func (this plugin_1) Start(Informations functions.ServiceWeakPassword) {
//fmt.Println(IP)
result := false
conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Informations.Server.Ip, Informations.Server.Port), 3 * time.Second)
if err == nil {
err = conn.Login(Informations.Username, Informations.Password)
if err == nil {
defer conn.Logout()
result = true
}
}
if result { fmt.Println(Informations)}
}

以此类推,进行插件增加。

改进/待增加

  1. 增加主机漏洞验证模块
  2. 扫描次数限制优化
  3. 尝试使用一些程序设计的原理

学习/参考资料

  1. nmap
  2. x-crack