├── CHANGES ├── Makefile ├── doc ├── check-springshell.1 └── check-springshell.1.txt ├── README.md └── src └── check-springshell.sh /CHANGES: -------------------------------------------------------------------------------- 1 | Version 0.4 (2022-04-01) 2 | * better detection of CVE-2022-22963 3 | 4 | Version 0.3 (2022-03-31) 5 | * add java version check (skip with '-s java') 6 | 7 | Version 0.2 (2022-03-31) 8 | * add support for checking for CVE-2022-22965 9 | 10 | Version 0.1 (2022-03-31) 11 | * hello T(java.lang.Runtime).getRuntime().exec("whoami") 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME= check-springshell 2 | 3 | PREFIX?=/usr/local 4 | 5 | help: 6 | @echo "The following targets are available:" 7 | @echo "install install ${NAME} under ${PREFIX}" 8 | @echo "man generate the formatted manual page" 9 | @echo "readme generate the README after a manual page update" 10 | 11 | install: 12 | mkdir -p ${PREFIX}/bin ${PREFIX}/share/man/man1 13 | install -c -m 555 src/${NAME}.sh ${PREFIX}/bin/${NAME} 14 | install -c -m 444 doc/${NAME}.1 ${PREFIX}/share/man/man1/${NAME}.1 15 | 16 | man: doc/${NAME}.1.txt 17 | 18 | doc/${NAME}.1.txt: doc/${NAME}.1 19 | mandoc -c -O width=80 $? | col -b >$@ 20 | 21 | readme: man 22 | sed -n -e '/^NAME/!p;//q' README.md >.readme 23 | sed -n -e '/^NAME/,$$p' -e '/emailing/q' doc/${NAME}.1.txt >>.readme 24 | echo '```' >>.readme 25 | mv .readme README.md 26 | -------------------------------------------------------------------------------- /doc/check-springshell.1: -------------------------------------------------------------------------------- 1 | .Dd March 31, 2022 2 | .Dt check-springshell 1 3 | .Os 4 | .Sh NAME 5 | .Nm check-springshell 6 | .Nd try to determine if a host is vulnerable to the SpringShell vulnerabilities 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl Vhv 10 | .Op Fl j Ar jar 11 | .Op Fl p Ar path 12 | .Op Fl s Ar skip 13 | .Sh DESCRIPTION 14 | The 15 | .Nm 16 | tool attempts to determine whether the host it is 17 | executed on is vulnerable to the vulnerabilities 18 | grouped together under the "SpringShell" name. 19 | .Pp 20 | This includes CVE-2022-22963, a Spring SpEL / 21 | Expression Resource Access Vulnerability, as well as 22 | CVE-2022-22965, the spring-webmvc/spring-webflux RCE 23 | termed "SpringShell". 24 | .Pp 25 | .Nm 26 | will look for nested Java archive files and so 27 | may be somewhat intrusive to run and should be 28 | executed with care and consideration of the system's 29 | load. 30 | Please see DETAILS for more information. 31 | .Sh OPTIONS 32 | The following options are supported by 33 | .Nm : 34 | .Bl -tag -width p_path_ 35 | .It Fl V 36 | Print version number and exit. 37 | .It Fl h 38 | Print a short help message and exit. 39 | .It Fl j Ar jar 40 | Check only this archive, nothing else. 41 | Can be specified multiple times for multiple JAR 42 | (or other zip formatted archive) files. 43 | .It Fl p Ar path 44 | Limit filesystem traversal to this directory. 45 | Can be specified multiple times. 46 | If not specified, 47 | .Nm 48 | will default to '/'. 49 | .It Fl s Ar skip 50 | Skip the given checks. 51 | Valid arguments are 'files', 'java', 'packages', and 52 | \'processes'. 53 | .It Fl v 54 | Be verbose. 55 | Can be specified multiple times. 56 | .El 57 | .Sh DETAILS 58 | CVE-2022-22963 describes a possible remote code 59 | execution (RCE) vulnerability in the popular Spring 60 | Boot framework. 61 | Simply sending a POST request with a specific payload 62 | can cause the vulnerable server to execute commands on 63 | the attacker's behalf. 64 | .Pp 65 | Likewise, CVE-2022-22965 describes another possible 66 | RCE vulnerability, termed "SpringShell", which follows 67 | a common webshell upload pattern. 68 | .Pp 69 | To determine whether a host is vulnerable, the 70 | .Nm 71 | tool will look for classes and java archives relating 72 | to the known vulnerable versions of the Spring 73 | framework. 74 | .Pp 75 | The discovery process may include running 76 | .Xr find 1 , 77 | .Xr lsof 1 , 78 | or 79 | .Xr rpm 1 ; 80 | please use the 81 | .Fl s 82 | flag to skip any checks that might have a negative 83 | impact on your host. 84 | .Pp 85 | The output of the command attempts to be human 86 | readable and provide sufficient information to judge 87 | whether the host requires attention. 88 | .Sh ENVIRONMENT 89 | The following environment variables influence the 90 | behavior of 91 | .Nm : 92 | .Bl -tag 93 | .It CHECK_SPRINGSHELL_FIND_OPTS_PRE 94 | Additional options to pass to 95 | .Xr find 1 96 | prior to the path name(s). 97 | .Pp 98 | By default, 99 | .Nm 100 | runs "find / -type f -name '*.[ejw]ar'"; 101 | the contents of this variable are placed immediately 102 | after the 'find' and before the path name(s). 103 | .It CHECK_SPRINGSHELL_FIND_OPTS_POST 104 | Additional options to pass to 105 | .Xr find 1 106 | immediately after the path name(s). 107 | .El 108 | .Sh EXAMPLES 109 | Sample invocation on a non-vulnerable host: 110 | .Bd -literal -offset indent 111 | $ check-springshell 112 | No obvious indicators of vulnerability found. 113 | $ 114 | .Ed 115 | .Pp 116 | Sample invocation only looking at processes 117 | .Bd -literal -offset indent 118 | $ check-springshell -s files -s packages -v -v 119 | => Running all checks... 120 | ==> Skipping package check. 121 | ==> Looking for jars... 122 | ==> Skipping files check. 123 | ==> Checking all found jars... 124 | check-springshell 1.0 localhost: Possibly vulnerable jar 'BOOT-INF/lib/spring-beans-5.3.16.jar' (inside of /usr/local/myapp/myservice-0.0.1.jar) used by process 15569. 125 | 126 | $ 127 | .Ed 128 | .Pp 129 | Sample invocation searching only /var and /usr/local/lib 130 | and skipping package and process checks: 131 | .Bd -literal -offset indent 132 | $ check-springshell -p /var -p /usr/local/lib -s packages -s processes 133 | The following archives of likely vulnerable versions were found: 134 | /var/./spring-beans-3.0.5.jar 135 | /usr/local/lib/./log4shell-vulnerable-app-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-webmvc-5.3.13.jar 136 | $ 137 | .Ed 138 | .Pp 139 | Note version comparisons are only done for packages, 140 | which is why the above output incudes files ending in 141 | a seemingly non-vulnerable version. 142 | .Pp 143 | To avoid mountpoint traversal on a Unix system where 144 | .Xr find 1 145 | requires the 146 | .Fl x 147 | flag to precede the paths: 148 | .Bd -literal -offset indent 149 | $ env CHECK_SPRINGSHELL_FIND_OPTS_PRE="-x" check-springshell 150 | No obvious indicators of vulnerability found. 151 | .Ed 152 | .Pp 153 | To only search files newer than '/tmp/foo': 154 | .Bd -literal -offset indent 155 | $ env CHECK_SPRINGSHELL_FIND_OPTS_POST="-newer /tmp/foo" check-springshell 156 | No obvious indicators of vulnerability found. 157 | .Ed 158 | .Sh EXIT STATUS 159 | .Nm 160 | will return 0 if the host was found not to be 161 | vulnerable and not in need of any update; 162 | it will return 1 if a vulnerable jar or package was 163 | detected. 164 | .Sh SEE ALSO 165 | .Xr find 1 , 166 | .Xr lsof 1 , 167 | .Xr rpm 1 , 168 | .Xr yinst 1 169 | .Sh HISTORY 170 | .Nm 171 | was originally written by 172 | .An Jan Schaumann 173 | .Aq jschauma@netmeister.org 174 | in March 2022. 175 | .Sh BUGS 176 | Please file bugs and feature requests via GitHub pull 177 | requests and issues or by emailing the author. 178 | -------------------------------------------------------------------------------- /doc/check-springshell.1.txt: -------------------------------------------------------------------------------- 1 | check-springshell(1) General Commands Manual check-springshell(1) 2 | 3 | NAME 4 | check-springshell - try to determine if a host is vulnerable to the 5 | SpringShell vulnerabilities 6 | 7 | SYNOPSIS 8 | check-springshell [-Vhv] [-j jar] [-p path] [-s skip] 9 | 10 | DESCRIPTION 11 | The check-springshell tool attempts to determine whether the host it is 12 | executed on is vulnerable to the vulnerabilities grouped together under the 13 | "SpringShell" name. 14 | 15 | This includes CVE-2022-22963, a Spring SpEL / Expression Resource Access 16 | Vulnerability, as well as CVE-2022-22965, the spring-webmvc/spring-webflux 17 | RCE termed "SpringShell". 18 | 19 | check-springshell will look for nested Java archive files and so may be 20 | somewhat intrusive to run and should be executed with care and 21 | consideration of the system's load. Please see DETAILS for more 22 | information. 23 | 24 | OPTIONS 25 | The following options are supported by check-springshell: 26 | 27 | -V Print version number and exit. 28 | 29 | -h Print a short help message and exit. 30 | 31 | -j jar Check only this archive, nothing else. Can be specified multiple 32 | times for multiple JAR (or other zip formatted archive) files. 33 | 34 | -p path Limit filesystem traversal to this directory. Can be specified 35 | multiple times. If not specified, check-springshell will default 36 | to '/'. 37 | 38 | -s skip Skip the given checks. Valid arguments are 'files', 'java', 39 | 'packages', and 'processes'. 40 | 41 | -v Be verbose. Can be specified multiple times. 42 | 43 | DETAILS 44 | CVE-2022-22963 describes a possible remote code execution (RCE) 45 | vulnerability in the popular Spring Boot framework. Simply sending a POST 46 | request with a specific payload can cause the vulnerable server to execute 47 | commands on the attacker's behalf. 48 | 49 | Likewise, CVE-2022-22965 describes another possible RCE vulnerability, 50 | termed "SpringShell", which follows a common webshell upload pattern. 51 | 52 | To determine whether a host is vulnerable, the check-springshell tool will 53 | look for classes and java archives relating to the known vulnerable 54 | versions of the Spring framework. 55 | 56 | The discovery process may include running find(1), lsof(1), or rpm(1); 57 | please use the -s flag to skip any checks that might have a negative impact 58 | on your host. 59 | 60 | The output of the command attempts to be human readable and provide 61 | sufficient information to judge whether the host requires attention. 62 | 63 | ENVIRONMENT 64 | The following environment variables influence the behavior of 65 | check-springshell: 66 | 67 | CHECK_SPRINGSHELL_FIND_OPTS_PRE 68 | Additional options to pass to find(1) prior to the path name(s). 69 | 70 | By default, check-springshell runs "find / -type f -name 71 | '*.[ejw]ar'"; the contents of this variable are placed immediately 72 | after the 'find' and before the path name(s). 73 | 74 | CHECK_SPRINGSHELL_FIND_OPTS_POST 75 | Additional options to pass to find(1) immediately after the path 76 | name(s). 77 | 78 | EXAMPLES 79 | Sample invocation on a non-vulnerable host: 80 | 81 | $ check-springshell 82 | No obvious indicators of vulnerability found. 83 | $ 84 | 85 | Sample invocation only looking at processes 86 | 87 | $ check-springshell -s files -s packages -v -v 88 | => Running all checks... 89 | ==> Skipping package check. 90 | ==> Looking for jars... 91 | ==> Skipping files check. 92 | ==> Checking all found jars... 93 | check-springshell 1.0 localhost: Possibly vulnerable jar 'BOOT-INF/lib/spring-beans-5.3.16.jar' (inside of /usr/local/myapp/myservice-0.0.1.jar) used by process 15569. 94 | 95 | $ 96 | 97 | Sample invocation searching only /var and /usr/local/lib and skipping 98 | package and process checks: 99 | 100 | $ check-springshell -p /var -p /usr/local/lib -s packages -s processes 101 | The following archives of likely vulnerable versions were found: 102 | /var/./spring-beans-3.0.5.jar 103 | /usr/local/lib/./log4shell-vulnerable-app-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-webmvc-5.3.13.jar 104 | $ 105 | 106 | Note version comparisons are only done for packages, which is why the above 107 | output incudes files ending in a seemingly non-vulnerable version. 108 | 109 | To avoid mountpoint traversal on a Unix system where find(1) requires the 110 | -x flag to precede the paths: 111 | 112 | $ env CHECK_SPRINGSHELL_FIND_OPTS_PRE="-x" check-springshell 113 | No obvious indicators of vulnerability found. 114 | 115 | To only search files newer than '/tmp/foo': 116 | 117 | $ env CHECK_SPRINGSHELL_FIND_OPTS_POST="-newer /tmp/foo" check-springshell 118 | No obvious indicators of vulnerability found. 119 | 120 | EXIT STATUS 121 | check-springshell will return 0 if the host was found not to be vulnerable 122 | and not in need of any update; it will return 1 if a vulnerable jar or 123 | package was detected. 124 | 125 | SEE ALSO 126 | find(1), lsof(1), rpm(1), yinst(1) 127 | 128 | HISTORY 129 | check-springshell was originally written by Jan Schaumann 130 | in March 2022. 131 | 132 | BUGS 133 | Please file bugs and feature requests via GitHub pull requests and issues 134 | or by emailing the author. 135 | 136 | NetBSD 8.0 March 31, 2022 NetBSD 8.0 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | check-springshell 2 | ================= 3 | 4 | This tool will try to determine if the host it is 5 | running on is likely vulnerable to 6 | [CVE-2022-22963](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-22963), 7 | a [SpEL / Spring Expression Resource Access 8 | Vulnerability](https://tanzu.vmware.com/security/cve-2022-22963), 9 | as well as 10 | [CVE-2022-22965](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-22965), 11 | the so-called 12 | "[SpringShell](https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement)" 13 | RCE vulnerability. 14 | 15 | This works very similar to the 16 | [check-log4](https://github.com/yahoo/check-log4j) 17 | tool, whereby it traverses the filesystem looking for 18 | Java archives, cracks those open, and then looks for 19 | known vulnerable jars or classes. 20 | 21 | Please see the [manual 22 | page](./doc/check-springshell.1.txt) for full 23 | details. 24 | 25 | Installation 26 | ============ 27 | 28 | To install the command and manual page somewhere 29 | convenient, run `make install`; the Makefile defaults 30 | to '/usr/local' but you can change the PREFIX: 31 | 32 | ``` 33 | $ make PREFIX=~ install 34 | ``` 35 | 36 | Documentation 37 | ============= 38 | 39 | ``` 40 | NAME 41 | check-springshell - try to determine if a host is vulnerable to the 42 | SpringShell vulnerabilities 43 | 44 | SYNOPSIS 45 | check-springshell [-Vhv] [-j jar] [-p path] [-s skip] 46 | 47 | DESCRIPTION 48 | The check-springshell tool attempts to determine whether the host it is 49 | executed on is vulnerable to the vulnerabilities grouped together under the 50 | "SpringShell" name. 51 | 52 | This includes CVE-2022-22963, a Spring SpEL / Expression Resource Access 53 | Vulnerability, as well as CVE-2022-22965, the spring-webmvc/spring-webflux 54 | RCE termed "SpringShell". 55 | 56 | check-springshell will look for nested Java archive files and so may be 57 | somewhat intrusive to run and should be executed with care and 58 | consideration of the system's load. Please see DETAILS for more 59 | information. 60 | 61 | OPTIONS 62 | The following options are supported by check-springshell: 63 | 64 | -V Print version number and exit. 65 | 66 | -h Print a short help message and exit. 67 | 68 | -j jar Check only this archive, nothing else. Can be specified multiple 69 | times for multiple JAR (or other zip formatted archive) files. 70 | 71 | -p path Limit filesystem traversal to this directory. Can be specified 72 | multiple times. If not specified, check-springshell will default 73 | to '/'. 74 | 75 | -s skip Skip the given checks. Valid arguments are 'files', 'java', 76 | 'packages', and 'processes'. 77 | 78 | -v Be verbose. Can be specified multiple times. 79 | 80 | DETAILS 81 | CVE-2022-22963 describes a possible remote code execution (RCE) 82 | vulnerability in the popular Spring Boot framework. Simply sending a POST 83 | request with a specific payload can cause the vulnerable server to execute 84 | commands on the attacker's behalf. 85 | 86 | Likewise, CVE-2022-22965 describes another possible RCE vulnerability, 87 | termed "SpringShell", which follows a common webshell upload pattern. 88 | 89 | To determine whether a host is vulnerable, the check-springshell tool will 90 | look for classes and java archives relating to the known vulnerable 91 | versions of the Spring framework. 92 | 93 | The discovery process may include running find(1), lsof(1), or rpm(1); 94 | please use the -s flag to skip any checks that might have a negative impact 95 | on your host. 96 | 97 | The output of the command attempts to be human readable and provide 98 | sufficient information to judge whether the host requires attention. 99 | 100 | ENVIRONMENT 101 | The following environment variables influence the behavior of 102 | check-springshell: 103 | 104 | CHECK_SPRINGSHELL_FIND_OPTS_PRE 105 | Additional options to pass to find(1) prior to the path name(s). 106 | 107 | By default, check-springshell runs "find / -type f -name 108 | '*.[ejw]ar'"; the contents of this variable are placed immediately 109 | after the 'find' and before the path name(s). 110 | 111 | CHECK_SPRINGSHELL_FIND_OPTS_POST 112 | Additional options to pass to find(1) immediately after the path 113 | name(s). 114 | 115 | EXAMPLES 116 | Sample invocation on a non-vulnerable host: 117 | 118 | $ check-springshell 119 | No obvious indicators of vulnerability found. 120 | $ 121 | 122 | Sample invocation only looking at processes 123 | 124 | $ check-springshell -s files -s packages -v -v 125 | => Running all checks... 126 | ==> Skipping package check. 127 | ==> Looking for jars... 128 | ==> Skipping files check. 129 | ==> Checking all found jars... 130 | check-springshell 1.0 localhost: Possibly vulnerable jar 'BOOT-INF/lib/spring-beans-5.3.16.jar' (inside of /usr/local/myapp/myservice-0.0.1.jar) used by process 15569. 131 | 132 | $ 133 | 134 | Sample invocation searching only /var and /usr/local/lib and skipping 135 | package and process checks: 136 | 137 | $ check-springshell -p /var -p /usr/local/lib -s packages -s processes 138 | The following archives of likely vulnerable versions were found: 139 | /var/./spring-beans-3.0.5.jar 140 | /usr/local/lib/./log4shell-vulnerable-app-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-webmvc-5.3.13.jar 141 | $ 142 | 143 | Note version comparisons are only done for packages, which is why the above 144 | output incudes files ending in a seemingly non-vulnerable version. 145 | 146 | To avoid mountpoint traversal on a Unix system where find(1) requires the 147 | -x flag to precede the paths: 148 | 149 | $ env CHECK_SPRINGSHELL_FIND_OPTS_PRE="-x" check-springshell 150 | No obvious indicators of vulnerability found. 151 | 152 | To only search files newer than '/tmp/foo': 153 | 154 | $ env CHECK_SPRINGSHELL_FIND_OPTS_POST="-newer /tmp/foo" check-springshell 155 | No obvious indicators of vulnerability found. 156 | 157 | EXIT STATUS 158 | check-springshell will return 0 if the host was found not to be vulnerable 159 | and not in need of any update; it will return 1 if a vulnerable jar or 160 | package was detected. 161 | 162 | SEE ALSO 163 | find(1), lsof(1), rpm(1), yinst(1) 164 | 165 | HISTORY 166 | check-springshell was originally written by Jan Schaumann 167 | in March 2022. 168 | 169 | BUGS 170 | Please file bugs and feature requests via GitHub pull requests and issues 171 | or by emailing the author. 172 | ``` 173 | -------------------------------------------------------------------------------- /src/check-springshell.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # Originally written by Jan Schaumann 4 | # in March 2022. 5 | # 6 | # This script attempts to determine whether the host 7 | # it runs on is likely to be vulnerable to Spring 8 | # Framework CVEs CVE-2022-22963 and CVE-2022-22965 9 | # (aka "SpringShell"). 10 | # 11 | # Copyright 2022 Yahoo Inc. 12 | # 13 | # Licensed under the Apache License, Version 2.0 (the 14 | # "License"); you may not use this file except in 15 | # compliance with the License. You may obtain a copy of 16 | # the License at 17 | # 18 | # http://www.apache.org/licenses/LICENSE-2.0 19 | # 20 | # Unless required by applicable law or agreed to in 21 | # writing, software distributed under the License is 22 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 23 | # CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing 25 | # permissions and limitations under the License. 26 | 27 | 28 | set -eu 29 | IFS="$(printf '\n\t')" 30 | 31 | umask 077 32 | 33 | ### 34 | ### Globals 35 | ### 36 | 37 | PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/home/y/bin:/home/y/sbin 38 | 39 | # Per 40 | # https://tanzu.vmware.com/security/cve-2022-22963, we 41 | # are looking for Cloud Function 42 | # spring-cloud-function-* versions >= 3.1.6 or >= 3.2.2 43 | SCF_MAJOR_WANTED="3" 44 | SCF_MINOR_WANTED="1" 45 | SCF_TINY_WANTED="7" 46 | 47 | SCF_MAJOR_ALT_WANTED="3" 48 | SCF_MINOR_ALT_WANTED="2" 49 | SCF_TINY_ALT_WANTED="2" 50 | 51 | SCF_CVE="CVE-2022-22963" 52 | SCF_NAME="spring-cloud-function" 53 | SCF_SUSPECT_JARS="" 54 | SCF_SUSPECT_PACKAGES="" 55 | 56 | # Per 57 | # https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement, 58 | # https://tanzu.vmware.com/security/cve-2022-22965 59 | # we are looking for Spring Framework webmvc or 60 | # webflux >= 5.3.18 or 5.2.20. 61 | WEB_MAJOR_WANTED="5" 62 | WEB_MINOR_WANTED="3" 63 | WEB_TINY_WANTED="18" 64 | 65 | WEB_MAJOR_ALT_WANTED="5" 66 | WEB_MINOR_ALT_WANTED="2" 67 | WEB_TINY_ALT_WANTED="20" 68 | 69 | WEB_CVE="CVE-2022-22965" 70 | WEB_SUSPECT_JARS="" 71 | WEB_SUSPECT_PACKAGES="" 72 | 73 | PACKAGE_NAMES="${SCF_NAME} spring-webmvc spring-webflux" 74 | 75 | FATAL_CLASSES="CachedIntrospectionResults.class RoutingFunction.class EnableWebFlux.class EnableWebMvc.class" 76 | 77 | _TMPDIR="" 78 | CHECK_JARS="" 79 | ENV_VAR_SET="no" 80 | FOUND_JARS="" 81 | JAVA="$(command -v java 2>/dev/null || true)" 82 | JAVA_WANTED="9" 83 | PROGNAME="${0##*/}" 84 | RETVAL=1 85 | SEARCH_PATHS="" 86 | SKIP="" 87 | SEEN_JARS="" 88 | SUSPECT_CLASSES="" 89 | UNZIP="$(command -v unzip 2>/dev/null || true)" 90 | VERBOSITY=0 91 | VERSION="0.4" 92 | 93 | LOGPREFIX="${PROGNAME} ${VERSION} ${HOSTNAME:-"localhost"}" 94 | 95 | ### 96 | ### Functions 97 | ### 98 | 99 | 100 | cdtmp() { 101 | if [ -z "${_TMPDIR}" ]; then 102 | _TMPDIR=$(mktemp -d ${TMPDIR:-/tmp}/${PROGNAME}.XXXX) 103 | fi 104 | cd "${_TMPDIR}" 105 | } 106 | 107 | checkFilesystem() { 108 | local class="" 109 | local classes="" 110 | local okVersion="" 111 | local newjars="" 112 | local findCmd="" 113 | 114 | if expr "${SKIP}" : ".*files" >/dev/null; then 115 | verbose "Skipping files check." 2 116 | return 117 | fi 118 | 119 | verbose "Searching for java archives on the filesystem..." 3 120 | findCmd=$(echo find ${CHECK_SPRINGSHELL_FIND_OPTS_PRE:-""} "${SEARCH_PATHS:-/}" ${CHECK_SPRINGSHELL_FIND_OPTS_POST:-""}) 121 | 122 | verbose "Running '${findCmd}'..." 4 123 | 124 | newjars=$(eval ${findCmd} -type f -iname \'*.[ejw]ar\' 2>/dev/null || true) 125 | FOUND_JARS="$(printf "${FOUND_JARS:+${FOUND_JARS}\n}${newjars}")" 126 | 127 | verbose "Searching for ${FATAL_CLASSES} on the filesystem..." 3 128 | for class in ${FATAL_CLASSES}; do 129 | classes=$(eval ${findCmd} -type f -iname "${class}" 2>/dev/null || true) 130 | 131 | for class in ${classes}; do 132 | log "Possibly vulnerable class ${class}." 133 | SUSPECT_CLASSES="$(printf "${SUSPECT_CLASSES:+${SUSPECT_CLASSES}\n}${class}")" 134 | done 135 | done 136 | } 137 | 138 | checkInJar() { 139 | local jar="${1}" 140 | local cve="${2}" 141 | local needle="${3}" 142 | local pid="${4}" 143 | local parent="${5:-""}" 144 | local msg="" 145 | local match="" 146 | local okVersion="" 147 | local rval=0 148 | 149 | local thisJar="${parent:+${parent}:}${jar}" 150 | for j in $(echo "${SEEN_JARS}" | tr ' ' '\n'); do 151 | if [ x"${j}" = x"${thisJar}" ]; then 152 | verbose "Skipping already seen archive '${thisJar}'..." 6 153 | return 154 | fi 155 | done 156 | SEEN_JARS="${SEEN_JARS:+${SEEN_JARS} }${thisJar}" 157 | 158 | if [ -z "${parent}" ]; then 159 | if zeroSize "${thisJar}"; then 160 | verbose "Skipping zero-size file '${thisJar}'..." 6 161 | return 162 | fi 163 | fi 164 | 165 | verbose "Checking for '${needle}' inside of ${jar}..." 5 166 | 167 | set +e 168 | if [ -n "${UNZIP}" ]; then 169 | ${UNZIP} -q -l "${jar}" 2>/dev/null | egrep -q "${needle}" 170 | else 171 | warn "unzip(1) not found, trying to grep..." 172 | egrep -q "${needle}" "${jar}" 173 | fi 174 | rval=$? 175 | set -e 176 | 177 | if [ ${rval} -eq 0 ]; then 178 | if checkManifest "${jar}"; then 179 | return 180 | fi 181 | 182 | if [ -n "${parent}" ]; then 183 | msg=" (inside of ${parent})" 184 | fi 185 | if [ x"${jar}" != x"${pid}" ] && expr "${pid}" : "[0-9]*$" >/dev/null; then 186 | msg="${msg} used by process ${pid}" 187 | fi 188 | 189 | if [ x"${cve}" = x"${SCF_CVE}" ]; then 190 | SCF_SUSPECT_JARS="${SCF_SUSPECT_JARS} ${thisJar}" 191 | else 192 | WEB_SUSPECT_JARS="${WEB_SUSPECT_JARS} ${thisJar}" 193 | fi 194 | fi 195 | } 196 | 197 | checkJars() { 198 | local classnames cve found jar jarname jarjar msg names pid 199 | 200 | if [ -z "${CHECK_JARS}" ]; then 201 | findJars 202 | fi 203 | 204 | if [ -z "${FOUND_JARS}" ]; then 205 | return 206 | fi 207 | 208 | verbose "Checking all found jars and wars..." 2 209 | 210 | if [ -z "${UNZIP}" ]; then 211 | warn "unzip(1) not found, unable to peek into jars inside of jar!" 212 | fi 213 | 214 | names="($(echo "${PACKAGE_NAMES}" | sed -e 's/ /|/g'))" 215 | classnames="$(echo ${FATAL_CLASSES} | sed -e 's/ /|/g')" 216 | 217 | for found in ${FOUND_JARS}; do 218 | pid="${found%%--*}" 219 | jar="${found#*--}" 220 | jarname="${jar##*/}" 221 | 222 | cve="${SCF_CVE}" 223 | if ! expr "${jarname}" : "${SCF_NAME}" >/dev/null ; then 224 | cve="${WEB_CVE}" 225 | fi 226 | 227 | if [ -n "${UNZIP}" ]; then 228 | if zeroSize "${jar}"; then 229 | verbose "Skipping zero-size file '${jar}'..." 3 230 | continue 231 | fi 232 | jarjar="$(${UNZIP} -q -l "${jar}" 2>/dev/null | egrep -i "^ .*${names}-.*.[ejw]ar$" | awk '{ print $NF; }')" 233 | for j in ${jarjar}; do 234 | extractAndInspect "${jar}" "${cve}" "${j}" ${pid} 235 | done 236 | fi 237 | 238 | checkInJar "${jar}" "${cve}" "(${classnames})" "${pid}" 239 | done 240 | } 241 | 242 | checkJava() { 243 | if expr "${SKIP}" : ".*java" >/dev/null; then 244 | verbose "Skipping java check." 2 245 | return 246 | fi 247 | 248 | local major version 249 | 250 | verbose "Checking for vulnerable java version..." 2 251 | 252 | if [ -z "${JAVA}" ]; then 253 | warn "java(1) not found, unable to check for vulnerable version!" 254 | return 255 | fi 256 | 257 | version="$(${JAVA} -version 2>&1 | sed -n -e 's/.*version "\(.*\)".*/\1/p')" 258 | 259 | # We only care about the major version: 260 | major="${version%%.*}" 261 | 262 | if ! expr "${major}" : "[0-9]*$" >/dev/null; then 263 | warn "Non-numeric Java version number '${version}'?" 264 | return 265 | fi 266 | 267 | if [ ${major} -lt ${JAVA_WANTED} ]; then 268 | echo "Java version ${version} detected and believed to be non-vulnerable." 269 | echo "Skipping all other checks. Specify '-s java' to run other checks anyway." 270 | exit 0 271 | # NOTREACHED 272 | fi 273 | } 274 | 275 | checkManifest() { 276 | local jar="${1}" 277 | local manifest name version 278 | 279 | manifest="META-INF/MANIFEST.MF" 280 | 281 | verbose "Extracting ${jar} to check ${manifest}..." 5 282 | 283 | cdtmp 284 | if ${UNZIP} -o -q "${jar}" ${manifest} 2>/dev/null; then 285 | name="$(awk '/^Implementation-Title:/ { print $NF }' ${manifest} | tr -d [:space:])" 286 | version="$(awk '/^Implementation-Version:/ { print $NF }' ${manifest} | tr -d [:space:])" 287 | if echo "${PACKAGE_NAMES}" | grep -w -q "${name}" ; then 288 | if isFixedVersion "${name}" "${version}"; then 289 | return 0 290 | fi 291 | fi 292 | fi 293 | return 1 294 | } 295 | 296 | checkOnlyGivenJars() { 297 | verbose "Checking only given jars..." 1 298 | FOUND_JARS="${CHECK_JARS}" 299 | checkJars 300 | } 301 | 302 | checkRpms() { 303 | verbose "Checking rpms..." 4 304 | 305 | local name names pkg version 306 | 307 | names="($(echo "${PACKAGE_NAMES}" | sed -e 's/ /|/g'))" 308 | 309 | for pkg in $(rpm -qa --queryformat '%{NAME}--%{VERSION}\n' | egrep "${names}"); do 310 | name="${pkg%%--*}" 311 | version="${pkg##*--}" 312 | if ! isFixedVersion "${name}" "${version}"; then 313 | # Squeeze '--' so users don't get confused. 314 | pkg="$(echo "${pkg}" | tr -s -)" 315 | if expr "${name}" : "${SCF_NAME}" >/dev/null ; then 316 | SCF_SUSPECT_PACKAGES="${SCF_SUSPECT_PACKAGES} ${pkg}" 317 | else 318 | WEB_SUSPECT_PACKAGES="${WEB_SUSPECT_PACKAGES} ${pkg}" 319 | fi 320 | fi 321 | done 322 | } 323 | 324 | checkPackages() { 325 | if expr "${SKIP}" : ".*packages" >/dev/null; then 326 | verbose "Skipping package check." 2 327 | return 328 | fi 329 | 330 | verbose "Checking for vulnerable packages..." 2 331 | 332 | if [ x"$(command -v rpm 2>/dev/null)" != x"" ]; then 333 | checkRpms 334 | fi 335 | } 336 | 337 | checkProcesses() { 338 | local jars 339 | if expr "${SKIP}" : ".*processes" >/dev/null; then 340 | verbose "Skipping process check." 2 341 | return 342 | fi 343 | 344 | verbose "Checking running processes..." 3 345 | local lsof="$(command -v lsof 2>/dev/null || true)" 346 | if [ -z "${lsof}" ]; then 347 | jars="$(ps -o pid,command= -wwwax | awk 'tolower($0) ~ /[ejw]ar$/ { print $1 "--" $NF; }' | uniq)" 348 | else 349 | jars="$(${lsof} -c java 2>/dev/null | awk 'tolower($0) ~ /reg.*[ejw]ar$/ { print $2 "--" $NF; }' | uniq)" 350 | fi 351 | FOUND_JARS="${FOUND_JARS:+${FOUND_JARS} }${jars}" 352 | } 353 | 354 | cleanup() { 355 | if [ -n "${_TMPDIR}" ]; then 356 | rm -fr "${_TMPDIR}" 357 | fi 358 | } 359 | 360 | extractAndInspect() { 361 | local jar="${1}" 362 | local cve="${2}" 363 | local jarjar="${3}" 364 | local pid="${4}" 365 | local classnames f 366 | 367 | verbose "Extracting ${jar} to look inside jars inside of jars..." 5 368 | classnames="$(echo ${FATAL_CLASSES} | sed -e 's/ /|/g')" 369 | 370 | cdtmp 371 | if ${UNZIP} -o -q "${jar}" ${jarjar} 2>/dev/null; then 372 | for f in ${jarjar}; do 373 | checkInJar "${f}" "${cve}" "(${classnames})" ${pid} "${jar}" 374 | done 375 | fi 376 | } 377 | 378 | findJars() { 379 | verbose "Looking for jars..." 2 380 | checkProcesses 381 | checkFilesystem 382 | } 383 | 384 | isFixedVersion() { 385 | local pkg="${1}" 386 | local version="${2}" 387 | local major minor tiny 388 | 389 | local wanted_major wanted_minor wanted_tiny 390 | local alt_wanted_major alt_wanted_minor alt_wanted_tiny 391 | 392 | major="${version%%.*}" # 2.15.0 => 2 393 | minor="${version#*.}" # 2.15.0 => 15.0 394 | tiny="${minor#*.}" # 15.0 => 0 395 | 396 | # strip off any possible other sub-versions 397 | # e.g., 2.15.0.12345 398 | tiny="${tiny%%.*}" # 0.12345 => 0 399 | minor="${minor%%.*}" # 15.0 => 15 400 | 401 | # NaN => unknown 402 | if ! expr "${major}" : "[0-9]*$" >/dev/null; then 403 | return 1 404 | fi 405 | if ! expr "${minor}" : "[0-9]*$" >/dev/null; then 406 | return 1 407 | fi 408 | 409 | wanted_major="${SCF_MAJOR_WANTED}" 410 | wanted_minor="${SCF_MINOR_WANTED}" 411 | wanted_tiny="${SCF_TINY_WANTED}" 412 | alt_wanted_major="${SCF_MAJOR_ALT_WANTED}" 413 | alt_wanted_minor="${SCF_MINOR_ALT_WANTED}" 414 | alt_wanted_tiny="${SCF_TINY_ALT_WANTED}" 415 | if ! expr "${pkg}" : "${SCF_NAME}" >/dev/null ; then 416 | wanted_major="${WEB_MAJOR_WANTED}" 417 | wanted_minor="${WEB_MINOR_WANTED}" 418 | wanted_tiny="${WEB_TINY_WANTED}" 419 | alt_wanted_major="${WEB_MAJOR_ALT_WANTED}" 420 | alt_wanted_minor="${WEB_MINOR_ALT_WANTED}" 421 | alt_wanted_tiny="${WEB_TINY_ALT_WANTED}" 422 | fi 423 | 424 | if [ ${major} -lt ${wanted_major} ] || 425 | [ ${major} -eq ${wanted_major} -a ${minor} -lt ${wanted_minor} ] || 426 | [ ${major} -eq ${wanted_major} -a ${minor} -eq ${wanted_minor} -a ${tiny} -lt ${wanted_tiny} ]; then 427 | 428 | if [ ${major} -lt ${alt_wanted_major} ] || 429 | [ ${major} -eq ${alt_wanted_major} -a ${minor} -lt ${alt_wanted_minor} ] || 430 | [ ${major} -eq ${alt_wanted_major} -a ${minor} -eq ${alt_wanted_minor} -a ${tiny} -lt ${alt_wanted_tiny} ]; then 431 | return 0 432 | fi 433 | return 1 434 | fi 435 | 436 | return 0 437 | } 438 | 439 | log() { 440 | msg="${1}" 441 | echo "${LOGPREFIX}: ${msg}" 442 | } 443 | 444 | springshellCheck() { 445 | verbose "Running all checks..." 1 446 | 447 | checkJava 448 | checkPackages 449 | checkJars 450 | } 451 | 452 | usage() { 453 | cat < ${msg}" >&2 474 | fi 475 | } 476 | 477 | verdict() { 478 | if [ -z "${SCF_SUSPECT_JARS}" -a -z "${WEB_SUSPECT_JARS}" -a \ 479 | -z "${SCF_SUSPECT_PACKAGES}" -a -z "${WEB_SUSPECT_PACKAGES}" -a \ 480 | -z "${SUSPECT_CLASSES}" ]; then 481 | log "No obvious indicators of vulnerability to ${SCF_CVE} or ${WEB_CVE} found." 482 | RETVAL=0 483 | fi 484 | 485 | if [ -n "${SCF_SUSPECT_JARS}" ]; then 486 | echo 487 | echo "The following archives appear to be vulnerable to ${SCF_CVE}:" 488 | echo "${SCF_SUSPECT_JARS# *}" | tr ' ' '\n' 489 | RETVAL=1 490 | fi 491 | if [ -n "${WEB_SUSPECT_JARS}" ]; then 492 | echo 493 | echo "The following archives appear to be vulnerable to ${WEB_CVE}:" 494 | echo "${WEB_SUSPECT_JARS# *}" | tr ' ' '\n' 495 | RETVAL=1 496 | fi 497 | 498 | if [ -n "${SCF_SUSPECT_PACKAGES}" ]; then 499 | echo 500 | echo "The following packages appear to be vulnerable to ${SCF_CVE}:" 501 | echo "${SCF_SUSPECT_PACKAGES}" 502 | RETVAL=1 503 | fi 504 | if [ -n "${WEB_SUSPECT_PACKAGES}" ]; then 505 | echo 506 | echo "The following packages appear to be vulnerable to ${WEB_CVE}:" 507 | echo "${WEB_SUSPECT_PACKAGES}" 508 | RETVAL=1 509 | fi 510 | } 511 | 512 | warn() { 513 | msg="${1}" 514 | echo "${LOGPREFIX}: ${msg}" >&2 515 | } 516 | 517 | zeroSize() { 518 | local file="${1}" 519 | local size 520 | 521 | # stat(1) is not portable :-/ 522 | size="$(ls -l "${file}" | awk '{print $5}')" 523 | if [ x"${size}" = x"0" ]; then 524 | return 0 525 | fi 526 | 527 | return 1 528 | } 529 | 530 | ### 531 | ### Main 532 | ### 533 | 534 | trap 'cleanup' 0 535 | 536 | while getopts 'Vhj:s:p:v' opt; do 537 | case "${opt}" in 538 | V) 539 | echo "${PROGNAME} ${VERSION}" 540 | exit 0 541 | # NOTREACHED 542 | ;; 543 | h\?) 544 | usage 545 | exit 0 546 | # NOTREACHED 547 | ;; 548 | j) 549 | d="${OPTARG%/*}" 550 | if [ x"${d}" = x"${OPTARG}" ]; then 551 | d="." 552 | fi 553 | f="$(cd "${d}" && pwd)/${OPTARG##*/}" 554 | CHECK_JARS="${CHECK_JARS:+${CHECK_JARS} }${f}" 555 | ;; 556 | p) 557 | SEARCH_PATHS="${SEARCH_PATHS:+${SEARCH_PATHS} }$(cd "${OPTARG}" && pwd)/." 558 | ;; 559 | s) 560 | SKIP="${SKIP} ${OPTARG}" 561 | ;; 562 | v) 563 | VERBOSITY=$(( ${VERBOSITY} + 1 )) 564 | ;; 565 | *) 566 | usage 567 | exit 1 568 | # NOTREACHED 569 | ;; 570 | esac 571 | done 572 | shift $(($OPTIND - 1)) 573 | 574 | if [ $# -gt 0 ]; then 575 | usage 576 | exit 1 577 | # NOTREACHED 578 | fi 579 | 580 | if [ -z "${CHECK_JARS}" ]; then 581 | springshellCheck 582 | else 583 | checkOnlyGivenJars 584 | fi 585 | verdict 586 | 587 | exit ${RETVAL} 588 | --------------------------------------------------------------------------------