mirror of
https://github.com/fleetdm/fleet
synced 2026-05-06 14:58:33 +00:00
168 lines
4.7 KiB
Go
168 lines
4.7 KiB
Go
package msrc
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/fleetdm/fleet/v4/server/vulnerabilities/msrc/parsed"
|
|
msrcxml "github.com/fleetdm/fleet/v4/server/vulnerabilities/msrc/xml"
|
|
)
|
|
|
|
func ParseFeed(fPath string) (map[string]*parsed.SecurityBulletin, error) {
|
|
r, err := os.Open(fPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("msrc parser: %w", err)
|
|
}
|
|
defer r.Close()
|
|
|
|
feedResultXML, err := parseXML(r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("msrc parser: %w", err)
|
|
}
|
|
|
|
bulletins, err := mapToSecurityBulletins(feedResultXML)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("msrc parser: %w", err)
|
|
}
|
|
|
|
return bulletins, nil
|
|
}
|
|
|
|
func mapToSecurityBulletins(rXML *msrcxml.FeedResult) (map[string]*parsed.SecurityBulletin, error) {
|
|
// We will have one bulletin for each product name.
|
|
bulletins := make(map[string]*parsed.SecurityBulletin)
|
|
pIDToPName := make(map[string]string, len(rXML.WinProducts))
|
|
|
|
for pID, p := range rXML.WinProducts {
|
|
name := parsed.NewProductFromFullName(p.FullName).Name()
|
|
// If the name could not be determined means that we have an un-supported Windows product
|
|
if name == "" {
|
|
continue
|
|
}
|
|
|
|
if bulletins[name] == nil {
|
|
bulletins[name] = parsed.NewSecurityBulletin(name)
|
|
}
|
|
bulletins[name].Products[pID] = parsed.NewProductFromFullName(p.FullName)
|
|
pIDToPName[pID] = name
|
|
}
|
|
|
|
for _, v := range rXML.WinVulnerabities {
|
|
for _, rem := range v.Remediations {
|
|
// We will only be able to detect vulns for which they are vendor fixes.
|
|
if !rem.IsVendorFix() {
|
|
continue
|
|
}
|
|
|
|
// We assume that rem.Description will contain the ID portion of a KBID, which should
|
|
// be always a numeric value.
|
|
remediatedKBIDRaw, err := strconv.Atoi(rem.Description)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid remediation KBID %q for %s", rem.Description, v.CVE)
|
|
}
|
|
if remediatedKBIDRaw < 0 {
|
|
return nil, fmt.Errorf("invalid remediation KBID %q for %s", rem.Description, v.CVE)
|
|
}
|
|
remediatedKBID := uint(remediatedKBIDRaw)
|
|
|
|
// rem.Supercedence should have the ID portion of a KBID which the current vendor fix replaces.
|
|
var supersedes *uint
|
|
if rem.Supercedence != "" {
|
|
r, err := strconv.Atoi(rem.Supercedence)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid supercedence KBID %q for %s", rem.Supercedence, v.CVE)
|
|
}
|
|
if r < 0 {
|
|
return nil, fmt.Errorf("invalid supercedence KBID %q for %s", rem.Supercedence, v.CVE)
|
|
}
|
|
supersedes = ptr.Uint(uint(r))
|
|
}
|
|
|
|
for _, pID := range rem.ProductIDs {
|
|
// Get the bulletin for the current product ID, skip further processing if is a
|
|
// non-windows product.
|
|
b, ok := bulletins[pIDToPName[pID]]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Check if the vulnerability referenced by this remediation exists, if not
|
|
// initialize it.
|
|
var vuln parsed.Vulnerability
|
|
if vuln, ok = b.Vulnerabities[v.CVE]; !ok {
|
|
vuln = parsed.NewVulnerability(v.PublishedDateEpoch())
|
|
}
|
|
// At this point we know that the remediation is a vendor fix that targets a windows
|
|
// product, so we add the remediation's product ID to the vulnerability's targeted products.
|
|
vuln.ProductIDs[pID] = true
|
|
vuln.RemediatedBy[remediatedKBID] = true
|
|
|
|
// Check if the vendor fix referenced by this remediation exists, if not
|
|
// initialize it.
|
|
var vFix parsed.VendorFix
|
|
if vFix, ok = b.VendorFixes[remediatedKBID]; !ok {
|
|
vFix = parsed.NewVendorFix(rem.FixedBuild)
|
|
}
|
|
vFix.Supersedes = supersedes
|
|
vFix.ProductIDs[pID] = true
|
|
|
|
// Update the bulletin
|
|
b.Vulnerabities[v.CVE] = vuln
|
|
b.VendorFixes[remediatedKBID] = vFix
|
|
}
|
|
}
|
|
}
|
|
|
|
return bulletins, nil
|
|
}
|
|
|
|
func parseXML(reader io.Reader) (*msrcxml.FeedResult, error) {
|
|
r := &msrcxml.FeedResult{
|
|
WinProducts: map[string]msrcxml.Product{},
|
|
}
|
|
d := xml.NewDecoder(reader)
|
|
|
|
for {
|
|
t, err := d.Token()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return r, nil
|
|
}
|
|
return nil, fmt.Errorf("decoding token: %v", err)
|
|
}
|
|
|
|
switch t := t.(type) {
|
|
case xml.StartElement:
|
|
if t.Name.Local == "Branch" {
|
|
branch := msrcxml.ProductBranch{}
|
|
if err = d.DecodeElement(&branch, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, p := range branch.WinProducts() {
|
|
r.WinProducts[p.ProductID] = p
|
|
}
|
|
}
|
|
|
|
if t.Name.Local == "Vulnerability" {
|
|
vuln := msrcxml.Vulnerability{}
|
|
if err = d.DecodeElement(&vuln, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for pID := range r.WinProducts {
|
|
// We only care about vulnerabilities that have a vendor fix targeting a Windows
|
|
// product.
|
|
if vuln.IncludesVendorFix(pID) {
|
|
r.WinVulnerabities = append(r.WinVulnerabities, vuln)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|