Skip to content

Commit 3f74951

Browse files
authored
Feat: add grabber for CISA KEV (#74)
* add grabber for cisa kev * complete VulnInfo.Severity
1 parent bb4555e commit 3f74951

File tree

4 files changed

+143
-1
lines changed

4 files changed

+143
-1
lines changed

ctrl/ctrl.go

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ func NewApp(config *WatchVulnAppConfig, textPusher push.TextPusher, rawPusher pu
8787
grabs = append(grabs, grab.NewThreatBookCrawler())
8888
case "struts2", "structs2":
8989
grabs = append(grabs, grab.NewStruts2Crawler())
90+
case "kev":
91+
grabs = append(grabs, grab.NewKEVCrawler())
9092
default:
9193
return nil, fmt.Errorf("invalid grab source %s", part)
9294
}

grab/kev.go

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package grab
2+
3+
import (
4+
"context"
5+
"github.com/imroc/req/v3"
6+
"github.com/kataras/golog"
7+
"github.com/pkg/errors"
8+
"time"
9+
)
10+
11+
const KEVUrl = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
12+
13+
// const CVEUrl = "https://cveawg.mitre.org/api/cve/" 查询原始评级预留url
14+
const PageSize = 5 //KEV每次都是返回全量数据,所以这里自己定义一下pagesize匹配原来的爬取逻辑
15+
16+
type KEVCrawler struct {
17+
client *req.Client
18+
log *golog.Logger
19+
}
20+
21+
func NewKEVCrawler() Grabber {
22+
client := NewHttpClient()
23+
client.SetCommonHeader("Referer", "Referer: https://www.cisa.gov/known-exploited-vulnerabilities-catalog")
24+
25+
return &KEVCrawler{
26+
log: golog.Child("[KEV]"),
27+
client: client,
28+
}
29+
}
30+
31+
func (c *KEVCrawler) GetUpdate(ctx context.Context, pageLimit int) ([]*VulnInfo, error) {
32+
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
33+
defer cancel()
34+
35+
var result kevResp
36+
_, err := c.client.R().SetContext(ctx).AddRetryCondition(func(resp *req.Response, err error) bool {
37+
if err != nil {
38+
return !errors.Is(err, context.Canceled)
39+
}
40+
if resp.StatusCode != 200 || !resp.IsSuccessState() {
41+
c.log.Warnf("failed to get content, msg: %s, retrying", resp.Status)
42+
return true
43+
}
44+
if err = resp.UnmarshalJson(&result); err != nil {
45+
c.log.Warnf("unmarshal json error, %s", err)
46+
return true
47+
}
48+
return false
49+
}).Get(KEVUrl)
50+
if err != nil {
51+
return nil, err
52+
}
53+
54+
var vulnInfos []*VulnInfo
55+
var itemLimit = 0
56+
var maxCount = len(result.Vulnerabilities)
57+
if pageLimit*PageSize > maxCount {
58+
itemLimit = maxCount
59+
} else {
60+
itemLimit = pageLimit * PageSize
61+
}
62+
for i := 1; i <= itemLimit; i++ {
63+
var vulnInfo VulnInfo
64+
vuln := result.Vulnerabilities[maxCount-i]
65+
vulnInfo.UniqueKey = vuln.CveID + "_KEV"
66+
vulnInfo.Title = vuln.VulnerabilityName
67+
vulnInfo.Description = vuln.ShortDescription
68+
vulnInfo.Severity = Critical //数据源本身无该字段,因为有在野利用直接提成Critical了,后续考虑要不要去CVE查询原始评级?
69+
vulnInfo.CVE = vuln.CveID
70+
vulnInfo.Solutions = vuln.RequiredAction
71+
vulnInfo.Disclosure = vuln.DateAdded
72+
vulnInfo.From = vuln.Notes
73+
vulnInfo.Tags = []string{vuln.VendorProject, vuln.Product, "在野利用"}
74+
vulnInfos = append(vulnInfos, &vulnInfo)
75+
}
76+
77+
return vulnInfos, nil
78+
79+
}
80+
81+
func (c *KEVCrawler) ProviderInfo() *Provider {
82+
return &Provider{
83+
Name: "KEV",
84+
DisplayName: "Known Exploited Vulnerabilities Catalog",
85+
Link: "https://www.cisa.gov/known-exploited-vulnerabilities-catalog",
86+
}
87+
}
88+
89+
func (c *KEVCrawler) IsValuable(info *VulnInfo) bool {
90+
return info.Severity == High || info.Severity == Critical
91+
}
92+
93+
type kevResp struct {
94+
Title string `json:"title"`
95+
CatalogVersion string `json:"catalogVersion"`
96+
DateReleased time.Time `json:"dateReleased"`
97+
Count int `json:"count"`
98+
Vulnerabilities []struct {
99+
CveID string `json:"cveID"`
100+
VendorProject string `json:"vendorProject"`
101+
Product string `json:"product"`
102+
VulnerabilityName string `json:"vulnerabilityName"`
103+
DateAdded string `json:"dateAdded"`
104+
ShortDescription string `json:"shortDescription"`
105+
RequiredAction string `json:"requiredAction"`
106+
DueDate string `json:"dueDate"`
107+
KnownRansomwareCampaignUse string `json:"knownRansomwareCampaignUse"`
108+
Notes string `json:"notes"`
109+
} `json:"vulnerabilities"`
110+
}

grab/kev_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package grab
2+
3+
import (
4+
"context"
5+
"github.com/stretchr/testify/require"
6+
"testing"
7+
"time"
8+
)
9+
10+
func TestKEV(t *testing.T) {
11+
assert := require.New(t)
12+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*300)
13+
defer cancel()
14+
grab := NewKEVCrawler()
15+
vulns, err := grab.GetUpdate(ctx, 5)
16+
assert.Nil(err)
17+
18+
count := 0
19+
for _, v := range vulns {
20+
t.Logf("get vuln info %s", v)
21+
count++
22+
assert.NotEmpty(v.UniqueKey)
23+
assert.NotEmpty(v.Description)
24+
assert.NotEmpty(v.Title)
25+
assert.NotEmpty(v.Disclosure)
26+
assert.NotEmpty(v.Severity)
27+
28+
}
29+
assert.Equal(count, 25)
30+
}

main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func main() {
136136
Name: "sources",
137137
Aliases: []string{"s"},
138138
Usage: "set vuln sources",
139-
Value: "avd,nox,oscs,threatbook,seebug,struts2",
139+
Value: "avd,nox,oscs,threatbook,seebug,struts2,kev",
140140
Category: "[Launch Options]",
141141
},
142142
&cli.StringFlag{

0 commit comments

Comments
 (0)