1package models 2 3import ( 4 "bytes" 5 "fmt" 6 "sort" 7 "strings" 8 "time" 9 10 "github.com/future-architect/vuls/config" 11 exploitmodels "github.com/mozqnet/go-exploitdb/models" 12) 13 14// VulnInfos has a map of VulnInfo 15// Key: CveID 16type VulnInfos map[string]VulnInfo 17 18// Find elements that matches the function passed in argument 19func (v VulnInfos) Find(f func(VulnInfo) bool) VulnInfos { 20 filtered := VulnInfos{} 21 for _, vv := range v { 22 if f(vv) { 23 filtered[vv.CveID] = vv 24 } 25 } 26 return filtered 27} 28 29// FindScoredVulns return scored vulnerabilities 30func (v VulnInfos) FindScoredVulns() VulnInfos { 31 return v.Find(func(vv VulnInfo) bool { 32 if 0 < vv.MaxCvss2Score().Value.Score || 33 0 < vv.MaxCvss3Score().Value.Score { 34 return true 35 } 36 return false 37 }) 38} 39 40// ToSortedSlice returns slice of VulnInfos that is sorted by Score, CVE-ID 41func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) { 42 for k := range v { 43 sorted = append(sorted, v[k]) 44 } 45 sort.Slice(sorted, func(i, j int) bool { 46 maxI := sorted[i].MaxCvssScore() 47 maxJ := sorted[j].MaxCvssScore() 48 if maxI.Value.Score != maxJ.Value.Score { 49 return maxJ.Value.Score < maxI.Value.Score 50 } 51 return sorted[i].CveID < sorted[j].CveID 52 }) 53 return 54} 55 56// CountGroupBySeverity summarize the number of CVEs group by CVSSv2 Severity 57func (v VulnInfos) CountGroupBySeverity() map[string]int { 58 m := map[string]int{} 59 for _, vInfo := range v { 60 score := vInfo.MaxCvss2Score().Value.Score 61 if score < 0.1 { 62 score = vInfo.MaxCvss3Score().Value.Score 63 } 64 switch { 65 case 7.0 <= score: 66 m["High"]++ 67 case 4.0 <= score: 68 m["Medium"]++ 69 case 0 < score: 70 m["Low"]++ 71 default: 72 m["Unknown"]++ 73 } 74 } 75 return m 76} 77 78// FormatCveSummary summarize the number of CVEs group by CVSSv2 Severity 79func (v VulnInfos) FormatCveSummary() string { 80 m := v.CountGroupBySeverity() 81 82 if config.Conf.IgnoreUnscoredCves { 83 return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)", 84 m["High"]+m["Medium"]+m["Low"], m["High"], m["Medium"], m["Low"]) 85 } 86 return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)", 87 m["High"]+m["Medium"]+m["Low"]+m["Unknown"], 88 m["High"], m["Medium"], m["Low"], m["Unknown"]) 89} 90 91// FormatFixedStatus summarize the number of cves are fixed. 92func (v VulnInfos) FormatFixedStatus(packs Packages) string { 93 total, fixed := 0, 0 94 for _, vInfo := range v { 95 if len(vInfo.CpeURIs) != 0 { 96 continue 97 } 98 total++ 99 if vInfo.PatchStatus(packs) == "fixed" { 100 fixed++ 101 } 102 } 103 return fmt.Sprintf("%d/%d Fixed", fixed, total) 104} 105 106// PackageFixStatuses is a list of PackageStatus 107type PackageFixStatuses []PackageFixStatus 108 109// Names return a slice of package names 110func (ps PackageFixStatuses) Names() (names []string) { 111 for _, p := range ps { 112 names = append(names, p.Name) 113 } 114 return names 115} 116 117// Store insert given pkg if missing, update pkg if exists 118func (ps PackageFixStatuses) Store(pkg PackageFixStatus) PackageFixStatuses { 119 for i, p := range ps { 120 if p.Name == pkg.Name { 121 ps[i] = pkg 122 return ps 123 } 124 } 125 ps = append(ps, pkg) 126 return ps 127} 128 129// Sort by Name 130func (ps PackageFixStatuses) Sort() { 131 sort.Slice(ps, func(i, j int) bool { 132 return ps[i].Name < ps[j].Name 133 }) 134 return 135} 136 137// PackageFixStatus has name and other status abount the package 138type PackageFixStatus struct { 139 Name string `json:"name,omitempty"` 140 NotFixedYet bool `json:"notFixedYet,omitempty"` 141 FixState string `json:"fixState,omitempty"` 142 FixedIn string `json:"fixedIn,omitempty"` 143} 144 145// VulnInfo has a vulnerability information and unsecure packages 146type VulnInfo struct { 147 CveID string `json:"cveID,omitempty"` 148 Confidences Confidences `json:"confidences,omitempty"` 149 AffectedPackages PackageFixStatuses `json:"affectedPackages,omitempty"` 150 DistroAdvisories DistroAdvisories `json:"distroAdvisories,omitempty"` // for Aamazon, RHEL, FreeBSD 151 CveContents CveContents `json:"cveContents,omitempty"` 152 Exploits []Exploit `json:"exploits,omitempty"` 153 Metasploits []Metasploit `json:"metasploits,omitempty"` 154 AlertDict AlertDict `json:"alertDict,omitempty"` 155 CpeURIs []string `json:"cpeURIs,omitempty"` // CpeURIs related to this CVE defined in config.toml 156 GitHubSecurityAlerts GitHubSecurityAlerts `json:"gitHubSecurityAlerts,omitempty"` 157 WpPackageFixStats WpPackageFixStats `json:"wpPackageFixStats,omitempty"` 158 LibraryFixedIns LibraryFixedIns `json:"libraryFixedIns,omitempty"` 159 160 VulnType string `json:"vulnType,omitempty"` 161} 162 163// Alert has XCERT alert information 164type Alert struct { 165 URL string `json:"url,omitempty"` 166 Title string `json:"title,omitempty"` 167 Team string `json:"team,omitempty"` 168} 169 170// GitHubSecurityAlerts is a list of GitHubSecurityAlert 171type GitHubSecurityAlerts []GitHubSecurityAlert 172 173// Add adds given arg to the slice and return the slice (immutable) 174func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlerts { 175 for _, a := range g { 176 if a.PackageName == alert.PackageName { 177 return g 178 } 179 } 180 return append(g, alert) 181} 182 183// Names return a slice of lib names 184func (g GitHubSecurityAlerts) Names() (names []string) { 185 for _, a := range g { 186 names = append(names, a.PackageName) 187 } 188 return names 189} 190 191// GitHubSecurityAlert has detected CVE-ID, PackageName, Status fetched via GitHub API 192type GitHubSecurityAlert struct { 193 PackageName string `json:"packageName"` 194 FixedIn string `json:"fixedIn"` 195 AffectedRange string `json:"affectedRange"` 196 Dismissed bool `json:"dismissed"` 197 DismissedAt time.Time `json:"dismissedAt"` 198 DismissReason string `json:"dismissReason"` 199} 200 201// LibraryFixedIns is a list of Library's FixedIn 202type LibraryFixedIns []LibraryFixedIn 203 204// Names return a slice of names 205func (lfs LibraryFixedIns) Names() (names []string) { 206 for _, lf := range lfs { 207 names = append(names, lf.Name) 208 } 209 return names 210} 211 212// WpPackageFixStats is a list of WpPackageFixStatus 213type WpPackageFixStats []WpPackageFixStatus 214 215// Names return a slice of names 216func (ws WpPackageFixStats) Names() (names []string) { 217 for _, w := range ws { 218 names = append(names, w.Name) 219 } 220 return names 221} 222 223// WpPackages has a list of WpPackage 224type WpPackages []WpPackage 225 226// Add adds given arg to the slice and return the slice (immutable) 227func (g WpPackages) Add(pkg WpPackage) WpPackages { 228 for _, a := range g { 229 if a.Name == pkg.Name { 230 return g 231 } 232 } 233 return append(g, pkg) 234} 235 236// Titles returns tilte (TUI) 237func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) { 238 if lang == "ja" { 239 if cont, found := v.CveContents[Jvn]; found && 0 < len(cont.Title) { 240 values = append(values, CveContentStr{Jvn, cont.Title}) 241 } 242 } 243 244 // RedHat API has one line title. 245 if cont, found := v.CveContents[RedHatAPI]; found && 0 < len(cont.Title) { 246 values = append(values, CveContentStr{RedHatAPI, cont.Title}) 247 } 248 249 order := CveContentTypes{Trivy, Nvd, NvdXML, NewCveContentType(myFamily)} 250 order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...) 251 for _, ctype := range order { 252 // Only JVN has meaningful title. so return first 100 char of summary 253 if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) { 254 summary := strings.Replace(cont.Summary, "\n", " ", -1) 255 values = append(values, CveContentStr{ 256 Type: ctype, 257 Value: summary, 258 }) 259 } 260 } 261 262 for _, adv := range v.DistroAdvisories { 263 values = append(values, CveContentStr{ 264 Type: "Vendor", 265 Value: strings.Replace(adv.Description, "\n", " ", -1), 266 }) 267 } 268 269 if len(values) == 0 { 270 values = []CveContentStr{{ 271 Type: Unknown, 272 Value: "-", 273 }} 274 } 275 return 276} 277 278// Summaries returns summaries 279func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) { 280 if lang == "ja" { 281 if cont, found := v.CveContents[Jvn]; found && 0 < len(cont.Summary) { 282 summary := cont.Title 283 summary += "\n" + strings.Replace( 284 strings.Replace(cont.Summary, "\n", " ", -1), "\r", " ", -1) 285 values = append(values, CveContentStr{Jvn, summary}) 286 } 287 } 288 289 order := CveContentTypes{Trivy, NewCveContentType(myFamily), Nvd, NvdXML} 290 order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...) 291 for _, ctype := range order { 292 if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) { 293 summary := strings.Replace(cont.Summary, "\n", " ", -1) 294 values = append(values, CveContentStr{ 295 Type: ctype, 296 Value: summary, 297 }) 298 } 299 } 300 301 for _, adv := range v.DistroAdvisories { 302 values = append(values, CveContentStr{ 303 Type: "Vendor", 304 Value: adv.Description, 305 }) 306 } 307 308 if v, ok := v.CveContents[WPVulnDB]; ok { 309 values = append(values, CveContentStr{ 310 Type: "WPVDB", 311 Value: v.Title, 312 }) 313 } 314 315 if len(values) == 0 { 316 return []CveContentStr{{ 317 Type: Unknown, 318 Value: "-", 319 }} 320 } 321 322 return 323} 324 325// Mitigations returns mitigations 326func (v VulnInfo) Mitigations(myFamily string) (values []CveContentStr) { 327 order := CveContentTypes{RedHatAPI} 328 for _, ctype := range order { 329 if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Mitigation) { 330 values = append(values, CveContentStr{ 331 Type: ctype, 332 Value: cont.Mitigation, 333 }) 334 } 335 } 336 337 if len(values) == 0 { 338 return []CveContentStr{{ 339 Type: Unknown, 340 Value: "-", 341 }} 342 } 343 return 344} 345 346// Cvss2Scores returns CVSS V2 Scores 347func (v VulnInfo) Cvss2Scores(myFamily string) (values []CveContentCvss) { 348 order := []CveContentType{Nvd, NvdXML, RedHatAPI, RedHat, Jvn} 349 if myFamily != config.RedHat && myFamily != config.CentOS { 350 order = append(order, NewCveContentType(myFamily)) 351 } 352 for _, ctype := range order { 353 if cont, found := v.CveContents[ctype]; found { 354 if cont.Cvss2Score == 0 || cont.Cvss2Severity == "" { 355 continue 356 } 357 // https://nvd.nist.gov/vuln-metrics/cvss 358 values = append(values, CveContentCvss{ 359 Type: ctype, 360 Value: Cvss{ 361 Type: CVSS2, 362 Score: cont.Cvss2Score, 363 Vector: cont.Cvss2Vector, 364 Severity: strings.ToUpper(cont.Cvss2Severity), 365 }, 366 }) 367 } 368 } 369 370 for _, adv := range v.DistroAdvisories { 371 if adv.Severity != "" { 372 values = append(values, CveContentCvss{ 373 Type: "Advisory", 374 Value: Cvss{ 375 Type: CVSS2, 376 Score: severityToV2ScoreRoughly(adv.Severity), 377 CalculatedBySeverity: true, 378 Vector: "-", 379 Severity: strings.ToUpper(adv.Severity), 380 }, 381 }) 382 } 383 } 384 385 // An OVAL entry in Ubuntu and Debian has only severity (CVSS score isn't included). 386 // Show severity and dummy score calculated roughly. 387 order = append(order, AllCveContetTypes.Except(order...)...) 388 for _, ctype := range order { 389 if cont, found := v.CveContents[ctype]; found && 390 cont.Cvss2Score == 0 && 391 cont.Cvss3Score == 0 && 392 cont.Cvss2Severity != "" { 393 394 values = append(values, CveContentCvss{ 395 Type: cont.Type, 396 Value: Cvss{ 397 Type: CVSS2, 398 Score: severityToV2ScoreRoughly(cont.Cvss2Severity), 399 CalculatedBySeverity: true, 400 Vector: "-", 401 Severity: strings.ToUpper(cont.Cvss2Severity), 402 }, 403 }) 404 } 405 } 406 407 return 408} 409 410// Cvss3Scores returns CVSS V3 Score 411func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) { 412 order := []CveContentType{Nvd, RedHatAPI, RedHat, Jvn} 413 for _, ctype := range order { 414 if cont, found := v.CveContents[ctype]; found { 415 // https://nvd.nist.gov/vuln-metrics/cvss 416 values = append(values, CveContentCvss{ 417 Type: ctype, 418 Value: Cvss{ 419 Type: CVSS3, 420 Score: cont.Cvss3Score, 421 Vector: cont.Cvss3Vector, 422 Severity: strings.ToUpper(cont.Cvss3Severity), 423 }, 424 }) 425 } 426 } 427 428 if cont, found := v.CveContents[Trivy]; found && cont.Cvss3Severity != "" { 429 values = append(values, CveContentCvss{ 430 Type: Trivy, 431 Value: Cvss{ 432 Type: CVSS3, 433 Score: severityToV2ScoreRoughly(cont.Cvss3Severity), 434 Severity: strings.ToUpper(cont.Cvss3Severity), 435 }, 436 }) 437 } 438 439 return 440} 441 442// MaxCvss3Score returns Max CVSS V3 Score 443func (v VulnInfo) MaxCvss3Score() CveContentCvss { 444 order := []CveContentType{Nvd, RedHat, RedHatAPI, Jvn} 445 max := 0.0 446 value := CveContentCvss{ 447 Type: Unknown, 448 Value: Cvss{Type: CVSS3}, 449 } 450 for _, ctype := range order { 451 if cont, found := v.CveContents[ctype]; found && max < cont.Cvss3Score { 452 // https://nvd.nist.gov/vuln-metrics/cvss 453 value = CveContentCvss{ 454 Type: ctype, 455 Value: Cvss{ 456 Type: CVSS3, 457 Score: cont.Cvss3Score, 458 Vector: cont.Cvss3Vector, 459 Severity: strings.ToUpper(cont.Cvss3Severity), 460 }, 461 } 462 max = cont.Cvss3Score 463 } 464 } 465 return value 466} 467 468// MaxCvssScore returns max CVSS Score 469// If there is no CVSS Score, return Severity as a numerical value. 470func (v VulnInfo) MaxCvssScore() CveContentCvss { 471 v3Max := v.MaxCvss3Score() 472 v2Max := v.MaxCvss2Score() 473 max := v3Max 474 if max.Type == Unknown { 475 return v2Max 476 } 477 478 if max.Value.Score < v2Max.Value.Score && !v2Max.Value.CalculatedBySeverity { 479 max = v2Max 480 } 481 return max 482} 483 484// MaxCvss2Score returns Max CVSS V2 Score 485func (v VulnInfo) MaxCvss2Score() CveContentCvss { 486 order := []CveContentType{Nvd, NvdXML, RedHat, RedHatAPI, Jvn} 487 max := 0.0 488 value := CveContentCvss{ 489 Type: Unknown, 490 Value: Cvss{Type: CVSS2}, 491 } 492 for _, ctype := range order { 493 if cont, found := v.CveContents[ctype]; found && max < cont.Cvss2Score { 494 // https://nvd.nist.gov/vuln-metrics/cvss 495 value = CveContentCvss{ 496 Type: ctype, 497 Value: Cvss{ 498 Type: CVSS2, 499 Score: cont.Cvss2Score, 500 Vector: cont.Cvss2Vector, 501 Severity: strings.ToUpper(cont.Cvss2Severity), 502 }, 503 } 504 max = cont.Cvss2Score 505 } 506 } 507 if 0 < max { 508 return value 509 } 510 511 // If CVSS score isn't on NVD, RedHat and JVN, use OVAL and advisory Severity. 512 // Convert severity to cvss srore roughly, then returns max severity. 513 // Only Ubuntu, RedHat and Oracle have severity data in OVAL. 514 order = []CveContentType{Ubuntu, RedHat, Oracle} 515 for _, ctype := range order { 516 if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Cvss2Severity) { 517 score := severityToV2ScoreRoughly(cont.Cvss2Severity) 518 if max < score { 519 value = CveContentCvss{ 520 Type: ctype, 521 Value: Cvss{ 522 Type: CVSS2, 523 Score: score, 524 CalculatedBySeverity: true, 525 Vector: cont.Cvss2Vector, 526 Severity: strings.ToUpper(cont.Cvss2Severity), 527 }, 528 } 529 } 530 max = score 531 } 532 } 533 534 // Only RedHat, Oracle and Amazon has severity data in advisory. 535 for _, adv := range v.DistroAdvisories { 536 if adv.Severity != "" { 537 score := severityToV2ScoreRoughly(adv.Severity) 538 if max < score { 539 value = CveContentCvss{ 540 Type: "Vendor", 541 Value: Cvss{ 542 Type: CVSS2, 543 Score: score, 544 CalculatedBySeverity: true, 545 Vector: "-", 546 Severity: adv.Severity, 547 }, 548 } 549 } 550 } 551 } 552 return value 553} 554 555// AttackVector returns attack vector string 556func (v VulnInfo) AttackVector() string { 557 for _, cnt := range v.CveContents { 558 if strings.HasPrefix(cnt.Cvss2Vector, "AV:N") || 559 strings.Contains(cnt.Cvss3Vector, "AV:N") { 560 return "AV:N" 561 } else if strings.HasPrefix(cnt.Cvss2Vector, "AV:A") || 562 strings.Contains(cnt.Cvss3Vector, "AV:A") { 563 return "AV:A" 564 } else if strings.HasPrefix(cnt.Cvss2Vector, "AV:L") || 565 strings.Contains(cnt.Cvss3Vector, "AV:L") { 566 return "AV:L" 567 } else if strings.Contains(cnt.Cvss3Vector, "AV:P") { 568 // no AV:P in CVSS v2 569 return "AV:P" 570 } 571 } 572 if cont, found := v.CveContents[DebianSecurityTracker]; found { 573 if attackRange, found := cont.Optional["attack range"]; found { 574 return attackRange 575 } 576 } 577 return "" 578} 579 580// PatchStatus returns fixed or unfixed string 581func (v VulnInfo) PatchStatus(packs Packages) string { 582 // Vuls don't know patch status of the CPE 583 if len(v.CpeURIs) != 0 { 584 return "" 585 } 586 for _, p := range v.AffectedPackages { 587 if p.NotFixedYet { 588 return "unfixed" 589 } 590 591 // Fast and offline mode can not get the candidate version. 592 // Vuls can be considered as 'fixed' if not-fixed-yet==true and 593 // the fixed-in-version (information in the oval) is not an empty. 594 if p.FixedIn != "" { 595 continue 596 } 597 598 // fast, offline mode doesn't have new version 599 if pack, ok := packs[p.Name]; ok { 600 if pack.NewVersion == "" { 601 return "unknown" 602 } 603 } 604 } 605 return "fixed" 606} 607 608// CveContentCvss has CVSS information 609type CveContentCvss struct { 610 Type CveContentType `json:"type"` 611 Value Cvss `json:"value"` 612} 613 614// CvssType Represent the type of CVSS 615type CvssType string 616 617const ( 618 // CVSS2 means CVSS vesion2 619 CVSS2 CvssType = "2" 620 621 // CVSS3 means CVSS vesion3 622 CVSS3 CvssType = "3" 623) 624 625// Cvss has CVSS Score 626type Cvss struct { 627 Type CvssType `json:"type"` 628 Score float64 `json:"score"` 629 CalculatedBySeverity bool `json:"calculatedBySeverity"` 630 Vector string `json:"vector"` 631 Severity string `json:"severity"` 632} 633 634// Format CVSS Score and Vector 635func (c Cvss) Format() string { 636 if c.Score == 0 || c.Vector == "" { 637 return c.Severity 638 } 639 switch c.Type { 640 case CVSS2: 641 return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity) 642 case CVSS3: 643 return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity) 644 } 645 return "" 646} 647 648// Amazon Linux Security Advisory 649// Critical, Important, Medium, Low 650// https://alas.aws.amazon.com/ 651// 652// RedHat, Oracle OVAL 653// Critical, Important, Moderate, Low 654// https://access.redhat.com/security/updates/classification 655// 656// Ubuntu OVAL 657// Critical, High, Medium, Low 658// https://wiki.ubuntu.com/Bugs/Importance 659// https://people.canonical.com/~ubuntu-security/cve/priority.html 660func severityToV2ScoreRoughly(severity string) float64 { 661 switch strings.ToUpper(severity) { 662 case "CRITICAL": 663 return 10.0 664 case "IMPORTANT", "HIGH": 665 return 8.9 666 case "MODERATE", "MEDIUM": 667 return 6.9 668 case "LOW": 669 return 3.9 670 } 671 return 0 672} 673 674// FormatMaxCvssScore returns Max CVSS Score 675func (v VulnInfo) FormatMaxCvssScore() string { 676 max := v.MaxCvssScore() 677 return fmt.Sprintf("%3.1f %s (%s)", 678 max.Value.Score, 679 strings.ToUpper(max.Value.Severity), 680 max.Type) 681} 682 683// Cvss2CalcURL returns CVSS v2 caluclator's URL 684func (v VulnInfo) Cvss2CalcURL() string { 685 return "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=" + v.CveID 686} 687 688// Cvss3CalcURL returns CVSS v3 caluclator's URL 689func (v VulnInfo) Cvss3CalcURL() string { 690 return "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=" + v.CveID 691} 692 693// VendorLinks returns links of vendor support's URL 694func (v VulnInfo) VendorLinks(family string) map[string]string { 695 links := map[string]string{} 696 if strings.HasPrefix(v.CveID, "WPVDBID") { 697 links["WPVulnDB"] = fmt.Sprintf("https://wpvulndb.com/vulnerabilities/%s", 698 strings.TrimPrefix(v.CveID, "WPVDBID-")) 699 return links 700 } 701 702 switch family { 703 case config.RedHat, config.CentOS: 704 links["RHEL-CVE"] = "https://access.redhat.com/security/cve/" + v.CveID 705 for _, advisory := range v.DistroAdvisories { 706 aidURL := strings.Replace(advisory.AdvisoryID, ":", "-", -1) 707 links[advisory.AdvisoryID] = fmt.Sprintf("https://rhn.redhat.com/errata/%s.html", aidURL) 708 } 709 return links 710 case config.Oracle: 711 links["Oracle-CVE"] = fmt.Sprintf("https://linux.oracle.com/cve/%s.html", v.CveID) 712 for _, advisory := range v.DistroAdvisories { 713 links[advisory.AdvisoryID] = 714 fmt.Sprintf("https://linux.oracle.com/errata/%s.html", advisory.AdvisoryID) 715 } 716 return links 717 case config.Amazon: 718 links["RHEL-CVE"] = "https://access.redhat.com/security/cve/" + v.CveID 719 for _, advisory := range v.DistroAdvisories { 720 if strings.HasPrefix(advisory.AdvisoryID, "ALAS2") { 721 links[advisory.AdvisoryID] = 722 fmt.Sprintf("https://alas.aws.amazon.com/AL2/%s.html", 723 strings.Replace(advisory.AdvisoryID, "ALAS2", "ALAS", -1)) 724 } else { 725 links[advisory.AdvisoryID] = 726 fmt.Sprintf("https://alas.aws.amazon.com/%s.html", advisory.AdvisoryID) 727 } 728 } 729 return links 730 case config.Ubuntu: 731 links["Ubuntu-CVE"] = "http://people.ubuntu.com/~ubuntu-security/cve/" + v.CveID 732 return links 733 case config.Debian: 734 links["Debian-CVE"] = "https://security-tracker.debian.org/tracker/" + v.CveID 735 case config.SUSEEnterpriseServer: 736 links["SUSE-CVE"] = "https://www.suse.com/security/cve/" + v.CveID 737 case config.FreeBSD: 738 for _, advisory := range v.DistroAdvisories { 739 links["FreeBSD-VuXML"] = fmt.Sprintf("https://vuxml.freebsd.org/freebsd/%s.html", advisory.AdvisoryID) 740 741 } 742 return links 743 } 744 return links 745} 746 747// DistroAdvisories is a list of DistroAdvisory 748type DistroAdvisories []DistroAdvisory 749 750// AppendIfMissing appends if missing 751func (advs *DistroAdvisories) AppendIfMissing(adv *DistroAdvisory) bool { 752 for _, a := range *advs { 753 if a.AdvisoryID == adv.AdvisoryID { 754 return false 755 } 756 } 757 *advs = append(*advs, *adv) 758 return true 759} 760 761// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information. 762type DistroAdvisory struct { 763 AdvisoryID string `json:"advisoryID"` 764 Severity string `json:"severity"` 765 Issued time.Time `json:"issued"` 766 Updated time.Time `json:"updated"` 767 Description string `json:"description"` 768} 769 770// Format the distro advisory information 771func (p DistroAdvisory) Format() string { 772 if p.AdvisoryID == "" { 773 return "" 774 } 775 776 var delim bytes.Buffer 777 for i := 0; i < len(p.AdvisoryID); i++ { 778 delim.WriteString("-") 779 } 780 buf := []string{p.AdvisoryID, delim.String(), p.Description} 781 return strings.Join(buf, "\n") 782} 783 784// Exploit : 785type Exploit struct { 786 ExploitType exploitmodels.ExploitType `json:"exploitType"` 787 ID string `json:"id"` 788 URL string `json:"url"` 789 Description string `json:"description"` 790 DocumentURL *string `json:"documentURL,omitempty"` 791 ShellCodeURL *string `json:"shellCodeURL,omitempty"` 792 BinaryURL *string `json:"binaryURL,omitempty"` 793} 794 795// Metasploit : 796type Metasploit struct { 797 Name string `json:"name"` 798 Title string `json:"title"` 799 Description string `json:"description,omitempty"` 800 URLs []string `json:",omitempty"` 801} 802 803// AlertDict has target cve's JPCERT and USCERT alert data 804type AlertDict struct { 805 Ja []Alert `json:"ja"` 806 En []Alert `json:"en"` 807} 808 809// FormatSource returns which source has this alert 810func (a AlertDict) FormatSource() string { 811 s := []string{} 812 if len(a.En) != 0 { 813 s = append(s, "USCERT") 814 } 815 if len(a.Ja) != 0 { 816 s = append(s, "JPCERT") 817 } 818 return strings.Join(s, "/") 819} 820 821// Confidences is a list of Confidence 822type Confidences []Confidence 823 824// AppendIfMissing appends confidence to the list if missiong 825func (cs *Confidences) AppendIfMissing(confidence Confidence) { 826 for _, c := range *cs { 827 if c.DetectionMethod == confidence.DetectionMethod { 828 return 829 } 830 } 831 *cs = append(*cs, confidence) 832} 833 834// SortByConfident sorts Confidences 835func (cs Confidences) SortByConfident() Confidences { 836 sort.Slice(cs, func(i, j int) bool { 837 return cs[i].SortOrder < cs[j].SortOrder 838 }) 839 return cs 840} 841 842// Confidence is a ranking how confident the CVE-ID was deteted correctly 843// Score: 0 - 100 844type Confidence struct { 845 Score int `json:"score"` 846 DetectionMethod DetectionMethod `json:"detectionMethod"` 847 SortOrder int `json:"-"` 848} 849 850func (c Confidence) String() string { 851 return fmt.Sprintf("%d / %s", c.Score, c.DetectionMethod) 852} 853 854// DetectionMethod indicates 855// - How to detect the CveID 856// - How to get the changelog difference between installed and candidate version 857type DetectionMethod string 858 859const ( 860 // CpeNameMatchStr is a String representation of CpeNameMatch 861 CpeNameMatchStr = "CpeNameMatch" 862 863 // YumUpdateSecurityMatchStr is a String representation of YumUpdateSecurityMatch 864 YumUpdateSecurityMatchStr = "YumUpdateSecurityMatch" 865 866 // PkgAuditMatchStr is a String representation of PkgAuditMatch 867 PkgAuditMatchStr = "PkgAuditMatch" 868 869 // OvalMatchStr is a String representation of OvalMatch 870 OvalMatchStr = "OvalMatch" 871 872 // RedHatAPIStr is a String representation of RedHatAPIMatch 873 RedHatAPIStr = "RedHatAPIMatch" 874 875 // DebianSecurityTrackerMatchStr is a String representation of DebianSecurityTrackerMatch 876 DebianSecurityTrackerMatchStr = "DebianSecurityTrackerMatch" 877 878 // TrivyMatchStr is a String representation of Trivy 879 TrivyMatchStr = "TrivyMatch" 880 881 // ChangelogExactMatchStr is a String representation of ChangelogExactMatch 882 ChangelogExactMatchStr = "ChangelogExactMatch" 883 884 // ChangelogLenientMatchStr is a String representation of ChangelogLenientMatch 885 ChangelogLenientMatchStr = "ChangelogLenientMatch" 886 887 // GitHubMatchStr is a String representation of GitHubMatch 888 GitHubMatchStr = "GitHubMatch" 889 890 // WPVulnDBMatchStr is a String representation of WordPress VulnDB scanning 891 WPVulnDBMatchStr = "WPVulnDBMatch" 892 893 // FailedToGetChangelog is a String representation of FailedToGetChangelog 894 FailedToGetChangelog = "FailedToGetChangelog" 895 896 // FailedToFindVersionInChangelog is a String representation of FailedToFindVersionInChangelog 897 FailedToFindVersionInChangelog = "FailedToFindVersionInChangelog" 898) 899 900var ( 901 // CpeNameMatch is a ranking how confident the CVE-ID was deteted correctly 902 CpeNameMatch = Confidence{100, CpeNameMatchStr, 1} 903 904 // YumUpdateSecurityMatch is a ranking how confident the CVE-ID was deteted correctly 905 YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr, 2} 906 907 // PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly 908 PkgAuditMatch = Confidence{100, PkgAuditMatchStr, 2} 909 910 // OvalMatch is a ranking how confident the CVE-ID was deteted correctly 911 OvalMatch = Confidence{100, OvalMatchStr, 0} 912 913 // RedHatAPIMatch ranking how confident the CVE-ID was deteted correctly 914 RedHatAPIMatch = Confidence{100, RedHatAPIStr, 0} 915 916 // DebianSecurityTrackerMatch ranking how confident the CVE-ID was deteted correctly 917 DebianSecurityTrackerMatch = Confidence{100, DebianSecurityTrackerMatchStr, 0} 918 919 // TrivyMatch ranking how confident the CVE-ID was deteted correctly 920 TrivyMatch = Confidence{100, TrivyMatchStr, 0} 921 922 // ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly 923 ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr, 3} 924 925 // ChangelogLenientMatch is a ranking how confident the CVE-ID was deteted correctly 926 ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr, 4} 927 928 // GitHubMatch is a ranking how confident the CVE-ID was deteted correctly 929 GitHubMatch = Confidence{97, GitHubMatchStr, 2} 930 931 // WPVulnDBMatch is a ranking how confident the CVE-ID was deteted correctly 932 WPVulnDBMatch = Confidence{100, WPVulnDBMatchStr, 0} 933) 934