github.com/google/osv-scalibr@v0.4.1/detector/cve/untested/cve202233891/cve202233891.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package cve202233891 implements a detector for CVE-2022-33891. 16 // To test, install a vulnerable pyspark version: python3 -m pip install pyspark==3.2.1 17 // Run the spark-shell: spark-shell --conf spark.acls.enable=true 18 // If spark-shell crashes with an error, change your java version to an old one: sudo update-alternatives --config java (JAVA 11 works) 19 // Run this detector. 20 package cve202233891 21 22 import ( 23 "context" 24 "fmt" 25 "io/fs" 26 "math/rand" 27 "net" 28 "net/http" 29 "os" 30 "strconv" 31 "strings" 32 "time" 33 34 "github.com/google/osv-scalibr/detector" 35 "github.com/google/osv-scalibr/extractor" 36 "github.com/google/osv-scalibr/extractor/filesystem/language/python/wheelegg" 37 scalibrfs "github.com/google/osv-scalibr/fs" 38 "github.com/google/osv-scalibr/inventory" 39 "github.com/google/osv-scalibr/log" 40 "github.com/google/osv-scalibr/packageindex" 41 "github.com/google/osv-scalibr/plugin" 42 43 osvpb "github.com/ossf/osv-schema/bindings/go/osvschema" 44 structpb "google.golang.org/protobuf/types/known/structpb" 45 ) 46 47 type sparkUIPackageNames struct { 48 packageType string 49 name string 50 affectedVersions []string 51 } 52 53 const ( 54 // Name of the detector. 55 Name = "cve/cve-2022-33891" 56 57 defaultTimeout = 5 * time.Second 58 ) 59 60 var ( 61 seededRand = rand.New(rand.NewSource(time.Now().UnixNano())) 62 sparkServersPorts = []int{4040, 8080} 63 sparkUIPackages = []sparkUIPackageNames{ 64 { 65 packageType: "pypi", 66 name: "pyspark", 67 affectedVersions: []string{ 68 "3.0.0", 69 "3.0.1", 70 "3.0.2", 71 "3.0.3", 72 "3.1.1", 73 "3.1.2", 74 "3.2.0", 75 "3.2.1", 76 }, 77 }, 78 } 79 ) 80 81 // Detector is a SCALIBR Detector for CVE-2022-33891. 82 type Detector struct{} 83 84 // New returns a detector. 85 func New() detector.Detector { 86 return &Detector{} 87 } 88 89 // Name of the detector. 90 func (Detector) Name() string { return Name } 91 92 // Version of the detector. 93 func (Detector) Version() int { return 0 } 94 95 // Requirements of the detector. 96 func (Detector) Requirements() *plugin.Capabilities { 97 return &plugin.Capabilities{OS: plugin.OSLinux, DirectFS: true, RunningSystem: true} 98 } 99 100 // RequiredExtractors returns the list of OS package extractors needed to detect 101 // the presence of the pyspark package in various OSes. 102 func (Detector) RequiredExtractors() []string { 103 return []string{wheelegg.Name} 104 } 105 106 // DetectedFinding returns generic vulnerability information about what is detected. 107 func (d Detector) DetectedFinding() inventory.Finding { 108 return d.findingForPackage(nil, nil) 109 } 110 111 func (Detector) findingForPackage(dbSpecific *structpb.Struct, pkg *extractor.Package) inventory.Finding { 112 pySparkPkg := &extractor.Package{ 113 Name: "pyspark", 114 PURLType: "pypi", 115 } 116 return inventory.Finding{PackageVulns: []*inventory.PackageVuln{{ 117 Package: pkg, 118 Vulnerability: &osvpb.Vulnerability{ 119 Id: "CVE-2022-33891", 120 Summary: "CVE-2022-33891", 121 Details: "CVE-2022-33891", 122 Affected: inventory.PackageToAffected(pySparkPkg, "3.2.2", &osvpb.Severity{ 123 Type: osvpb.Severity_CVSS_V3, 124 Score: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", 125 }), 126 DatabaseSpecific: dbSpecific, 127 }, 128 }}} 129 } 130 131 // Scan scans for the vulnerability, doh! 132 func (d Detector) Scan(ctx context.Context, scanRoot *scalibrfs.ScanRoot, px *packageindex.PackageIndex) (inventory.Finding, error) { 133 sparkUIVersion, pkg, affectedVersions := findApacheSparkUIPackage(px) 134 if sparkUIVersion == "" { 135 log.Debugf("No Apache Spark UI version found") 136 return inventory.Finding{}, nil 137 } 138 139 isVulnVersion := false 140 for _, affected := range affectedVersions { 141 if sparkUIVersion == affected { 142 isVulnVersion = true 143 } 144 } 145 146 if !isVulnVersion { 147 log.Infof("Version %q not vuln", sparkUIVersion) 148 return inventory.Finding{}, nil 149 } 150 log.Infof("Found Potentially vulnerable Apache Spark UI version %v", sparkUIVersion) 151 152 vulnerable := false 153 for _, port := range sparkServersPorts { 154 randFilePath := "/tmp/" + randomString(16) 155 testCmd := "touch%20" + randFilePath 156 retCode := sparkUIHTTPQuery(ctx, "127.0.0.1", port, testCmd) 157 // We expect to receive a 403 error 158 if retCode != 403 { 159 log.Infof("Version %q not vuln (HTTP query didn't return 403: %v)", sparkUIVersion, retCode) 160 continue 161 } 162 163 if fileExists(scanRoot.FS, randFilePath) { 164 err := os.Remove(randFilePath) 165 if err != nil { 166 log.Infof("Error when removing file %v: %v", randFilePath, err) 167 } 168 log.Infof("File %v found, this server is vulnerable. Removing the file now", randFilePath) 169 vulnerable = true 170 break 171 } 172 log.Infof("Version %q not vuln (Temp file not found)", sparkUIVersion) 173 } 174 if !vulnerable { 175 return inventory.Finding{}, nil 176 } 177 178 dbSpecific := &structpb.Struct{ 179 Fields: map[string]*structpb.Value{ 180 "extra": {Kind: &structpb.Value_StringValue{StringValue: fmt.Sprintf("%s %s %s", pkg.Name, pkg.Version, strings.Join(pkg.Locations, ", "))}}, 181 }, 182 } 183 return d.findingForPackage(dbSpecific, pkg), nil 184 } 185 186 func sparkUIHTTPQuery(ctx context.Context, sparkDomain string, sparkPort int, cmdExec string) int { 187 client := &http.Client{Timeout: defaultTimeout} 188 189 targetURL := fmt.Sprintf("http://%s/?doAs=`%s`", net.JoinHostPort(sparkDomain, strconv.Itoa(sparkPort)), cmdExec) 190 req, _ := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil) 191 req.Header.Add("Accept", "*/*") 192 resp, err := client.Do(req) 193 194 if err != nil { 195 log.Infof("Error when sending request %s to the server", targetURL) 196 return 0 197 } 198 199 defer resp.Body.Close() 200 201 return resp.StatusCode 202 } 203 204 func findApacheSparkUIPackage(px *packageindex.PackageIndex) (string, *extractor.Package, []string) { 205 for _, r := range sparkUIPackages { 206 pkg := px.GetSpecific(r.name, r.packageType) 207 for _, p := range pkg { 208 return p.Version, p, r.affectedVersions 209 } 210 } 211 return "", nil, []string{} 212 } 213 214 func fileExists(filesys scalibrfs.FS, path string) bool { 215 _, err := fs.Stat(filesys, path) 216 return !os.IsNotExist(err) 217 } 218 219 func randomString(length int) string { 220 charSet := "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ" 221 b := make([]byte, length) 222 for i := range b { 223 b[i] = charSet[seededRand.Intn(len(charSet)-1)] 224 } 225 return string(b) 226 }