Skip to content

Commit 28cac34

Browse files
committed
feat: 将CVE、CWE和Remote/Local状态从Tags中分离为独立字段
1 parent e1de5bf commit 28cac34

5 files changed

Lines changed: 195 additions & 62 deletions

File tree

cmd/exploit.go

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,6 @@ func printExploitResult(result interface{}, outputPath string) {
111111
date = v.Date.Format("2006-01-02")
112112
}
113113

114-
// 标签信息
115-
tags := "无"
116-
if len(v.Tags) > 0 {
117-
tags = strings.Join(v.Tags, ", ")
118-
}
119-
120114
// 计算内容区域宽度
121115
contentWidth := borderWidth - 2 // 左右各减1个字符的padding
122116

@@ -150,7 +144,34 @@ func printExploitResult(result interface{}, outputPath string) {
150144
printLine("漏洞标题", v.Title)
151145
printLine("风险级别", riskLevel) // 已经着色
152146
printLine("发布日期", date)
153-
printLine("漏洞标签", tags, text.FgHiGreen)
147+
148+
// 输出CVE编号(如果有)
149+
if v.CVE != "" {
150+
printLine("CVE编号", v.CVE, text.FgHiYellow)
151+
}
152+
153+
// 输出CWE编号(如果有)
154+
if v.CWE != "" {
155+
printLine("CWE编号", v.CWE, text.FgHiYellow)
156+
}
157+
158+
// 输出漏洞位置信息
159+
locationInfo := []string{}
160+
if v.IsRemote {
161+
locationInfo = append(locationInfo, "远程")
162+
}
163+
if v.IsLocal {
164+
locationInfo = append(locationInfo, "本地")
165+
}
166+
if len(locationInfo) > 0 {
167+
printLine("漏洞位置", strings.Join(locationInfo, ", "), text.FgHiGreen)
168+
}
169+
170+
// 输出其他标签(如果有)
171+
if len(v.Tags) > 0 {
172+
printLine("其他标签", strings.Join(v.Tags, ", "), text.FgHiGreen)
173+
}
174+
154175
printLine("作者", v.Author, text.FgHiMagenta)
155176

156177
// 添加原始URL链接行
@@ -178,32 +199,41 @@ func printExploitResult(result interface{}, outputPath string) {
178199

179200
// 动态计算各列宽度
180201
// 终端宽度减去表格边框和列分隔符所占用的空间(大约是每列2个字符和表边框4个字符)
181-
availableWidth := width - (4 + 2*5) // 5列: ID、日期、风险、标题、作者
202+
availableWidth := width - (4 + 2*7) // 7列: ID、日期、风险、CVE、CWE、位置、标题、作者
182203

183204
// 根据内容特点分配各列宽度占比
184-
idRatio := 0.15 // ID列 - 约15%
185-
dateRatio := 0.10 // 日期列 - 约10%
186-
riskRatio := 0.08 // 风险列 - 约8%
187-
titleRatio := 0.47 // 标题列 - 约47%
188-
authorRatio := 0.20 // 作者列 - 约20%
189-
190-
// 计算各列实际宽度(最小保证有10个字符)
205+
idRatio := 0.13 // ID列 - 约13%
206+
dateRatio := 0.08 // 日期列 - 约8%
207+
riskRatio := 0.07 // 风险列 - 约7%
208+
cveRatio := 0.12 // CVE列 - 约12%
209+
cweRatio := 0.07 // CWE列 - 约7%
210+
locRatio := 0.08 // 位置列 - 约8%
211+
titleRatio := 0.30 // 标题列 - 约30%
212+
authorRatio := 0.15 // 作者列 - 约15%
213+
214+
// 计算各列实际宽度(最小保证有合理的字符数)
191215
idWidth := max(15, int(float64(availableWidth)*idRatio))
192-
dateWidth := max(12, int(float64(availableWidth)*dateRatio))
193-
riskWidth := max(8, int(float64(availableWidth)*riskRatio))
216+
dateWidth := max(10, int(float64(availableWidth)*dateRatio))
217+
riskWidth := max(6, int(float64(availableWidth)*riskRatio))
218+
cveWidth := max(14, int(float64(availableWidth)*cveRatio))
219+
cweWidth := max(8, int(float64(availableWidth)*cweRatio))
220+
locWidth := max(8, int(float64(availableWidth)*locRatio))
194221
titleWidth := max(25, int(float64(availableWidth)*titleRatio))
195-
authorWidth := max(15, int(float64(availableWidth)*authorRatio))
222+
authorWidth := max(12, int(float64(availableWidth)*authorRatio))
196223

197224
// 设置表头
198-
t.AppendHeader(table.Row{"ID", "日期", "风险", "标题", "作者"})
225+
t.AppendHeader(table.Row{"ID", "日期", "风险", "CVE", "CWE", "位置", "标题", "作者"})
199226

200227
// 设置表头样式 - 深色背景
201228
t.SetColumnConfigs([]table.ColumnConfig{
202229
{Number: 1, Align: text.AlignCenter, AlignHeader: text.AlignCenter, Colors: text.Colors{text.FgHiCyan}, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: idWidth},
203230
{Number: 2, Align: text.AlignCenter, AlignHeader: text.AlignCenter, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: dateWidth},
204231
{Number: 3, Align: text.AlignCenter, AlignHeader: text.AlignCenter, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: riskWidth},
205-
{Number: 4, AlignHeader: text.AlignCenter, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: titleWidth},
206-
{Number: 5, AlignHeader: text.AlignCenter, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: authorWidth},
232+
{Number: 4, Align: text.AlignCenter, AlignHeader: text.AlignCenter, Colors: text.Colors{text.FgHiYellow}, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: cveWidth},
233+
{Number: 5, Align: text.AlignCenter, AlignHeader: text.AlignCenter, Colors: text.Colors{text.FgHiYellow}, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: cweWidth},
234+
{Number: 6, Align: text.AlignCenter, AlignHeader: text.AlignCenter, Colors: text.Colors{text.FgHiGreen}, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: locWidth},
235+
{Number: 7, AlignHeader: text.AlignCenter, Colors: text.Colors{text.FgHiWhite}, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: titleWidth},
236+
{Number: 8, AlignHeader: text.AlignCenter, Colors: text.Colors{text.FgHiMagenta}, ColorsHeader: text.Colors{text.BgBlack, text.FgHiWhite, text.Bold}, WidthMax: authorWidth},
207237
})
208238

209239
// 添加数据行
@@ -219,7 +249,7 @@ func printExploitResult(result interface{}, outputPath string) {
219249
}
220250

221251
// 日期格式化
222-
date := "未知"
252+
date := ""
223253
if !item.Date.IsZero() {
224254
date = item.Date.Format("2006-01-02")
225255
}
@@ -244,6 +274,28 @@ func printExploitResult(result interface{}, outputPath string) {
244274
}
245275
}
246276

277+
// CVE编号处理
278+
cve := item.CVE
279+
if len(cve) > cveWidth-3 && cveWidth > 6 {
280+
cve = cve[:cveWidth-6] + "..."
281+
}
282+
283+
// CWE编号处理
284+
cwe := item.CWE
285+
if len(cwe) > cweWidth-3 && cweWidth > 6 {
286+
cwe = cwe[:cweWidth-6] + "..."
287+
}
288+
289+
// 位置信息处理
290+
location := ""
291+
if item.IsRemote && item.IsLocal {
292+
location = "远程/本地"
293+
} else if item.IsRemote {
294+
location = "远程"
295+
} else if item.IsLocal {
296+
location = "本地"
297+
}
298+
247299
// 根据风险级别设置不同颜色
248300
riskLevel := item.RiskLevel
249301
var riskColor text.Colors
@@ -267,6 +319,9 @@ func printExploitResult(result interface{}, outputPath string) {
267319
text.Colors{text.FgHiCyan}.Sprint(vulnID),
268320
date,
269321
coloredRisk,
322+
text.Colors{text.FgHiYellow}.Sprint(cve),
323+
text.Colors{text.FgHiYellow}.Sprint(cwe),
324+
text.Colors{text.FgHiGreen}.Sprint(location),
270325
title,
271326
text.Colors{text.FgHiMagenta}.Sprint(author),
272327
})
@@ -277,6 +332,9 @@ func printExploitResult(result interface{}, outputPath string) {
277332
fmt.Sprintf("总计: %d 条记录", len(v.Items)),
278333
"",
279334
"",
335+
"",
336+
"",
337+
"",
280338
fmt.Sprintf("页码: %d/%d", v.CurrentPage, v.TotalPages),
281339
""})
282340

