EDUCAÇÃO E TECNOLOGIA

SAP on IBM i: System scan for Log4j vulnerability (CVE-2021-44228)

For sure you have heard about the Apache Log4j CVE-2021-44228 vulnerability by now. Apache Log4j is a Java-based logging utility that is available under a free permissive license and is being used by many software products, including those shipped by SAP and IBM. A description of the problem from the Apache Software Foundation can be found here: Log4j – Apache Log4j Security Vulnerabilities. Since the logging utility is embedded in software products, the solution to the problem cannot be given by the Apache Software Foundation alone, but it also requires action from the software vendors that have embedded the utility. In the meantime, you will find plenty of articles and documents, and it is very difficult to keep track of everything that is available. Note that the JDBC drivers for IBM i (both the Native driver and the Toolbox/JTOpen driver) are not affected by this vulnerability.

These are some important links for customers running SAP on IBM i:

SAP:
https://support.sap.com/content/dam/support/en_us/library/ssp/my-support/trust-center/sap-tc-01-5025.pdf
SAP Note 3130882 (SAP Knowledgebase document KB0343390) – IBM Db2 log4j vulnerability

IBM:
IBM Product Security Incident Response Blog at https://www.ibm.com/blogs/psirt/ (https://www.ibm.com/blogs/psirt/an-update-on-the-apache-log4j-cve-2021-44228-vulnerability/)
Security Bulletin: Vulnerability in Apache Log4j (CVE-2021-44228) affects Power HMC: https://www.ibm.com/support/pages/node/6526172

With this article, we will provide a shell script that you can use to scan your file system for not only file names but also Java archives (.jar files) containing the strings “log4j” or “jndilookup”. This may help to identify application areas that need special attention. Please note that this information is not guaranteed to identify all places that may expose the vulnerability and that you will have to contact the provider of the identified software for suggestions how to resolve the issue. The error only affects certain releases of the utility, so even when the strings were found, your system may be safe.

When copying the source to your IBM i server, make sure the file is tagged with an ASCII/UTF-8 CCSID like 819, 850 or 1208 and that the text is stored with line ending option *LF. You can control that in the EDTF Options screen, which can be reached from EDTF through function key F15. If other values than mentioned before are shown after pressing F15, you can change them by typing the related option (3 for CCSID, 5 for EOL option) in the Selection field, then type the requested option and press Enter.

To execute the script, enter the PASE environment through the IBM i command CALL PGM(QP2TERM). You can execute the script by providing the top-level path name to scan. There are several options to control the amount of output that are documented in the comments at the beginning of the script. If the script is named find_log4j_occurrences.sh and located in your current directory, the recommended command for scanning the directories that most likely contain Java code is:

./find_log4j_occurrences.sh -l -v /usr /QOpenSys /QIBM

The script will run for quite a while (up to several hours), depending on the number of objects you have underneath the specified path. During this time, it will add some single-threaded CPU workload, page faults and disk I/O to the system, so it is not advised to run it during peak workload times. The output could look like this:

