Files
vuls/gost/microsoft.go
MaineK00n 48f7597bcf feat(ms): import gost:MaineK00n/new-windows (#1481)
* feat(ms): import gost:MaineK00n/new-windows

* chore(discover): add CTI section

* feat(ms): fill KB with VulnInfo.DistroAdvisories instead of CveContent.Optional

* fix(ms): Change bitSize from 32 to 64

* fix(ms): delete KB prefix

* chore(ms): change logger

* fix(ms): fill in correct AdvisoryID

Co-authored-by: Sadayuki Matsuno <sadayuki.matsuno@gmail.com>
2022-07-04 14:26:41 +09:00

219 lines
5.8 KiB
Go

//go:build !scanner
// +build !scanner
package gost
import (
"fmt"
"regexp"
"strconv"
"strings"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
gostmodels "github.com/vulsio/gost/models"
)
// Microsoft is Gost client for windows
type Microsoft struct {
Base
}
var kbIDPattern = regexp.MustCompile(`KB(\d{6,7})`)
// DetectCVEs fills cve information that has in Gost
func (ms Microsoft) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
if ms.driver == nil {
return 0, nil
}
var osName string
osName, ok := r.Optional["OSName"].(string)
if !ok {
logging.Log.Warnf("This Windows has wrong type option(OSName). UUID: %s", r.ServerUUID)
}
var products []string
if _, ok := r.Optional["InstalledProducts"]; ok {
switch ps := r.Optional["InstalledProducts"].(type) {
case []interface{}:
for _, p := range ps {
pname, ok := p.(string)
if !ok {
logging.Log.Warnf("skip products: %v", p)
continue
}
products = append(products, pname)
}
case []string:
for _, p := range ps {
products = append(products, p)
}
case nil:
logging.Log.Warnf("This Windows has no option(InstalledProducts). UUID: %s", r.ServerUUID)
}
}
applied, unapplied := map[string]struct{}{}, map[string]struct{}{}
if _, ok := r.Optional["KBID"]; ok {
switch kbIDs := r.Optional["KBID"].(type) {
case []interface{}:
for _, kbID := range kbIDs {
s, ok := kbID.(string)
if !ok {
logging.Log.Warnf("skip KBID: %v", kbID)
continue
}
unapplied[strings.TrimPrefix(s, "KB")] = struct{}{}
}
case []string:
for _, kbID := range kbIDs {
unapplied[strings.TrimPrefix(kbID, "KB")] = struct{}{}
}
case nil:
logging.Log.Warnf("This Windows has no option(KBID). UUID: %s", r.ServerUUID)
}
for _, pkg := range r.Packages {
matches := kbIDPattern.FindAllStringSubmatch(pkg.Name, -1)
for _, match := range matches {
applied[match[1]] = struct{}{}
}
}
} else {
switch kbIDs := r.Optional["AppliedKBID"].(type) {
case []interface{}:
for _, kbID := range kbIDs {
s, ok := kbID.(string)
if !ok {
logging.Log.Warnf("skip KBID: %v", kbID)
continue
}
applied[strings.TrimPrefix(s, "KB")] = struct{}{}
}
case []string:
for _, kbID := range kbIDs {
applied[strings.TrimPrefix(kbID, "KB")] = struct{}{}
}
case nil:
logging.Log.Warnf("This Windows has no option(AppliedKBID). UUID: %s", r.ServerUUID)
}
switch kbIDs := r.Optional["UnappliedKBID"].(type) {
case []interface{}:
for _, kbID := range kbIDs {
s, ok := kbID.(string)
if !ok {
logging.Log.Warnf("skip KBID: %v", kbID)
continue
}
unapplied[strings.TrimPrefix(s, "KB")] = struct{}{}
}
case []string:
for _, kbID := range kbIDs {
unapplied[strings.TrimPrefix(kbID, "KB")] = struct{}{}
}
case nil:
logging.Log.Warnf("This Windows has no option(UnappliedKBID). UUID: %s", r.ServerUUID)
}
}
logging.Log.Debugf(`GetCvesByMicrosoftKBID query body {"osName": %s, "installedProducts": %q, "applied": %q, "unapplied: %q"}`, osName, products, maps.Keys(applied), maps.Keys(unapplied))
cves, err := ms.driver.GetCvesByMicrosoftKBID(osName, products, maps.Keys(applied), maps.Keys(unapplied))
if err != nil {
return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err)
}
for cveID, cve := range cves {
cveCont, mitigations := ms.ConvertToModel(&cve)
advisories := []models.DistroAdvisory{}
for _, p := range cve.Products {
for _, kb := range p.KBs {
adv := models.DistroAdvisory{
AdvisoryID: kb.Article,
Description: "Microsoft Knowledge Base",
}
if _, err := strconv.Atoi(kb.Article); err == nil {
adv.AdvisoryID = fmt.Sprintf("KB%s", kb.Article)
}
advisories = append(advisories, adv)
}
}
r.ScannedCves[cveID] = models.VulnInfo{
CveID: cveID,
Confidences: models.Confidences{models.WindowsUpdateSearch},
DistroAdvisories: advisories,
CveContents: models.NewCveContents(*cveCont),
Mitigations: mitigations,
}
}
return len(cves), nil
}
// ConvertToModel converts gost model to vuls model
func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveContent, []models.Mitigation) {
slices.SortFunc(cve.Products, func(i, j gostmodels.MicrosoftProduct) bool {
return i.ScoreSet.Vector < j.ScoreSet.Vector
})
v3score := 0.0
var v3Vector string
for _, p := range cve.Products {
v, err := strconv.ParseFloat(p.ScoreSet.BaseScore, 64)
if err != nil {
continue
}
if v3score < v {
v3score = v
v3Vector = p.ScoreSet.Vector
}
}
var v3Severity string
for _, p := range cve.Products {
v3Severity = p.Severity
}
option := map[string]string{}
if 0 < len(cve.ExploitStatus) {
// TODO: CVE-2020-0739
// "exploit_status": "Publicly Disclosed:No;Exploited:No;Latest Software Release:Exploitation Less Likely;Older Software Release:Exploitation Less Likely;DOS:N/A",
option["exploit"] = cve.ExploitStatus
}
mitigations := []models.Mitigation{}
if cve.Mitigation != "" {
mitigations = append(mitigations, models.Mitigation{
CveContentType: models.Microsoft,
Mitigation: cve.Mitigation,
URL: cve.URL,
})
}
if cve.Workaround != "" {
mitigations = append(mitigations, models.Mitigation{
CveContentType: models.Microsoft,
Mitigation: cve.Workaround,
URL: cve.URL,
})
}
return &models.CveContent{
Type: models.Microsoft,
CveID: cve.CveID,
Title: cve.Title,
Summary: cve.Description,
Cvss3Score: v3score,
Cvss3Vector: v3Vector,
Cvss3Severity: v3Severity,
Published: cve.PublishDate,
LastModified: cve.LastUpdateDate,
SourceLink: cve.URL,
Optional: option,
}, mitigations
}