github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/examples/module/spring4shell/spring4shell.go (about) 1 //go:generate tinygo build -o spring4shell.wasm -scheduler=none -target=wasi --no-debug spring4shell.go 2 //go:build tinygo.wasm 3 4 package main 5 6 import ( 7 "bufio" 8 "fmt" 9 "io" 10 "os" 11 "regexp" 12 "strconv" 13 "strings" 14 15 "github.com/devseccon/trivy/pkg/module/api" 16 "github.com/devseccon/trivy/pkg/module/serialize" 17 "github.com/devseccon/trivy/pkg/module/wasm" 18 "github.com/devseccon/trivy/pkg/types" 19 ) 20 21 const ( 22 ModuleVersion = 1 23 ModuleName = "spring4shell" 24 TypeJavaMajor = ModuleName + "/java-major-version" 25 TypeTomcatVersion = ModuleName + "/tomcat-version" 26 ) 27 28 var ( 29 tomcatVersionRegex = regexp.MustCompile(`Apache Tomcat Version ([\d.]+)`) 30 ) 31 32 // main is required for TinyGo to compile to Wasm. 33 func main() { 34 wasm.RegisterModule(Spring4Shell{}) 35 } 36 37 type Spring4Shell struct { 38 // Cannot define fields as modules can't keep state. 39 } 40 41 func (Spring4Shell) Version() int { 42 return ModuleVersion 43 } 44 45 func (Spring4Shell) Name() string { 46 return ModuleName 47 } 48 49 func (Spring4Shell) RequiredFiles() []string { 50 return []string{ 51 `\/openjdk-\d+\/release`, // For OpenJDK version 52 `\/jdk\d+\/release`, // For JDK version 53 `tomcat\/RELEASE-NOTES`, // For Tomcat version 54 } 55 } 56 57 func (s Spring4Shell) Analyze(filePath string) (*serialize.AnalysisResult, error) { 58 wasm.Info(fmt.Sprintf("analyzing %s...", filePath)) 59 f, err := os.Open(filePath) 60 if err != nil { 61 return nil, err 62 } 63 defer f.Close() 64 65 switch { 66 case strings.HasSuffix(filePath, "/release"): 67 return s.parseJavaRelease(f, filePath) 68 case strings.HasSuffix(filePath, "/RELEASE-NOTES"): 69 return s.parseTomcatReleaseNotes(f, filePath) 70 } 71 72 return nil, nil 73 } 74 75 // Parse a jdk release file like "/usr/local/openjdk-11/release" 76 func (Spring4Shell) parseJavaRelease(f *os.File, filePath string) (*serialize.AnalysisResult, error) { 77 var javaVersion string 78 scanner := bufio.NewScanner(f) 79 for scanner.Scan() { 80 line := scanner.Text() 81 if !strings.HasPrefix(line, "JAVA_VERSION=") { 82 continue 83 } 84 85 ss := strings.Split(line, "=") 86 if len(ss) != 2 { 87 return nil, fmt.Errorf("invalid java version: %s", line) 88 } 89 90 javaVersion = strings.Trim(ss[1], `"`) 91 } 92 93 if err := scanner.Err(); err != nil { 94 return nil, err 95 } 96 97 return &serialize.AnalysisResult{ 98 CustomResources: []serialize.CustomResource{ 99 { 100 Type: TypeJavaMajor, 101 FilePath: filePath, 102 Data: javaVersion, 103 }, 104 }, 105 }, nil 106 } 107 108 func (Spring4Shell) parseTomcatReleaseNotes(f *os.File, filePath string) (*serialize.AnalysisResult, error) { 109 b, err := io.ReadAll(f) 110 if err != nil { 111 return nil, err 112 } 113 114 m := tomcatVersionRegex.FindStringSubmatch(string(b)) 115 if len(m) != 2 { 116 return nil, fmt.Errorf("unknown tomcat release notes format") 117 } 118 119 return &serialize.AnalysisResult{ 120 CustomResources: []serialize.CustomResource{ 121 { 122 Type: TypeTomcatVersion, 123 FilePath: filePath, 124 Data: m[1], 125 }, 126 }, 127 }, nil 128 } 129 130 func (Spring4Shell) PostScanSpec() serialize.PostScanSpec { 131 return serialize.PostScanSpec{ 132 Action: api.ActionUpdate, // Update severity 133 IDs: []string{"CVE-2022-22965"}, 134 } 135 } 136 137 // PostScan takes results including custom resources and detected CVE-2022-22965. 138 // 139 // Example input: 140 // [ 141 // 142 // { 143 // "Target": "", 144 // "Class": "custom", 145 // "CustomResources": [ 146 // { 147 // "Type": "spring4shell/java-major-version", 148 // "FilePath": "/usr/local/openjdk-8/release", 149 // "Layer": { 150 // "Digest": "sha256:d7b564a873af313eb2dbcb1ed0d393c57543e3666bdedcbe5d75841d72b1f791", 151 // "DiffID": "sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1" 152 // }, 153 // "Data": "1.8.0_322" 154 // }, 155 // { 156 // "Type": "spring4shell/tomcat-version", 157 // "FilePath": "/usr/local/tomcat/RELEASE-NOTES", 158 // "Layer": { 159 // "Digest": "sha256:59c0978ccb117247fd40d936973c40df89195f60466118c5acc6a55f8ba29f06", 160 // "DiffID": "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6" 161 // }, 162 // "Data": "8.5.77" 163 // } 164 // ] 165 // }, 166 // { 167 // "Target": "Java", 168 // "Class": "lang-pkgs", 169 // "Type": "jar", 170 // "Vulnerabilities": [ 171 // { 172 // "VulnerabilityID": "CVE-2022-22965", 173 // "PkgName": "org.springframework.boot:spring-boot", 174 // "PkgPath": "usr/local/tomcat/webapps/helloworld.war", 175 // "InstalledVersion": "2.6.3", 176 // "FixedVersion": "2.5.12, 2.6.6", 177 // "Layer": { 178 // "Digest": "sha256:cc44af318e91e6f9f9bf73793fa4f0639487613f46aa1f819b02b6e8fb5c6c07", 179 // "DiffID": "sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5" 180 // }, 181 // "SeveritySource": "nvd", 182 // "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-22965", 183 // "DataSource": { 184 // "ID": "glad", 185 // "Name": "GitLab Advisory Database Community", 186 // "URL": "https://gitlab.com/gitlab-org/advisories-community" 187 // }, 188 // "Title": "spring-framework: RCE via Data Binding on JDK 9+", 189 // "Description": "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.", 190 // "Severity": "CRITICAL", 191 // "CweIDs": [ 192 // "CWE-94" 193 // ], 194 // "VendorSeverity": { 195 // "ghsa": 4, 196 // "nvd": 4, 197 // "redhat": 3 198 // }, 199 // "CVSS": { 200 // "ghsa": { 201 // "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", 202 // "V3Score": 9.8 203 // }, 204 // "nvd": { 205 // "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", 206 // "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", 207 // "V2Score": 7.5, 208 // "V3Score": 9.8 209 // }, 210 // "redhat": { 211 // "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", 212 // "V3Score": 8.1 213 // } 214 // }, 215 // "References": [ 216 // "https://github.com/advisories/GHSA-36p3-wjmg-h94x" 217 // ], 218 // "PublishedDate": "2022-04-01T23:15:00Z", 219 // "LastModifiedDate": "2022-05-19T14:21:00Z" 220 // } 221 // ] 222 // } 223 // 224 // ] 225 func (Spring4Shell) PostScan(results serialize.Results) (serialize.Results, error) { 226 var javaMajorVersion int 227 var tomcatVersion string 228 for _, result := range results { 229 if result.Class != types.ClassCustom { 230 continue 231 } 232 233 for _, c := range result.CustomResources { 234 if c.Type == TypeJavaMajor { 235 v := c.Data.(string) 236 ss := strings.Split(v, ".") 237 if len(ss) == 0 || len(ss) < 2 { 238 wasm.Warn("Invalid Java version: " + v) 239 continue 240 } 241 242 ver := ss[0] 243 if ver == "1" { 244 ver = ss[1] 245 } 246 247 var err error 248 javaMajorVersion, err = strconv.Atoi(ver) 249 if err != nil { 250 wasm.Warn("Invalid Java version: " + v) 251 continue 252 } 253 } else if c.Type == TypeTomcatVersion { 254 tomcatVersion = c.Data.(string) 255 } 256 } 257 } 258 259 wasm.Info(fmt.Sprintf("Java Version: %d, Tomcat Version: %s", javaMajorVersion, tomcatVersion)) 260 261 vulnerable := true 262 // TODO: version comparison 263 if tomcatVersion == "10.0.20" || tomcatVersion == "9.0.62" || tomcatVersion == "8.5.78" { 264 vulnerable = false 265 } else if javaMajorVersion <= 8 { 266 vulnerable = false 267 } 268 269 for i, result := range results { 270 for j, vuln := range result.Vulnerabilities { 271 // Look up Spring4Shell 272 if vuln.VulnerabilityID != "CVE-2022-22965" { 273 continue 274 } 275 276 // If it doesn't satisfy any of requirements, the severity should be changed to LOW. 277 if !strings.Contains(vuln.PkgPath, ".war") || !vulnerable { 278 wasm.Info(fmt.Sprintf("change %s CVE-2022-22965 severity from CRITICAL to LOW", vuln.PkgName)) 279 results[i].Vulnerabilities[j].Severity = "LOW" 280 } 281 } 282 } 283 284 return results, nil 285 }