2021-12-20 14:55:30 INFO: Scanning jar files started at 2021-12-20 14:55:30 on myserver within dir(s): /usr /QOpenSys /QIBM
2021-12-20 15:15:56 NOTICE: Found 'jndilookup' (but not 'log4j') in the following file: /usr/sap/V75/J03/j2ee/cluster/bin/ext/com.sap.xi.rwb.api/lib/com.sap.xi.rwb.api_api.jar 2021-12-20 15:15:56 NOTICE: Details: ===== BEGIN (first 10 occurrences at most) ===== 2021-12-20 15:15:56 NOTICE: Details: com/sap/aii/rwb/exceptions/JndiLookupException.class 2021-12-20 15:15:56 NOTICE: Details: com/sap/aii/rwb/exceptions/JndiLookupException.classPK 2021-12-20 15:15:56 NOTICE: Details: ===== END ===== 2021-12-20 15:15:56 NOTICE: 2021-12-20 15:16:57 NOTICE: Found 'log4j' (but not 'jndilookup') in the following file: /usr/sap/V75/J03/j2ee/cluster/bin/ext/tc~jax-rs~primary/cxf-api-2.6.16.jar 2021-12-20 15:16:57 NOTICE: Details: ===== BEGIN (first 10 occurrences at most) ===== 2021-12-20 15:16:57 NOTICE: Details: org/apache/cxf/common/logging/Log4jLogger.class 2021-12-20 15:16:57 NOTICE: Details: org/apache/cxf/common/logging/Log4jLogger$HandlerWrapper.class 2021-12-20 15:16:57 NOTICE: Details: org/apache/cxf/common/logging/Log4jLogger.classPK 2021-12-20 15:16:57 NOTICE: Details: org/apache/cxf/common/logging/Log4jLogger$HandlerWrapper.classPK 2021-12-20 15:16:57 NOTICE: Details: ===== END ===== 2021-12-20 15:16:57 NOTICE: 2021-12-20 15:17:59 NOTICE: Found 'jndilookup' (but not 'log4j') in the following file: /usr/sap/V75/J03/j2ee/cluster/bin/services/naming/lib/private/sap.com~tc~je~naming~impl.jar 2021-12-20 15:17:59 NOTICE: Details: ===== BEGIN (first 10 occurrences at most) =====
2021-12-20 15:17:59 NOTICE: Details: com/sap/engine/services/jndi/shellcmd/JNDILookupServ.class
2021-12-20 15:17:59 NOTICE: Details: com/sap/engine/services/jndi/shellcmd/JNDILookupServ.classPK
2021-12-20 15:17:59 NOTICE: Details: ===== END =====
2021-12-20 15:17:59 NOTICE:
...
2021-12-20 17:24:01 INFO: Scan finished at 2021-12-20 17:24:01 (elapsed: 8910 seconds)

In the above example, you should check the SAP documentation, if your Java applications are affected. SAP Knowledge Base Article 3129883 – CVE-2021-44228 – AS Java Core Components’ impact for Log4j vulnerability may be a starting point for further analysis.

Example script find_log4j_occurrences.sh:

