Projects STRLCPY text4shell-tools Commits 28455bf9
🤬
  • ■ ■ ■ ■
    README.md
    skipped 1 lines
    2 2   
    3 3  ### Overview
    4 4   
    5  -CVE-2022-33980 may pose a serious threat to a wide range of Java-based applications. The important questions a developer may ask in this context are:
     5 +CVE-2022-42889 may pose a serious threat to a wide range of Java-based applications. The important questions a developer may ask in this context are:
    6 6   
    7 7  ### 1. Does my code include `commons-text`? Which versions?
    8 8   
    skipped 63 lines
  • ■ ■ ■ ■ ■ ■
    scan_commons_text_versions/python/scan_commons_text_versions.py
     1 +import os
     2 +import sys
     3 +from collections import namedtuple
     4 +from enum import Enum, IntEnum
     5 +from zipfile import BadZipFile, ZipFile
     6 +from tarfile import open as tar_open
     7 +from tarfile import CompressionError, ReadError
     8 +import zlib
     9 + 
     10 +TARGET_CLASS_NAME = "StringLookupFactory.class"
     11 +PATCH_STRING = b"defaultStringLookups"
     12 +PATCH_STRING_2 = b"base64StringLookup"
     13 + 
     14 +RED = "\x1b[31m"
     15 +GREEN = "\x1b[32m"
     16 +YELLOW = "\x1b[33m"
     17 +RESET_ALL = "\x1b[0m"
     18 + 
     19 +ZIP_EXTENSIONS = {".jar", ".war", ".sar", ".ear", ".par", ".zip", ".apk"}
     20 +TAR_EXTENSIONS = {".gz", ".tar"}
     21 + 
     22 + 
     23 +class StringLookupFactoryVer(IntEnum):
     24 + NOT_FOUND = 0
     25 + v1_10 = 1
     26 + v1_4_or_below = 2
     27 + v1_5_to_1_9 = 3
     28 + 
     29 + 
     30 +class Status(Enum):
     31 + INCONSISTENT = 0
     32 + VULN = 1
     33 + PARTIAL = 2
     34 + FIX = 3
     35 + 
     36 + 
     37 +Diag = namedtuple("Diag", ["status", "note"])
     38 + 
     39 + 
     40 +DIAGNOSIS_TABLE = {
     41 + StringLookupFactoryVer.v1_10: Diag(Status.FIX, "1.10 or above"),
     42 + StringLookupFactoryVer.v1_4_or_below: Diag(Status.FIX, "1.4 or below"),
     43 + StringLookupFactoryVer.v1_5_to_1_9: Diag(Status.VULN, "1.5 .. 1.9"),
     44 +}
     45 + 
     46 + 
     47 +def confusion_message(filename: str, classname: str):
     48 + print(
     49 + "Warning: "
     50 + + filename
     51 + + " contains multiple copies of "
     52 + + classname
     53 + + "; result may be invalid"
     54 + )
     55 + 
     56 + 
     57 +def version_message(filename: str, diagnosis: Diag):
     58 + messages = {
     59 + Status.FIX: GREEN + "fixed" + RESET_ALL,
     60 + Status.VULN: RED + "vulnerable" + RESET_ALL,
     61 + Status.PARTIAL: YELLOW + "mitigated" + RESET_ALL,
     62 + Status.INCONSISTENT: RED + "inconsistent" + RESET_ALL,
     63 + }
     64 + print(filename + " >> " + messages[diagnosis.status] + " " + diagnosis.note)
     65 + 
     66 + 
     67 +def class_version_string_lookup_factory(
     68 + classfile_content: bytes,
     69 +) -> StringLookupFactoryVer:
     70 + if PATCH_STRING in classfile_content:
     71 + return StringLookupFactoryVer.v1_10
     72 + if PATCH_STRING_2 in classfile_content:
     73 + return StringLookupFactoryVer.v1_5_to_1_9
     74 + 
     75 + return StringLookupFactoryVer.v1_4_or_below
     76 + 
     77 + 
     78 +def zip_file(file, rel_path: str, silent_mode: bool):
     79 + try:
     80 + with ZipFile(file) as jarfile:
     81 + string_lookup_factory_status = StringLookupFactoryVer.NOT_FOUND
     82 + 
     83 + for file_name in jarfile.namelist():
     84 + if acceptable_filename(file_name):
     85 + next_file = jarfile.open(file_name, "r")
     86 + test_file(next_file, os.path.join(rel_path, file_name), silent_mode)
     87 + continue
     88 + 
     89 + if file_name.endswith(TARGET_CLASS_NAME):
     90 + if string_lookup_factory_status != StringLookupFactoryVer.NOT_FOUND:
     91 + if not silent_mode:
     92 + confusion_message(file_name, TARGET_CLASS_NAME)
     93 + classfile_content = jarfile.read(file_name)
     94 + string_lookup_factory_status = class_version_string_lookup_factory(
     95 + classfile_content
     96 + )
     97 + 
     98 + # went over all the files in the current layer; draw conclusions
     99 + if string_lookup_factory_status:
     100 + diagnosis = DIAGNOSIS_TABLE.get(
     101 + string_lookup_factory_status,
     102 + Diag(
     103 + Status.INCONSISTENT,
     104 + "StringLookupFactory: " + string_lookup_factory_status.name,
     105 + ),
     106 + )
     107 + version_message(rel_path, diagnosis)
     108 + except (IOError, BadZipFile, UnicodeDecodeError, zlib.error, RuntimeError) as e:
     109 + if not silent_mode:
     110 + print(rel_path + ": " + str(e))
     111 + return
     112 + 
     113 + 
     114 +def tar_file(file, rel_path: str, silent_mode: bool):
     115 + try:
     116 + with tar_open(fileobj=file) as tarfile:
     117 + for item in tarfile.getmembers():
     118 + if "../" in item.name:
     119 + continue
     120 + if item.isfile() and acceptable_filename(item.name):
     121 + fileobj = tarfile.extractfile(item)
     122 + new_path = rel_path + "/" + item.name
     123 + test_file(fileobj, new_path, silent_mode)
     124 + item = tarfile.next()
     125 + except (
     126 + IOError,
     127 + FileExistsError,
     128 + CompressionError,
     129 + ReadError,
     130 + RuntimeError,
     131 + UnicodeDecodeError,
     132 + zlib.error,
     133 + ) as e:
     134 + if not silent_mode:
     135 + print(rel_path + ": " + str(e))
     136 + return
     137 + 
     138 + 
     139 +def test_file(file, rel_path: str, silent_mode: bool):
     140 + if any(rel_path.endswith(ext) for ext in ZIP_EXTENSIONS):
     141 + zip_file(file, rel_path, silent_mode)
     142 + 
     143 + elif any(rel_path.endswith(ext) for ext in TAR_EXTENSIONS):
     144 + tar_file(file, rel_path, silent_mode)
     145 + 
     146 + 
     147 +def acceptable_filename(filename: str):
     148 + return any(filename.endswith(ext) for ext in ZIP_EXTENSIONS | TAR_EXTENSIONS)
     149 + 
     150 + 
     151 +def run_scanner(root_dir: str, exclude_dirs, silent_mode: bool):
     152 + if os.path.isdir(root_dir):
     153 + for directory, dirs, files in os.walk(root_dir, topdown=True):
     154 + [
     155 + dirs.remove(excluded_dir)
     156 + for excluded_dir in list(dirs)
     157 + if os.path.join(directory, excluded_dir) in exclude_dirs
     158 + ]
     159 + 
     160 + for filename in files:
     161 + if acceptable_filename(filename):
     162 + full_path = os.path.join(directory, filename)
     163 + rel_path = os.path.relpath(full_path, root_dir)
     164 + try:
     165 + with open(full_path, "rb") as file:
     166 + test_file(file, rel_path, silent_mode)
     167 + except FileNotFoundError as fnf_error:
     168 + if not silent_mode:
     169 + print(fnf_error)
     170 + elif os.path.isfile(root_dir):
     171 + if acceptable_filename(root_dir):
     172 + with open(root_dir, "rb") as file:
     173 + test_file(file, "", silent_mode)
     174 + 
     175 + 
     176 +def print_usage():
     177 + print(
     178 + "Usage: "
     179 + + sys.argv[0]
     180 + + " <root_folder> [-quiet] [-exclude <folder1> <folder2> ...]"
     181 + )
     182 + print("or: " + sys.argv[0] + "<archive_file> [-quiet]")
     183 + exit()
     184 + 
     185 + 
     186 +def parse_command_line():
     187 + if len(sys.argv) < 2:
     188 + print_usage()
     189 + 
     190 + root_dir = sys.argv[1]
     191 + exclude_folders = []
     192 + 
     193 + silent = len(sys.argv) > 2 and sys.argv[2] == "-quiet"
     194 + exclude_start = 3 if silent else 2
     195 + if len(sys.argv) > exclude_start:
     196 + if not sys.argv[exclude_start] == "-exclude":
     197 + print_usage()
     198 + exclude_folders = sys.argv[exclude_start + 1 :]
     199 + 
     200 + return root_dir, exclude_folders, silent
     201 + 
     202 + 
     203 +if __name__ == "__main__":
     204 + 
     205 + root_dir, exclude_dirs, silent_mode = parse_command_line()
     206 + 
     207 + for dir_to_check in exclude_dirs:
     208 + if not os.path.isdir(dir_to_check):
     209 + print(dir_to_check + " is not a directory")
     210 + print_usage()
     211 + if not os.path.isdir(root_dir) and not (
     212 + os.path.isfile(root_dir) and acceptable_filename(root_dir)
     213 + ):
     214 + print(root_dir + " is not a directory or an archive")
     215 + print_usage()
     216 + 
     217 + print("Scanning " + root_dir)
     218 + if exclude_dirs:
     219 + print("Excluded: " + ", ".join(exclude_dirs))
     220 + 
     221 + run_scanner(root_dir, set(exclude_dirs), silent_mode)
     222 + 
Please wait...
Page is in error, reload to recover