pkg/crawler/crawler.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,9 @@ func (c *Crawler) CrawlCveDetail(cveID string, outputPath string) (*model.CveDet
140140
return result, nil
141141
}
142142

143-
// CrawlExploit 爬取漏洞列表或指定ID的漏洞,返回对应的结果
143+
// CrawlExploit 爬取漏洞列表或漏洞详情
144144
func (c *Crawler) CrawlExploit(id string, outputPath string, fields string) (interface{}, error) {
145+
// 确定路径
145146
var path string
146147
if id == "" {
147148
// 默认爬取漏洞列表页面
@@ -181,7 +182,13 @@ func (c *Crawler) CrawlExploit(id string, outputPath string, fields string) (int
181182
for i := range result.Items {
182183
if result.Items[i].URL != "" {
183184
if idx := strings.Index(result.Items[i].URL, "WLB-"); idx != -1 {
184-
result.Items[i].ID = result.Items[i].URL[idx:]
185+
// 提取URL中的ID
186+
urlPart := result.Items[i].URL[idx:]
187+
endIdx := len(urlPart)
188+
if slashIdx := strings.IndexByte(urlPart, '/'); slashIdx != -1 {
189+
endIdx = slashIdx
190+
}
191+
result.Items[i].ID = urlPart[:endIdx]
185192
}
186193
}
187194
}

pkg/crawler/detail_parser.go

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package crawler
22

33
import (
44
"fmt"
5+
"regexp"
56
"strings"
67
"time"
78

@@ -21,7 +22,9 @@ func (p *Parser) ParseVulnerabilityDetailPage(htmlContent string) (*model.Vulner
2122
return nil, fmt.Errorf("failed to parse HTML: %w", err)
2223
}
2324

24-
vulnerability := &model.Vulnerability{}
25+
vulnerability := &model.Vulnerability{
26+
Tags: []string{}, // 初始化为空切片,用于存储其他标签
27+
}
2528

2629
// 提取标题 - 更精确的选择器
2730
vulnerability.Title = strings.TrimSpace(doc.Find("h4 > B").First().Text())
@@ -35,42 +38,48 @@ func (p *Parser) ParseVulnerabilityDetailPage(htmlContent string) (*model.Vulner
3538
riskLevelLabel := doc.Find(".well-sm:contains('Risk:')").Find("span.label")
3639
vulnerability.RiskLevel = strings.TrimSpace(riskLevelLabel.Text())
3740

38-
// 提取标签 - 逐个精确查找
39-
vulnerability.Tags = []string{} // 初始化为空切片
41+
// 正则表达式用于提取CVE和CWE编号
42+
cvePattern := regexp.MustCompile(`CVE-\d{4}-\d+`)
43+
cwePattern := regexp.MustCompile(`CWE-\d+`)
4044

41-
// CVE
45+
// 提取CVE编号
4246
cveLink := doc.Find(".well-sm:contains('CVE:')").Find("a[href*='cveshow']")
4347
cveText := strings.TrimSpace(cveLink.Text())
4448
if cveText != "" {
45-
vulnerability.Tags = append(vulnerability.Tags, cveText)
49+
// 使用正则表达式匹配CVE编号
50+
if matches := cvePattern.FindStringSubmatch(cveText); len(matches) > 0 {
51+
vulnerability.CVE = matches[0]
52+
} else {
53+
vulnerability.CVE = cveText // 如果无法匹配模式,保留原始文本
54+
}
4655
}
4756

48-
// CWE
57+
// 提取CWE编号
4958
cweLink := doc.Find(".well-sm:contains('CWE:')").Find("a[href*='cwe']")
5059
cweText := strings.TrimSpace(cweLink.Text())
5160
if cweText != "" {
52-
vulnerability.Tags = append(vulnerability.Tags, cweText)
61+
// 使用正则表达式匹配CWE编号
62+
if matches := cwePattern.FindStringSubmatch(cweText); len(matches) > 0 {
63+
vulnerability.CWE = matches[0]
64+
} else {
65+
vulnerability.CWE = cweText // 如果无法匹配模式,保留原始文本
66+
}
5367
}
5468

55-
// Local - HTML: <U>Local:</U> <b><B>Yes</B></span></b> or similar
69+
// 提取Local状态 - 设置bool字段
5670
doc.Find(".well-sm:contains('Local:')").Each(func(_ int, s *goquery.Selection) {
57-
// 查找此 well 内部的 <b> 或 <B> 标签,并检查文本
5871
s.Find("b, B").Each(func(_ int, b *goquery.Selection) {
5972
if strings.TrimSpace(b.Text()) == "Yes" {
60-
// 只有当找到文本为 Yes 的 b 标签时才添加
61-
vulnerability.Tags = append(vulnerability.Tags, "Local")
62-
return // 假设每个 well 最多一个 Yes
73+
vulnerability.IsLocal = true
6374
}
6475
})
6576
})
6677

67-
// Remote - HTML: <U>Remote:</U> <b>No</b> or similar
78+
// 提取Remote状态 - 设置bool字段
6879
doc.Find(".well-sm:contains('Remote:')").Each(func(_ int, s *goquery.Selection) {
69-
// 查找此 well 内部的 <b> 或 <B> 标签,并检查文本
7080
s.Find("b, B").Each(func(_ int, b *goquery.Selection) {
7181
if strings.TrimSpace(b.Text()) == "Yes" {
72-
vulnerability.Tags = append(vulnerability.Tags, "Remote")
73-
return
82+
vulnerability.IsRemote = true
7483
}
7584
})
7685
})
@@ -110,6 +119,26 @@ func (p *Parser) ParseVulnerabilityDetailPage(htmlContent string) (*model.Vulner
110119
}
111120
}
112121

122+
// 提取其他标签 - 例如漏洞类型、平台等
123+
doc.Find(".well-sm").Each(func(_ int, s *goquery.Selection) {
124+
// 跳过已处理的字段
125+
wellText := s.Text()
126+
if strings.Contains(wellText, "CVE:") ||
127+
strings.Contains(wellText, "CWE:") ||
128+
strings.Contains(wellText, "Local:") ||
129+
strings.Contains(wellText, "Remote:") ||
130+
strings.Contains(wellText, "Risk:") ||
131+
strings.Contains(wellText, "Credit:") {
132+
return
133+
}
134+
135+
// 寻找可能的标签值
136+
labelText := strings.TrimSpace(s.Find("label, span.label").Text())
137+
if labelText != "" && labelText != "N/A" && !strings.Contains(labelText, ":") {
138+
vulnerability.Tags = append(vulnerability.Tags, labelText)
139+
}
140+
})
141+
113142
// --- 去重 Tags ---
114143
if len(vulnerability.Tags) > 0 {
115144
uniqueTags := make(map[string]struct{})

pkg/crawler/list_parser.go

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ func (p *Parser) ParseListPage(htmlContent string) (*model.VulnerabilityList, er
2929

3030
var currentDate time.Time // 用于存储最近解析到的日期
3131

32+
// 编译正则表达式用于匹配CVE和CWE
33+
cvePattern := regexp.MustCompile(`CVE-\d{4}-\d+`)
34+
cwePattern := regexp.MustCompile(`CWE-\d+`)
35+
3236
// 查找表格
3337
table := doc.Find("table.table-striped")
3438

@@ -72,41 +76,68 @@ func (p *Parser) ParseListPage(htmlContent string) (*model.VulnerabilityList, er
7276
}
7377
}
7478

79+
// 初始化漏洞对象,设置基本信息
80+
vulnerability := model.Vulnerability{
81+
Date: currentDate,
82+
Title: title,
83+
URL: url,
84+
RiskLevel: riskLevel,
85+
Tags: []string{}, // 用于保存其他标签
86+
Author: "", // 默认为空,后面会设置
87+
AuthorURL: "", // 默认为空,后面会设置
88+
}
89+
7590
// 标签 (第二列,右侧)
76-
var tags []string
7791
cells.Eq(1).Find("div.row div.col-md-5 span.label").Each(func(j int, tagSelection *goquery.Selection) {
92+
// 跳过作者标签
7893
if tagSelection.Find("a[href*='/author/']").Length() == 0 {
7994
tag := strings.TrimSpace(tagSelection.Text())
80-
if tag != "" {
81-
tags = append(tags, tag)
95+
if tag == "" {
96+
return
97+
}
98+
99+
// 检查是否是CVE编号
100+
if cveMatches := cvePattern.FindStringSubmatch(tag); len(cveMatches) > 0 {
101+
vulnerability.CVE = cveMatches[0]
102+
return
103+
}
104+
105+
// 检查是否是CWE编号
106+
if cweMatches := cwePattern.FindStringSubmatch(tag); len(cweMatches) > 0 {
107+
vulnerability.CWE = cweMatches[0]
108+
return
109+
}
110+
111+
// 检查是否是Remote/Local标记
112+
if tag == "Remote" {
113+
vulnerability.IsRemote = true
114+
return
82115
}
116+
if tag == "Local" {
117+
vulnerability.IsLocal = true
118+
return
119+
}
120+
121+
// 添加到其他标签列表
122+
vulnerability.Tags = append(vulnerability.Tags, tag)
83123
}
84124
})
85125

86126
// 作者信息 (第二列,右侧的作者链接)
87127
authorSelection := cells.Eq(1).Find("div.row div.col-md-5 a[href*='/author/']")
88-
author := strings.TrimSpace(authorSelection.Text())
89-
authorURL, _ := authorSelection.Attr("href")
128+
vulnerability.Author = strings.TrimSpace(authorSelection.Text())
129+
vulnerability.AuthorURL, _ = authorSelection.Attr("href")
90130
// 修正作者URL
91-
if authorURL != "" && !strings.HasPrefix(authorURL, "http") {
92-
if strings.HasPrefix(authorURL, "/") {
93-
authorURL = "https://cxsecurity.com" + authorURL
131+
if vulnerability.AuthorURL != "" && !strings.HasPrefix(vulnerability.AuthorURL, "http") {
132+
if strings.HasPrefix(vulnerability.AuthorURL, "/") {
133+
vulnerability.AuthorURL = "https://cxsecurity.com" + vulnerability.AuthorURL
94134
} else {
95-
authorURL = "https://cxsecurity.com/" + authorURL
135+
vulnerability.AuthorURL = "https://cxsecurity.com/" + vulnerability.AuthorURL
96136
}
97137
}
98138

99-
// 创建漏洞对象 - 只要标题不为空就创建,使用最近解析到的日期
100-
if title != "" {
101-
vulnerability := model.Vulnerability{
102-
Date: currentDate, // 使用最近解析到的日期,如果从未解析到则为零值
103-
Title: title,
104-
URL: url,
105-
RiskLevel: riskLevel,
106-
Tags: tags,
107-
Author: author,
108-
AuthorURL: authorURL,
109-
}
139+
// 只有标题不为空才添加该漏洞
140+
if vulnerability.Title != "" {
110141
result.Items = append(result.Items, vulnerability)
111142
}
112143
})

0 commit comments

Comments
 (0)