feat(scan): WordPress Vulnerability Scan (core, plugin, theme) (#769)

https://github.com/future-architect/vuls/pull/769
This commit is contained in:
kazuminn
2019-04-08 17:27:44 +09:00
committed by Kota Kanbe
parent 91df593566
commit 99c65eff48
59 changed files with 1284 additions and 602 deletions

View File

@@ -140,6 +140,7 @@ func (v CveContents) References(myFamily string) (values []CveContentRefs) {
})
}
}
return
}
@@ -230,6 +231,8 @@ func NewCveContentType(name string) CveContentType {
return DebianSecurityTracker
case "microsoft":
return Microsoft
case "wordpress":
return WPVulnDB
default:
return Unknown
}
@@ -269,6 +272,9 @@ const (
// Microsoft is Microsoft
Microsoft CveContentType = "microsoft"
// WPVulnDB is WordPress
WPVulnDB CveContentType = "wpvulndb"
// Unknown is Unknown
Unknown CveContentType = "unknown"
)
@@ -286,6 +292,7 @@ var AllCveContetTypes = CveContentTypes{
Ubuntu,
RedHatAPI,
DebianSecurityTracker,
WPVulnDB,
}
// Except returns CveContentTypes except for given args

View File

@@ -21,6 +21,8 @@ import (
"bytes"
"fmt"
"strings"
"golang.org/x/xerrors"
)
// Packages is Map of Package
@@ -83,7 +85,7 @@ func (ps Packages) FindByFQPN(nameVerRelArc string) (*Package, error) {
return &p, nil
}
}
return nil, fmt.Errorf("Failed to find the package: %s", nameVerRelArc)
return nil, xerrors.Errorf("Failed to find the package: %s", nameVerRelArc)
}
// Package has installed binary packages.

View File

