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
(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消息推送。

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

444

被折叠的 条评论
为什么被折叠?



