Skip to content

ROX-12784: fix unpatched OpenShift 4 vulnerability detection #1006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2etests/grpc_full_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func verifyImage(t *testing.T, imgScan *v1.Image, test testCase) {
foundMatch = true
checkGRPCMatch(t, expectedVuln, matchingVuln)
}
assert.True(t, foundMatch)
assert.Truef(t, foundMatch, "Expected to find %s in scan results", expectedVuln.Name)
}
}
feature.Vulnerabilities = nil
Expand Down
2 changes: 1 addition & 1 deletion e2etests/sanity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func verifyImageHasExpectedFeatures(t *testing.T, client *client.Clairify, test
foundMatch = true
checkMatch(t, test.source, expectedVuln, matchingVuln)
}
assert.True(t, foundMatch)
assert.Truef(t, foundMatch, "Expected to find %s in scan results", expectedVuln.Name)
}
}
feature.Vulnerabilities = nil
Expand Down
115 changes: 113 additions & 2 deletions e2etests/testcase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3444,7 +3444,7 @@ var testCases = []testCase{
NamespaceName: "rhel:9",
Version: "1:3.0.1-23.el9_0.x86_64",
VersionFormat: "rpm",
FixedBy: "1:3.0.1-43.el9_0",
FixedBy: "1:3.0.1-43.el9_0",
Vulnerabilities: []apiV1.Vulnerability{
{
Name: "RHSA-2022:7288",
Expand Down Expand Up @@ -3486,7 +3486,7 @@ For more details about the security issue(s), including the impact, a CVSS score
NamespaceName: "rhel:9",
Version: "1:3.0.1-23.el9_0.x86_64",
VersionFormat: "rpm",
FixedBy: "1:3.0.1-43.el9_0",
FixedBy: "1:3.0.1-43.el9_0",
Vulnerabilities: []apiV1.Vulnerability{
{
Name: "RHSA-2022:7288",
Expand Down Expand Up @@ -3801,4 +3801,115 @@ Applications using RegexRequestMatcher with '.' in the regular expression are po
},
},
},
{
image: "quay.io/rhacs-eng/qa:ose-jenkins",
registry: "https://quay.io",
username: os.Getenv("QUAY_RHACS_ENG_RO_USERNAME"),
password: os.Getenv("QUAY_RHACS_ENG_RO_PASSWORD"),
source: "Red Hat",
onlyCheckSpecifiedVulns: true,
namespace: "rhel:8",
expectedFeatures: []apiV1.Feature{
{
Name: "jenkins-2-plugins",
NamespaceName: "rhel:8",
VersionFormat: "rpm",
Version: "4.10.1650890594-1.el8.noarch",
AddedBy: "sha256:3fa3f612bdcb92746bf76be1b9c9e1c1c80de777aedaf48b7068f4a129ded3c2",
FixedBy: "4.10.1663147786-1.el8",
Vulnerabilities: []apiV1.Vulnerability{
{
Name: "CVE-2021-26291",
NamespaceName: "rhel:8",
Description: `DOCUMENTATION: A flaw was found in maven. Repositories that are defined in a dependency’s Project Object Model (pom), which may be unknown to users, are used by default resulting in potential risk if a malicious actor takes over that repository or is able to insert themselves into a position to pretend to be that repository. The highest threat from this vulnerability is to data confidentiality and integrity.

MITIGATION: To avoid possible man-in-the-middle related attacks with this flaw, ensure any linked repositories in maven POMs use https and not http.`,
Link: "https://access.redhat.com/security/cve/CVE-2021-26291",
Severity: "Moderate",
Metadata: map[string]interface{}{
"Red Hat": map[string]interface{}{
"CVSSv2": map[string]interface{}{
"ExploitabilityScore": 0.0,
"ImpactScore": 0.0,
"Score": 0.0,
"Vectors": "",
},
"CVSSv3": map[string]interface{}{
"ExploitabilityScore": 2.2,
"ImpactScore": 5.2,
"Score": 7.4,
"Vectors": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
},
},
},
},
{
Name: "CVE-2022-30945",
NamespaceName: "rhel:8",
Description: "DOCUMENTATION: The MITRE CVE dictionary describes this issue as: Jenkins Pipeline: Groovy Plugin 2689.v434009a_31b_f1 and earlier allows loading any Groovy source files on the classpath of Jenkins and Jenkins plugins in sandboxed pipelines.",
Link: "https://access.redhat.com/security/cve/CVE-2022-30945",
Severity: "Important",
Metadata: map[string]interface{}{
"Red Hat": map[string]interface{}{
"CVSSv2": map[string]interface{}{
"ExploitabilityScore": 0.0,
"ImpactScore": 0.0,
"Score": 0.0,
"Vectors": "",
},
"CVSSv3": map[string]interface{}{
"ExploitabilityScore": 1.6,
"ImpactScore": 5.9,
"Score": 7.5,
"Vectors": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H",
},
},
},
},
{
Name: "RHSA-2022:6531",
NamespaceName: "rhel:8",
Description: `Red Hat OpenShift Container Platform is Red Hat's cloud computing
Kubernetes application platform solution designed for on-premise or private
cloud deployments.

This advisory contains the RPM packages for Red Hat OpenShift Container Platform 4.10.33. See the following advisory for the container images for this release:

https://access.redhat.com/errata/RHBA-2022:6532

Security Fix(es):

* jenkins-plugin: Arbitrary file write vulnerability in Pipeline Input Step
Plugin (CVE-2022-34177)

For more details about the security issue(s), including the impact, a CVSS
score, acknowledgments, and other related information, refer to the CVE
page(s)
listed in the References section.

All OpenShift Container Platform 4.10 users are advised to upgrade to these updated packages and images when they are available in the appropriate release channel. To check for available updates, use the OpenShift Console or the CLI oc command. Instructions for upgrading a cluster are available at https://docs.openshift.com/container-platform/4.10/updating/updating-cluster-cli.html`,
Link: "https://access.redhat.com/errata/RHSA-2022:6531",
Severity: "Important",
Metadata: map[string]interface{}{
"Red Hat": map[string]interface{}{
"CVSSv2": map[string]interface{}{
"ExploitabilityScore": 0.0,
"ImpactScore": 0.0,
"Score": 0.0,
"Vectors": "",
},
"CVSSv3": map[string]interface{}{
"ExploitabilityScore": 3.9,
"ImpactScore": 3.6,
"Score": 7.5,
"Vectors": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
},
},
},
FixedBy: "0:4.10.1663147786-1.el8",
},
},
},
},
},
}
60 changes: 59 additions & 1 deletion pkg/cpeutils/utils.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
package cpeutils

import (
"regexp"
"strconv"
"strings"

"github.com/facebookincubator/nvdtools/wfn"
"github.com/pkg/errors"
)

// Note: this must be updated with each new OpenShift release.
const maxKnownOpenShift4MinorVersion = 12

// *** START Regex-related consts/vars. ***

// These must all stay in-sync at all times.
const (
openshiftVersionIdx = 1
minorVersionIdx = 3
submatchLen = 5
)

var (
openshift4CPEPattern = regexp.MustCompile(`^cpe:/a:redhat:openshift:(?P<openshiftVersion>4(\.(?P<minorVersion>\d+))?)(::el8)?$`)
)

// *** END Regex-related consts/vars. ***

// GetMostSpecificCPE deterministically returns the CPE that is the most specific
// from the set of matches. This function requires that len(cpes) > 0
func GetMostSpecificCPE(cpes []wfn.AttributesWithFixedIn) wfn.AttributesWithFixedIn {
Expand All @@ -28,7 +49,44 @@ func compareAttributes(c1, c2 wfn.AttributesWithFixedIn) int {
return strings.Compare(c1.Version, c2.Version)
}

// IsOpenShiftCPE returns if the passed CPE is a known OpenShift CPE
// IsOpenShiftCPE determines whether the passed CPE is an OpenShift CPE.
func IsOpenShiftCPE(cpe string) bool {
return strings.HasPrefix(cpe, "cpe:/a:redhat:openshift:")
}

// IsOpenShift4CPE determines whether the passed CPE is an OpenShift 4 CPE.
func IsOpenShift4CPE(cpe string) bool {
// This should be faster than performing regex matching.
return strings.HasPrefix(cpe, "cpe:/a:redhat:openshift:4")
}

// GetAllOpenShift4CPEs returns a slice of other CPEs related to the given Red Hat OpenShift 4 CPE.
// For example, given "cpe:/a:redhat:openshift:4.2", this returns
// ["cpe:/a:redhat:openshift:4.0", "cpe:/a:redhat:openshift:4.1", "cpe:/a:redhat:openshift:4.2"].
// If the CPE does not contain minor-version information,
// then all known minor versions are returned.
func GetAllOpenShift4CPEs(cpe string) ([]string, error) {
match := openshift4CPEPattern.FindStringSubmatch(cpe)
if len(match) != submatchLen {
return nil, errors.New("CPE does not match an expected OpenShift 4 CPE format")
}

maxMinorVersion := maxKnownOpenShift4MinorVersion

// If an explicit minor version is given, assume it is the highest maximum version.
if match[minorVersionIdx] != "" {
var err error
maxMinorVersion, err = strconv.Atoi(match[minorVersionIdx])
if err != nil {
return nil, err
}
}

openshiftVersion := match[openshiftVersionIdx]
cpes := make([]string, 0, maxMinorVersion)
for i := 0; i <= maxMinorVersion; i++ {
version := strconv.Itoa(i)
cpes = append(cpes, strings.Replace(cpe, openshiftVersion, "4."+version, 1))
}
return cpes, nil
}
94 changes: 94 additions & 0 deletions pkg/cpeutils/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package cpeutils

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestGetAllOpenShift4CPEs(t *testing.T) {
testcases := []struct {
cpe string
expected []string
}{
{
cpe: "cpe:/a:redhat:openshift:4.12",
expected: []string{
"cpe:/a:redhat:openshift:4.0",
"cpe:/a:redhat:openshift:4.1",
"cpe:/a:redhat:openshift:4.2",
"cpe:/a:redhat:openshift:4.3",
"cpe:/a:redhat:openshift:4.4",
"cpe:/a:redhat:openshift:4.5",
"cpe:/a:redhat:openshift:4.6",
"cpe:/a:redhat:openshift:4.7",
"cpe:/a:redhat:openshift:4.8",
"cpe:/a:redhat:openshift:4.9",
"cpe:/a:redhat:openshift:4.10",
"cpe:/a:redhat:openshift:4.11",
"cpe:/a:redhat:openshift:4.12",
},
},
{
cpe: "cpe:/a:redhat:openshift:4.12::el8",
expected: []string{
"cpe:/a:redhat:openshift:4.0::el8",
"cpe:/a:redhat:openshift:4.1::el8",
"cpe:/a:redhat:openshift:4.2::el8",
"cpe:/a:redhat:openshift:4.3::el8",
"cpe:/a:redhat:openshift:4.4::el8",
"cpe:/a:redhat:openshift:4.5::el8",
"cpe:/a:redhat:openshift:4.6::el8",
"cpe:/a:redhat:openshift:4.7::el8",
"cpe:/a:redhat:openshift:4.8::el8",
"cpe:/a:redhat:openshift:4.9::el8",
"cpe:/a:redhat:openshift:4.10::el8",
"cpe:/a:redhat:openshift:4.11::el8",
"cpe:/a:redhat:openshift:4.12::el8",
},
},
{
cpe: "cpe:/a:redhat:openshift:4",
expected: []string{
"cpe:/a:redhat:openshift:4.0",
"cpe:/a:redhat:openshift:4.1",
"cpe:/a:redhat:openshift:4.2",
"cpe:/a:redhat:openshift:4.3",
"cpe:/a:redhat:openshift:4.4",
"cpe:/a:redhat:openshift:4.5",
"cpe:/a:redhat:openshift:4.6",
"cpe:/a:redhat:openshift:4.7",
"cpe:/a:redhat:openshift:4.8",
"cpe:/a:redhat:openshift:4.9",
"cpe:/a:redhat:openshift:4.10",
"cpe:/a:redhat:openshift:4.11",
"cpe:/a:redhat:openshift:4.12",
},
},
{
cpe: "cpe:/a:redhat:openshift:4::el8",
expected: []string{
"cpe:/a:redhat:openshift:4.0::el8",
"cpe:/a:redhat:openshift:4.1::el8",
"cpe:/a:redhat:openshift:4.2::el8",
"cpe:/a:redhat:openshift:4.3::el8",
"cpe:/a:redhat:openshift:4.4::el8",
"cpe:/a:redhat:openshift:4.5::el8",
"cpe:/a:redhat:openshift:4.6::el8",
"cpe:/a:redhat:openshift:4.7::el8",
"cpe:/a:redhat:openshift:4.8::el8",
"cpe:/a:redhat:openshift:4.9::el8",
"cpe:/a:redhat:openshift:4.10::el8",
"cpe:/a:redhat:openshift:4.11::el8",
"cpe:/a:redhat:openshift:4.12::el8",
},
},
}
for _, testcase := range testcases {
t.Run(testcase.cpe, func(t *testing.T) {
cpes, err := GetAllOpenShift4CPEs(testcase.cpe)
assert.NoError(t, err)
assert.ElementsMatch(t, testcase.expected, cpes)
})
}
}
20 changes: 13 additions & 7 deletions pkg/rhelv2/ovalutil/rpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@ import (
coreovalutil "github.com/stackrox/scanner/pkg/ovalutil"
)

// DefinitionType represents a vulnerability definition's type.
type DefinitionType string

const (
// CVEDefinition indicates the vulnerability definition is for a CVE.
CVEDefinition = "cve"
CVEDefinition DefinitionType = "cve"
// RHBADefinition indicates the vulnerability definition is for a RHBA.
RHBADefinition = "rhba"
RHBADefinition DefinitionType = "rhba"
// RHEADefinition indicates the vulnerability definition is for a RHEA.
RHEADefinition = "rhea"
RHEADefinition DefinitionType = "rhea"
// RHSADefinition indicates the vulnerability definition is for a RHSA.
RHSADefinition = "rhsa"
RHSADefinition DefinitionType = "rhsa"
// UnaffectedDefinition indicates the vulnerability definition is for
// a package that is unaffected by the CVE.
UnaffectedDefinition = "unaffected"
UnaffectedDefinition DefinitionType = "unaffected"
// NoneDefinition indicates this is not a vulnerability.
// This is typically used to indicate an, essentially, empty OVAL v2 file.
NoneDefinition DefinitionType = "none"
)

var (
Expand Down Expand Up @@ -228,12 +234,12 @@ func rpmStateLookup(root *oval.Root, ref string) (*oval.RPMInfoState, error) {
}

// GetDefinitionType parses an OVAL definition and extracts its type from ID.
func GetDefinitionType(def oval.Definition) (string, error) {
func GetDefinitionType(def oval.Definition) (DefinitionType, error) {
match := definitionTypeRegex.FindStringSubmatch(def.ID)
if len(match) != 2 { // we should have match of the whole string and one submatch
return "", errors.New("cannot parse definition ID for its type")
}
return match[1], nil
return DefinitionType(match[1]), nil
}

// getPackageResolutions parses the given oval.Definition to determine a mapping from package to its resolution state.
Expand Down
7 changes: 4 additions & 3 deletions pkg/rhelv2/ovalutil/rpm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import (
)

type definitionTypeTestCase struct {
def oval.Definition
want, name string
err bool
def oval.Definition
want DefinitionType
name string
err bool
}

func simpleProtoVulnFunc(def oval.Definition) (*database.RHELv2Vulnerability, error) {
Expand Down
Loading