@@ -36,35 +36,37 @@ type ScanResults []ScanResult
// ScanResult has the result of scanned CVE information.
type ScanResult struct {
JSONVersion int `json:"jsonVersion"`
Lang string `json:"lang"`
ServerUUID string `json:"serverUUID"`
ServerName string `json:"serverName"` // TOML Section key
Family string `json:"family"`
Release string `json:"release"`
Container Container `json:"container"`
Platform Platform `json:"platform"`
IPv4Addrs []string `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
IPv6Addrs []string `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
ScannedAt time.Time `json:"scannedAt"`
ScanMode string `json:"scanMode"`
ScannedVersion string `json:"scannedVersion"`
ScannedRevision string `json:"scannedRevision"`
ScannedBy string `json:"scannedBy"`
ScannedIPv4Addrs []string `json:"scannedIpv4Addrs"`
ScannedIPv6Addrs []string `json:"scannedIpv6Addrs"`
ReportedAt time.Time `json:"reportedAt"`
ReportedVersion string `json:"reportedVersion"`
ReportedRevision string `json:"reportedRevision"`
ReportedBy string `json:"reportedBy"`
ScannedCves VulnInfos `json:"scannedCves"`
RunningKernel Kernel `json:"runningKernel"`
Packages Packages `json:"packages"`
CweDict CweDict `json:"cweDict"`
Optional map[string]interface{} `json:",omitempty"`
SrcPackages SrcPackages `json:",omitempty"`
Errors []string `json:"errors"`
Config struct {
JSONVersion int `json:"jsonVersion"`
Lang string `json:"lang"`
ServerUUID string `json:"serverUUID"`
ServerName string `json:"serverName"` // TOML Section key
Family string `json:"family"`
Release string `json:"release"`
Container Container `json:"container"`
Platform Platform `json:"platform"`
IPv4Addrs []string `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
IPv6Addrs []string `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
ScannedAt time.Time `json:"scannedAt"`
ScanMode string `json:"scanMode"`
ScannedVersion string `json:"scannedVersion"`
ScannedRevision string `json:"scannedRevision"`
ScannedBy string `json:"scannedBy"`
ScannedIPv4Addrs []string `json:"scannedIpv4Addrs,omitempty"`
ScannedIPv6Addrs []string `json:"scannedIpv6Addrs,omitempty"`
ReportedAt time.Time `json:"reportedAt"`
ReportedVersion string `json:"reportedVersion"`
ReportedRevision string `json:"reportedRevision"`
ReportedBy string `json:"reportedBy"`
Errors []string `json:"errors"`
ScannedCves VulnInfos `json:"scannedCves"`
RunningKernel Kernel `json:"runningKernel"`
Packages Packages `json:"packages"`
SrcPackages SrcPackages `json:",omitempty"`
WordPressPackages *WordPressPackages `json:",omitempty"`
CweDict CweDict `json:"cweDict,omitempty"`
Optional map[string]interface{} `json:",omitempty"`
Config struct {
Scan config.Config `json:"scan"`
Report config.Config `json:"report"`
} `json:"config"`
@@ -252,6 +254,30 @@ func (r ScanResult) FilterIgnorePkgs() ScanResult {
return r
}
// FilterInactiveWordPressLibs is filter function.
func (r ScanResult) FilterInactiveWordPressLibs() ScanResult {
if !config.Conf.Servers[r.ServerName].WordPress.IgnoreInactive {
return r
}
filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
if len(v.WpPackageFixStats) == 0 {
return true
}
// Ignore if all libs in this vulnInfo inactive
for _, wp := range v.WpPackageFixStats {
if p, ok := r.WordPressPackages.Find(wp.Name); ok {
if p.Status != Inactive {
return true
}
}
}
return false
})
r.ScannedCves = filtered
return r
}
// ReportFileName returns the filename on localhost without extention
func (r ScanResult) ReportFileName() (name string) {
if len(r.Container.ContainerID) == 0 {

View File

@@ -348,7 +348,7 @@ func TestFilterUnfixed(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{
Name: "a",
NotFixedYet: true,
@@ -357,7 +357,7 @@ func TestFilterUnfixed(t *testing.T) {
},
"CVE-2017-0002": {
CveID: "CVE-2017-0002",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{
Name: "b",
NotFixedYet: false,
@@ -366,7 +366,7 @@ func TestFilterUnfixed(t *testing.T) {
},
"CVE-2017-0003": {
CveID: "CVE-2017-0003",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{
Name: "c",
NotFixedYet: true,
@@ -383,7 +383,7 @@ func TestFilterUnfixed(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0002": {
CveID: "CVE-2017-0002",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{
Name: "b",
NotFixedYet: false,
@@ -392,7 +392,7 @@ func TestFilterUnfixed(t *testing.T) {
},
"CVE-2017-0003": {
CveID: "CVE-2017-0003",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{
Name: "c",
NotFixedYet: true,
@@ -435,7 +435,7 @@ func TestFilterIgnorePkgs(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{Name: "kernel"},
},
},
@@ -462,7 +462,7 @@ func TestFilterIgnorePkgs(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{Name: "kernel"},
{Name: "vim"},
},
@@ -475,7 +475,7 @@ func TestFilterIgnorePkgs(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{Name: "kernel"},
{Name: "vim"},
},
@@ -491,7 +491,7 @@ func TestFilterIgnorePkgs(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{Name: "kernel"},
{Name: "vim"},
},
@@ -545,7 +545,7 @@ func TestFilterIgnorePkgsContainer(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{Name: "kernel"},
},
},
@@ -574,7 +574,7 @@ func TestFilterIgnorePkgsContainer(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{Name: "kernel"},
{Name: "vim"},
},
@@ -588,7 +588,7 @@ func TestFilterIgnorePkgsContainer(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{Name: "kernel"},
{Name: "vim"},
},
@@ -605,7 +605,7 @@ func TestFilterIgnorePkgsContainer(t *testing.T) {
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
AffectedPackages: PackageFixStatuses{
{Name: "kernel"},
{Name: "vim"},
},

View File

@@ -122,20 +122,19 @@ func (v VulnInfos) FormatFixedStatus(packs Packages) string {
return fmt.Sprintf("%d/%d Fixed", fixed, total)
}
// PackageStatuses is a list of PackageStatus
type PackageStatuses []PackageStatus
// PackageFixStatuses is a list of PackageStatus
type PackageFixStatuses []PackageFixStatus
// FormatTuiSummary format packname to show TUI summary
func (ps PackageStatuses) FormatTuiSummary() string {
names := []string{}
// Names return a slice of package names
func (ps PackageFixStatuses) Names() (names []string) {
for _, p := range ps {
names = append(names, p.Name)
}
return strings.Join(names, ", ")
return names
}
// Store insert given pkg if missing, update pkg if exists
func (ps PackageStatuses) Store(pkg PackageStatus) PackageStatuses {
func (ps PackageFixStatuses) Store(pkg PackageFixStatus) PackageFixStatuses {
for i, p := range ps {
if p.Name == pkg.Name {
ps[i] = pkg
@@ -147,15 +146,15 @@ func (ps PackageStatuses) Store(pkg PackageStatus) PackageStatuses {
}
// Sort by Name
func (ps PackageStatuses) Sort() {
func (ps PackageFixStatuses) Sort() {
sort.Slice(ps, func(i, j int) bool {
return ps[i].Name < ps[j].Name
})
return
}
// PackageStatus has name and other status abount the package
type PackageStatus struct {
// PackageFixStatus has name and other status abount the package
type PackageFixStatus struct {
Name string `json:"name"`
NotFixedYet bool `json:"notFixedYet"`
FixState string `json:"fixState"`
@@ -163,22 +162,24 @@ type PackageStatus struct {
// VulnInfo has a vulnerability information and unsecure packages
type VulnInfo struct {
CveID string `json:"cveID,omitempty"`
Confidences Confidences `json:"confidences,omitempty"`
AffectedPackages PackageStatuses `json:"affectedPackages,omitempty"`
DistroAdvisories []DistroAdvisory `json:"distroAdvisories,omitempty"` // for Aamazon, RHEL, FreeBSD
CveContents CveContents `json:"cveContents,omitempty"`
Exploits []Exploit `json:"exploits,omitempty"`
AlertDict AlertDict `json:"alertDict,omitempty"`
CveID string `json:"cveID,omitempty"`
Confidences Confidences `json:"confidences,omitempty"`
AffectedPackages PackageFixStatuses `json:"affectedPackages,omitempty"`
DistroAdvisories []DistroAdvisory `json:"distroAdvisories,omitempty"` // for Aamazon, RHEL, FreeBSD
CveContents CveContents `json:"cveContents,omitempty"`
Exploits []Exploit `json:"exploits,omitempty"`
AlertDict AlertDict `json:"alertDict,omitempty"`
CpeURIs []string `json:"cpeURIs,omitempty"` // CpeURIs related to this CVE defined in config.toml
GitHubSecurityAlerts GitHubSecurityAlerts `json:"gitHubSecurityAlerts,omitempty"`
WpPackageFixStats WpPackageFixStats `json:"wpPackageFixStats,omitempty"`
VulnType string `json:"vulnType,omitempty"`
}
// GitHubSecurityAlerts is a list of GitHubSecurityAlert
type GitHubSecurityAlerts []GitHubSecurityAlert
// Add adds given arg to the slice and return the slice (imutable)
// Add adds given arg to the slice and return the slice (immutable)
func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlerts {
for _, a := range g {
if a.PackageName == alert.PackageName {
@@ -188,12 +189,12 @@ func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlert
return append(g, alert)
}
func (g GitHubSecurityAlerts) String() string {
ss := []string{}
// Names return a slice of lib names
func (g GitHubSecurityAlerts) Names() (names []string) {
for _, a := range g {
ss = append(ss, a.PackageName)
names = append(names, a.PackageName)
}
return strings.Join(ss, ", ")
return names
}
// GitHubSecurityAlert has detected CVE-ID, PackageName, Status fetched via GitHub API
@@ -206,6 +207,30 @@ type GitHubSecurityAlert struct {
DismissReason string `json:"dismissReason"`
}
// WpPackageFixStats is a list of WpPackageFixStatus
type WpPackageFixStats []WpPackageFixStatus
// Names return a slice of names
func (ws WpPackageFixStats) Names() (names []string) {
for _, w := range ws {
names = append(names, w.Name)
}
return names
}
// WpPackages has a list of WpPackage
type WpPackages []WpPackage
// Add adds given arg to the slice and return the slice (immutable)
func (g WpPackages) Add(pkg WpPackage) WpPackages {
for _, a := range g {
if a.Name == pkg.Name {
return g
}
}
return append(g, pkg)
}
// Titles returns tilte (TUI)
func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
if lang == "ja" {
@@ -278,6 +303,13 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
})
}
if v, ok := v.CveContents[WPVulnDB]; ok {
values = append(values, CveContentStr{
Type: "WPVDB",
Value: v.Title,
})
}
if len(values) == 0 {
return []CveContentStr{{
Type: Unknown,
@@ -511,15 +543,15 @@ func (v VulnInfo) AttackVector() string {
for _, cnt := range v.CveContents {
if strings.HasPrefix(cnt.Cvss2Vector, "AV:N") ||
strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:N") {
return "Network"
return "N"
} else if strings.HasPrefix(cnt.Cvss2Vector, "AV:A") ||
strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:A") {
return "Adjacent"
return "A"
} else if strings.HasPrefix(cnt.Cvss2Vector, "AV:L") ||
strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:L") {
return "Local"
return "L"
} else if strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:P") {
return "Physical"
return "P"
}
}
if cont, found := v.CveContents[DebianSecurityTracker]; found {
@@ -538,17 +570,17 @@ func (v VulnInfo) PatchStatus(packs Packages) string {
}
for _, p := range v.AffectedPackages {
if p.NotFixedYet {
return "Unfixed"
return "unfixed"
}
// fast, offline mode doesn't have new version
if pack, ok := packs[p.Name]; ok {
if pack.NewVersion == "" {
return "Unknown"
return "unknown"
}
}
}
return "Fixed"
return "fixed"
}
// CveContentCvss has CVSS information
@@ -648,6 +680,12 @@ func (v VulnInfo) Cvss3CalcURL() string {
// VendorLinks returns links of vendor support's URL
func (v VulnInfo) VendorLinks(family string) map[string]string {
links := map[string]string{}
if strings.HasPrefix(v.CveID, "WPVDBID") {
links["WPVulnDB"] = fmt.Sprintf("https://wpvulndb.com/vulnerabilities/%s",
strings.TrimPrefix(v.CveID, "WPVDBID-"))
return links
}
switch family {
case config.RedHat, config.CentOS:
links["RHEL-CVE"] = "https://access.redhat.com/security/cve/" + v.CveID
@@ -810,6 +848,9 @@ const (
// GitHubMatchStr is a String representation of GitHubMatch
GitHubMatchStr = "GitHubMatch"
// WPVulnDBMatchStr is a String representation of WordPress VulnDB scanning
WPVulnDBMatchStr = "WPVulnDBMatch"
// FailedToGetChangelog is a String representation of FailedToGetChangelog
FailedToGetChangelog = "FailedToGetChangelog"
@@ -844,4 +885,7 @@ var (
// GitHubMatch is a ranking how confident the CVE-ID was deteted correctly
GitHubMatch = Confidence{97, GitHubMatchStr, 2}
// WPVulnDBMatch is a ranking how confident the CVE-ID was deteted correctly
WPVulnDBMatch = Confidence{100, WPVulnDBMatchStr, 0}
)

View File

@@ -916,15 +916,15 @@ func TestFormatMaxCvssScore(t *testing.T) {
func TestSortPackageStatues(t *testing.T) {
var tests = []struct {
in PackageStatuses
out PackageStatuses
in PackageFixStatuses
out PackageFixStatuses
}{
{
in: PackageStatuses{
in: PackageFixStatuses{
{Name: "b"},
{Name: "a"},
},
out: PackageStatuses{
out: PackageFixStatuses{
{Name: "a"},
{Name: "b"},
},
@@ -940,19 +940,19 @@ func TestSortPackageStatues(t *testing.T) {
func TestStorePackageStatueses(t *testing.T) {
var tests = []struct {
pkgstats PackageStatuses
in PackageStatus
out PackageStatuses
pkgstats PackageFixStatuses
in PackageFixStatus
out PackageFixStatuses
}{
{
pkgstats: PackageStatuses{
pkgstats: PackageFixStatuses{
{Name: "a"},
{Name: "b"},
},
in: PackageStatus{
in: PackageFixStatus{
Name: "c",
},
out: PackageStatuses{
out: PackageFixStatuses{
{Name: "a"},
{Name: "b"},
{Name: "c"},

88
models/wordpress.go Normal file
View File

@@ -0,0 +1,88 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Corporation , Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package models
// WordPressPackages has Core version, plugins and themes.
type WordPressPackages []WpPackage
// CoreVersion returns the core version of the installed WordPress
func (w WordPressPackages) CoreVersion() string {
for _, p := range w {
if p.Type == WPCore {
return p.Version
}
}
return ""
}
// Plugins returns a slice of plugins of the installed WordPress
func (w WordPressPackages) Plugins() (ps []WpPackage) {
for _, p := range w {
if p.Type == WPPlugin {
ps = append(ps, p)
}
}
return
}
// Themes returns a slice of themes of the installed WordPress
func (w WordPressPackages) Themes() (ps []WpPackage) {
for _, p := range w {
if p.Type == WPTheme {
ps = append(ps, p)
}
}
return
}
// Find searches by specified name
func (w WordPressPackages) Find(name string) (ps *WpPackage, found bool) {
for _, p := range w {
if p.Name == name {
return &p, true
}
}
return nil, false
}
const (
// WPCore is a type `core` in WPPackage struct
WPCore = "core"
// WPPlugin is a type `plugin` in WPPackage struct
WPPlugin = "plugin"
// WPTheme is a type `theme` in WPPackage struct
WPTheme = "theme"
// Inactive is a inactive status in WPPackage struct
Inactive = "inactive"
)
// WpPackage has a details of plugin and theme
type WpPackage struct {
Name string `json:"name,omitempty"`
Status string `json:"status,omitempty"` // active, inactive or must-use
Update string `json:"update,omitempty"` // available or none
Version string `json:"version,omitempty"`
Type string `json:"type,omitempty"` // core, plugin, theme
}
// WpPackageFixStatus is used in Vulninfo.WordPress
type WpPackageFixStatus struct {
Name string `json:"name,omitempty"`
FixedIn string `json:"fixedIn,omitempty"`
}