CVE漏洞7*24监控本地部署。

1.历史已有docker

2.下载watchvuln(可能还有其他的项目,不过这里选择的是这个源码,比较好找。)

watchvuln:高价值漏洞实时采集与多渠道推送工具 - AtomGit | GitCode

3.安装后,运行

(1)切到本地解压后的路径

(2)当前 watchvuln-main 文件夹地址栏输入cmd回车,打开命令行

(3)直接执行这一行命令:

docker-compose up -d

(4)更改配置再运行 docker-compose down && docker-compose up -d

(5)如果本地查看,不推送,命令查看本地端口是什么

docker-compose ps,如果主程序完全没有映射 Web 端口。那进入doker-compose.yaml,配置一下port注意层级和格式,注意挂载!!让AI给你调整。配置完,再运行docker-compose down && docker-compose up -d。

(6)访问web

http://localhost:9000

(7)以上我没成功,不知道哪里配置的有问题。


(8)下载了go语言,直接运行

1:main.go(修改)

package main

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

// 全局配置
const (
	wechatWebhookKey = "你的企业微信群key"
	csvFilePath      = "cnnvd_vulns.csv"
	scanInterval     = 1 * time.Hour  // 每1小时扫描1次,可自行修改
	maxWechatContent = 1800            // 微信消息安全字符上限
	pageFetchSize    = 50
	maxFetchPage     = 10
	fetchSleepDelay  = 1500 * time.Millisecond
)

var (
	seenVulnMap = make(map[string]bool)
	globalLock  sync.Mutex
)

// 漏洞统一结构体
type Vuln struct {
	ID              int
	CveCode         string
	CnnvdCode       string
	VulName         string
	HazardLevel     int
	HazardLevelText string
	PublishTime     string
	RecordTime      string
}

// 风险等级文字映射
func getRiskLabel(level int) string {
	switch level {
	case 0:
		return "🔴 高危"
	case 1:
		return "🟡 中危"
	case 2:
		return "🟢 低危"
	default:
		return "⚪ 未知"
	}
}

