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  }