#!/bin/sh
###
### NAME
### find_log4j_occurrences.sh - find files with 'log4j' and/or 'jndilookup' in their names or content
### ### SYNOPSIS
### find_log4j_occurrences.sh [ -a ] [ -l ] [ -p ] [ -v ] [ -x ] path ...
### ### DESCRIPTION
### This script scans the given path(s) for
### ### 1) jar files (or all files, if option '-a' is given), for
### which 'log4j' together with 'jndilookup' are found in
### their data (in either case, lower or upper case).
### Note: When specifying option '-l' (lazy), the script also
### reports files for which just one of both search
### strings is found (either 'log4j' or 'jndilookup').
### ### 2) files that contain 'log4j' and/or 'jndilookup' in
### their names (in either case, lower or upper case) - only
### if option '-a' is given.
### ### This can be used to identify possibly dangerous files and applications,
### that may use the vulnerable 'log4j' logging class.
### ### See: CVE-2021-44228
### (e.g.: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44228)
### ### OPTIONS
### -a Scan all files in the given path(s), not just jar files (or
### files with 'log4j' or 'jndilookup' in their names)
### ### -l Be a little bit lazy.
### With this option, the script also reports files for which
### only one of the two seach strings ('log4j' or 'jndilookup')
### were found.
### Without this option (=default), only files will be reported
### that contain both strings, 'log4j' and 'jndilookup'.
### ### -p Print a processing animation.
### This will print a little processing animation to show
### the script still is working.
### ATTENTION: This will slow down the processing!
### The execution time approximately will
### be doubled!
### ### -v Be verbose.
### By specifying this option, the script also prints the
### findinges within a file, not just the filename itself.
### ### EXAMPLES
### To scan the directories '/usr' and '/QOpenSys' (just
### concentrating on jar files), use:
### ### find_log4j_occurrences.sh /usr /QOpenSys
### ### To scan the directories '/usr' and '/QOpenSys' (by
### insepcting _all_ files), use:
### ### find_log4j_occurrences.sh -a /usr /QOpenSys
### ### VERSION
### 20. Dec 2021
### ### Treat unset variables as error
set -u ### Set some information about this invocation
MY_PID=$$
MY_NAME=`basename "${0}"`
MY_HOST=`uname -n` ### Set PATH to the absolute required minimum
PATH="/QOpenSys/usr/bin"
export PATH ### Define some commands that are used frequently in this script with
### their absolute path to speed things up.
CMD_STRINGS='/QOpenSys/usr/bin/strings'
CMD_EGREP='/QOpenSys/usr/bin/egrep'
CMD_DATE='/QOpenSys/usr/bin/date'
CMD_AWK='/QOpenSys/usr/bin/awk'
CMD_CUT='/QOpenSys/usr/bin/cut' ### If this variable is set to 'TRUE' (e.g. by option '-a'), all files
### are scanned, not just jar files.
GLOB_BOOL_CLI_OPTION_SCAN_ALL_FILES='FALSE' GLOB_BOOL_CLI_OPTION_LAZY='FALSE' GLOB_BOOL_CLI_OPTION_PROCESSING_ANIMATION='FALSE' GLOB_BOOL_CLI_OPTION_VERBOSE='FALSE' #cd / ###
### Notes about some commands and statements within this script:
### ### 1) When just looking for at least one occurence of a pattern within
### a file, the option '-l' (list file(s)) to command 'grep' is used.
### This means, grep ends the scanning of a file as soon as it has
### found the search pattern.
### Why option '-l' and not '-q' (be quiet) or '-s' (silent)?
### Because some version of grep just support '-q', some just
### support '-s', and some even do not support any of both!
### ### 2) Why using 'strings -a' instead of 'jar -tvf' to look into (jar-)files?
### Because it is
### 1. much faster!
### 2. also supports scanning other files, not just jar files.
### ### 3) A statement like
### ### grep -i -l "log4j|jndilookup" "${file}" >/dev/null 2>&1
### ### directly would be even faster, but somehow does not work!
### Option '-i' does not work here - don't no why!?
### ###
### Print out the text along with the current date and time
###
out()
{ local TEXT="$@" local DATE=`date '+%Y-%m-%d %H:%M:%S'` echo "${DATE} ${TEXT}"
} cat_out()
{ local PREFIX="$1" local LINE='' while read LINE do out "${PREFIX}${LINE}" done
} ############
### Main ###
############ ### Check CLI options
keep_looping='TRUE'
while test \( $# -ge 1 \) -a \( ${keep_looping} = 'TRUE' \)
do case "$1" in '-a') GLOB_BOOL_CLI_OPTION_SCAN_ALL_FILES='TRUE' out "INFO: Scanning all files (because option '-a' was given)" shift ;; '-l') GLOB_BOOL_CLI_OPTION_LAZY='TRUE' shift ;; '-p') GLOB_BOOL_CLI_OPTION_PROCESSING_ANIMATION='TRUE' shift ;; '-v') GLOB_BOOL_CLI_OPTION_VERBOSE='TRUE' shift ;; *) keep_looping='FALSE' ;; esac
done ### Check that at least one argument is given
if test $# -lt 1
then echo "ERROR: Usage: ${MY_NAME} [ -a ] [ -l ] [ -p ] [ -v ] path ..." >&2 exit 1
fi ### Define some settings depending on option '-a'
if test "${GLOB_BOOL_CLI_OPTION_SCAN_ALL_FILES}" = 'TRUE'
then CMD_FILTER="cat" SCAN_TEXT='all files'
else ### NOTE: Do NOT surround the argument (i.e. the search pattern) ### with quotes or apostrophes! It won't work! #CMD_FILTER="egrep -i log4j|jndilookup|\.jar" CMD_FILTER="egrep -i \.jar" SCAN_TEXT='jar files'
fi ###
### Main work starts here
###
TIME_START=`${CMD_DATE} '+%s'`
DATE=`${CMD_DATE} '+%Y-%m-%d %H:%M:%S'`
out "INFO: Scanning ${SCAN_TEXT} started at ${DATE} on ${MY_HOST} within dir(s): $@" processing_chars='-\|/'
index_processing_char=1
#out "DEBUG: EXEC: find '$@' -xdev -type f -print | ${CMD_FILTER} | while read file"
find "$@" -xdev -type f -print | ${CMD_FILTER} | while read file
do #out "DEBUG: file='${file}'" ### ### Print the processing animation ### if test "${GLOB_BOOL_CLI_OPTION_PROCESSING_ANIMATION}" = 'TRUE' then processing_char=`echo "${processing_chars}" | ${CMD_CUT} -c${index_processing_char}` if test "${index_processing_char}" -ge 4; then index_processing_char=1; else index_processing_char=`expr "${index_processing_char}" + 1`; fi echo "${processing_char}" | ${CMD_AWK} '{printf("Processing ... %c \r", $1);}' fi ### ### Now do the scanning ... ### bool_log4j_found_in_file='FALSE' bool_jndilookup_found_in_file='FALSE' ### ### Now also look into the file for 'log4j' and/or 'jndilookup' ### #out "DEBUG: EXEC: ${CMD_STRINGS} -a '${file}' | ${CMD_EGREP} -i -l 'log4j|jndilookup' >/dev/null 2>&1" ${CMD_STRINGS} -a "${file}" | ${CMD_EGREP} -i -l 'log4j|jndilookup' >/dev/null 2>&1 if test $? -eq 0 then ### One (or both) strings appear in the file, check out which one ${CMD_STRINGS} -a "${file}" | ${CMD_EGREP} -i -l 'log4j' >/dev/null 2>&1 if test $? -eq 0; then bool_log4j_found_in_file='TRUE'; fi ${CMD_STRINGS} -a "${file}" | ${CMD_EGREP} -i -l 'jndilookup' >/dev/null 2>&1 if test $? -eq 0; then bool_jndilookup_found_in_file='TRUE'; fi fi ### ### look at the result and print information and warnings accordingly ### bool_file_reported='FALSE' if test "${bool_log4j_found_in_file}${bool_jndilookup_found_in_file}" = 'TRUETRUE' then ### ### Found both, 'log4j' _and_ 'jndilookup' in the file! Print a warning! ### out "WARNING: Found 'log4j' _and_ 'jndilookup' in the following file: ${file}" if test "${GLOB_BOOL_CLI_OPTION_VERBOSE}" = 'TRUE' then out "WARNING: Details: ===== BEGIN (first 10 occurrences at most) =====" ${CMD_STRINGS} -a "${file}" | ${CMD_EGREP} -i 'log4j|jndilookup' | head -10 | cat_out "WARNING: Details: " out "WARNING: Details: ===== END =====" out "WARNING: " fi bool_file_reported='TRUE' else if test "${GLOB_BOOL_CLI_OPTION_LAZY}" = 'TRUE' then ### ### just found one of both strings? ### if test "${bool_log4j_found_in_file}" = 'TRUE' then out "NOTICE: Found 'log4j' (but not 'jndilookup') in the following file: ${file}" if test "${GLOB_BOOL_CLI_OPTION_VERBOSE}" = 'TRUE' then out "NOTICE: Details: ===== BEGIN (first 10 occurrences at most) =====" ${CMD_STRINGS} -a "${file}" | ${CMD_EGREP} -i 'log4j|jndilookup' | head -10 | cat_out "NOTICE: Details: " out "NOTICE: Details: ===== END =====" out "NOTICE: " fi bool_file_reported='TRUE' else if test "${bool_jndilookup_found_in_file}" = 'TRUE' then out "NOTICE: Found 'jndilookup' (but not 'log4j') in the following file: ${file}" if test "${GLOB_BOOL_CLI_OPTION_VERBOSE}" = 'TRUE' then out "NOTICE: Details: ===== BEGIN (first 10 occurrences at most) =====" ${CMD_STRINGS} -a "${file}" | ${CMD_EGREP} -i 'log4j|jndilookup' | head -10 | cat_out "NOTICE: Details: " out "NOTICE: Details: ===== END =====" out "NOTICE: " fi bool_file_reported='TRUE' fi fi fi fi if test "${bool_file_reported}" = 'FALSE' then ### ### Nothing found within the file. Check if at least the file name contains one of the problematic strings. ### #out "DEBUG: EXEC: echo '${file}' | ${CMD_EGREP} -i -l 'log4j|jndilookup' >/dev/null 2>&1" echo "${file}" | ${CMD_EGREP} -i -l 'log4j|jndilookup' >/dev/null 2>&1 if test $? -eq 0 then out "INFO: Found 'log4j' or 'jndilookup' in the name of the file: ${file}" fi fi done TIME_END=`${CMD_DATE} '+%s'`
TIME_ELAPSED=`expr "${TIME_END}" - "${TIME_START}"`
DATE=`${CMD_DATE} '+%Y-%m-%d %H:%M:%S'`
out "INFO: Scan finished at ${DATE} (elapsed: ${TIME_ELAPSED} seconds)"