diff --git a/syft/pkg/cataloger/internal/cpegenerate/generate.go b/syft/pkg/cataloger/internal/cpegenerate/generate.go index 3d58ea9f534..4860116a03f 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/generate.go +++ b/syft/pkg/cataloger/internal/cpegenerate/generate.go @@ -129,23 +129,26 @@ func FromDictionaryFind(p pkg.Package) ([]cpe.CPE, bool) { func FromPackageAttributes(p pkg.Package) []cpe.CPE { vendors := candidateVendors(p) products := candidateProducts(p) + targetSWs := candidateTargetSw(p) if len(products) == 0 { return nil } keys := strset.New() cpes := make([]cpe.Attributes, 0) - for _, product := range products { - for _, vendor := range vendors { - // prevent duplicate entries... - key := fmt.Sprintf("%s|%s|%s", product, vendor, p.Version) - if keys.Has(key) { - continue - } - keys.Add(key) - // add a new entry... - if c := newCPE(product, vendor, p.Version, cpe.Any); c != nil { - cpes = append(cpes, *c) + for _, ts := range targetSWs { + for _, product := range products { + for _, vendor := range vendors { + // prevent duplicate entries... + key := fmt.Sprintf("%s|%s|%s|%s", product, vendor, p.Version, ts) + if keys.Has(key) { + continue + } + keys.Add(key) + // add a new entry... + if c := newCPE(product, vendor, p.Version, ts); c != nil { + cpes = append(cpes, *c) + } } } } @@ -162,6 +165,13 @@ func FromPackageAttributes(p pkg.Package) []cpe.CPE { return result } +func candidateTargetSw(p pkg.Package) []string { + if p.Type == pkg.WordpressPluginPkg { + return []string{"wordpress"} + } + return []string{cpe.Any} +} + //nolint:funlen func candidateVendors(p pkg.Package) []string { // in ecosystems where the packaging metadata does not have a clear field to indicate a vendor (or a field that diff --git a/syft/pkg/cataloger/internal/cpegenerate/generate_test.go b/syft/pkg/cataloger/internal/cpegenerate/generate_test.go index 633fe32073f..e0b9af6cb6f 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/generate_test.go +++ b/syft/pkg/cataloger/internal/cpegenerate/generate_test.go @@ -719,6 +719,31 @@ func TestGeneratePackageCPEs(t *testing.T) { "cpe:2.3:a:ruby_rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", }, }, + { + name: "wordpress plugin", + p: pkg.Package{ + Name: "WP Coder", + Version: "2.5.1", + Type: pkg.WordpressPluginPkg, + Metadata: pkg.WordpressPluginEntry{ + PluginInstallDirectory: "wp-coder", + Author: "Wow-Company", + AuthorURI: "https://wow-estore.com", + }, + }, + expected: []string{ + "cpe:2.3:a:wow-company:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:wow-company:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", // this is the correct CPE relative to CVE-2021-25053 + "cpe:2.3:a:wow-estore:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:wow-estore:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:wow:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:wow:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:wow_company:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:wow_company:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:wow_estore:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:wow_estore:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", + }, + }, } for _, test := range tests { diff --git a/syft/pkg/cataloger/internal/cpegenerate/wordpress.go b/syft/pkg/cataloger/internal/cpegenerate/wordpress.go index 3a28b270294..ae71b9fe2ba 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/wordpress.go +++ b/syft/pkg/cataloger/internal/cpegenerate/wordpress.go @@ -21,12 +21,18 @@ func candidateVendorsForWordpressPlugin(p pkg.Package) fieldCandidateSet { vendors := newFieldCandidateSet() + if metadata.Author != "" { + vendors.addValue(strings.ToLower(metadata.Author)) + } + if metadata.AuthorURI != "" { matchMap := internal.MatchNamedCaptureGroups(vendorFromURLRegexp, metadata.AuthorURI) if vendor, ok := matchMap["vendor"]; ok && vendor != "" { - vendors.addValue(vendor) + vendors.addValue(strings.ToLower(vendor)) } - } else { + } + + if len(vendors) == 0 { // add plugin_name + _project as a vendor if no Author URI found vendors.addValue(fmt.Sprintf("%s_project", normalizeWordpressPluginName(p.Name))) } diff --git a/syft/pkg/cataloger/internal/cpegenerate/wordpress_test.go b/syft/pkg/cataloger/internal/cpegenerate/wordpress_test.go index 939588e926d..f292db9152e 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/wordpress_test.go +++ b/syft/pkg/cataloger/internal/cpegenerate/wordpress_test.go @@ -24,7 +24,7 @@ func Test_candidateVendorsForWordpressPlugin(t *testing.T) { AuthorURI: "https://automattic.com/wordpress-plugins/", }, }, - expected: []string{"automattic"}, + expected: []string{"automattic - anti-spam team", "automattic"}, }, { name: "All-in-One WP Migration", @@ -47,7 +47,7 @@ func Test_candidateVendorsForWordpressPlugin(t *testing.T) { AuthorURI: "https://bookingultrapro.com/", }, }, - expected: []string{"bookingultrapro"}, + expected: []string{"booking ultra pro", "bookingultrapro"}, }, { name: "Coming Soon Chop Chop", @@ -58,7 +58,7 @@ func Test_candidateVendorsForWordpressPlugin(t *testing.T) { AuthorURI: "https://www.chop-chop.org", }, }, - expected: []string{"chop-chop"}, + expected: []string{"chop-chop.org", "chop-chop"}, }, { name: "Access Code Feeder", @@ -71,6 +71,22 @@ func Test_candidateVendorsForWordpressPlugin(t *testing.T) { // When a plugin as no `Author URI` use plugin_name + _project as a vendor expected: []string{"access_code_feeder_project"}, }, + { + name: "WP Coder", + pkg: pkg.Package{ + Name: "WP Coder", + Metadata: pkg.WordpressPluginEntry{ + PluginInstallDirectory: "wp-coder", + Author: "Wow-Company", + AuthorURI: "https://wow-estore.com/", + }, + }, + // found in the wild https://plugins.trac.wordpress.org/browser/wp-coder/tags/2.5.1/wp-coder.php + expected: []string{ + "wow-company", // this is the correct answer relative to CPEs registered on CVE-2021-25053 + "wow-estore", + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) {