// 1. 单页CNNVD接口抓取
func fetchCNNVDPage(pageIndex int, targetDate string) ([]Vuln, error) {
	payload := map[string]any{
		"pageIndex":   pageIndex,
		"pageSize":    pageFetchSize,
		"keyword":     "",
		"hazardLevel": "",
		"vulType":     "",
		"vendor":      "",
		"product":     "",
		"dateType":    "publishTime",
		"beginTime":   targetDate,
		"endTime":     targetDate,
	}
	jsonPayload, _ := json.Marshal(payload)

	req, _ := http.NewRequest("POST",
		"https://www.cnnvd.org.cn/web/homePage/cnnvdVulList", bytes.NewBuffer(jsonPayload))
	req.Header.Set("User-Agent", "Apifox/1.0.0")
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Referer", "https://www.cnnvd.org.cn/home/loophole")
	req.Header.Set("Origin", "https://www.cnnvd.org.cn")

	client := &http.Client{Timeout: 25 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	bodyBytes, _ := io.ReadAll(resp.Body)
	var respData struct {
		Data struct {
			Records []struct {
				CveCode     string `json:"cveCode"`
				CnnvdCode   string `json:"cnnvdCode"`
				VulName     string `json:"vulName"`
				PublishTime string `json:"publishTime"`
				HazardLevel int    `json:"hazardLevel"`
			} `json:"records"`
		}
	}
	_ = json.Unmarshal(bodyBytes, &respData)

	var vulns []Vuln
	nowTime := time.Now().Format("2006-01-02 15:04:05")
	for _, item := range respData.Data.Records {
		vulns = append(vulns, Vuln{
			CveCode:         strings.TrimSpace(item.CveCode),
			CnnvdCode:       strings.TrimSpace(item.CnnvdCode),
			VulName:         strings.TrimSpace(item.VulName),
			HazardLevel:     item.HazardLevel,
			HazardLevelText: getRiskLabel(item.HazardLevel),
			PublishTime:     strings.TrimSpace(item.PublishTime)[:10],
			RecordTime:      nowTime,
		})
	}

	fmt.Printf("📄 第%d页抓取完成,获取%d条\n", pageIndex, len(vulns))
	return vulns, nil
}

// 2. 全页分页抓取目标日期漏洞
func fetchAllCNNVDVulns() ([]Vuln, error) {
	// 固定抓取 前天 收录的漏洞
	targetDate := time.Now().AddDate(0, 0, -2).Format("2006-01-02")
	fmt.Printf("\n📅 本轮扫描收录日期:%s(前天)\n", targetDate)

	var allVulns []Vuln
	for page := 1; page <= maxFetchPage; page++ {
		pageVulns, err := fetchCNNVDPage(page, targetDate)
		if err != nil || len(pageVulns) == 0 {
			fmt.Println("📭 已抓取完所有数据页")
			break
		}
		allVulns = append(allVulns, pageVulns...)
		time.Sleep(fetchSleepDelay)
	}

	fmt.Printf("📊 本次原始抓取总条数:%d\n", len(allVulns))
	return allVulns, nil
}

// 3. 全局漏洞去重
func deduplicateVulns(vulns []Vuln) []Vuln {
	globalLock.Lock()
	defer globalLock.Unlock()

	var uniqueList []Vuln
	for _, v := range vulns {
		uniqueKey := fmt.Sprintf("%s|%s", v.CveCode, v.CnnvdCode)
		if !seenVulnMap[uniqueKey] {
			seenVulnMap[uniqueKey] = true
			uniqueList = append(uniqueList, v)
		}
	}

	// 按收录时间倒序
	sort.Slice(uniqueList, func(i, j int) bool {
		return uniqueList[i].PublishTime > uniqueList[j].PublishTime
	})
	fmt.Printf("🆕 本轮新增未推送漏洞:%d条\n", len(uniqueList))
	return uniqueList
}

// 4. CSV永久追加写入(UTF8-BOM彻底解决Excel中文乱码)
func appendToCSV(vulns []Vuln) error {
	if len(vulns) == 0 {
		return nil
	}

	// 读取历史数据,计算连续自增ID
	var historyVulns []Vuln
	if _, err := os.Stat(csvFilePath); err == nil {
		file, _ := os.Open(csvFilePath)
		defer file.Close()
		rows, _ := csv.NewReader(file).ReadAll()
		for i, row := range rows {
			if i == 0 {
				continue
			}
			id, _ := strconv.Atoi(row[0])
			level, _ := strconv.Atoi(row[4])
			historyVulns = append(historyVulns, Vuln{
				ID:          id,
				HazardLevel: level,
			})
		}
	}

	maxID := 0
	for _, v := range historyVulns {
		if v.ID > maxID {
			maxID = v.ID
		}
	}
	for i := range vulns {
		maxID++
		vulns[i].ID = maxID
	}

	// 追加写入文件
	file, err := os.OpenFile(csvFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	// 新文件写入UTF8 BOM + 表头
	if len(historyVulns) == 0 {
		_, _ = file.Write([]byte{0xEF, 0xBB, 0xBF})
		writer := csv.NewWriter(file)
		writer.Write([]string{
			"序号ID", "CVE编号", "CNNVD编号", "漏洞名称",
			"风险等级数字", "风险等级", "收录时间", "抓取时间",
		})
		writer.Flush()
	}

	// 写入新增漏洞
	writer := csv.NewWriter(file)
	for _, v := range vulns {
		writer.Write([]string{
			strconv.Itoa(v.ID), v.CveCode, v.CnnvdCode, v.VulName,
			strconv.Itoa(v.HazardLevel), v.HazardLevelText,
			v.PublishTime, v.RecordTime,
		})
	}
	writer.Flush()

	fmt.Printf("✅ CSV追加完成,累计漏洞总数:%d条\n", maxID)
	return nil
}

// 5. 企业微信单条消息推送
func sendWechatSingle(content string) bool {
	url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s", wechatWebhookKey)
	msgBody, _ := json.Marshal(map[string]any{
		"msgtype": "text",
		"text":    map[string]string{"content": content},
	})
	resp, err := http.Post(url, "application/json;charset=utf-8", bytes.NewBuffer(msgBody))
	if err != nil {
		return false
	}
	defer resp.Body.Close()
	return resp.StatusCode == 200
}

// 6. 企业微信分批推送(解决字数超限截断)
func sendWechatBatch(vulns []Vuln) {
	if len(vulns) == 0 {
		fmt.Println("ℹ️ 无新增漏洞,无需微信推送")
		return
	}

	targetDate := time.Now().AddDate(0, 0, -2).Format("2006-01-02")
	baseHeader := fmt.Sprintf("🚨 CNNVD【%s 前日收录漏洞更新】\n📢 本轮新增:%d条\n=====================================\n", targetDate, len(vulns))

	var contentBuf strings.Builder
	contentBuf.WriteString(baseHeader)
	batchCount, batchItemCount := 1, 0

	for i, v := range vulns {
		itemText := fmt.Sprintf("%d. %s %s\n漏洞名称:%s\n收录日期:%s\n-------------------------------------\n",
			i+1, v.HazardLevelText, v.CveCode, v.VulName, v.PublishTime)

		// 字符超限就推送当前批次,新建批次
		if contentBuf.Len()+len(itemText) > maxWechatContent {
			fmt.Printf("📤 推送微信第%d批次,共%d条\n", batchCount, batchItemCount)
			sendWechatSingle(contentBuf.String())
			contentBuf.Reset()
			contentBuf.WriteString(fmt.Sprintf("🚨 CNNVD漏洞更新 第%d批次\n=====================================\n", batchCount+1))
			batchCount++
			batchItemCount = 0
		}

		contentBuf.WriteString(itemText)
		batchItemCount++
	}

	// 推送最后剩余批次
	if batchItemCount > 0 {
		fmt.Printf("📤 推送微信第%d批次,共%d条\n", batchCount, batchItemCount)
		sendWechatSingle(contentBuf.String())
	}

	fmt.Printf("✅ 企业微信推送全部完成,共拆分为%d个批次\n", batchCount)
}

// 单次完整扫描任务
func fullScanTask() {
	fmt.Println("\n======================================")
	fmt.Printf("🕒 本轮扫描开始:%s\n", time.Now().Format("2006-01-02 15:04:05"))
	fmt.Println("======================================")

	// 1. 抓取
	rawVulns, _ := fetchAllCNNVDVulns()
	// 2. 去重
	newVulns := deduplicateVulns(rawVulns)
	// 3. 微信推送
	sendWechatBatch(newVulns)
	// 4. 写入CSV
	_ = appendToCSV(newVulns)

	fmt.Println("====================================== 本轮扫描结束 ======================================\n")
}

// 预加载历史漏洞,启动去重库
func initHistoryDedup() {
	if _, err := os.Stat(csvFilePath); os.IsNotExist(err) {
		return
	}
	file, _ := os.Open(csvFilePath)
	defer file.Close()
	rows, _ := csv.NewReader(file).ReadAll()
	for i, row := range rows {
		if i == 0 {
			continue
		}
		key := fmt.Sprintf("%s|%s", row[1], row[2])
		seenVulnMap[key] = true
	}
	fmt.Println("✅ 历史漏洞去重库加载完成\n")
}

func main() {
	fmt.Println("🚀 CNNVD 漏洞7×24小时监控程序 正式启动")
	fmt.Printf("⏱️  自动扫描间隔:%v\n", scanInterval)
	fmt.Println("====================================================\n")

	// 初始化
	initHistoryDedup()

	// 启动立即执行一次
	fullScanTask()

	// 常驻定时轮询,永久运行
	ticker := time.NewTicker(scanInterval)
	defer ticker.Stop()
	for range ticker.C {
		fullScanTask()
	}
}

2:运行

3:查看

CVE漏洞的7*24H,抓到了国家信息安全漏洞库的(我先推送我自己的群,还有生成了csv,定时追加)。CNNVD,https://www.cnnvd.org.cn/home/loophole


其他说明:config.example.yaml配置推送频率,途径。

1.如果有钉钉,企微,飞书的key,可用公司的。这里写了如何获取wechat消息推送。

wechat消息推送-CSDN博客

2.也可以推送Pushplus,会提供token,关注这个公众号就行。然后会给你token,但是实名要3.9单认证。会员一个月9.9含认证。坑坑的。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值